View Javadoc
1   /*
2    * Copyright (C) 2003-2007 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.link.impl;
18  
19  import java.security.AccessControlException;
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  
25  import javax.jcr.Item;
26  import javax.jcr.ItemNotFoundException;
27  import javax.jcr.Node;
28  import javax.jcr.NodeIterator;
29  import javax.jcr.PathNotFoundException;
30  import javax.jcr.RepositoryException;
31  import javax.jcr.Session;
32  import javax.jcr.ValueFormatException;
33  import javax.jcr.query.Query;
34  import javax.jcr.query.QueryManager;
35  import javax.jcr.query.QueryResult;
36  
37  import org.exoplatform.services.cms.CmsService;
38  import org.exoplatform.services.cms.impl.Utils;
39  import org.exoplatform.services.cms.link.LinkManager;
40  import org.exoplatform.services.cms.link.NodeLinkAware;
41  import org.exoplatform.services.jcr.access.AccessControlEntry;
42  import org.exoplatform.services.jcr.access.PermissionType;
43  import org.exoplatform.services.jcr.core.ExtendedNode;
44  import org.exoplatform.services.jcr.core.ManageableRepository;
45  import org.exoplatform.services.jcr.ext.app.SessionProviderService;
46  import org.exoplatform.services.jcr.ext.common.SessionProvider;
47  import org.exoplatform.services.listener.ListenerService;
48  import org.exoplatform.services.log.ExoLogger;
49  import org.exoplatform.services.log.Log;
50  import org.exoplatform.services.security.IdentityConstants;
51  import org.exoplatform.services.wcm.core.NodetypeConstant;
52  import org.exoplatform.services.wcm.utils.WCMCoreUtils;
53  
54  /**
55   * Created by The eXo Platform SARL
56   * Author : Ly Dinh Quang
57   *          quang.ly@exoplatform.com
58   * Mar 13, 2009
59   */
60  public class LinkManagerImpl implements LinkManager {
61  
62    private final static String    SYMLINK       = "exo:symlink";
63  
64    private final static String    WORKSPACE     = "exo:workspace";
65  
66    private final static String    UUID          = "exo:uuid";
67  
68    private final static String    PRIMARY_TYPE  = "exo:primaryType";
69  
70    private final static String    SYMLINK_NAME  = "exo:name";
71  
72    private final static String    SYMLINK_TITLE = "exo:title";
73  
74    private final static Log       LOG  = ExoLogger.getLogger(LinkManagerImpl.class.getName());
75  
76    private final SessionProviderService providerService_;
77  
78    public LinkManagerImpl(SessionProviderService providerService) throws Exception {
79      providerService_ = providerService;
80    }
81  
82    /**
83     * {@inheritDoc}
84     */
85    public Node createLink(Node parent, String linkType, Node target) throws RepositoryException {
86      return createLink(parent, linkType, target, null);
87    }
88  
89    /**
90     * {@inheritDoc}
91     */
92    public Node createLink(Node parent, Node target) throws RepositoryException {
93      return createLink(parent, null, target, null);
94    }
95  
96    /**
97     * {@inheritDoc}
98     */  
99    public Node createLink(Node parent, String linkType, Node target, String linkName) throws RepositoryException {
100     return createLink(parent, linkType, target, linkName, linkName);
101   }
102 
103   /**
104    * {@inheritDoc}
105    */
106   public Node createLink(Node parent, String linkType, Node target, String linkName, String linkTitle)
107       throws RepositoryException {
108     if (!target.isNodeType(SYMLINK)) {
109       boolean targetEdited = false;
110       if (target.canAddMixin("mix:referenceable")) {
111         target.addMixin("mix:referenceable");
112         target.getSession().save();
113         targetEdited = true;
114       }
115       if (linkType == null || linkType.trim().length() == 0)
116         linkType = SYMLINK;
117       if (linkName == null || linkName.trim().length() == 0)
118         linkName = target.getName();
119       Node linkNode = parent.addNode(linkName, linkType);
120       try {
121         updateAccessPermissionToLink(linkNode, target);
122       } catch(Exception e) {
123         if (LOG.isErrorEnabled()) {
124           LOG.error("CAN NOT UPDATE ACCESS PERMISSIONS FROM TARGET NODE TO LINK NODE", e);
125         }
126       }
127       linkNode.setProperty(WORKSPACE, target.getSession().getWorkspace().getName());
128       linkNode.setProperty(PRIMARY_TYPE, target.getPrimaryNodeType().getName());
129       linkNode.setProperty(UUID, target.getUUID());
130       if(linkNode.canAddMixin("exo:sortable")) {
131         linkNode.addMixin("exo:sortable");
132       }
133       linkNode.setProperty(SYMLINK_TITLE, linkTitle);
134       linkNode.setProperty(SYMLINK_NAME, linkName);
135       linkNode.getSession().save();
136       ListenerService listenerService = WCMCoreUtils.getService(ListenerService.class);
137       try {
138         String remoteUser = WCMCoreUtils.getRemoteUser();
139         if (remoteUser != null) {
140           if (Utils.isDocument(target) && targetEdited) {
141             listenerService.broadcast(CmsService.POST_EDIT_CONTENT_EVENT, null, target);
142           }
143         }
144       } catch (Exception e) {
145         if (LOG.isErrorEnabled()) {
146           LOG.error("Error while broadcasting event: " + e.getMessage());
147         }
148       }
149       return linkNode;
150     }
151     return null;
152   }
153 
154   /**
155    * {@inheritDoc}
156    */
157   public Node getTarget(Node link, boolean system) throws ItemNotFoundException,
158   RepositoryException {
159     String uuid = link.getProperty(UUID).getString();
160     Node targetNode = getSession(link, system).getNodeByUUID(uuid);
161     return targetNode;
162   }
163 
164   private Session getSession(Node link, boolean system) throws RepositoryException {
165     String workspaceTarget = link.getProperty(WORKSPACE).getString();
166     return getSession((ManageableRepository) link.getSession().getRepository(), workspaceTarget,
167                       system);
168   }
169 
170   private Session getSession(ManageableRepository manageRepository, String workspaceName,
171                              boolean system) throws RepositoryException {
172     if (system)
173       return providerService_.getSystemSessionProvider(null).getSession(workspaceName, manageRepository);
174     return providerService_.getSessionProvider(null).getSession(workspaceName, manageRepository);
175   }
176 
177   /**
178    * {@inheritDoc}
179    */
180   public Node getTarget(Node link) throws ItemNotFoundException, RepositoryException {
181     return getTarget(link, false);
182   }
183 
184   /**
185    * {@inheritDoc}
186    */
187   public boolean isTargetReachable(Node link) throws RepositoryException {
188     return isTargetReachable(link, false);
189   }
190 
191   /**
192    * {@inheritDoc}
193    */
194   public boolean isTargetReachable(Node link, boolean system) throws RepositoryException {
195     Session session = null;
196     try {
197       session = getSession(link, system);
198       session.getNodeByUUID(link.getProperty(UUID).getString());
199     } catch (ItemNotFoundException e) {
200       return false;
201     }
202     return true;
203   }
204 
205   /**
206    * {@inheritDoc}
207    */
208   public Node updateLink(Node linkNode, Node targetNode) throws RepositoryException {
209     if (targetNode.canAddMixin("mix:referenceable")) {
210       targetNode.addMixin("mix:referenceable");
211       targetNode.getSession().save();
212     }
213     try {
214       updateAccessPermissionToLink(linkNode, targetNode);
215     } catch(Exception e) {
216       if (LOG.isErrorEnabled()) {
217         LOG.error("CAN NOT UPDATE ACCESS PERMISSIONS FROM TARGET NODE TO LINK NODE", e);
218       }
219     }
220     linkNode.setProperty(UUID, targetNode.getUUID());
221     linkNode.setProperty(PRIMARY_TYPE, targetNode.getPrimaryNodeType().getName());
222     linkNode.setProperty(WORKSPACE, targetNode.getSession().getWorkspace().getName());
223     linkNode.getSession().save();
224     return linkNode;
225   }
226 
227   /**
228    * {@inheritDoc}
229    */
230   public boolean isLink(Item item) throws RepositoryException {
231     if (item instanceof Node) {
232       Node node = (Node) item;
233       if (node instanceof NodeLinkAware) {
234         node = ((NodeLinkAware) node).getRealNode();
235       }
236       return node.isNodeType(SYMLINK);
237     }
238     return false;
239   }
240 
241   /**
242    * {@inheritDoc}
243    */
244   public String getTargetPrimaryNodeType(Node link) throws RepositoryException {
245     return link.getProperty(PRIMARY_TYPE).getString();
246   }
247 
248   /**
249    * Update the permission between two given node
250    * @param linkNode    The node to update permission
251    * @param targetNode  The target node to get permission
252    * @throws Exception
253    */
254   private void updateAccessPermissionToLink(Node linkNode, Node targetNode) throws Exception {
255     if(canChangePermission(linkNode)) {
256       if(linkNode.canAddMixin("exo:privilegeable")) {
257         linkNode.addMixin("exo:privilegeable");
258         ((ExtendedNode)linkNode).setPermission(getNodeOwner(linkNode),PermissionType.ALL);
259       }
260       removeCurrentIdentites(linkNode);
261       Map<String, String[]> perMap = new HashMap<String, String[]>();
262       List<String> permsList = new ArrayList<String>();
263       List<String> idList = new ArrayList<String>();
264       for(AccessControlEntry accessEntry : ((ExtendedNode)targetNode).getACL().getPermissionEntries()) {
265         if(!idList.contains(accessEntry.getIdentity())) {
266           idList.add(accessEntry.getIdentity());
267           permsList = ((ExtendedNode)targetNode).getACL().getPermissions(accessEntry.getIdentity());
268           perMap.put(accessEntry.getIdentity(), permsList.toArray(new String[permsList.size()]));
269         }
270       }
271       ((ExtendedNode)linkNode).setPermissions(perMap);
272     }
273   }
274 
275   /**
276    * Remove all identity of the given node
277    * @param linkNode  The node to remove all identity
278    * @throws AccessControlException
279    * @throws RepositoryException
280    */
281   private void removeCurrentIdentites(Node linkNode) throws AccessControlException, RepositoryException {
282     String currentUser = linkNode.getSession().getUserID();
283     if (currentUser != null)
284       ((ExtendedNode)linkNode).setPermission(currentUser, PermissionType.ALL);
285     for(AccessControlEntry accessEntry : ((ExtendedNode)linkNode).getACL().getPermissionEntries()) {
286       if(canRemovePermission(linkNode, accessEntry.getIdentity())
287           && ((ExtendedNode)linkNode).getACL().getPermissions(accessEntry.getIdentity()).size() > 0
288           && !accessEntry.getIdentity().equals(currentUser)) {
289         ((ExtendedNode) linkNode).removePermission(accessEntry.getIdentity());
290       }
291     }
292   }
293 
294   /**
295    * Remove the permission from the given node
296    * @param node      The node to remove permission
297    * @param identity  The identity of the permission
298    * @return
299    * @throws ValueFormatException
300    * @throws PathNotFoundException
301    * @throws RepositoryException
302    */
303   private boolean canRemovePermission(Node node, String identity) throws ValueFormatException,
304   PathNotFoundException, RepositoryException {
305     String owner = getNodeOwner(node);
306     if(identity.equals(IdentityConstants.SYSTEM)) return false;
307     if(owner != null && owner.equals(identity)) return false;
308     return true;
309   }
310 
311   /**
312    * Get the owner of the given node
313    * @param node      The node to get owner
314    * @return
315    * @throws ValueFormatException
316    * @throws PathNotFoundException
317    * @throws RepositoryException
318    */
319   private String getNodeOwner(Node node) throws ValueFormatException, PathNotFoundException, RepositoryException {
320     if(node.hasProperty("exo:owner")) {
321       return node.getProperty("exo:owner").getString();
322     }
323     return IdentityConstants.SYSTEM;
324   }
325 
326   /**
327    * Check permission of the given node
328    * @param node      The Node to check permission
329    * @return
330    * @throws RepositoryException
331    */
332   private boolean canChangePermission(Node node) throws RepositoryException {
333     try {
334       ((ExtendedNode)node).checkPermission(PermissionType.CHANGE_PERMISSION);
335       return true;
336     } catch(AccessControlException e) {
337       return false;
338     }
339   }
340 
341   /**
342    * {@inheritDoc}
343    */
344   public List<Node> getAllLinks(Node targetNode, String linkType, SessionProvider sessionProvider) {
345     try {
346       List<Node> result = new ArrayList<Node>();
347       ManageableRepository repository  = WCMCoreUtils.getRepository();
348       String[] workspaces = repository.getWorkspaceNames();
349       String queryString = new StringBuilder().append("SELECT * FROM ").
350           append(linkType).
351           append(" WHERE exo:uuid='").
352           append(targetNode.getUUID()).append("'").
353           append(" AND exo:workspace='").
354           append(targetNode.getSession().getWorkspace().getName()).
355           append("'").toString();
356 
357       for (String workspace : workspaces) {
358         Session session = sessionProvider.getSession(workspace, repository);
359         //Continue In the case cannot access to a workspace
360         if(session == null) continue;
361         QueryManager queryManager = session.getWorkspace().getQueryManager();
362         Query query = queryManager.createQuery(queryString, Query.SQL);
363         QueryResult queryResult = query.execute();
364         NodeIterator iter = queryResult.getNodes();
365         while (iter.hasNext()) {
366           result.add(iter.nextNode());
367         }
368       }
369 
370       return result;
371     } catch (RepositoryException e) {
372       // return empty node list if there are errors in execution or user has no right to access nodes
373       return new ArrayList<Node>();
374     }
375   }
376 
377   /**
378    * {@inheritDoc}
379    */
380   public List<Node> getAllLinks(Node targetNode, String linkType) {
381     return getAllLinks(targetNode, linkType, WCMCoreUtils.getUserSessionProvider());
382   }
383 
384   /**
385    * {@inheritDoc}
386    * @throws RepositoryException 
387    */
388   public void updateSymlink(Node node) throws RepositoryException {
389     if (node.isNodeType(NodetypeConstant.EXO_SYMLINK)) {
390       try {
391         ((ExtendedNode)node).checkPermission(PermissionType.SET_PROPERTY);
392       } catch(AccessControlException e) {
393         SessionProvider provider = WCMCoreUtils.getSystemSessionProvider();
394         node = (Node)provider.getSession(node.getSession().getWorkspace().getName(), 
395                                          WCMCoreUtils.getRepository()).getItem(node.getPath());
396       }
397       if (node.canAddMixin(NodetypeConstant.EXO_TARGET_DATA)) {
398         node.addMixin(NodetypeConstant.EXO_TARGET_DATA);
399       }
400       Node target = this.getTarget(node, true);
401       if (!node.hasProperty(NodetypeConstant.EXO_LAST_MODIFIED_DATE) || 
402           node.getProperty(NodetypeConstant.EXO_LAST_MODIFIED_DATE).getDate().compareTo( 
403           target.getProperty(NodetypeConstant.EXO_LAST_MODIFIED_DATE).getDate()) < 0) {
404         String[] propList = {NodetypeConstant.EXO_DATE_CREATED,
405             NodetypeConstant.EXO_LAST_MODIFIED_DATE, NodetypeConstant.PUBLICATION_LIVE_DATE,
406             NodetypeConstant.EXO_START_EVENT, NodetypeConstant.EXO_INDEX};
407         for (String p : propList) {
408           try {
409             if (target.hasProperty(p)) {
410               node.setProperty(p, target.getProperty(p).getValue());
411               node.save();
412             }
413           } catch (RepositoryException e) {
414             if (LOG.isErrorEnabled()) {
415               LOG.error("Can not update property: " + p + " for node: " + node.getPath(), e);
416             }
417           }
418         }
419     }
420     }
421   }
422 
423 }