/***************************************************************************
 * 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.ecm.webui.component.explorer.rightclick.manager;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.ResourceBundle;
import java.util.regex.Matcher;

import javax.jcr.AccessDeniedException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.PropertyIterator;
import javax.jcr.ReferentialIntegrityException;
import javax.jcr.Session;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.query.Query;
import javax.jcr.version.VersionException;
import javax.portlet.PortletPreferences;

import org.apache.commons.codec.binary.StringUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.Validate;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.ecm.utils.lock.LockUtil;
import org.exoplatform.ecm.webui.component.explorer.UIConfirmMessage;
import org.exoplatform.ecm.webui.component.explorer.UIJCRExplorer;
import org.exoplatform.ecm.webui.component.explorer.UIWorkingArea;
import org.exoplatform.ecm.webui.component.explorer.control.filter.CanDeleteNodeFilter;
import org.exoplatform.ecm.webui.component.explorer.control.filter.IsNotEditingDocumentFilter;
import org.exoplatform.ecm.webui.component.explorer.control.filter.IsNotLockedFilter;
import org.exoplatform.ecm.webui.component.explorer.control.filter.IsNotMandatoryChildNode;
import org.exoplatform.ecm.webui.component.explorer.control.filter.IsNotSpecificFolderNodeFilter;
import org.exoplatform.ecm.webui.component.explorer.control.filter.IsNotTrashHomeNodeFilter;
import org.exoplatform.ecm.webui.component.explorer.control.listener.UIWorkingAreaActionListener;
import org.exoplatform.ecm.webui.utils.JCRExceptionManager;
import org.exoplatform.ecm.webui.utils.PermissionUtil;
import org.exoplatform.ecm.webui.utils.Utils;
import org.exoplatform.portal.webui.util.Util;
import org.exoplatform.services.cms.actions.ActionServiceContainer;
import org.exoplatform.services.cms.documents.TrashService;
import org.exoplatform.services.cms.folksonomy.NewFolksonomyService;
import org.exoplatform.services.cms.jcrext.activity.ActivityCommonService;
import org.exoplatform.services.cms.link.LinkManager;
import org.exoplatform.services.cms.link.LinkUtils;
import org.exoplatform.services.cms.relations.RelationsService;
import org.exoplatform.services.cms.taxonomy.TaxonomyService;
import org.exoplatform.services.cms.templates.TemplateService;
import org.exoplatform.services.cms.thumbnail.ThumbnailService;
import org.exoplatform.services.ecm.publication.PublicationService;
import org.exoplatform.services.jcr.core.ManageableRepository;
import org.exoplatform.services.jcr.ext.audit.AuditService;
import org.exoplatform.services.jcr.ext.common.SessionProvider;
import org.exoplatform.services.listener.ListenerService;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.wcm.core.NodetypeConstant;
import org.exoplatform.services.wcm.extensions.publication.lifecycle.authoring.AuthoringPublicationConstant;
import org.exoplatform.services.wcm.publication.PublicationDefaultStates;
import org.exoplatform.services.wcm.publication.WCMPublicationService;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;
import org.exoplatform.web.application.ApplicationMessage;
import org.exoplatform.web.application.RequestContext;
import org.exoplatform.webui.config.annotation.ComponentConfig;
import org.exoplatform.webui.config.annotation.EventConfig;
import org.exoplatform.webui.core.UIApplication;
import org.exoplatform.webui.core.UIComponent;
import org.exoplatform.webui.core.UIPopupWindow;
import org.exoplatform.webui.event.Event;
import org.exoplatform.webui.ext.filter.UIExtensionFilter;
import org.exoplatform.webui.ext.filter.UIExtensionFilters;
import org.exoplatform.webui.ext.manager.UIAbstractManager;
import org.exoplatform.webui.ext.manager.UIAbstractManagerComponent;

/**
 * Created by The eXo Platform SARL
 * Author : Hoang Van Hung
 *          hunghvit@gmail.com
 * Aug 6, 2009
 */

@ComponentConfig(
                 events = {
                     @EventConfig(listeners = DeleteManageComponent.DeleteActionListener.class)
                 }
    )

public class DeleteManageComponent extends UIAbstractManagerComponent {

  private static final Log LOG = ExoLogger.getLogger(DeleteManageComponent.class.getName());

  private static final String DELETE_FILE_CONFIRM_TITLE = "UIDeleteFileConfirmMessage";
  private static final String DELETE_FOLDER_CONFIRM_TITLE = "UIDeleteFolderConfirmMessage";
  private static final String DELETE_ITEMS_CONFIRM_TITLE = "UIDeleteItemsConfirmMessage";

  private static final int GENERIC_TYPE = 1;
  private static final int FILE_TYPE = 2;
  private static final int FOLDER_TYPE = 3;


  private static final int FOLDERS = 1; //"001";
  private static final int FILES = 2; //"010";
  private static final int FILES_AND_FOLDERS = 3; //"011";
  private static final int GENERIC = 4; //"100";
  private static final int GENERICS_AND_FOLDERS = 5; //"101";
  private static final int GENERICS_AND_FILES = 6; //"110";
  private static final int GENERICS_AND_FILES_AND_FOLDERS = 7; //"111";

  private static final List<UIExtensionFilter> FILTERS

      = Arrays.asList(new UIExtensionFilter[]{new IsNotSpecificFolderNodeFilter(),
                                              new IsNotLockedFilter(),
                                              new CanDeleteNodeFilter(),
                                              new IsNotTrashHomeNodeFilter(),
                                              new IsNotEditingDocumentFilter(),
                                              new IsNotMandatoryChildNode()});

  @UIExtensionFilters
  public List<UIExtensionFilter> getFilters() {
    return FILTERS;
  }

  private String processRemoveMultiple(String[] nodePaths, Event<?> event) throws Exception {
     StringBuilder trashId = new StringBuilder();
    UIJCRExplorer uiExplorer = getAncestorOfType(UIJCRExplorer.class);
    UIApplication uiApp = uiExplorer.getAncestorOfType(UIApplication.class);
    Arrays.sort(nodePaths,Collections.reverseOrder());
    for (int i = 0; i < nodePaths.length ; i++) {
      try {
        Node node = this.getNodeByPath(nodePaths[i]);
        Validate.isTrue(node != null, "The ObjectId is invalid '" + nodePaths[i] + "'");
        trashId.append(processRemoveOrMoveToTrash(nodePaths[i], node, event, true, true)).append(";");
      } catch (PathNotFoundException path) {
        uiApp.addMessage(new ApplicationMessage("UIPopupMenu.msg.path-not-found-exception", null, ApplicationMessage.WARNING));
        event.getRequestContext().addUIComponentToUpdateByAjax(uiApp.getUIPopupMessages());
      } catch (Exception e) {
        JCRExceptionManager.process(uiApp, e);
      }
    }

    return trashId.substring(0,trashId.length() - 1);
  }

  private void removeAuditForNode(Node node) throws Exception {
    UIJCRExplorer uiExplorer = this.getAncestorOfType(UIJCRExplorer.class);
    ManageableRepository repository = uiExplorer.getRepository();
    SessionProvider sessionProvider = uiExplorer.getSystemProvider();
    Session session = null;
    session = sessionProvider.getSession(node.getSession().getWorkspace().getName(), repository);
    Node rootNode = session.getRootNode();
    if (rootNode.hasNode("exo:audit") && rootNode.getNode("exo:audit").hasNode(node.getUUID())) {
      rootNode.getNode("exo:audit").getNode(node.getUUID()).remove();
      session.save();
    }
  }

  /**
   * Remove or MoveToTrash
   *
   * @param nodePath
   * @param node
   * @param event
   * @param isMultiSelect
   * @param checkToMoveToTrash
   * @return
   *  0: node removed
   * -1: move to trash failed
   * trashId: moved to trash successfully
   * @throws Exception
   */
  private String processRemoveOrMoveToTrash(String nodePath,
                                          Node node,
                                          Event<?> event,
                                          boolean isMultiSelect,
                                          boolean checkToMoveToTrash)
                                              throws Exception {
    String trashId="-1";
    if (!checkToMoveToTrash || Utils.isInTrash(node)) {
      processRemoveNode(nodePath, node, event, isMultiSelect);
      return "0";
    }else {
      trashId = moveToTrash(nodePath, node, event, isMultiSelect);
      if (!trashId.equals("-1")) {
        //Broadcast the event when delete folder, in case deleting file, Thrash service will broadcast event 
        ListenerService listenerService =  WCMCoreUtils.getService(ListenerService.class);

        TrashService trashService = WCMCoreUtils.getService(TrashService.class);
        node = trashService.getNodeByTrashId(trashId);
        if (node != null
            && !isDocumentNodeType(node)
            && !node.getPrimaryNodeType().getName().equals(NodetypeConstant.NT_FILE)) {
          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 (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(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());
            }
          }
        }
      }
    }
    return trashId;
  }

  /**
   * Move Node to Trash
   * Return -1: move failed
   * Return trashId: move successfully with trashId
   * @param srcPath
   * @param node
   * @param event
   * @param isMultiSelect
   * @return
   * @throws Exception
   */
  private String moveToTrash(String srcPath, Node node, Event<?> event, boolean isMultiSelect) throws Exception {
    TrashService trashService = WCMCoreUtils.getService(TrashService.class);
    AuditService auditService = WCMCoreUtils.getService(AuditService.class);
    boolean ret = true;
    String trashId="-1";
    final String virtualNodePath = srcPath;
    UIJCRExplorer uiExplorer = getAncestorOfType(UIJCRExplorer.class);
    UIApplication uiApp = uiExplorer.getAncestorOfType(UIApplication.class);
    try {
      uiExplorer.addLockToken(node);
    } catch (Exception e) {
      JCRExceptionManager.process(uiApp, e);
      return trashId;
    }

    try {
      if (node.isLocked()) {
        LockUtil.removeLock(node);
        node.unlock();
      }
      //remove audit relations 
      if(auditService.hasHistory(node)) {
        auditService.removeHistory(node);
      }
      //remove mixin auditable
      if( node.isNodeType(Utils.EXO_AUDITABLE)){
        node.removeMixin(Utils.EXO_AUDITABLE);
      }
      node.save();
      //remove all relations that refer to this node
      RelationsService relationService = uiApp.getApplicationComponent(RelationsService.class) ;
      PropertyIterator iter = node.getReferences();
      while (iter.hasNext()) {
        Node refNode = iter.nextProperty().getParent();
        relationService.removeRelation(refNode, node.getPath());
      }

      if (!node.isCheckedOut())
        throw new VersionException("node is locked, can't move to trash node :" + node.getPath());
      if (!PermissionUtil.canRemoveNode(node))
        throw new AccessDeniedException("access denied, can't move to trash node:" + node.getPath());
      SessionProvider sessionProvider = uiExplorer.getSessionProvider();
      Node currentNode = uiExplorer.getCurrentNode();

      try {
        trashId = trashService.moveToTrash(node, sessionProvider);
      } catch (PathNotFoundException ex) {
        ret = false;
      }
      String currentPath = LinkUtils.getExistPath(currentNode, uiExplorer.getCurrentPath());
      uiExplorer.setCurrentPath(currentPath);
      uiExplorer.updateAjax(event);

    } catch (LockException e) {
      if (LOG.isErrorEnabled()) {
        LOG.error("node is locked, can't move to trash node :" + node.getPath());
      }
      ApplicationMessage appMessage =
          new ApplicationMessage("UIPopupMenu.msg.can-not-remove-locked-node",
                                 new String[] {node.getPath()}, ApplicationMessage.ERROR);
      appMessage.setArgsLocalized(false);
      uiApp.addMessage(appMessage);
      uiExplorer.updateAjax(event);
      ret = false;
    } catch (VersionException e) {
      if (LOG.isErrorEnabled()) {
        LOG.error("node is checked in, can't move to trash node:" + node.getPath());
      }
      removeMixinEXO_RESTORE_LOCATION(node);
      ApplicationMessage appMessage =
          new ApplicationMessage("UIPopupMenu.msg.can-not-remove-checked-in-node",
                                 new String[] {node.getPath()}, ApplicationMessage.ERROR);
      appMessage.setArgsLocalized(false);
      uiApp.addMessage(appMessage);
      uiExplorer.updateAjax(event);
      ret = false;
    } catch (AccessDeniedException e) {
      if (LOG.isErrorEnabled()) {
        LOG.error("access denied, can't move to trash node:" + node.getPath());
      }
      ApplicationMessage appMessage =
          new ApplicationMessage("UIPopupMenu.msg.access-denied-to-delete",
                                 new String[] {node.getPath()}, ApplicationMessage.ERROR);
      appMessage.setArgsLocalized(false);
      uiApp.addMessage(appMessage);
      uiExplorer.updateAjax(event);
      ret = false;
    } catch (Exception e) {
      if (LOG.isErrorEnabled()) {
        LOG.error("an unexpected error occurs", e);
      }
      uiApp.addMessage(new ApplicationMessage("UIPopupMenu.msg.unexpected-error",
                                              new String[] {node.getPath()}, ApplicationMessage.ERROR));
      uiExplorer.updateAjax(event);
      ret = false;
    }

    if (!isMultiSelect) {
      if (uiExplorer.getCurrentPath().equals(virtualNodePath))
        uiExplorer.setSelectNode(LinkUtils.getParentPath(virtualNodePath));
      else
        uiExplorer.setSelectNode(uiExplorer.getCurrentPath());
    }
    return (ret)?trashId:"-1";
  }

  private void processRemoveNode(String nodePath, Node node, Event<?> event, boolean isMultiSelect)
      throws Exception {
    final String virtualNodePath = nodePath;
    UIJCRExplorer uiExplorer = getAncestorOfType(UIJCRExplorer.class);
    Node currentNode = uiExplorer.getCurrentNode();
    Session session = node.getSession();
    UIApplication uiApp = uiExplorer.getAncestorOfType(UIApplication.class);
    try {
      uiExplorer.addLockToken(node);
    } catch (Exception e) {
      JCRExceptionManager.process(uiApp, e);
      return;
    }
    Node parentNode = node.getParent();
    uiExplorer.addLockToken(parentNode);
    try {

      // If node has taxonomy
      TaxonomyService taxonomyService = uiExplorer.getApplicationComponent(TaxonomyService.class);
      List<Node> listTaxonomyTrees = taxonomyService.getAllTaxonomyTrees();
      List<Node> listExistedTaxonomy = taxonomyService.getAllCategories(node);
      for (Node existedTaxonomy : listExistedTaxonomy) {
        for (Node taxonomyTrees : listTaxonomyTrees) {
          if(existedTaxonomy.getPath().contains(taxonomyTrees.getPath())) {
            taxonomyService.removeCategory(node, taxonomyTrees.getName(),
                                           existedTaxonomy.getPath().substring(taxonomyTrees.getPath().length()));
            break;
          }
        }
      }

      ActionServiceContainer actionService = getApplicationComponent(ActionServiceContainer.class);
      actionService.removeAction(node, uiExplorer.getRepositoryName());
      ThumbnailService thumbnailService = getApplicationComponent(ThumbnailService.class);
      thumbnailService.processRemoveThumbnail(node);
      NewFolksonomyService newFolksonomyService = getApplicationComponent(NewFolksonomyService.class);

      newFolksonomyService.removeTagsOfNodeRecursively(node,
                                                       uiExplorer.getRepository()
                                                       .getConfiguration()
                                                       .getDefaultWorkspaceName(),
                                                       WCMCoreUtils.getRemoteUser(),
                                                       getGroups());
      //trashService.removeRelations(node, uiExplorer.getSystemProvider(), uiExplorer.getRepositoryName());
      if (PermissionUtil.canRemoveNode(node) && node.isNodeType(Utils.EXO_AUDITABLE)) {
        removeAuditForNode(node);
      }

      // Workaround TASK-55628 : Remove referenced live revisions
      updateReferencedLiveRevisionFromCopies(node, session);
      // End Workaround TASK-55628

      //Remove symlinks
      LinkManager linkManager = WCMCoreUtils.getService(LinkManager.class);
      if(!node.isNodeType(NodetypeConstant.EXO_SYMLINK)) {
        for(Node symlink : linkManager.getAllLinks(node, NodetypeConstant.EXO_SYMLINK)) {
          symlink.remove();
          symlink.getSession().save();
        }
      }
      node.remove();
      parentNode.save();
    } catch (VersionException ve) {
      LOG.warn("Error deleting node", ve);
      uiApp.addMessage(new ApplicationMessage("UIPopupMenu.msg.remove-verion-exception", null,
                                              ApplicationMessage.WARNING));

      uiExplorer.updateAjax(event);
      return;
    } catch (ReferentialIntegrityException ref) {
      LOG.warn("Error deleting node", ref);
      session.refresh(false);
      uiExplorer.refreshExplorer();
      uiApp.addMessage(new ApplicationMessage(
                                         "UIPopupMenu.msg.remove-referentialIntegrityException", null,
                                         ApplicationMessage.WARNING));

      uiExplorer.updateAjax(event);
      return;
    } catch (ConstraintViolationException cons) {
      LOG.warn("Error deleting node", cons);
      session.refresh(false);
      uiExplorer.refreshExplorer();
      uiApp.addMessage(new ApplicationMessage("UIPopupMenu.msg.constraintviolation-exception",
                                              null, ApplicationMessage.WARNING));

      uiExplorer.updateAjax(event);
      return;
    } catch (LockException lockException) {
      LOG.warn("Error deleting node", lockException);
      uiApp.addMessage(new ApplicationMessage("UIPopupMenu.msg.node-locked-other-person", null,
                                              ApplicationMessage.WARNING));

      uiExplorer.updateAjax(event);
      return;
    } catch (Exception e) {
      LOG.warn("an unexpected error occurs while removing the node", e);

      JCRExceptionManager.process(uiApp, e);
      return;
    }
    if (!isMultiSelect) {
      if (currentNode.getPath().equals(virtualNodePath))
        uiExplorer.setSelectNode(LinkUtils.getParentPath(virtualNodePath));
      else
        uiExplorer.setSelectNode(currentNode.getPath());
    }
  }

  /**
   * Workaround TASK-55628 : This method will search for nodes which references
   * the same 'liveRevision' than the current node to delete.
   * 
   * In general case, this incoherence is due to the fact that the copy/paste node didn't
   * considered the fact that a copied node must reference its own liveRevision.
   * 
   * @param nodeToDelete
   * @param session
   * @throws Exception
   */
  private void updateReferencedLiveRevisionFromCopies(Node nodeToDelete, Session session) throws Exception {
    PublicationService publicationService = PortalContainer.getInstance().getComponentInstanceOfType(PublicationService.class);
    WCMPublicationService wcmPublicationService = PortalContainer.getInstance()
                                                                 .getComponentInstanceOfType(WCMPublicationService.class);
    if (wcmPublicationService.isEnrolledInWCMLifecycle(nodeToDelete)
        && nodeToDelete.hasProperty(AuthoringPublicationConstant.LIVE_REVISION_PROP)) {
      String liveRevisionReference = nodeToDelete.getProperty(AuthoringPublicationConstant.LIVE_REVISION_PROP).getString();
      Query query = session.getWorkspace()
                           .getQueryManager()
                           .createQuery("SELECT * from " + AuthoringPublicationConstant.PUBLICATION_LIFECYCLE_TYPE + " WHERE "
                               + AuthoringPublicationConstant.LIVE_REVISION_PROP + " = '" + liveRevisionReference + "'",
                                        Query.SQL);
      NodeIterator nodes = query.execute().getNodes();
      String siteName = Util.getPortalRequestContext().getPortalOwner();
      String remoteUser = Util.getPortalRequestContext().getRemoteUser();

      while (nodes.hasNext()) {
        Node copiedNode = nodes.nextNode();
        String lifecycleName = publicationService.getNodeLifecycleName(copiedNode);
        String originalState = null;
        if (copiedNode.hasProperty(AuthoringPublicationConstant.CURRENT_STATE)) {
          originalState = copiedNode.getProperty(AuthoringPublicationConstant.CURRENT_STATE).getString();
        } else {
          originalState = PublicationDefaultStates.DRAFT;
        }

        wcmPublicationService.unsubcribeLifecycle(copiedNode);
        wcmPublicationService.enrollNodeInLifecycle(copiedNode, lifecycleName);
        // Make content draft again
        wcmPublicationService.updateLifecyleOnChangeContent(copiedNode, siteName, remoteUser);

        // Change node publication status to original state
        String newState = publicationService.getCurrentState(copiedNode);
        if (!StringUtils.equals(newState, originalState)) {
          wcmPublicationService.updateLifecyleOnChangeContent(copiedNode, siteName, remoteUser, originalState);
        }
      }
    }
  }

  private void processRemoveMultiple(String[] nodePaths, String[] wsNames, Event<?> event)
      throws Exception {
    for (int i = 0; i < nodePaths.length; i++) {
      processRemove(nodePaths[i], wsNames[i], event, true);
    }
  }

  private void processRemove(String nodePath, String wsName, Event<?> event, boolean isMultiSelect)
      throws Exception {
    if (wsName == null) {
      wsName = getDefaultWorkspace();
    }
    doDelete(wsName.concat(":").concat(nodePath), event);
  }

  private String getDefaultWorkspace() {
    UIJCRExplorer uiExplorer = getAncestorOfType(UIJCRExplorer.class);
    return uiExplorer.getCurrentDriveWorkspace();
  }

  public void doDelete(String nodePath, String wsName, Event<?> event) throws Exception {
    UIJCRExplorer uiExplorer = getAncestorOfType(UIJCRExplorer.class);
    if (nodePath.indexOf(";") > -1) {
      processRemoveMultiple(nodePath.split(";"), wsName.split(";"), event);
    } else {
      processRemove(nodePath, wsName, event, false);
    }
    uiExplorer.updateAjax(event);
  }

  public void doDeleteWithoutTrash(String nodePath, Event<?> event) throws Exception {
    doDelete(nodePath, event, false);
  }

  public void doDelete(String nodePath, Event<?> event) throws Exception {
    doDelete(nodePath, event, true);
  }

  public void doDelete(String nodePath, Event<?> event, boolean checkToMoveToTrash) throws Exception {
    RequestContext context = RequestContext.getCurrentInstance();
    UIJCRExplorer uiExplorer = getAncestorOfType(UIJCRExplorer.class);
    UIWorkingArea uiWorkingArea = getAncestorOfType(UIWorkingArea.class);
    ResourceBundle res = context.getApplicationResourceBundle();
    String deleteNotice = "";
    String deleteNoticeParam = "";
    String trashId = "";
    if (nodePath.indexOf(";") > -1) {
      trashId = processRemoveMultiple(Utils.removeChildNodes(nodePath), event);
      if(checkToMoveToTrash) deleteNotice = "UIWorkingArea.msg.feedback-delete-multi";
      else deleteNotice = "UIWorkingArea.msg.feedback-delete-permanently-multi";
      deleteNoticeParam = String.valueOf(nodePath.split(";").length);
    } else {
      UIApplication uiApp = uiExplorer.getAncestorOfType(UIApplication.class);
      // Prepare to remove
      try {
        Node node = this.getNodeByPath(nodePath);
        if(checkToMoveToTrash) deleteNotice = "UIWorkingArea.msg.feedback-delete";
        else deleteNotice = "UIWorkingArea.msg.feedback-delete-permanently";
        deleteNoticeParam = StringEscapeUtils.unescapeHtml4(Utils.getTitle(node));
        if (node != null) {
          trashId = processRemoveOrMoveToTrash(node.getPath(), node, event, false, checkToMoveToTrash);
        }
      } catch (PathNotFoundException path) {
        uiApp.addMessage(new ApplicationMessage("UIPopupMenu.msg.path-not-found-exception", null,
                                                ApplicationMessage.WARNING));
        event.getRequestContext().addUIComponentToUpdateByAjax(uiApp.getUIPopupMessages());
        return;
      } catch (Exception e) {
        JCRExceptionManager.process(uiApp, e);
        return;
      }
    }
    deleteNotice = res.getString(deleteNotice);
    deleteNotice = deleteNotice.replace("{" + 0 + "}", deleteNoticeParam);
    deleteNotice = deleteNotice.replace("\"", "'");
    deleteNotice = StringEscapeUtils.escapeHtml4(deleteNotice);
    if(checkToMoveToTrash) {
      String undoLink = getUndoLink(trashId);
      uiWorkingArea.setDeleteNotice(deleteNotice);
      uiWorkingArea.setNodePathDelete(undoLink);
    } else {
      uiWorkingArea.setWCMNotice(deleteNotice);
    }
    uiExplorer.updateAjax(event);
  }
  /**
   * Get undo link to restore nodes that deleted
   *
   * @param trashId node path of nodes want to restore
   * @throws Exception
   */
  private String getUndoLink(String trashId) throws Exception {
    String undoLink = "";
    TrashService trashService = WCMCoreUtils.getService(TrashService.class);
    
    UIJCRExplorer uiExplorer = getAncestorOfType(UIJCRExplorer.class);
    PortletPreferences portletPrefs = uiExplorer.getPortletPreferences();

    String trashWorkspace = portletPrefs.getValue(Utils.TRASH_WORKSPACE, "");
    StringBuffer sb = new StringBuffer();
    if (trashId.indexOf(";") > -1) {
      String[] nodePaths = trashId.split(";");
      for(int i=0; i<nodePaths.length; i++) {        
        trashId = nodePaths[i].substring(nodePaths[i].indexOf(":") + 1, nodePaths[i].length());
        sb.append(trashWorkspace).append(":").append(trashService.getNodeByTrashId(trashId).getPath()).append(";");
      }
      undoLink = sb.toString();
      if(undoLink.length() > 0) undoLink = undoLink.substring(0,undoLink.length()-1);
    } else {
      trashId = trashId.substring(trashId.indexOf(":") + 1, trashId.length());
      Node tmpNode = trashService.getNodeByTrashId(trashId);
      sb.append(tmpNode.getPath()).append(";");
      undoLink = sb.toString();
      if(undoLink.length() > 0) {
        undoLink = undoLink.substring(0,undoLink.length()-1);
        undoLink =  trashWorkspace + ":" +undoLink;
      }
    }
    return undoLink;
  }

  private boolean isInTrashFolder(String nodePath) throws Exception {
    UIJCRExplorer uiExplorer = getAncestorOfType(UIJCRExplorer.class);
    String wsName = null;
    Session session = null;
    String[] nodePaths = nodePath.split(";");
    for(int i=0; i<nodePaths.length; i++) {
      Matcher matcher = UIWorkingArea.FILE_EXPLORER_URL_SYNTAX.matcher(nodePaths[i]);
      if (matcher.find()) {
        wsName = matcher.group(1);
        nodePath = matcher.group(2);
        session = uiExplorer.getSessionByWorkspace(wsName);
        Node node = uiExplorer.getNodeByPath(nodePath, session, false);
        return Utils.isInTrash(node);
      }
    }
    return false;
  }

  private boolean isDocumentNodeType(Node node) throws Exception {
    boolean isDocument = true;
    TemplateService templateService = PortalContainer.getInstance().getComponentInstanceOfType(TemplateService.class);
    isDocument = templateService.getAllDocumentNodeTypes().contains(node.getPrimaryNodeType().getName());
    return isDocument;
  }
  /**
   * Get the content type of one node
   *
   * @param nodePath node path of one node
   * @throws Exception
   */
  private int getContentType(String nodePath) throws Exception {
    int content_type = 1;
    Node node = getNodeByPath(nodePath);
    String primaryType = node.getPrimaryNodeType().getName();
    if(node.isNodeType(NodetypeConstant.NT_FILE)) content_type = 2;
    else if (primaryType.equals(NodetypeConstant.NT_FOLDER) || primaryType.equals(NodetypeConstant.NT_UNSTRUCTURED))
      content_type = 3;
    else content_type = 1;
    return content_type;
  }
  /**
   * Get the content type of multiple nodes
   *
   * @param nodePath node path of multiple nodes
   * @throws Exception
   */
  private int getMultiContentType(String nodePath) throws Exception {
    StringBuffer sBuffer = new StringBuffer();
    String[] nodePaths = nodePath.split(";");
    boolean isGeneric = false;
    boolean isFile = false;
    boolean isFolder = false;

    for(int i=0; i<nodePaths.length; i++) {
      Node node = getNodeByPath(nodePaths[i]);
      String primaryType = node.getPrimaryNodeType().getName();
      if(node.isNodeType(NodetypeConstant.NT_FILE)) isFile = true;
      else if (primaryType.equals(NodetypeConstant.NT_FOLDER) || primaryType.equals(NodetypeConstant.NT_UNSTRUCTURED))
        isFolder = true;
      else isGeneric = true;
    }
    if(isGeneric) sBuffer.append("1");
    else sBuffer.append("0");

    if(isFile) sBuffer.append("1");
    else sBuffer.append("0");

    if(isFolder) sBuffer.append("1");
    else sBuffer.append("0");

    return Integer.parseInt(sBuffer.toString(),2);
  }

  public static void deleteManage(Event<? extends UIComponent> event) throws Exception {
    UIWorkingArea uiWorkingArea = event.getSource().getParent();
    UIJCRExplorer uiExplorer = event.getSource().getAncestorOfType(UIJCRExplorer.class);
    String nodePath = event.getRequestContext().getRequestParameter(OBJECTID);
    UIConfirmMessage uiConfirmMessage = uiWorkingArea.createUIComponent(UIConfirmMessage.class, null, null);
    UIApplication uiApp = uiExplorer.getAncestorOfType(UIApplication.class);
    DeleteManageComponent deleteManageComponent = uiWorkingArea.getChild(DeleteManageComponent.class);

    //get nodes that have relations referring to them
    List<String> listNodesHaveRelations = null;
    try {
      listNodesHaveRelations = checkRelations(nodePath, uiExplorer);
    } catch (PathNotFoundException pathEx) {
      uiApp.addMessage(new ApplicationMessage("UIPopupMenu.msg.path-not-found-exception", null, ApplicationMessage.WARNING));

      return;
    }

    boolean isInTrashFolder = deleteManageComponent.isInTrashFolder(nodePath);
    uiConfirmMessage.setNodeInTrash(isInTrashFolder);
    String nodeName = nodePath;
    int contentType = 1;
    int multiContentType = 1;
    String message_key = "";
    // Check and set the title for Delete Confirmation Dialog
    if(nodePath.indexOf(";") > 0) {
      uiConfirmMessage.setId(DELETE_ITEMS_CONFIRM_TITLE);
      multiContentType = deleteManageComponent.getMultiContentType(nodePath);
    } else {
      Node node = deleteManageComponent.getNodeByPath(nodePath);
      if(node != null)
        nodeName = StringEscapeUtils.unescapeHtml4(Utils.getTitle(node));
      contentType = deleteManageComponent.getContentType(nodePath);
      if(contentType == FILE_TYPE)
        uiConfirmMessage.setId(DELETE_FILE_CONFIRM_TITLE);
      else if (contentType == FOLDER_TYPE)
        uiConfirmMessage.setId(DELETE_FOLDER_CONFIRM_TITLE);
    }

    //show confirm message
    if (listNodesHaveRelations != null && listNodesHaveRelations.size() > 0) { 
      // there are some nodes which have relations referring to them
      // in the deleting node list
      // build node list to string to add into the confirm message
      StringBuffer sb = new StringBuffer();
      for (int i = 0; i < listNodesHaveRelations.size(); i++) {
        sb.append("'").append(listNodesHaveRelations.get(i)).append("', ");
      }
      //remove "," character at the end of string

      // Node has relations means it is not in Trash folder and is not Folder
      if (nodePath.indexOf(";") < 0) { // in case: delete one node that has relations
        if(contentType == GENERIC_TYPE)
          message_key = "UIWorkingArea.msg.confirm-delete-has-relations";
        else if(contentType == FILE_TYPE)
          message_key = "UIWorkingArea.msg.confirm-delete-file-has-relations";
        else if (contentType == FOLDER_TYPE) {
          message_key = "UIWorkingArea.msg.confirm-delete-folder-has-relations";
        }
        uiConfirmMessage.setMessageKey(message_key);
        uiConfirmMessage.setArguments(new String[] { nodeName });
      } else { // in case: delete multiple node have relations

        switch(multiContentType) {
        case FOLDERS: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-folder-have-relations"; break;
        case FILES: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-file-have-relations"; break;
        case FILES_AND_FOLDERS: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-file-and-folder-have-relations";
        break;
        case GENERIC: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-generic-have-relations"; break;
        case GENERICS_AND_FOLDERS: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-generic-and" +
            "-folder-have-relations"; break;
        case GENERICS_AND_FILES: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-generic-" +
            "and-file-have-relations"; break;
        case GENERICS_AND_FILES_AND_FOLDERS: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-generic-" +
            "and-file-and-folder-have-relations"; break;
        default: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-generic-have-relations"; break;
        }

        uiConfirmMessage.setMessageKey(message_key);
        uiConfirmMessage.setArguments(new String[] { Integer.toString(nodePath.split(";").length) });
      }

    } else {  //there isn't any node which has relations referring to it in the deleting node list
      if (isInTrashFolder) {
        if (nodePath.indexOf(";") > -1) { // delete multi

          switch(multiContentType) {
          case FOLDERS: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-folder-permanently"; break;
          case FILES: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-file-permanently"; break;
          case FILES_AND_FOLDERS: 
            message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-file-and-folder-permanently"; break;
          case GENERIC: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-generic-permanently"; break;
          case GENERICS_AND_FOLDERS: 
            message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-generic-and-folder-permanently"; break;
          case GENERICS_AND_FILES: 
            message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-generic-and-file-permanently"; break;
          case GENERICS_AND_FILES_AND_FOLDERS: 
            message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-generic-and-file-and-folder-permanently"; break;
          default: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-generic-permanently"; break;
          }

          uiConfirmMessage.setMessageKey(message_key);
          uiConfirmMessage.setArguments(new String[] { Integer.toString(nodePath.split(";").length) });
        } else { // delete one
          if(contentType == GENERIC_TYPE)
            message_key = "UIWorkingArea.msg.confirm-delete-permanently";
          else if(contentType == FILE_TYPE)
            message_key = "UIWorkingArea.msg.confirm-delete-file-permanently";
          else if(contentType == FOLDER_TYPE)
            message_key = "UIWorkingArea.msg.confirm-delete-folder-permanently";
          uiConfirmMessage.setMessageKey(message_key);
          uiConfirmMessage.setArguments(new String[] { nodeName });
        }
      } else {
        if (nodePath.indexOf(";") > -1) { // delete multi

          switch(multiContentType) {
          case FOLDERS: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-folder"; break;
          case FILES: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-file"; break;
          case FILES_AND_FOLDERS: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-file-and-folder"; break;
          case GENERIC: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-generic"; break;
          case GENERICS_AND_FOLDERS: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-generic-and-folder"; break;
          case GENERICS_AND_FILES: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-generic-and-file"; break;
          case GENERICS_AND_FILES_AND_FOLDERS: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-generic-and-file-" +
              "and-folder"; break;
          default: message_key = "UIWorkingArea.msg.confirm-delete-multi-nodes-generic"; break;
          }
          uiConfirmMessage.setMessageKey(message_key);
          uiConfirmMessage.setArguments(new String[] { Integer.toString(nodePath.split(";").length) });
        } else { // delete one
          if(contentType == GENERIC_TYPE)
            message_key = "UIWorkingArea.msg.confirm-delete";
          else if(contentType == FILE_TYPE)
            message_key = "UIWorkingArea.msg.confirm-delete-file";
          else if(contentType == FOLDER_TYPE)
            message_key = "UIWorkingArea.msg.confirm-delete-folder";
          uiConfirmMessage.setMessageKey(message_key);
          uiConfirmMessage.setArguments(new String[] { nodeName });
        }
      }
    }

    uiConfirmMessage.setNodePath(nodePath);
    UIPopupWindow popUp = uiExplorer.getChild(UIPopupWindow.class);
    popUp.setUIComponent(uiConfirmMessage);
    popUp.setShowMask(true);
    popUp.setShow(true);
    event.getRequestContext().addUIComponentToUpdateByAjax(popUp);

  }

  /**
   * This function uses to get a node list that have relations referring to them in the deleting node list
   *
   * @param nodePath The list of nodes that user wants to delete
   * @param uiExplorer uiExplorer
   * @return The list of nodes that have relations referring to them
   * @throws Exception
   */
  private static List<String> checkRelations(String nodePath, UIJCRExplorer uiExplorer) throws Exception{

    Node node = null;
    String wsName = null;
    Session session = null;
    String[] nodePaths = nodePath.split(";");
    RelationsService rlService = WCMCoreUtils.getService(RelationsService.class);
    SessionProvider sessionProvider = WCMCoreUtils.getUserSessionProvider();
    List<String> listNodesHaveRelations = new ArrayList<String>();
    for (int i = 0; i < nodePaths.length; i++) {
      Matcher matcher = UIWorkingArea.FILE_EXPLORER_URL_SYNTAX.matcher(nodePaths[i]);
      if (matcher.find()) {
        wsName = matcher.group(1);
        nodePath = matcher.group(2);
        session = uiExplorer.getSessionByWorkspace(wsName);
        node = uiExplorer.getNodeByPath(nodePath, session, false);
        if (rlService.getRelations(node, sessionProvider).size()>0) {
          //check references
          listNodesHaveRelations.add(nodePath);
        }
      } else {
        throw new IllegalArgumentException("The ObjectId is invalid '" + nodePath + "'");
      }
    }
    return listNodesHaveRelations;
  }

  /**
   * Get node by node path.
   *
   * @param nodePath node path of specific node with syntax [workspace:node path]
   * @return Node of specific node nath
   * @throws Exception
   */
  private Node getNodeByPath(String nodePath) throws Exception {
    UIJCRExplorer uiExplorer = this.getAncestorOfType(UIJCRExplorer.class);
    Matcher matcher = UIWorkingArea.FILE_EXPLORER_URL_SYNTAX.matcher(nodePath);
    if (!matcher.find()) return null;
    String wsName = matcher.group(1);
    nodePath = matcher.group(2);
    Session session = uiExplorer.getSessionByWorkspace(wsName);
    return uiExplorer.getNodeByPath(nodePath, session, false);
  }

  private String getGroups() throws Exception {
    StringBuilder ret = new StringBuilder();
    for (String group : Utils.getGroups())
      ret.append(group).append(';');
    ret.deleteCharAt(ret.length() - 1);
    return ret.toString();
  }

  @Override
  public Class<? extends UIAbstractManager> getUIAbstractManagerClass() {
    return null;
  }

  public static class DeleteActionListener extends UIWorkingAreaActionListener<DeleteManageComponent> {
    public void processEvent(Event<DeleteManageComponent> event) throws Exception {
      deleteManage(event);
    }
  }

  private void removeMixinEXO_RESTORE_LOCATION(Node node) throws Exception {
    if (node.isNodeType(Utils.EXO_RESTORELOCATION)) {
      node.removeMixin(Utils.EXO_RESTORELOCATION);
      node.save();
    }
  }
}
