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.folksonomy.impl;
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.Comparator;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Map.Entry;
28  import java.util.Set;
29  import java.util.TreeSet;
30  
31  import javax.jcr.ItemExistsException;
32  import javax.jcr.ItemNotFoundException;
33  import javax.jcr.Node;
34  import javax.jcr.NodeIterator;
35  import javax.jcr.PathNotFoundException;
36  import javax.jcr.RepositoryException;
37  import javax.jcr.Session;
38  import javax.jcr.query.Query;
39  import javax.jcr.query.QueryManager;
40  import javax.jcr.query.QueryResult;
41  
42  import org.apache.commons.lang.StringUtils;
43  import org.picocontainer.Startable;
44  
45  import org.exoplatform.container.component.ComponentPlugin;
46  import org.exoplatform.container.xml.InitParams;
47  import org.exoplatform.services.cache.CacheService;
48  import org.exoplatform.services.cache.ExoCache;
49  import org.exoplatform.services.cms.folksonomy.NewFolksonomyService;
50  import org.exoplatform.services.cms.jcrext.activity.ActivityCommonService;
51  import org.exoplatform.services.cms.link.LinkManager;
52  import org.exoplatform.services.jcr.core.ManageableRepository;
53  import org.exoplatform.services.jcr.ext.app.SessionProviderService;
54  import org.exoplatform.services.jcr.ext.common.SessionProvider;
55  import org.exoplatform.services.jcr.ext.distribution.DataDistributionManager;
56  import org.exoplatform.services.jcr.ext.distribution.DataDistributionMode;
57  import org.exoplatform.services.jcr.ext.distribution.DataDistributionType;
58  import org.exoplatform.services.jcr.ext.hierarchy.NodeHierarchyCreator;
59  import org.exoplatform.services.listener.ListenerService;
60  import org.exoplatform.services.log.ExoLogger;
61  import org.exoplatform.services.log.Log;
62  import org.exoplatform.services.wcm.core.NodetypeConstant;
63  import org.exoplatform.services.wcm.utils.WCMCoreUtils;
64  /**
65   * Created by The eXo Platform SARL
66   * Author : Dang Van Minh
67   *          minh.dang@exoplatform.com
68   * Nov 16, 2009
69   * 10:30:24 AM
70   */
71  public class NewFolksonomyServiceImpl implements NewFolksonomyService, Startable {
72  
73    private static final String       USER_FOLKSONOMY_ALIAS  = "userPrivateFolksonomy";
74  
75    private static final String       GROUPS_ALIAS           = "groupsPath";
76  
77    private static final String       TAG_STYLE_ALIAS        = "exoTagStylePath";
78  
79    private static final String       PUBLIC_TAG_NODE_PATH   = "exoPublicTagNode";
80  
81    private static final String       EXO_TRASH_FOLDER       = "exo:trashFolder";
82  
83    private static final String       EXO_HIDDENABLE         = "exo:hiddenable";
84  
85    private static final String       TAG_PERMISSION_LIST    = "tagPermissionList";
86  
87    private static final String       CACHE_NAME             = "ecms.FolksonomyService";
88  
89    private static final Log          LOG                    = ExoLogger.getLogger(NewFolksonomyServiceImpl.class.getName());
90  
91    private NodeHierarchyCreator      nodeHierarchyCreator;
92  
93    private LinkManager               linkManager;
94  
95    private InitParams                initParams_;
96  
97    private List<TagStylePlugin>      plugin_                = new ArrayList<TagStylePlugin>();
98  
99    private List<TagPermissionPlugin> tagPermissionPlugin_   = new ArrayList<TagPermissionPlugin>();
100 
101   private ExoCache<String, List<String>> tagPermissionList;
102 
103   private Map<String, String>       sitesTagPath           = new HashMap<String, String>();
104 
105   private ListenerService           listenerService;
106 
107   private ActivityCommonService     activityService;
108 
109   //The DataDistributionType used to store tagNodes
110   private DataDistributionType dataDistributionType;
111 
112   public NewFolksonomyServiceImpl(InitParams initParams,
113                                   NodeHierarchyCreator nodeHierarchyCreator,
114                                   LinkManager linkManager,
115                                   DataDistributionManager dataDistributionManager,
116                                   SessionProviderService sessionProviderService,
117                                   CacheService cacheService) throws Exception {
118     this.nodeHierarchyCreator = nodeHierarchyCreator;
119     this.linkManager = linkManager;
120     this.initParams_ = initParams;
121     listenerService = WCMCoreUtils.getService(ListenerService.class);
122     this.activityService = WCMCoreUtils.getService(ActivityCommonService.class);
123     this.tagPermissionList = cacheService.getCacheInstance(CACHE_NAME);
124     //get the DataDistributionType object;
125     if (initParams != null && initParams.getValueParam("tagDistributionMode") != null) {
126       String strTagDistributionMode = initParams.getValueParam("tagDistributionMode").getValue();
127       if ("none".equals(strTagDistributionMode)) {
128         dataDistributionType = dataDistributionManager.getDataDistributionType(DataDistributionMode.NONE);
129       } else if ("readable".equals(strTagDistributionMode)) {
130         dataDistributionType = dataDistributionManager.getDataDistributionType(DataDistributionMode.READABLE);
131       } else if ("optimized".equals(strTagDistributionMode)) {
132         dataDistributionType = dataDistributionManager.getDataDistributionType(DataDistributionMode.OPTIMIZED);
133       }
134     } else {
135       dataDistributionType = dataDistributionManager.getDataDistributionType(DataDistributionMode.READABLE);
136     }    
137   }
138 
139   /**
140    * Implement method in Startable Call init() method
141    *
142    * @see #init()
143    */
144   public void start() {
145     try {
146       init();
147     } catch (Exception e) {
148       if (LOG.isErrorEnabled()) {
149         LOG.error("===>>>>Exception when init FolksonomySerice", e);
150       }
151     }
152   }
153 
154   /**
155    * Implement method in Startable
156    */
157   public void stop() {
158     tagPermissionList.clearCache();
159   }
160 
161 
162 
163   /**
164    * {@inheritDoc}
165    */
166   public void addPrivateTag(String[] tagsName,
167                             Node documentNode,
168                             String workspace,
169                             String userName) throws Exception {
170     Node userFolksonomyNode = getUserFolksonomyFolder(userName);
171     userFolksonomyNode.getSession().save();
172     Node targetNode = getTargetNode(documentNode);
173     boolean firstTagFlag = true;
174     StringBuffer tagValue = new StringBuffer();
175     for (String tag : tagsName) {
176       try {
177         // Find tag node
178         Node tagNode = getTagNode(userFolksonomyNode, tag);
179         // Add symlink and total
180         addTag(tagNode, targetNode);
181         userFolksonomyNode.getSession().save();
182         if (firstTagFlag) {
183           firstTagFlag = false;
184           tagValue.append(tag);
185         }else {
186           tagValue.append(",").append(tag);
187         }
188 
189       } catch (Exception e) {
190         if (LOG.isErrorEnabled()) {
191           LOG.error("can't add tag '" + tag + "' to node: " + targetNode.getPath() + " for user: "
192               + userName);
193         }
194       }
195     }//
196     broadcastActivityTag(documentNode, tagValue.toString());
197   }
198   private void broadcastActivityTag(Node documentNode, String tagValue ) {
199     if (listenerService!=null && activityService !=null) {
200       try {
201         if (activityService.isAcceptedNode(documentNode) || 
202             documentNode.getPrimaryNodeType().getName().equals(NodetypeConstant.NT_FILE)) {
203           listenerService.broadcast(ActivityCommonService.TAG_ADDED_ACTIVITY, documentNode, tagValue);
204         }
205       } catch (Exception e) {
206         if (LOG.isErrorEnabled()) {
207           LOG.error("Can not notify Tag Added Activity because of: " + e.getMessage());
208         }
209       }
210     }
211   }
212 
213   /**
214    * {@inheritDoc}
215    */
216   public void addGroupsTag(String[] tagsName,
217                            Node documentNode,
218                            String workspace,
219                            String[] roles) throws Exception {
220     Node targetNode = getTargetNode(documentNode);
221     for (String group : roles) {
222       Node groupFolksonomyNode = getGroupFolksonomyFolder(group, workspace);
223       for (String tag : tagsName) {
224         try {
225           // Find tag node
226           Node tagNode = getTagNode(groupFolksonomyNode, tag);
227 
228           // Add symlink and total
229           addTag(tagNode, targetNode);
230 
231           groupFolksonomyNode.getSession().save();
232         } catch (Exception e) {
233           if (LOG.isErrorEnabled()) {
234             LOG.error("can't add tag '" + tag + "' to node: " + targetNode.getPath() + " for group: "
235                 + group);
236           }
237         }
238       }
239     }
240   }
241 
242   /**
243    * {@inheritDoc}
244    */
245   public void addPublicTag(String treePath,
246                            String[] tagsName,
247                            Node documentNode,
248                            String workspace) throws Exception {
249     Node publicFolksonomyTreeNode = getNode(workspace, treePath);
250     Node targetNode = getTargetNode(documentNode);
251     boolean firstTagFlag = true;
252     StringBuffer tagValue = new StringBuffer();
253 
254     for (String tag : tagsName) {
255       try {
256         // Find tag node
257         Node tagNode = getTagNode(publicFolksonomyTreeNode, tag);
258         // Add symlink and total
259         addTag(tagNode, targetNode);
260         publicFolksonomyTreeNode.getSession().save();
261         if (firstTagFlag) {
262           firstTagFlag = false;
263           tagValue.append(tag);
264         }else {
265           tagValue.append(",").append(tag);
266         }
267       } catch (Exception e) {
268         if (LOG.isErrorEnabled()) {
269           LOG.error("can't add tag '" + tag + "' to node: " + targetNode.getPath()
270                     + " in public folksonomy tree!");
271         }
272       }
273     }//off for
274     broadcastActivityTag(documentNode, tagValue.toString());
275   }
276 
277   private Node getTargetNode(Node showingNode) throws Exception {
278     Node targetNode = null;
279     if (linkManager.isLink(showingNode)) {
280       try {
281         targetNode = linkManager.getTarget(showingNode);
282       } catch (ItemNotFoundException e) {
283         targetNode = showingNode;
284       }
285     } else {
286       targetNode = showingNode;
287     }
288     return targetNode;
289   }
290 
291   /**
292    * {@inheritDoc}
293    */
294   public void addSiteTag(String siteName,
295                          String[] tagsName,
296                          Node node,
297                          String workspace) throws Exception {
298     if (sitesTagPath.get(getRepoName()) == null) {
299       createSiteTagPath();
300     }
301     addPublicTag(sitesTagPath.get(getRepoName()) + "/" + siteName,
302                  tagsName,
303                  node,
304                  workspace);
305   }
306 
307   /**
308    * {@inheritDoc}
309    */
310   @Override
311   public List<Node> getAllDocumentsByTagsAndPath(String selectedPath,
312                                                   Set<String> tagPaths,
313                                                   String workspace,
314                                                  SessionProvider sessionProvider) throws Exception {
315     if (StringUtils.isBlank(selectedPath)) {
316       throw new IllegalArgumentException("Parent path is empty");
317     }
318     if (tagPaths == null || tagPaths.isEmpty()) {
319       throw new IllegalArgumentException("Tags is empty");
320     }
321     // The Parent Node must ends with '/' and the original selected node path
322     // without '/'
323     String selectedParentPath = selectedPath;
324     if (selectedPath.endsWith("/")) {
325       selectedPath = selectedPath.substring(0, selectedPath.length() - 1);
326     } else {
327       selectedParentPath += "/";
328     }
329 
330     List<Node> ret = new ArrayList<Node>();
331     Set<String> nodesPaths = new HashSet<>();
332     boolean firstSearch = true;
333     Iterator<String> tagPathsIterator = tagPaths.iterator();
334     while (tagPathsIterator.hasNext()) {
335       String tagPath = (String) tagPathsIterator.next();
336       Node tagNode = getNode(workspace, tagPath, sessionProvider);
337       if (tagNode == null) {
338         tagPathsIterator.remove();
339         continue;
340       }
341       NodeIterator nodeIter = tagNode.getNodes();
342 
343       Set<String> singleTagNodesPaths = new HashSet<>();
344       while (nodeIter.hasNext()) {
345         Node node = nodeIter.nextNode();
346         if (linkManager.isLink(node)) {
347           Node targetNode = null;
348           try {
349             targetNode = linkManager.getTarget(node);
350           } catch (Exception e) {
351             if (LOG.isWarnEnabled()) {
352               LOG.warn(e.getMessage());
353             }
354           }
355           if (targetNode != null && !((Node) targetNode.getAncestor(1)).isNodeType(EXO_TRASH_FOLDER)
356               && targetNode.getSession().getWorkspace().getName().equals(workspace)
357               && (targetNode.getPath().equals(selectedPath) || targetNode.getPath().startsWith(selectedParentPath))) {
358             if (firstSearch) {
359               ret.add(targetNode);
360             }
361             singleTagNodesPaths.add(targetNode.getPath());
362           }
363         }
364       }
365       if (firstSearch) {
366         nodesPaths.addAll(singleTagNodesPaths);
367       } else {
368         nodesPaths.retainAll(singleTagNodesPaths);
369       }
370       firstSearch = false;
371     }
372     Iterator<Node> nodesIterator = ret.iterator();
373     while (nodesIterator.hasNext()) {
374       Node node = nodesIterator.next();
375       if (!nodesPaths.contains(node.getPath())) {
376         nodesIterator.remove();
377       }
378     }
379     return ret;
380   }
381 
382   /**
383    * {@inheritDoc}
384    */
385   public List<Node> getAllDocumentsByTag(String tagPath,
386                                          String workspace,
387                                          SessionProvider sessionProvider) throws Exception {
388     List<Node> ret = new ArrayList<Node>();
389     Node tagNode = getNode(workspace, tagPath, sessionProvider);
390     if (tagNode == null) {
391       return Collections.emptyList();
392     }
393     NodeIterator nodeIter = tagNode.getNodes();
394 
395     while (nodeIter.hasNext()) {
396       Node node = nodeIter.nextNode();
397       if (linkManager.isLink(node)) {
398         Node targetNode = null;
399         try {
400           targetNode = linkManager.getTarget(node);
401         } catch (Exception e) {
402           if (LOG.isWarnEnabled()) {
403             LOG.warn(e.getMessage());
404           }
405         }
406         if (targetNode != null && !((Node) targetNode.getAncestor(1)).isNodeType(EXO_TRASH_FOLDER)) {
407           ret.add(targetNode);
408         }
409       }
410     }
411     return ret;
412   }
413 
414   /**
415    * {@inheritDoc}
416    */
417   public List<Node> getAllGroupTags(String[] roles, String workspace) throws Exception {
418     Set<Node> tagSet = new TreeSet<Node>(new NodeComparator());
419     for (String group : roles) {
420       Node groupFolksonomyNode = getGroupFolksonomyFolder(group, workspace);
421       List<Node> tagNodes = queryTagNodes(groupFolksonomyNode);
422       for(Node tag : tagNodes) {
423         if (!((Node) tag.getAncestor(1)).isNodeType(EXO_TRASH_FOLDER)) {
424           tagSet.add(tag);
425         }
426       }
427     }
428     return new ArrayList<Node>(tagSet);
429   }
430 
431   private List<Node> queryTagNodes(Node rootNode) throws Exception {
432     List<Node> ret = new ArrayList<Node>();
433     StringBuilder queryStr = new StringBuilder().append("select * from ").append(EXO_TAGGED).append(" where jcr:path like '").
434         append(rootNode.getPath()).append("/%'");
435     Query query = rootNode.getSession().getWorkspace().getQueryManager().createQuery(queryStr.toString(), Query.SQL); 
436     for (NodeIterator iter = query.execute().getNodes(); iter.hasNext();) {
437       ret.add(iter.nextNode());
438     }
439     Collections.sort(ret, new NodeComparator());
440     return ret;
441   }  
442 
443   /**
444    * {@inheritDoc}
445    */
446   public List<Node> getAllGroupTags(String role, String workspace) throws Exception {
447     List<Node> tagSet = new ArrayList<Node>();
448     Node groupFolksonomyNode = getGroupFolksonomyFolder(role, workspace);
449     for (Node tagNode : queryTagNodes(groupFolksonomyNode)) {
450       tagSet.add(tagNode);
451     }
452     return tagSet;
453   }
454 
455   /**
456    * {@inheritDoc}
457    */
458   public List<Node> getAllPrivateTags(String userName) throws Exception {
459     Node userFolksonomyNode = getUserFolksonomyFolder(userName);
460     if (userFolksonomyNode == null) {
461       return Collections.emptyList();
462     }
463     return queryTagNodes(userFolksonomyNode);
464   }
465 
466   /**
467    * {@inheritDoc}
468    */
469   public List<Node> getAllPublicTags(String treePath, String workspace) throws Exception {
470     Node publicFolksonomyTreeNode = getNode(workspace, treePath);
471     return queryTagNodes(publicFolksonomyTreeNode);
472   }
473 
474   /**
475    * {@inheritDoc}
476    */
477   public List<Node> getAllSiteTags(String siteName, String workspace) throws Exception {
478     if (sitesTagPath.get(getRepoName()) == null) {
479       createSiteTagPath();
480     }
481     return getAllPublicTags(sitesTagPath.get(getRepoName()) + "/" + siteName, workspace);
482   }
483 
484   private String getRepoName() {
485     try {
486       String repositoryName = WCMCoreUtils.getRepository().getConfiguration().getName();
487       if (LOG.isDebugEnabled()) {
488         LOG.debug("The repository name is: " + repositoryName);
489       }
490       return repositoryName;
491     } catch (NullPointerException e) {
492       String repositoryName = System.getProperty("gatein.tenant.repository.name");
493       if (repositoryName != null) {
494         return repositoryName;
495       }
496       if (LOG.isErrorEnabled()) {
497         LOG.error("Repository exception occurs:", e);
498       }
499       return null;
500     }
501   }
502 
503   /**
504    * {@inheritDoc}
505    */
506   public List<Node> getAllTagStyle(String workspace) throws Exception {
507     String tagStylesPath = nodeHierarchyCreator.getJcrPath(TAG_STYLE_ALIAS);
508     Node tagStylesNode = getNode(workspace, tagStylesPath);
509     return getChildNodes(tagStylesNode);
510   }
511 
512   /**
513    * {@inheritDoc}
514    */
515   public String getTagStyle(String tagStylePath, String workspace) throws Exception {
516     Node tagStyleNode = getNode(workspace, tagStylePath);
517     return tagStyleNode.getProperty(HTML_STYLE_PROP).getString();
518   }
519 
520   /**
521    * Add new TagStylePlugin in plugin_
522    *
523    * @param plugin
524    */
525   public void addTagStylePlugin(ComponentPlugin plugin) {
526     if (plugin instanceof TagStylePlugin) {
527       plugin_.add((TagStylePlugin) plugin);
528     }
529   }
530 
531   /**
532    * init all available TagStylePlugin
533    *
534    * @throws Exception
535    */
536   public void init() throws Exception {
537     for (TagStylePlugin plugin : plugin_) {
538       try {
539         plugin.init();
540       } catch (Exception e) {
541         if (LOG.isErrorEnabled()) {
542           LOG.error("can not init tag style: ", e);
543         }
544       }
545     }
546     initTagPermissionListCache();
547   }
548 
549   /**
550    * init the cache tagPermissionList
551    * @throws Exception
552    */
553   public void initTagPermissionListCache() throws Exception {
554     List<String> _tagPermissionList = new ArrayList<String>();
555     for (TagPermissionPlugin plugin : tagPermissionPlugin_) {
556       try {
557         _tagPermissionList.addAll(plugin.initPermission());
558       } catch (Exception e) {
559         if (LOG.isErrorEnabled()) {
560           LOG.error("can not init tag permission: ", e);
561         }
562       }
563     }
564     tagPermissionList.clearCache();
565     tagPermissionList.put(TAG_PERMISSION_LIST, _tagPermissionList);
566   }
567 
568   /**
569    * {@inheritDoc}
570    */
571   @Deprecated
572   public Node modifyTagName(String tagPath, String newTagName, String workspace) throws Exception {
573     Node oldTagNode = getNode(workspace, tagPath);
574     if (oldTagNode.getParent().hasNode(newTagName))
575       throw new ItemExistsException("node " + newTagName + " has already existed!");
576 
577     StringBuilder newPath = new StringBuilder(oldTagNode.getParent().getPath()).append('/')
578         .append(newTagName);
579 
580     ManageableRepository manageableRepository = WCMCoreUtils.getRepository();
581 
582     SessionProvider sessionProvider = WCMCoreUtils.getSystemSessionProvider();
583     Session session = sessionProvider.getSession(workspace, manageableRepository);
584     session.move(tagPath, newPath.toString());
585     session.save();
586     return getNode(workspace, newPath.toString());
587   }
588 
589   /**
590    * {@inheritDoc}
591    */
592   public Node modifyPublicTagName(String tagPath, String newTagName, String workspace, String treePath) throws Exception {
593     Node oldTagNode = getNode(workspace, tagPath);
594     Node publicFolksonomyTreeNode = getNode(workspace, treePath);
595     Node newTagNode = null;
596     try {
597       newTagNode = dataDistributionType.getDataNode(publicFolksonomyTreeNode, newTagName);
598       throw new ItemExistsException("node " + newTagName + " has already existed!");
599     } catch (PathNotFoundException e) {
600       //Path not found means newTagName does not exists, it is expected behavior.
601       newTagNode = dataDistributionType.getOrCreateDataNode(publicFolksonomyTreeNode, newTagName);
602     }
603     newTagNode.addMixin(EXO_TAGGED);
604     if (oldTagNode.hasProperty(EXO_TOTAL)) {
605       newTagNode.setProperty(EXO_TOTAL, oldTagNode.getProperty(EXO_TOTAL).getValue());
606     }
607 
608     Map<String, String> pathMap = new HashMap<String, String>();
609     for (NodeIterator iter = oldTagNode.getNodes(); iter.hasNext();) {
610       Node node = iter.nextNode();
611       pathMap.put(node.getPath(), newTagNode.getPath() + "/" + node.getName());
612     }
613 
614     Session session = newTagNode.getSession();
615     for (Entry<String, String> entry : pathMap.entrySet()) {
616       session.move(entry.getKey(), entry.getValue());
617     }
618     oldTagNode.remove();
619     session.save();
620     return newTagNode;
621   }      
622 
623 
624   /**
625    * {@inheritDoc}
626    */
627   public void removeTag(String tagPath, String workspace) throws Exception {
628     Node tagNode = getNode(workspace, tagPath);
629     NodeIterator nodeIterator = tagNode.getNodes();
630     Exception e = null;
631     if (nodeIterator.hasNext()) {
632       while (nodeIterator.hasNext()) {
633         try {
634           Node document = linkManager.getTarget(nodeIterator.nextNode());
635           removeTagOfDocument(tagPath, document, workspace);
636         }catch(Exception exception){
637           if(e!=null) {
638             e.addSuppressed(exception);
639           }else{
640             e = exception;
641           }
642         }
643       }
644     } else {
645       tagNode.remove();
646       tagNode.getSession().save();
647     }
648     if(e!=null) throw e;
649   }
650 
651   /**
652    * {@inheritDoc}
653    */
654   public void removeTagOfDocument(String tagPath, Node document, String workspace) throws Exception {
655     Node tagNode = getNode(workspace, tagPath);
656     NodeIterator nodeIter = tagNode.getNodes();
657     StringBuffer removedTags = new StringBuffer();
658     String tagName ;
659     boolean isFirstFlag =  true;
660     while (nodeIter.hasNext()) {
661       Node link = nodeIter.nextNode();
662       if (linkManager.isLink(link)) {
663         Node targetNode = null;
664         try {
665           targetNode = linkManager.getTarget(link);
666         } catch (RepositoryException e) {
667           if (LOG.isWarnEnabled()) {
668             LOG.warn(e.getMessage());
669           }
670         }
671         if (document.isSame(targetNode)) {
672           tagName = tagNode.getName();
673           if (isFirstFlag) {
674             removedTags.append(tagName);
675             isFirstFlag = false;
676           }else {
677             removedTags.append(ActivityCommonService.VALUE_SEPERATOR).append(tagName);
678           }
679           link.remove();
680 
681           long total = tagNode.getProperty(EXO_TOTAL).getLong();
682           tagNode.setProperty(EXO_TOTAL, total - 1);
683           Node parentNode = tagNode.getParent();
684           if (tagNode.getProperty(EXO_TOTAL).getLong() == 0L)
685             tagNode.remove();
686           parentNode.getSession().save();
687           break;
688         }
689       }
690     }
691     if (listenerService!=null && activityService!=null) {
692       try {
693         if (activityService.isAcceptedNode(document) || (document.getPrimaryNodeType().getName().equals(NodetypeConstant.NT_FILE)
694             && activityService.isBroadcastNTFileEvents(document))) {
695           listenerService.broadcast(ActivityCommonService.TAG_REMOVED_ACTIVITY, document, removedTags.toString());
696         }
697       } catch (Exception e) {
698         if (LOG.isErrorEnabled()) {
699           LOG.error("Can not notify RemoveTag Activity because of: " + e.getMessage());
700         }
701       }
702     }
703   }
704 
705   /**
706    * {@inheritDoc}
707    */
708   public void updateTagStyle(String styleName, String tagRange, String htmlStyle, String workspace) throws Exception {
709     String tagStylesPath = nodeHierarchyCreator.getJcrPath(TAG_STYLE_ALIAS);
710     Node tagStylesNode = getNode(workspace, tagStylesPath);
711     Node styleNode = tagStylesNode.getNode(styleName);
712     styleNode.setProperty(TAG_RATE_PROP, tagRange);
713     styleNode.setProperty(HTML_STYLE_PROP, htmlStyle);
714     tagStylesNode.getSession().save();
715   }
716 
717   /**
718    * {@inheritDoc}
719    */
720   public void addTagStyle(String styleName, String tagRange, String htmlStyle, String workspace) throws Exception {
721     String tagStylesPath = nodeHierarchyCreator.getJcrPath(TAG_STYLE_ALIAS);
722     Node tagStylesNode = getNode(workspace, tagStylesPath);
723     Node styleNode = tagStylesNode.addNode(styleName, EXO_TAGSTYLE);
724     styleNode.addMixin("exo:privilegeable");
725     styleNode.setProperty(TAG_RATE_PROP, tagRange);
726     styleNode.setProperty(HTML_STYLE_PROP, htmlStyle);
727     tagStylesNode.getSession().save();
728   }
729 
730   /**
731    * {@inheritDoc}
732    */
733   public void removeTagsOfNodeRecursively(Node node,
734                                           String workspace,
735                                           String username,
736                                           String groups) throws Exception {
737     int[] scopes = new int[] { PRIVATE, PUBLIC, GROUP, SITE };
738     Map<Integer, String> map = new HashMap<Integer, String>();
739     map.put(PUBLIC, "");
740     map.put(PRIVATE, username);
741     map.put(GROUP, groups);
742     map.put(SITE, "");
743     for (int scope : scopes) {
744       for (Node child : getAllNodes(node)) {
745         List<Node> tags = getLinkedTagsOfDocumentByScope(scope, map.get(scope), child, workspace);
746         for (Node tag : tags)
747           removeTagOfDocument(tag.getPath(), child, workspace);
748       }
749     }
750   }
751 
752   private List<Node> getAllNodes(Node node) throws Exception {
753     List<Node> ret = new ArrayList<Node>();
754     getAllNodes(node, ret);
755     return ret;
756   }
757 
758   private void getAllNodes(Node node, List<Node> list) throws Exception {
759     list.add(node);
760     for (NodeIterator iter = node.getNodes(); iter.hasNext();) {
761       getAllNodes(iter.nextNode(), list);
762     }
763   }
764 
765   private List<Node> getChildNodes(Node node) throws Exception {
766     List<Node> ret = new ArrayList<Node>();
767     NodeIterator nodeIter = node.getNodes();
768     while (nodeIter.hasNext()) {
769       ret.add(nodeIter.nextNode());
770     } 
771     Collections.sort(ret, new NodeComparator());
772     return ret;
773   }
774 
775   private Node getGroupFolksonomyFolder(String group, String workspace) throws Exception {
776     String groupsPath = nodeHierarchyCreator.getJcrPath(GROUPS_ALIAS);
777     String folksonomyPath = "ApplicationData/Tags";
778     Node groupsNode = getNode(workspace, groupsPath);
779     return groupsNode.getNode(group.substring(1)).getNode(folksonomyPath);
780   }
781 
782   private Node getUserFolksonomyFolder(String userName) throws Exception {
783     // code for running
784     SessionProvider sessionProvider = WCMCoreUtils.getSystemSessionProvider();
785     Node userNode = nodeHierarchyCreator.getUserNode(sessionProvider, userName);
786     String folksonomyPath = nodeHierarchyCreator.getJcrPath(USER_FOLKSONOMY_ALIAS);
787     if (folksonomyPath == null || !userNode.hasNode(folksonomyPath)) {
788       return null;
789     }
790     return userNode.getNode(folksonomyPath);
791   }
792 
793   private Node getNode(String workspace, String path) throws Exception {
794     return getNode(workspace, path, WCMCoreUtils.getSystemSessionProvider());
795   }
796 
797   private Node getNode(String workspace, String path, SessionProvider sessionProvider) throws Exception {
798     ManageableRepository manageableRepository = WCMCoreUtils.getRepository();
799     Session session = sessionProvider.getSession(workspace, manageableRepository);
800 
801     return session.itemExists(path) ? ((Node) session.getItem(path)) : null;
802   }
803 
804   private boolean existSymlink(Node parentNode, Node targetNode) throws Exception {
805     NodeIterator nodeIter = parentNode.getNodes();
806     while (nodeIter.hasNext()) {
807       Node link = nodeIter.nextNode();
808       Node pointTo = null;
809       try {
810         if (linkManager.isLink(link))
811           pointTo = linkManager.getTarget(link, true);
812       } catch (ItemNotFoundException e) {
813         continue;// target of symlink does not exist -> no exist symlink
814       } catch (Exception e) {
815         if (LOG.isWarnEnabled()) {
816           LOG.warn(e.getMessage());
817         }
818       }
819       if (targetNode != null && targetNode.isSame(pointTo))
820         return true;
821     }
822     return false;
823   }
824 
825   private static class NodeComparator implements Comparator<Node> {
826     public int compare(Node o1, Node o2) {
827       try {
828         if (o1.isSame(o2))
829           return 0;
830         int pathComparison = o1.getPath().compareTo(o2.getPath());
831         return (pathComparison == 0) ? 1 : pathComparison;
832       } catch (RepositoryException e) {
833         return 1;
834       }
835     }
836   }
837 
838   public List<Node> getLinkedTagsOfDocument(Node documentNode, String workspace) throws Exception {
839 
840     Set<Node> ret = new HashSet<Node>();
841     // prepare query
842     StringBuilder queryStr = new StringBuilder("SELECT * FROM ").append(EXO_TAGGED);
843     ManageableRepository manageableRepository = WCMCoreUtils.getRepository();
844 
845     SessionProvider sessionProvider = WCMCoreUtils.getSystemSessionProvider();
846     QueryManager queryManager = sessionProvider.getSession(workspace, manageableRepository)
847         .getWorkspace()
848         .getQueryManager();
849     Query query = queryManager.createQuery(queryStr.toString(), Query.SQL);
850     QueryResult queryResult = query.execute();
851     NodeIterator nodeIter = queryResult.getNodes();
852     while (nodeIter.hasNext()) {
853       Node tagNode = nodeIter.nextNode();
854       if (existSymlink(tagNode, documentNode))
855         ret.add(tagNode);
856     }
857     return new ArrayList<Node>(ret);
858   }
859 
860   public List<Node> getLinkedTagsOfDocumentByScope(int scope,
861                                                    String value,
862                                                    Node documentNode,
863                                                    String workspace) throws Exception {
864 
865     List<Node> ret = new ArrayList<Node>();
866     if (scope == PRIVATE) {
867       Node userFolksonomyNode = getUserFolksonomyFolder(value);
868       for (Node tagNode : queryTagNodes(userFolksonomyNode)) {
869         if (existSymlink(tagNode, documentNode))
870           ret.add(tagNode);
871       }
872     }
873 
874     else if (scope == PUBLIC) {
875       String publicTagNodePath = nodeHierarchyCreator.getJcrPath(PUBLIC_TAG_NODE_PATH);
876       Node publicFolksonomyTreeNode = getNode(workspace, publicTagNodePath);
877       for (Node tagNode : queryTagNodes(publicFolksonomyTreeNode)) {
878         if (existSymlink(tagNode, documentNode))
879           ret.add(tagNode);
880       }
881     }
882 
883     else if (scope == GROUP) {
884       String[] roles = value.split(";");
885       for (String group : roles) {
886         if (group.length() < 1)
887           continue;
888         Node groupFolksonomyNode = getGroupFolksonomyFolder(group, workspace);
889         for (Node tagNode : queryTagNodes(groupFolksonomyNode)) {
890           if (existSymlink(tagNode, documentNode))
891             ret.add(tagNode);
892         }
893       }
894     } 
895     Collections.sort(ret, new NodeComparator());
896     return ret;
897   }
898 
899   /**
900    * Add new users or groups into tagPermissionPlugin_
901    *
902    * @param plugin
903    */
904   public void addTagPermissionPlugin(ComponentPlugin plugin) {
905     if (plugin instanceof TagPermissionPlugin)
906       tagPermissionPlugin_.add((TagPermissionPlugin) plugin);
907   }
908 
909   /**
910    * {@inheritDoc}
911    */
912   public void addTagPermission(String usersOrGroups) {
913     List<String> _tagPermissionList = tagPermissionList.get(TAG_PERMISSION_LIST);
914     if (_tagPermissionList!=null && !_tagPermissionList.contains(usersOrGroups)) {
915       _tagPermissionList.add(usersOrGroups);
916       tagPermissionList.put(TAG_PERMISSION_LIST, _tagPermissionList);
917     } else if (_tagPermissionList == null) {
918       _tagPermissionList = new ArrayList<String>();
919       _tagPermissionList.add(usersOrGroups);
920       tagPermissionList.put(TAG_PERMISSION_LIST, _tagPermissionList);
921     }
922   }
923 
924   /**
925    * {@inheritDoc}
926    */
927   public void removeTagPermission(String usersOrGroups) {
928     List<String> _tagPermissionList = tagPermissionList.get(TAG_PERMISSION_LIST);
929     if(_tagPermissionList!=null && _tagPermissionList.contains(usersOrGroups)) {
930       _tagPermissionList.remove(usersOrGroups);
931       tagPermissionList.put(TAG_PERMISSION_LIST, _tagPermissionList);
932     }
933   }
934 
935   /**
936    * {@inheritDoc}
937    */
938   public List<String> getTagPermissionList() {
939     return tagPermissionList.get(TAG_PERMISSION_LIST);
940   }
941 
942   /**
943    * {@inheritDoc}
944    */
945   public boolean canEditTag(int scope, List<String> memberships) {
946     if (scope == PUBLIC) {
947       List<String> _tagPermissionList = tagPermissionList.get(TAG_PERMISSION_LIST);
948       if (_tagPermissionList == null || _tagPermissionList.size() == 0) {
949         return false;
950       }
951       for (String membership : memberships) {
952         if (_tagPermissionList.contains(membership))
953           return true;
954         if (membership.contains(":")) {
955           if (_tagPermissionList.contains("*" + membership.substring(membership.indexOf(":"))))
956             return true;
957         }
958       }
959       return false;
960     }
961     return true;
962   }
963 
964   /**
965    * {@inheritDoc}
966    */
967   public List<String> getAllTagNames(String workspace, int scope, String value) throws Exception {
968     List<String> ret = new ArrayList<String>();
969     List<Node> tags = new ArrayList<Node>();
970     switch (scope) {
971     case PUBLIC:
972       tags = getAllPublicTags(value, workspace);
973       break;
974     case PRIVATE:
975       tags = getAllPrivateTags(value);
976       break;
977     case GROUP:
978       tags = value.indexOf(";") >= 0 ? getAllGroupTags(value.split(";"), workspace)
979                                      : getAllGroupTags(value, workspace);
980       break;
981     case SITE:
982       tags = getAllSiteTags(value, workspace);
983     }
984     for (Node tag : tags)
985       ret.add(tag.getName());
986     Collections.sort(ret);
987     return ret;
988   }
989 
990   private void createSiteTagPath() throws Exception {
991     if (sitesTagPath.get(getRepoName()) == null) {
992       // init path to site tags
993       ManageableRepository manageableRepository = WCMCoreUtils.getRepository();
994 
995       SessionProvider sessionProvider = WCMCoreUtils.getSystemSessionProvider();
996       Session session = sessionProvider.getSession(initParams_.getValueParam("workspace")
997                                                    .getValue(), manageableRepository);
998 
999       String[] paths = initParams_.getValueParam("path").getValue().split("/");
1000       Node currentNode = session.getRootNode();
1001       int depth = 0;
1002       for (String path : paths) {
1003         if (path.length() > 0) {
1004           Node cnode = currentNode.hasNode(path) ? currentNode.getNode(path)
1005                                                  : currentNode.addNode(path);
1006           currentNode = cnode;
1007           if (depth++ == 0)
1008             if (!currentNode.isNodeType(EXO_HIDDENABLE))
1009               currentNode.addMixin(EXO_HIDDENABLE);
1010         }
1011       }
1012       session.save();
1013       sitesTagPath.put(getRepoName(), currentNode.getPath());
1014     }
1015   }
1016 
1017   private Node getTagNode(Node folksonomyHome, String tagName) throws Exception {
1018     Node tagNode = dataDistributionType.getOrCreateDataNode(folksonomyHome, tagName);
1019     if (!tagNode.isNodeType(EXO_TAGGED)) {
1020       tagNode.addMixin(EXO_TAGGED);
1021       tagNode.setProperty(EXO_TOTAL, 0);
1022     }
1023     return tagNode;
1024   }
1025 
1026   private void addTag(Node tagNode, Node targetNode) throws Exception {
1027     if (!existSymlink(tagNode, targetNode)) {
1028       linkManager.createLink(tagNode, targetNode);
1029       long total = tagNode.getProperty(EXO_TOTAL).getLong();
1030       tagNode.setProperty(EXO_TOTAL, total + 1);
1031     }
1032   }
1033 
1034   /**
1035    * {@inheritDoc}
1036    */
1037   public DataDistributionType getDataDistributionType() {
1038     return this.dataDistributionType;
1039   }
1040 
1041 }