View Javadoc
1   /*
2    * Copyright (C) 2003-2008 eXo Platform SAS.
3    *
4    * This program is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Affero General Public License
6    * as published by the Free Software Foundation; either version 3
7    * of the License, or (at your option) any later version.
8    *
9    * This program is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU General Public License
15   * along with this program; if not, see<http://www.gnu.org/licenses/>.
16   */
17  package org.exoplatform.services.cms.documents.impl;
18  
19  import org.exoplatform.container.ExoContainer;
20  import org.exoplatform.container.ExoContainerContext;
21  import org.exoplatform.container.xml.InitParams;
22  import org.exoplatform.services.cache.CacheService;
23  import org.exoplatform.services.cache.ExoCache;
24  import org.exoplatform.services.cms.documents.TrashService;
25  import org.exoplatform.services.cms.folksonomy.NewFolksonomyService;
26  import org.exoplatform.services.cms.impl.Utils;
27  import org.exoplatform.services.cms.jcrext.activity.ActivityCommonService;
28  import org.exoplatform.services.cms.link.LinkManager;
29  import org.exoplatform.services.cms.taxonomy.TaxonomyService;
30  import org.exoplatform.services.jcr.RepositoryService;
31  import org.exoplatform.services.jcr.access.PermissionType;
32  import org.exoplatform.services.jcr.core.ManageableRepository;
33  import org.exoplatform.services.jcr.ext.common.SessionProvider;
34  import org.exoplatform.services.jcr.impl.core.ItemImpl;
35  import org.exoplatform.services.jcr.impl.core.SessionImpl;
36  import org.exoplatform.services.jcr.impl.core.query.QueryImpl;
37  import org.exoplatform.services.listener.ListenerService;
38  import org.exoplatform.services.log.ExoLogger;
39  import org.exoplatform.services.log.Log;
40  import org.exoplatform.services.seo.SEOService;
41  import org.exoplatform.services.wcm.core.NodetypeConstant;
42  import org.exoplatform.services.wcm.utils.WCMCoreUtils;
43  import org.gatein.pc.api.PortletInvoker;
44  import org.gatein.pc.api.info.PortletInfo;
45  import org.gatein.pc.api.info.PreferencesInfo;
46  
47  import javax.jcr.Node;
48  import javax.jcr.NodeIterator;
49  import javax.jcr.RepositoryException;
50  import javax.jcr.Session;
51  import javax.jcr.query.Query;
52  import javax.jcr.query.QueryManager;
53  import javax.jcr.query.QueryResult;
54  
55  import java.util.ArrayList;
56  import java.util.List;
57  import java.util.Set;
58  
59  /**
60   * Created by The eXo Platform SARL Author : Dang Van Minh
61   * minh.dang@exoplatform.com Oct 6, 2009 3:39:53 AM
62   */
63  public class TrashServiceImpl implements TrashService {
64  
65    private static final String FILE_EXPLORER_PORTLET = "FileExplorerPortlet";
66    private final static String CACHE_NAME = "ecms.seo";
67    final static public String EXO_TOTAL = "exo:total";
68    final static public String MIX_REFERENCEABLE = "mix:referenceable";
69    final static public String TAXONOMY_LINK   = "exo:taxonomyLink";
70    final static public String UUID         = "exo:uuid";
71    final static public String SYMLINK      = "exo:symlink";
72    final static public String EXO_WORKSPACE = "exo:workspace";
73    final static public String EXO_TARGETWS = "exo:targetWorkspace";
74    final static public String EXO_TARGETPATH = "exo:targetPath";
75  
76    private RepositoryService repositoryService;
77    private LinkManager linkManager;
78    private TaxonomyService taxonomyService_;
79    private String trashWorkspace_;
80    private String trashHome_;
81    private ExoCache<String, Object> cache;
82  
83    /** The log. */
84    private static final Log LOG = ExoLogger.getLogger(TrashServiceImpl.class.getName());
85  
86    public TrashServiceImpl(RepositoryService repositoryService,
87                            LinkManager linkManager,
88                            TaxonomyService taxonomyService,
89                            InitParams initParams) throws Exception {
90      this.repositoryService = repositoryService;
91      this.linkManager = linkManager;
92      this.taxonomyService_ = taxonomyService;
93      this.trashWorkspace_ = initParams.getValueParam("trashWorkspace").getValue();
94      this.trashHome_ = initParams.getValueParam("trashHomeNodePath").getValue();
95      cache = WCMCoreUtils.getService(CacheService.class).getCacheInstance(CACHE_NAME);
96      ExoContainer manager = ExoContainerContext.getCurrentContainer();
97      PortletInvoker portletInvoker = (PortletInvoker)manager.getComponentInstance(PortletInvoker.class);
98      if (portletInvoker != null) {
99        Set<org.gatein.pc.api.Portlet> portlets = portletInvoker.getPortlets();
100       for (org.gatein.pc.api.Portlet portlet : portlets) {
101         PortletInfo info = portlet.getInfo();
102         String portletName = info.getName();
103         if (FILE_EXPLORER_PORTLET.equalsIgnoreCase(portletName)) {
104           PreferencesInfo prefs = info.getPreferences();
105           String trashWorkspace = prefs.getPreference("trashWorkspace").getDefaultValue().get(0);
106           String trashHome = prefs.getPreference("trashHomeNodePath").getDefaultValue().get(0);
107           if (trashWorkspace != null && !trashWorkspace.equals(this.trashWorkspace_)) {
108             this.trashWorkspace_ = trashWorkspace;
109           }
110 
111           if (trashHome != null && !trashHome.equals(this.trashHome_)) {
112             this.trashHome_ = trashHome;
113           }
114           break;
115         }
116       }
117     }
118   }
119 
120 
121   /**
122    * {@inheritDoc}
123    */
124   public String moveToTrash(Node node, SessionProvider sessionProvider) throws Exception {
125     return moveToTrash(node, sessionProvider, 0);
126   }
127 
128 
129   /**
130    *{@inheritDoc}
131    */
132   @Override
133   public String moveToTrash(Node node,
134                           SessionProvider sessionProvider,
135                           int deep) throws Exception {
136     ((SessionImpl)node.getSession()).getActionHandler().preRemoveItem((ItemImpl)node);
137     String trashId="-1";
138     String nodeName = node.getName();
139     Session nodeSession = node.getSession();
140     nodeSession.checkPermission(node.getPath(), PermissionType.REMOVE);  
141     if (deep == 0 && !node.isNodeType(SYMLINK)) {
142       try {
143         Utils.removeDeadSymlinks(node);
144       } catch (Exception e) {
145         if (LOG.isWarnEnabled()) {
146           LOG.warn(e.getMessage());
147         }
148       }
149     }
150     ListenerService listenerService =  WCMCoreUtils.getService(ListenerService.class);
151     //listenerService.broadcast(ActivityCommonService.FILE_REMOVE_ACTIVITY, null, node);
152     if (node.getPrimaryNodeType().getName().equals(NodetypeConstant.NT_FILE) || node.isNodeType(NodetypeConstant.EXO_SYMLINK)) {
153       ActivityCommonService activityService = WCMCoreUtils.getService(ActivityCommonService.class);
154       if (activityService.isBroadcastNTFileEvents(node)) {
155         listenerService.broadcast(ActivityCommonService.FILE_REMOVE_ACTIVITY, null, node);
156       }
157     } else{
158       listenerService.broadcast(ActivityCommonService.FILE_REMOVE_ACTIVITY, null, node);
159     }
160     String originalPath = node.getPath();
161     String nodeWorkspaceName = nodeSession.getWorkspace().getName();
162     //List<Node> categories = taxonomyService_.getAllCategories(node, true);
163     String nodeUUID = node.isNodeType(MIX_REFERENCEABLE) ? node.getUUID() : null;
164     if (node.isNodeType(SYMLINK)) nodeUUID = null;
165     String taxonomyLinkUUID = node.isNodeType(TAXONOMY_LINK) ? node.getProperty(UUID).getString() : null;
166     String taxonomyLinkWS = node.isNodeType(TAXONOMY_LINK) ? node.getProperty(EXO_WORKSPACE).getString() : null;
167     if(nodeUUID != null) {
168       SEOService seoService = WCMCoreUtils.getService(SEOService.class);
169       cache.remove(seoService.getHash(nodeUUID));
170     }
171     if (!node.isNodeType(EXO_RESTORE_LOCATION)) {
172       String restorePath = fixRestorePath(node.getPath());
173       ManageableRepository manageableRepository = repositoryService.getCurrentRepository();
174       Session trashSession = WCMCoreUtils.getSystemSessionProvider().getSession(this.trashWorkspace_, manageableRepository);
175       String actualTrashPath = this.trashHome_ + (this.trashHome_.endsWith("/") ? "" : "/")
176           + fixRestorePath(nodeName);
177       if (trashSession.getWorkspace().getName().equals(
178           nodeSession.getWorkspace().getName())) {
179         trashSession.getWorkspace().move(node.getPath(),
180             actualTrashPath);
181       } else {
182         //clone node in trash folder
183         trashSession.getWorkspace().clone(nodeWorkspaceName,
184             node.getPath(), actualTrashPath, true);
185         if (node.isNodeType(MIX_REFERENCEABLE)) {
186             Node clonedNode = trashSession.getNodeByUUID(node.getUUID());
187             //remove link from tag to node
188 
189             NewFolksonomyService newFolksonomyService = WCMCoreUtils.getService(NewFolksonomyService.class);
190 
191             String tagWorkspace = manageableRepository.getConfiguration().getDefaultWorkspaceName();
192             List<Node> tags = newFolksonomyService.getLinkedTagsOfDocument(node, tagWorkspace);
193             for (Node tag : tags) {
194               newFolksonomyService.removeTagOfDocument(tag.getPath(), node, tagWorkspace);
195               linkManager.createLink(tag, clonedNode);
196               long total = tag.hasProperty(EXO_TOTAL) ?
197                   tag.getProperty(EXO_TOTAL).getLong() : 0;
198                   tag.setProperty(EXO_TOTAL, total - 1);
199                   tag.getSession().save();
200             }
201         }
202         node.remove();
203       }
204       
205       trashId = addRestorePathInfo(nodeName, restorePath, nodeWorkspaceName);
206 
207       trashSession.save();
208       
209       //check and delete target node when there is no its symlink
210       if (deep == 0 && taxonomyLinkUUID != null && taxonomyLinkWS != null) {
211         Session targetNodeSession = sessionProvider.getSession(taxonomyLinkWS, manageableRepository);
212         Node targetNode = null;
213         try {
214           targetNode = targetNodeSession.getNodeByUUID(taxonomyLinkUUID);
215         } catch (Exception e) {
216           if (LOG.isWarnEnabled()) {
217             LOG.warn(e.getMessage());
218           }
219         }
220         if (targetNode != null && isInTaxonomyTree(originalPath, targetNode)) {
221           List<Node> symlinks = linkManager.getAllLinks(targetNode, SYMLINK, sessionProvider);
222           boolean found = false;
223           for (Node symlink : symlinks)
224             if (!symlink.isNodeType(EXO_RESTORE_LOCATION)) {
225               found = true;
226               break;
227             }
228           if (!found) {
229             this.moveToTrash(targetNode, sessionProvider);
230           }
231         }
232       }
233       
234       trashSession.save();
235     }
236     return trashId;
237   }
238  
239 /** Store original path of deleted node.
240   * Return restore_id of deleted node. Use when find node in trash to undo
241   * @param nodeName name of removed node
242   * @param restorePath path of node before removing
243   * @param nodeWs node workspace before removing
244   * @throws RepositoryException 
245   * @throws LockException 
246   * @throws ConstraintViolationException 
247   * @throws VersionException 
248   * @throws NoSuchNodeTypeException 
249   */
250   private String addRestorePathInfo(String nodeName, String restorePath, String nodeWs) throws Exception {
251     String restoreId = java.util.UUID.randomUUID().toString();
252     NodeIterator nodes = this.getTrashHomeNode().getNodes(nodeName);
253     Node node = null;
254     while (nodes.hasNext()) {
255       Node currentNode = nodes.nextNode();
256       if (node == null) {
257         node = currentNode;
258       } else {
259         if (node.getIndex() < currentNode.getIndex()) {
260           node = currentNode;
261         }
262       }
263     }
264     if (node != null) {
265       node.addMixin(EXO_RESTORE_LOCATION);
266       node.setProperty(RESTORE_PATH, restorePath);
267       node.setProperty(RESTORE_WORKSPACE, nodeWs);
268       node.setProperty(TRASH_ID, restoreId);
269       node.save();
270     }
271     return restoreId;
272   }
273   
274   /**
275    *
276    * @param path
277    * @param targetNode
278    * @return
279    */
280   private boolean isInTaxonomyTree(String path, Node targetNode) {
281     try {
282       List<Node> taxonomyTrees = taxonomyService_.getAllTaxonomyTrees(true);
283       for (Node tree : taxonomyTrees)
284         if (path.contains(tree.getPath())) {
285           Node taxonomyActionNode = tree.getNode("exo:actions/taxonomyAction");
286           String targetWorkspace = taxonomyActionNode.getProperty(EXO_TARGETWS).getString();
287           String targetPath = taxonomyActionNode.getProperty(EXO_TARGETPATH).getString();
288           if (targetNode.getSession().getWorkspace().getName().equals(targetWorkspace)
289               && targetNode.getPath().contains(targetPath))
290             return true;
291           break;
292         }
293       return false;
294     } catch (Exception e) {
295       return false;
296     }
297   }
298 
299 
300   /**
301    * {@inheritDoc}
302    */
303   public void restoreFromTrash(String trashNodePath,
304                                SessionProvider sessionProvider) throws Exception {
305     restoreFromTrash(trashNodePath, sessionProvider, 0);
306   }
307 
308   private void restoreFromTrash(String trashNodePath,
309       SessionProvider sessionProvider, int deep) throws Exception {
310 
311     Node trashHomeNode = this.getTrashHomeNode();
312     Session trashNodeSession = trashHomeNode.getSession();
313     Node trashNode = (Node)trashNodeSession.getItem(trashNodePath);
314     String trashWorkspace = trashNodeSession.getWorkspace().getName();
315     String restoreWorkspace = trashNode.getProperty(RESTORE_WORKSPACE).getString();
316     String restorePath = trashNode.getProperty(RESTORE_PATH).getString();
317     String nodeUUID = trashNode.isNodeType(MIX_REFERENCEABLE) ? trashNode.getUUID() : null;
318     if (trashNode.isNodeType(SYMLINK)) nodeUUID = null;
319     String taxonomyLinkUUID = trashNode.isNodeType(TAXONOMY_LINK) ? trashNode.getProperty(UUID).getString() : null;
320     String taxonomyLinkWS = trashNode.isNodeType(TAXONOMY_LINK) ? trashNode.getProperty(EXO_WORKSPACE).getString() : null;
321 
322     ManageableRepository manageableRepository = repositoryService.getCurrentRepository();
323     Session restoreSession = sessionProvider.getSession(restoreWorkspace,  manageableRepository);
324 
325     if (restoreWorkspace.equals(trashWorkspace)) {
326       trashNodeSession.getWorkspace().move(trashNodePath, restorePath);
327     } else {
328       //clone node
329       restoreSession.getWorkspace().clone(
330           trashWorkspace, trashNodePath, restorePath, true);
331       if (trashNode.isNodeType(MIX_REFERENCEABLE)) {
332         Node restoredNode = restoreSession.getNodeByUUID(trashNode.getUUID());
333 
334         //remove link from tag to node in trash
335         NewFolksonomyService newFolksonomyService = WCMCoreUtils.getService(NewFolksonomyService.class);
336 
337         String tagWorkspace = manageableRepository.getConfiguration().getDefaultWorkspaceName();
338         List<Node> tags = newFolksonomyService.getLinkedTagsOfDocument(trashNode, tagWorkspace);
339         for (Node tag : tags) {
340           newFolksonomyService.removeTagOfDocument(tag.getPath(), trashNode, tagWorkspace);
341           linkManager.createLink(tag, restoredNode);
342           long total = tag.hasProperty(EXO_TOTAL) ?
343               tag.getProperty(EXO_TOTAL).getLong() : 0;
344               tag.setProperty(EXO_TOTAL, total + 1);
345               tag.getSession().save();
346         }
347       }
348 
349       trashNodeSession.getItem(trashNodePath).remove();
350     }
351 
352     removeMixinEXO_RESTORE_LOCATION(restoreSession, restorePath);
353 
354     trashNodeSession.save();
355     restoreSession.save();
356 
357     //also restore categories of node
358     if (deep == 0 && nodeUUID != null) {
359       while (true) {
360         boolean found = false;
361         NodeIterator iter = trashHomeNode.getNodes();
362         while (iter.hasNext()) {
363           Node trashChild = iter.nextNode();
364           if (trashChild.isNodeType(TAXONOMY_LINK) && trashChild.hasProperty(UUID)
365               && trashChild.hasProperty(EXO_WORKSPACE)
366               && nodeUUID.equals(trashChild.getProperty(UUID).getString())
367               && restoreWorkspace.equals(trashChild.getProperty(EXO_WORKSPACE))) {
368             try {
369                 restoreFromTrash(trashChild.getPath(), sessionProvider, deep + 1);
370                 found = true;
371                 break;
372             } catch (Exception e) {
373               if (LOG.isWarnEnabled()) {
374                 LOG.warn(e.getMessage());
375               }
376             }
377           }
378         }
379         if (!found) break;
380       }
381     }
382 
383     trashNodeSession.save();
384     restoreSession.save();
385     //restore target node of the restored categories.
386     if (deep == 0 && taxonomyLinkUUID != null && taxonomyLinkWS != null) {
387       while (true) {
388         boolean found = false;
389         NodeIterator iter = trashHomeNode.getNodes();
390         while (iter.hasNext()) {
391           Node trashChild = iter.nextNode();
392           if (trashChild.isNodeType(MIX_REFERENCEABLE)
393               && taxonomyLinkUUID.equals(trashChild.getUUID())
394               && taxonomyLinkWS.equals(trashChild.getProperty(RESTORE_WORKSPACE).getString())) {
395             try {
396               restoreFromTrash(trashChild.getPath(),
397                                sessionProvider,
398                                deep + 1);
399               found = true;
400               break;
401             } catch (Exception e) {
402               if (LOG.isWarnEnabled()) {
403                 LOG.warn(e.getMessage());
404               }
405             }
406           }
407         }
408         if (!found) break;
409       }
410     }
411 
412     trashNodeSession.save();
413     restoreSession.save();
414   }
415 
416 
417   /**
418    * {@inheritDoc}
419    */
420   public List<Node> getAllNodeInTrash(SessionProvider sessionProvider) throws Exception {
421 
422     StringBuilder query = new StringBuilder("SELECT * FROM nt:base WHERE exo:restorePath IS NOT NULL");
423 
424     return selectNodesByQuery(sessionProvider, query.toString(), Query.SQL);
425   }
426 
427   /**
428    * {@inheritDoc}
429    */
430   public List<Node> getAllNodeInTrashByUser(SessionProvider sessionProvider,
431                                             String userName) throws Exception {
432     StringBuilder query = new StringBuilder(
433         "SELECT * FROM nt:base WHERE exo:restorePath IS NOT NULL AND exo:lastModifier='").append(userName).append("'");
434     return selectNodesByQuery(sessionProvider, query.toString(), Query.SQL);
435   }
436 
437 
438   public void removeRelations(Node node, SessionProvider sessionProvider) throws Exception {
439     ManageableRepository manageableRepository = repositoryService.getCurrentRepository();
440     String[] workspaces = manageableRepository.getWorkspaceNames();
441 
442     String queryString = "SELECT * FROM exo:relationable WHERE exo:relation IS NOT NULL";
443     boolean error = false;
444 
445     for (String ws : workspaces) {
446       Session session = sessionProvider.getSession(ws, manageableRepository);
447       QueryManager queryManager = session.getWorkspace().getQueryManager();
448       Query query = queryManager.createQuery(queryString, Query.SQL);
449       QueryResult queryResult = query.execute();
450 
451       NodeIterator iter = queryResult.getNodes();
452       while (iter.hasNext()) {
453         try {
454           iter.nextNode().removeMixin("exo:relationable");
455           session.save();
456         } catch (Exception e) {
457           error = true;
458         }
459       }
460     }
461     if (error) throw new Exception("Can't remove exo:relationable of all related nodes");
462   }
463 
464   /**
465    * {@inheritDoc}
466    */
467   public boolean isInTrash(Node node) throws RepositoryException {
468     return node.getPath().startsWith(this.trashHome_) && !node.getPath().equals(this.trashHome_);
469   }
470 
471   /**
472    * {@inheritDoc}
473    */
474   public Node getTrashHomeNode() {
475     try {
476       Session session = WCMCoreUtils.getSystemSessionProvider()
477                                     .getSession(trashWorkspace_,
478                                                 repositoryService.getCurrentRepository());
479       return (Node) session.getItem(trashHome_);
480     } catch (Exception e) {
481       return null;
482     }
483 
484   }
485   
486   public Node getNodeByTrashId(String trashId) throws RepositoryException{
487     QueryResult queryResult;
488     NodeIterator iter;
489     Session session = WCMCoreUtils.getSystemSessionProvider()
490         .getSession(trashWorkspace_,
491                     repositoryService.getCurrentRepository());
492     QueryManager queryManager = session.getWorkspace().getQueryManager();
493     StringBuilder sb = new StringBuilder();
494     sb.append("SELECT * from exo:restoreLocation WHERE exo:trashId = '").append(trashId).append("'");
495     QueryImpl query = (QueryImpl) queryManager.createQuery(sb.toString(), Query.SQL);
496     query.setLimit(1);
497     queryResult = query.execute();
498     iter = queryResult.getNodes();
499     if(iter.hasNext()) return iter.nextNode();
500     else return null;
501   }
502 
503 
504   private List<Node> selectNodesByQuery(SessionProvider sessionProvider,
505                                         String queryString,
506                                         String language) throws Exception {
507     List<Node> ret = new ArrayList<Node>();
508     ManageableRepository manageableRepository = repositoryService.getCurrentRepository();
509     Session session = sessionProvider.getSession(this.trashWorkspace_, manageableRepository);
510     QueryManager queryManager = session.getWorkspace().getQueryManager();
511     Query query = queryManager.createQuery(queryString, language);
512     QueryResult queryResult = query.execute();
513 
514     NodeIterator iter = queryResult.getNodes();
515     while (iter.hasNext()) {
516       ret.add(iter.nextNode());
517     }
518 
519     return ret;
520   }
521 
522   private String fixRestorePath(String path) {
523     int leftBracket = path.lastIndexOf('[');
524     int rightBracket = path.lastIndexOf(']');
525     if (leftBracket == -1 || rightBracket == -1 ||
526         (leftBracket >= rightBracket)) return path;
527 
528     try {
529       Integer.parseInt(path.substring(leftBracket+1, rightBracket));
530     } catch (Exception ex) {
531       return path;
532     }
533     return path.substring(0, leftBracket);
534   }
535 
536   private void removeMixinEXO_RESTORE_LOCATION(Session session, String restorePath) throws Exception {
537     Node sameNameNode = ((Node) session.getItem(restorePath));
538     Node parent = sameNameNode.getParent();
539     String name = sameNameNode.getName();
540     NodeIterator nodeIter = parent.getNodes(name);
541     while (nodeIter.hasNext()) {
542       Node node = nodeIter.nextNode();
543       if (node.isNodeType(EXO_RESTORE_LOCATION))
544         node.removeMixin(EXO_RESTORE_LOCATION);
545     }
546   }
547 
548 }