/*
 * Copyright (C) 2003-2009 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.services.cms.webdav;

import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import javax.jcr.Item;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import org.apache.commons.lang3.StringUtils;
import org.exoplatform.common.http.HTTPStatus;
import org.exoplatform.common.util.HierarchicalProperty;
import org.exoplatform.commons.utils.MimeTypeResolver;
import org.exoplatform.container.xml.InitParams;
import org.exoplatform.ecm.utils.text.Text;
import org.exoplatform.services.cms.CmsService;
import org.exoplatform.services.cms.documents.AutoVersionService;
import org.exoplatform.services.cms.drives.DriveData;
import org.exoplatform.services.cms.drives.ManageDriveService;
import org.exoplatform.services.cms.impl.Utils;
import org.exoplatform.services.cms.jcrext.activity.ActivityCommonService;
import org.exoplatform.services.cms.link.LinkUtils;
import org.exoplatform.services.cms.link.NodeFinder;
import org.exoplatform.services.jcr.RepositoryService;
import org.exoplatform.services.jcr.ext.app.ThreadLocalSessionProviderService;
import org.exoplatform.services.jcr.webdav.util.InitParamsDefaults;
import org.exoplatform.services.jcr.webdav.util.TextUtil;
import org.exoplatform.services.listener.ListenerService;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.rest.ExtHttpHeaders;
import org.exoplatform.services.rest.ext.webdav.method.ACL;
import org.exoplatform.services.rest.ext.webdav.method.CHECKIN;
import org.exoplatform.services.rest.ext.webdav.method.CHECKOUT;
import org.exoplatform.services.rest.ext.webdav.method.COPY;
import org.exoplatform.services.rest.ext.webdav.method.LOCK;
import org.exoplatform.services.rest.ext.webdav.method.MKCOL;
import org.exoplatform.services.rest.ext.webdav.method.MOVE;
import org.exoplatform.services.rest.ext.webdav.method.OPTIONS;
import org.exoplatform.services.rest.ext.webdav.method.ORDERPATCH;
import org.exoplatform.services.rest.ext.webdav.method.PROPFIND;
import org.exoplatform.services.rest.ext.webdav.method.PROPPATCH;
import org.exoplatform.services.rest.ext.webdav.method.REPORT;
import org.exoplatform.services.rest.ext.webdav.method.SEARCH;
import org.exoplatform.services.rest.ext.webdav.method.UNCHECKOUT;
import org.exoplatform.services.rest.ext.webdav.method.UNLOCK;
import org.exoplatform.services.rest.ext.webdav.method.VERSIONCONTROL;
import org.exoplatform.services.rest.impl.MultivaluedMapImpl;
import org.exoplatform.services.wcm.core.NodetypeConstant;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;

/**
 * This class is used to override the default WebDavServiceImpl in order to support symlinks
 *
 * Created by The eXo Platform SAS
 * Author : eXoPlatform
 *          nicolas.filotto@exoplatform.com
 * 9 avr. 2009
 */
@Path("/jcr/")
public class WebDavServiceImpl extends org.exoplatform.services.jcr.webdav.WebDavServiceImpl {

  /**
   * Logger.
   */
  private static final Log LOG = ExoLogger.getLogger(WebDavServiceImpl.class.getName());

  private final String POST_UPLOAD_CONTENT_EVENT = "WebDavService.event.postUpload";

  private final String PERSONAL_DRIVE_PREFIX = "/Users/${userId}/Private";

  private final String GROUP_DRIVE_PREFIX = "/Groups${groupId}/Documents";

  private final String PERSONAL_GROUP_DRIVE_WORKSPACE = "collaboration";


   private final NodeFinder nodeFinder;

  private final RepositoryService repositoryService;

  private ListenerService listenerService;

  private final MimeTypeResolver mimeTypeResolver;

   public WebDavServiceImpl(InitParams params,
                            RepositoryService repositoryService,
                            ThreadLocalSessionProviderService sessionProviderService,
                            NodeFinder nodeFinder, AutoVersionService autoVersionService, ManageDriveService manageDriveService) throws Exception
   {
      super(params, repositoryService, sessionProviderService);
      this.repositoryService = repositoryService;
      this.nodeFinder = nodeFinder;
      this.listenerService = WCMCoreUtils.getService(ListenerService.class);
      this.mimeTypeResolver = new MimeTypeResolver();
      this.mimeTypeResolver.setDefaultMimeType(InitParamsDefaults.FILE_MIME_TYPE);

      List<String> lstDriveAutoVersion = autoVersionService.getDriveAutoVersion();
      MultivaluedMap<String, String> allowedAutoVersionPath = new MultivaluedMapImpl();
      if (!lstDriveAutoVersion.isEmpty())
      {
         for (String driverName : lstDriveAutoVersion)
         {
            DriveData driveData = manageDriveService.getDriveByName(StringUtils.trim(driverName));
            if (driveData != null)
            {
               String driveHome = driveData.getHomePath();
               String workspace = driveData.getWorkspace();

               if (driveHome.startsWith(PERSONAL_DRIVE_PREFIX) && PERSONAL_GROUP_DRIVE_WORKSPACE.equals(workspace))
               {
                  allowedAutoVersionPath.add(driveData.getWorkspace(), "/Users");
               }
               else if (driveHome.startsWith(GROUP_DRIVE_PREFIX) && PERSONAL_GROUP_DRIVE_WORKSPACE.equals(workspace))
               {
                  allowedAutoVersionPath.add(driveData.getWorkspace(), "/Groups");
               }
               else
               {
                  allowedAutoVersionPath.add(driveData.getWorkspace(), driveHome);
               }
            }
         }
      }
      webDavServiceInitParams.setAllowedAutoVersionPath(allowedAutoVersionPath);
      webDavServiceInitParams.setEnableAutoVersion(true);
   }

  private String getRealDestinationHeader(String baseURI, String repoName, String destinationHeader) {
    String serverURI = baseURI + "/jcr/" + repoName;

    destinationHeader = TextUtil.unescape(destinationHeader, '%');

    if (!destinationHeader.startsWith(serverURI)) {
      return null;
    }

    String destPath = destinationHeader.substring(serverURI.length() + 1);

    try {
      Item item = nodeFinder.getItem(workspaceName(destPath),
                                     LinkUtils.getParentPath(path(destPath)),
                                     true);
      return item.getSession().getWorkspace().getName()
          + LinkUtils.createPath(item.getPath(), LinkUtils.getItemName(path(destPath)));
    } catch (RepositoryException e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + destPath, e);
      }
      return null;
    }
  }

  @CHECKIN
  @Path("/{repoName}/{repoPath:.*}/")
  public Response checkin(@PathParam("repoName") String repoName,
                          @PathParam("repoPath") String repoPath,
                          @HeaderParam(ExtHttpHeaders.LOCKTOKEN) String lockTokenHeader,
                          @HeaderParam(ExtHttpHeaders.IF) String ifHeader) {

    try {
      repoName = repositoryService.getCurrentRepository().getConfiguration().getName();
      repoPath = convertRepoPath(repoPath, true);
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    return super.checkin(repoName, repoPath, lockTokenHeader, ifHeader);
  }

  @CHECKOUT
  @Path("/{repoName}/{repoPath:.*}/")
  public Response checkout(@PathParam("repoName") String repoName,
                           @PathParam("repoPath") String repoPath,
                           @HeaderParam(ExtHttpHeaders.LOCKTOKEN) String lockTokenHeader,
                           @HeaderParam(ExtHttpHeaders.IF) String ifHeader) {
    try {
      repoName = repositoryService.getCurrentRepository().getConfiguration().getName();
      repoPath = convertRepoPath(repoPath, true);
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    return super.checkout(repoName, repoPath, lockTokenHeader, ifHeader);
  }

  @COPY
  @Path("/{repoName}/{repoPath:.*}/")
  public Response copy(@PathParam("repoName") String repoName,
                       @PathParam("repoPath") String repoPath,
                       @HeaderParam(ExtHttpHeaders.DESTINATION) String destinationHeader,
                       @HeaderParam(ExtHttpHeaders.LOCKTOKEN) String lockTokenHeader,
                       @HeaderParam(ExtHttpHeaders.IF) String ifHeader,
                       @HeaderParam(ExtHttpHeaders.DEPTH) String depthHeader,
                       @HeaderParam(ExtHttpHeaders.OVERWRITE) String overwriteHeader,
                       @Context UriInfo uriInfo,
                       HierarchicalProperty body) {

    try {
      repoPath = convertRepoPath(repoPath, false);
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    String realDestinationHeader = getRealDestinationHeader(uriInfo.getPath(), repoName, destinationHeader);
    if (realDestinationHeader != null) {
      destinationHeader = realDestinationHeader;
    }
    return super.copy(repoName,
                      repoPath,
                      destinationHeader,
                      lockTokenHeader,
                      ifHeader,
                      depthHeader,
                      overwriteHeader,
                      uriInfo,
                      body);
  }

  @GET
  @Path("/{repoName}/{repoPath:.*}/")
  public Response get(@PathParam("repoName") String repoName,
                      @PathParam("repoPath") String repoPath,
                      @HeaderParam(ExtHttpHeaders.RANGE) String rangeHeader,
                      @HeaderParam(ExtHttpHeaders.IF_MODIFIED_SINCE) String ifModifiedSince,
                      @HeaderParam(ExtHttpHeaders.IF_NONE_MATCH) String ifNoneMatch,
                      @QueryParam("version") String version,
                      @Context UriInfo uriInfo) {

    try {
      repoPath = convertRepoPath(repoPath, true);
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    Response response = super.get(repoName, repoPath, rangeHeader, ifModifiedSince, ifNoneMatch, version, uriInfo);
    if(HTTPStatus.OK == response.getStatus()) {
      return Response.fromResponse(response)
              .header("Access-Control-Allow-Origin", uriInfo.getRequestUri().getHost())
              .header("Access-Control-Allow-Credentials", true)
              .header("Access-Control-Allow-Methods", "ACL, CANCELUPLOAD, CHECKIN, CHECKOUT, COPY, DELETE, GET, HEAD, LOCK, MKCALENDAR, MKCOL, " +
                      "MOVE, OPTIONS, POST, PROPFIND, PROPPATCH, PUT, REPORT, SEARCH, UNCHECKOUT, UNLOCK, UPDATE, VERSION-CONTROL")
              .header("Access-Control-Allow-Headers", "Overwrite, Destination, Content-Type, Depth, User-Agent, Translate, Range, Content-Range," +
                      " Timeout, X-File-Size, X-Requested-With, If-Modified-Since, X-File-Name, Cache-Control, Location, Lock-Token, If")
              .header("Access-Control-Expose-Header", "DAV, content-length, Allow")
              .header("Access-Control-Max-Age", 3600)
              .build();
    }
    return response;
  }

  @HEAD
  @Path("/{repoName}/{repoPath:.*}/")
  public Response head(@PathParam("repoName") String repoName,
                       @PathParam("repoPath") String repoPath,
                       @Context UriInfo uriInfo) {

    try {
      repoName = repositoryService.getCurrentRepository().getConfiguration().getName();
      repoPath = convertRepoPath(repoPath, true);
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    return super.head(repoName, repoPath, uriInfo);
  }

  @LOCK
  @Path("/{repoName}/{repoPath:.*}/")
  public Response lock(@PathParam("repoName") String repoName,
                       @PathParam("repoPath") String repoPath,
                       @HeaderParam(ExtHttpHeaders.LOCKTOKEN) String lockTokenHeader,
                       @HeaderParam(ExtHttpHeaders.IF) String ifHeader,
                       @HeaderParam(ExtHttpHeaders.DEPTH) String depthHeader,
                       HierarchicalProperty body) {

    try {
      repoName = repositoryService.getCurrentRepository().getConfiguration().getName();
      repoPath = convertRepoPath(repoPath, true);
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    return super.lock(repoName, repoPath, lockTokenHeader, ifHeader, depthHeader, body);
  }

  @UNLOCK
  @Path("/{repoName}/{repoPath:.*}/")
  public Response unlock(@PathParam("repoName") String repoName,
                         @PathParam("repoPath") String repoPath,
                         @HeaderParam(ExtHttpHeaders.LOCKTOKEN) String lockTokenHeader,
                         @HeaderParam(ExtHttpHeaders.IF) String ifHeader) {

    try {
      repoName = repositoryService.getCurrentRepository().getConfiguration().getName();
      repoPath = convertRepoPath(repoPath, true);
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    return super.unlock(repoName, repoPath, lockTokenHeader, ifHeader);
  }

  @OPTIONS
  @Path("/{repoName}/{path:.*}/")
  public Response options(@PathParam("path") String path) {
    return super.options(path);
  }

  @ORDERPATCH
  @Path("/{repoName}/{repoPath:.*}/")
  public Response order(@PathParam("repoName") String repoName,
                        @PathParam("repoPath") String repoPath,
                        @HeaderParam(ExtHttpHeaders.LOCKTOKEN) String lockTokenHeader,
                        @HeaderParam(ExtHttpHeaders.IF) String ifHeader,
                        @Context UriInfo uriInfo,
                        HierarchicalProperty body) {

    try {
      repoName = repositoryService.getCurrentRepository().getConfiguration().getName();
      repoPath = convertRepoPath(repoPath, true);
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    return super.order(repoName, repoPath, lockTokenHeader, ifHeader, uriInfo, body);
  }

  @PROPFIND
  @Path("/{repoName}/{repoPath:.*}/")
  public Response propfind(@PathParam("repoName") String repoName,
                           @PathParam("repoPath") String repoPath,
                           @HeaderParam(ExtHttpHeaders.DEPTH) String depthHeader,
                           @Context UriInfo uriInfo,
                           HierarchicalProperty body) {

    try {
      int fileNameIndex = repoPath.lastIndexOf("/") + 1;
      String cleanPath = repoPath.substring(0, fileNameIndex) + Utils.cleanName(repoPath.substring(fileNameIndex));
      repoPath = convertRepoPath(cleanPath, true);
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    return super.propfind(repoName, repoPath, depthHeader, uriInfo, body);
  }

  @PROPPATCH
  @Path("/{repoName}/{repoPath:.*}/")
  public Response proppatch(@PathParam("repoName") String repoName,
                            @PathParam("repoPath") String repoPath,
                            @HeaderParam(ExtHttpHeaders.LOCKTOKEN) String lockTokenHeader,
                            @HeaderParam(ExtHttpHeaders.IF) String ifHeader,
                            @Context UriInfo uriInfo,
                            HierarchicalProperty body) {

    try {
      repoPath = convertRepoPath(repoPath, true);
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    return super.proppatch(repoName, repoPath, lockTokenHeader, ifHeader, uriInfo, body);
  }

  @PUT
  @Path("/{repoName}/{repoPath:.*}/")
  public Response put(@PathParam("repoName") String repoName,
                      @PathParam("repoPath") String repoPath,
                      @HeaderParam(ExtHttpHeaders.LOCKTOKEN) String lockTokenHeader,
                      @HeaderParam(ExtHttpHeaders.IF) String ifHeader,
                      @HeaderParam(ExtHttpHeaders.FILE_NODETYPE) String fileNodeTypeHeader,
                      @HeaderParam(ExtHttpHeaders.CONTENT_NODETYPE) String nodeTypeHeader,
                      @HeaderParam(ExtHttpHeaders.CONTENT_MIXINTYPES) String mixinTypes,
                      @HeaderParam(ExtHttpHeaders.CONTENTTYPE) MediaType mediaType,
                      @HeaderParam(ExtHttpHeaders.USER_AGENT) String userAgent,
                      InputStream inputStream,
                      @Context UriInfo uriInfo) {
    Session session = null;
    Item item = null;
    boolean isCreating = false;
    ActivityCommonService activityService = null;
    try {
      repoName = repositoryService.getCurrentRepository().getConfiguration().getName();
      try {
        item = nodeFinder.getItem(workspaceName(repoPath),
                                  LinkUtils.getParentPath(path(normalizePath(repoPath))),
                                  true);
        repoPath = item.getSession().getWorkspace().getName()
            + LinkUtils.createPath(item.getPath(), Text.escapeIllegalJcrChars(Utils.cleanName(LinkUtils.getItemName(path(repoPath)))));
        session = item.getSession();
      } catch (PathNotFoundException e) {
        item = nodeFinder.getItem(workspaceName(repoPath),
                                  LinkUtils.getParentPath(path(Text.escapeIllegalJcrChars(repoPath))),
                                  true);
        repoPath = item.getSession().getWorkspace().getName()
            + LinkUtils.createPath(item.getPath(), Text.escapeIllegalJcrChars(Utils.cleanName(LinkUtils.getItemName(path(repoPath)))));
        session = item.getSession();
      }
      activityService = WCMCoreUtils.getService(ActivityCommonService.class);
      if (!session.itemExists(path(repoPath))) {
        isCreating = true;
      }
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }

    Response res = super.put(repoName,
                             repoPath,
                             lockTokenHeader,
                             ifHeader,
                             null,
                             nodeTypeHeader,
                             mixinTypes,
                             mediaType,
                             userAgent,
                             inputStream,
                             uriInfo);
    try {

      boolean pushAs = markTempFilesToHidden(repoPath);
      Node currentNode = (Node) session.getItem(path(repoPath));
      if (isCreating) {
        if (userAgent!= null && userAgent.contains("Microsoft")) {
          activityService.setCreating(currentNode, true);
        }
      }else {
        activityService.setCreating(currentNode, false);
      }

      try {
        if(isCreating && pushAs)
          listenerService.broadcast(ActivityCommonService.FILE_CREATED_ACTIVITY, null, currentNode);
        
        if (currentNode.isCheckedOut() && !activityService.isCreating(currentNode) && pushAs)
          listenerService.broadcast(this.POST_UPLOAD_CONTENT_EVENT, this, currentNode);

        } catch (Exception e) {
          if (LOG.isWarnEnabled()) {
            LOG.warn("Cannot broadcast file create activity for the item at " + currentNode.getPath(), e);
          }
        }

    } catch (PathNotFoundException npfe) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(npfe.getMessage()).build();
    } catch (RepositoryException re) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(re.getMessage()).build();
    } catch (Exception e) {
      return Response.serverError().build();
    }

    return res;
  }

  @REPORT
  @Path("/{repoName}/{repoPath:.*}/")
  public Response report(@PathParam("repoName") String repoName,
                         @PathParam("repoPath") String repoPath,
                         @HeaderParam(ExtHttpHeaders.DEPTH) String depthHeader,
                         @Context UriInfo uriInfo,
                         HierarchicalProperty body) {

    try {
      repoName = repositoryService.getCurrentRepository().getConfiguration().getName();
      repoPath = convertRepoPath(repoPath, true);
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    return super.report(repoName, repoPath, depthHeader, uriInfo, body);
  }

  @SEARCH
  @Path("/{repoName}/{repoPath:.*}/")
  public Response search(@PathParam("repoName") String repoName,
                         @PathParam("repoPath") String repoPath,
                         @Context UriInfo uriInfo,
                         HierarchicalProperty body) {

    try {
      repoName = repositoryService.getCurrentRepository().getConfiguration().getName();
      repoPath = convertRepoPath(repoPath, true);
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    return super.search(repoName, repoPath, uriInfo, body);
  }

  @UNCHECKOUT
  @Path("/{repoName}/{repoPath:.*}/")
  public Response uncheckout(@PathParam("repoName") String repoName,
                             @PathParam("repoPath") String repoPath,
                             @HeaderParam(ExtHttpHeaders.LOCKTOKEN) String lockTokenHeader,
                             @HeaderParam(ExtHttpHeaders.IF) String ifHeader) {

    try {
      repoName = repositoryService.getCurrentRepository().getConfiguration().getName();
      repoPath = convertRepoPath(repoPath, true);
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    return super.uncheckout(repoName, repoPath, lockTokenHeader, ifHeader);
  }

  @VERSIONCONTROL
  @Path("/{repoName}/{repoPath:.*}/")
  public Response versionControl(@PathParam("repoName") String repoName,
                                 @PathParam("repoPath") String repoPath,
                                 @HeaderParam(ExtHttpHeaders.LOCKTOKEN) String lockTokenHeader,
                                 @HeaderParam(ExtHttpHeaders.IF) String ifHeader) {

    try {
      repoName = repositoryService.getCurrentRepository().getConfiguration().getName();
      repoPath = convertRepoPath(repoPath, true);
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    return super.versionControl(repoName, repoPath, lockTokenHeader, ifHeader);
  }

  @ACL
  @Path("/{repoName}/{repoPath:.*}/")
  public Response acl(@PathParam("repoName") String repoName,
                      @PathParam("repoPath") String repoPath,
                      @HeaderParam(ExtHttpHeaders.LOCKTOKEN) String lockTokenHeader,
                      @HeaderParam(ExtHttpHeaders.IF) String ifHeader,
                      HierarchicalProperty body) {
    try {
      repoName = repositoryService.getCurrentRepository().getConfiguration().getName();
      repoPath = convertRepoPath(repoPath, true);
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    return super.acl(repoName, repoPath, lockTokenHeader, ifHeader, body);
  }

  @MOVE
  @Path("/{repoName}/{repoPath:.*}/")
  public Response move(@PathParam("repoName") String repoName,
                       @PathParam("repoPath") String repoPath,
                       @HeaderParam(ExtHttpHeaders.DESTINATION) String destinationHeader,
                       @HeaderParam(ExtHttpHeaders.LOCKTOKEN) String lockTokenHeader,
                       @HeaderParam(ExtHttpHeaders.IF) String ifHeader,
                       @HeaderParam(ExtHttpHeaders.DEPTH) String depthHeader,
                       @HeaderParam(ExtHttpHeaders.OVERWRITE) String overwriteHeader,
                       @Context UriInfo uriInfo,
                       HierarchicalProperty body) {
    String destHeader;
    try {
      repoPath = convertRepoPath(repoPath, true);
      destHeader = destinationHeader.substring(0, destinationHeader.lastIndexOf("/") + 1)
          + Utils.cleanName(LinkUtils.getItemName(path(URLDecoder.decode(destinationHeader, StandardCharsets.UTF_8))));
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    Response response = super.move(repoName,
                                   repoPath,
                                   destHeader,
                                   lockTokenHeader,
                                   ifHeader,
                                   depthHeader,
                                   overwriteHeader,
                                   uriInfo,
                                   body);

    if (response.getStatus() == HTTPStatus.CREATED) {
      updateProperties(destinationHeader, repoName);
    }
    markTempFilesToHidden(repoPath);
    return response;
  }

  /**
   * update exo:name, exo:title and jcr:mimeType when rename a node
   * 
   * @param destinationHeader
   * @param repoName
   */
  private void updateProperties(String destinationHeader, String repoName) {
    try {
      URI dest = buildURI(destinationHeader);
      String destPath = dest.getPath();
      destPath = destPath.substring(0, destPath.lastIndexOf("/") + 1) + Utils.cleanName(LinkUtils.getItemName(destPath));
      int repoIndex = destPath.indexOf(repoName);
      destPath = normalizePath(repoIndex == -1 ? destPath : destPath.substring(repoIndex + repoName.length() + 1));
      String destNodePath = path(destPath);
      Node destNode = (Node) nodeFinder.getItem(workspaceName(destPath), path(normalizePath(destNodePath)), true);
      String nodeName = destNode.getName();
      destNode.setProperty("exo:name", Text.escapeIllegalJcrChars(Utils.cleanName(nodeName)));
      destNode.setProperty("exo:title", Utils.cleanDocumentTitle(nodeName));
      if (!Utils.isFolder(destNode)) {
        Node content = destNode.getNode("jcr:content");
        String mimeType = mimeTypeResolver.getMimeType(nodeName);
        content.setProperty("jcr:mimeType", mimeType);
        // Change publication status
        ListenerService listenerService =  WCMCoreUtils.getService(ListenerService.class);
        if (destNode.isNodeType("exo:datetime")) {
          destNode.setProperty("exo:dateModified", new GregorianCalendar());
        }
        listenerService.broadcast(CmsService.POST_EDIT_CONTENT_EVENT, destNode.getParent(), destNode);
      }
      destNode.save();     
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot change property of destNode" + destinationHeader, e);
      }
    }
  }

  /** 
   * Build URI from string. 
   */
  private URI buildURI(String path) throws URISyntaxException {
    try {
      return new URI(path);
    }
    catch (URISyntaxException e) {
      return new URI(TextUtil.escape(path, '%', true));
    }
  }

  /**
   * {@inheritDoc}
   */
  @MKCOL
  @Path("/{repoName}/{repoPath:.*}/")
  public Response mkcol(@PathParam("repoName") String repoName,
                        @PathParam("repoPath") String repoPath,
                        @HeaderParam(ExtHttpHeaders.LOCKTOKEN) String lockTokenHeader,
                        @HeaderParam(ExtHttpHeaders.IF) String ifHeader,
                        @HeaderParam(ExtHttpHeaders.CONTENT_NODETYPE) String nodeTypeHeader,
                        @HeaderParam(ExtHttpHeaders.CONTENT_MIXINTYPES) String mixinTypesHeader,
                        @Context UriInfo uriInfo) {
    try {
      repoName = repositoryService.getCurrentRepository().getConfiguration().getName();
      Item item = nodeFinder.getItem(workspaceName(repoPath), LinkUtils.getParentPath(path(normalizePath(repoPath))), true);
      repoPath =
          item.getSession().getWorkspace().getName() + LinkUtils.createPath(item.getPath(), 
                                                                            Utils.cleanName(LinkUtils.getItemName(path(repoPath))));
    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.CONFLICT).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.CONFLICT).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }
    return super.mkcol(repoName,
                       repoPath,
                       lockTokenHeader,
                       ifHeader,
                       nodeTypeHeader,
                       mixinTypesHeader,
                       uriInfo);
  }

  @DELETE
  @Path("/{repoName}/{repoPath:.*}/")
  public Response delete(@PathParam("repoName") String repoName,
                         @PathParam("repoPath") String repoPath,
                         @HeaderParam(ExtHttpHeaders.LOCKTOKEN) String lockTokenHeader,
                         @HeaderParam(ExtHttpHeaders.IF) String ifHeader) {
    Item item = null;
    try {
      repoName = repositoryService.getCurrentRepository().getConfiguration().getName();
      repoPath = convertRepoPath(repoPath, false);

      try {
        item = nodeFinder.getItem(workspaceName(repoPath),
                                  path(normalizePath(repoPath)),
                                  true);        
      } catch (PathNotFoundException e) {
        item = nodeFinder.getItem(workspaceName(repoPath),
                                  path(Text.escapeIllegalJcrChars(repoPath)),
                                  true);        
      }     

    } catch (PathNotFoundException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (NoSuchWorkspaceException exc) {
      return Response.status(HTTPStatus.NOT_FOUND).entity(exc.getMessage()).build();
    } catch (Exception e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Cannot find the item at " + repoName + "/" + repoPath, e);
      }
      return Response.serverError().build();
    }    

    try {
      //Broadcast the event when user move node to Trash
      Node node = (Node)item;
      ListenerService listenerService =  WCMCoreUtils.getService(ListenerService.class);
      ActivityCommonService activityService = WCMCoreUtils.getService(ActivityCommonService.class);
      Node parent = node.getParent();
      if (node.getPrimaryNodeType().getName().equals(NodetypeConstant.NT_FILE)) {        
        if (activityService.isBroadcastNTFileEvents(node)) {
          listenerService.broadcast(ActivityCommonService.FILE_REMOVE_ACTIVITY, parent, node);
        }
      } else if(!WCMCoreUtils.isDocumentNodeType(node)){
        Queue<Node> queue = new LinkedList<Node>();
        queue.add(node);

        //Broadcast event to remove file activities
        Node tempNode = null;
        try {
          while (!queue.isEmpty()) {
            tempNode = queue.poll();
            if (WCMCoreUtils.isDocumentNodeType(tempNode) 
                || tempNode.getPrimaryNodeType().getName().equals(NodetypeConstant.NT_FILE)) {
              listenerService.broadcast(ActivityCommonService.FILE_REMOVE_ACTIVITY, tempNode.getParent(), tempNode);
            } else {
              for (NodeIterator iter = tempNode.getNodes(); iter.hasNext(); ) {
                Node childNode = iter.nextNode();
                if(WCMCoreUtils.isDocumentNodeType(childNode) || childNode.isNodeType(NodetypeConstant.NT_UNSTRUCTURED) 
                    || childNode.isNodeType(NodetypeConstant.NT_FOLDER))
                  queue.add(childNode);
              }
            }
          }
        } catch (Exception e) {
          if (LOG.isWarnEnabled()) {
            LOG.warn(e.getMessage());
          }
        }         
      }
      //Remove the symlinks of deleted node. 
      Utils.removeSymlinks(node);
    } catch(Exception ex) {
      if (LOG.isWarnEnabled()) {
        LOG.warn(ex.getMessage());
      }
    }    
    return super.delete(repoName, repoPath, lockTokenHeader, ifHeader);
  }

  private String convertRepoPath(String repoPath, boolean giveTarget) throws Exception{
    try {
      Item item = nodeFinder.getItem(workspaceName(repoPath), path(normalizePath(repoPath)), giveTarget);
      return item.getSession().getWorkspace().getName() + item.getPath();
    } catch (PathNotFoundException e) {
      Item item = nodeFinder.getItem(workspaceName(repoPath), path(Text.escapeIllegalJcrChars(repoPath)), giveTarget);
      return item.getSession().getWorkspace().getName() + item.getPath();
    }
  }

  /**
   * hidden temporary files/folders
   * @param repoPath
   */
  private boolean markTempFilesToHidden(String repoPath){
    if(StringUtils.isBlank(repoPath)) return false;
    String tempNodeFolder      = ".TemporaryItems";
    String tempNodeFileChild = "._folders.501";
    String tempNodeFile        = "._.TemporaryItems";
    String txtTempRegex        = "/._";
    try {
      String txtTemp = repoPath.substring(repoPath.lastIndexOf("/"), repoPath.length());
      boolean isTxtTemp = txtTemp.startsWith(txtTempRegex)?true:false;
      if(repoPath.contains(tempNodeFile) || isTxtTemp){
        Node _tempNodeFile = (Node)nodeFinder.getItem(workspaceName(repoPath), path(repoPath), true);
        _tempNodeFile.remove();
        _tempNodeFile.getSession().save();
        return false;
      }else if(repoPath.contains(tempNodeFolder)) {
        String currentNodePath = repoPath.substring(0, repoPath.indexOf(tempNodeFolder));
        Node currentNode = (Node)nodeFinder.getItem(workspaceName(repoPath), path(currentNodePath), true);
        //make tmp folder to hidden
        if(currentNode.hasNode(tempNodeFolder)){
          Node _tmpFolderNode = currentNode.getNode(tempNodeFolder);
          if(_tmpFolderNode.canAddMixin(NodetypeConstant.EXO_HIDDENABLE)) _tmpFolderNode.addMixin(NodetypeConstant.EXO_HIDDENABLE);
          if (_tmpFolderNode.hasNode(tempNodeFileChild)){
            Node _tempNodeFileChild = _tmpFolderNode.getNode(tempNodeFileChild);
            _tempNodeFileChild.remove();
          }
          _tmpFolderNode.save();
        }
        return false;
      }
    }catch(RepositoryException ex){
      if (LOG.isWarnEnabled()) {
        LOG.warn("The hidden temp files has been ignored " + ex.getMessage());
      }
    }
    return true;
  }

}
