View Javadoc
1   /*
2    * Copyright (C) 2003-2011 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.commons.utils.CommonsUtils;
20  import org.exoplatform.container.xml.PortalContainerInfo;
21  import org.exoplatform.portal.config.UserPortalConfig;
22  import org.exoplatform.portal.config.UserPortalConfigService;
23  import org.exoplatform.portal.mop.SiteKey;
24  import org.exoplatform.portal.mop.user.UserNavigation;
25  import org.exoplatform.portal.mop.user.UserPortalContext;
26  import org.exoplatform.services.cms.BasePath;
27  import org.exoplatform.services.cms.documents.DocumentService;
28  import org.exoplatform.services.cms.documents.model.Document;
29  import org.exoplatform.services.cms.drives.DriveData;
30  import org.exoplatform.services.cms.drives.ManageDriveService;
31  import org.exoplatform.services.cms.drives.impl.ManageDriveServiceImpl;
32  import org.exoplatform.services.cms.impl.Utils;
33  import org.exoplatform.services.cms.link.LinkManager;
34  import org.exoplatform.services.jcr.RepositoryService;
35  import org.exoplatform.services.jcr.core.ManageableRepository;
36  import org.exoplatform.services.jcr.ext.app.SessionProviderService;
37  import org.exoplatform.services.jcr.ext.common.SessionProvider;
38  import org.exoplatform.services.jcr.ext.hierarchy.NodeHierarchyCreator;
39  import org.exoplatform.services.jcr.impl.core.NodeImpl;
40  import org.exoplatform.services.log.ExoLogger;
41  import org.exoplatform.services.log.Log;
42  import org.exoplatform.services.security.ConversationState;
43  import org.exoplatform.services.wcm.core.NodetypeConstant;
44  import org.exoplatform.services.wcm.utils.WCMCoreUtils;
45  import org.gatein.api.Portal;
46  import org.gatein.api.navigation.Navigation;
47  import org.gatein.api.navigation.Nodes;
48  import org.gatein.api.site.SiteId;
49  
50  import javax.jcr.Node;
51  import javax.jcr.NodeIterator;
52  import javax.jcr.RepositoryException;
53  import javax.jcr.Session;
54  import java.net.URLEncoder;
55  import java.util.*;
56  
57  /**
58   * Created by The eXo Platform SAS Author : eXoPlatform exo@exoplatform.com Mar
59   * 22, 2011
60   */
61  public class DocumentServiceImpl implements DocumentService {
62  
63    public static final String MIX_REFERENCEABLE = "mix:referenceable";
64    public static final String EXO_LAST_MODIFIER_PROP = "exo:lastModifier";
65    public static final String EXO_DATE_CREATED_PROP = "exo:dateCreated";
66    public static final String JCR_LAST_MODIFIED_PROP = "jcr:lastModified";
67    public static final String JCR_CONTENT = "jcr:content";
68    public static final String EXO_OWNER_PROP = "exo:owner";
69    public static final String EXO_TITLE_PROP = "exo:title";
70    public static final String CURRENT_STATE_PROP = "publication:currentState";
71    public static final String DOCUMENTS_APP_NAVIGATION_NODE_NAME = "documents";
72    public static final String DOCUMENT_NOT_FOUND = "?path=doc-not-found";
73    private static final String DOCUMENTS_NODE = "Documents";
74    private static final String SHARED_NODE = "Shared";
75    private static final Log LOG                 = ExoLogger.getLogger(DocumentServiceImpl.class);
76    private ManageDriveService manageDriveService;
77    private Portal portal;
78    private SessionProviderService sessionProviderService;
79    private RepositoryService repoService;
80    private NodeHierarchyCreator nodeHierarchyCreator;
81    private LinkManager linkManager;
82    private PortalContainerInfo portalContainerInfo;
83  
84    public DocumentServiceImpl(ManageDriveService manageDriveService, Portal portal, SessionProviderService sessionProviderService, RepositoryService repoService, NodeHierarchyCreator nodeHierarchyCreator, LinkManager linkManager, PortalContainerInfo portalContainerInfo) {
85      this.manageDriveService = manageDriveService;
86      this.sessionProviderService = sessionProviderService;
87      this.repoService = repoService;
88      this.nodeHierarchyCreator = nodeHierarchyCreator;
89      this.portal = portal;
90      this.linkManager = linkManager;
91      this.portalContainerInfo = portalContainerInfo;
92    }
93  
94    @Override
95    public Document findDocById(String documentId) throws RepositoryException {
96      RepositoryService repositoryService = WCMCoreUtils.getService(RepositoryService.class);
97      ManageableRepository manageRepo = repositoryService.getCurrentRepository();
98      SessionProvider sessionProvider = WCMCoreUtils.getUserSessionProvider();
99  
100     String ws = documentId.split(":/")[0];
101     String uuid = documentId.split(":/")[1];
102 
103     Node node = sessionProvider.getSession(ws, manageRepo).getNodeByUUID(uuid);
104     // Create Document
105     String title = node.hasProperty(EXO_TITLE_PROP) ? node.getProperty(EXO_TITLE_PROP).getString() : "";
106     String id = node.isNodeType(MIX_REFERENCEABLE) ? node.getUUID() : "";
107     String state = node.hasProperty(CURRENT_STATE_PROP) ? node.getProperty(CURRENT_STATE_PROP).getValue().getString() : "";
108     String author = node.hasProperty(EXO_OWNER_PROP) ? node.getProperty(EXO_OWNER_PROP).getString() : "";
109     Calendar lastModified = (node.hasNode(JCR_CONTENT) ? node.getNode(JCR_CONTENT)
110                                                              .getProperty(JCR_LAST_MODIFIED_PROP)
111                                                              .getValue()
112                                                              .getDate() : null);
113     Calendar dateCreated = (node.hasProperty(EXO_DATE_CREATED_PROP) ? node.getProperty(EXO_DATE_CREATED_PROP)
114                                                                           .getValue()
115                                                                           .getDate()
116                                                                    : null);
117     String lastEditor = (node.hasProperty(EXO_LAST_MODIFIER_PROP) ? node.getProperty(EXO_LAST_MODIFIER_PROP)
118                                                                         .getValue()
119                                                                         .getString()
120                                                                  : "");
121     Document doc = new Document(id, node.getName(), title, node.getPath(), 
122                                 ws, state, author, lastEditor, lastModified, dateCreated);
123     return doc;
124   }
125 
126   /**
127    *
128    * {@inheritDoc}
129    */
130   public String getDocumentUrlInPersonalDocuments(Node currentNode, String username) throws Exception {
131     Node rootNode = null;
132     try {
133       SessionProvider sessionProvider = sessionProviderService.getSystemSessionProvider(null);
134       ManageableRepository repository = repoService.getCurrentRepository();
135       Session session = sessionProvider.getSession(repository.getConfiguration().getDefaultWorkspaceName(), repository);
136       //add symlink to user folder destination
137       nodeHierarchyCreator.getJcrPath(BasePath.CMS_USERS_PATH);
138       rootNode = (Node) session.getItem(nodeHierarchyCreator.getJcrPath(BasePath.CMS_USERS_PATH) + getPrivatePath(username));
139       String sharedLink = getSharedLink(currentNode, rootNode);
140       return sharedLink;
141     } catch (Exception e) {
142       LOG.error(e.getMessage(), e);
143       return "";
144     }
145   }
146 
147   /**
148    *
149    * {@inheritDoc}
150    */
151   public String getDocumentUrlInSpaceDocuments(Node currentNode, String spaceId) throws Exception {
152     Node rootNode = null;
153     try {
154       SessionProvider sessionProvider = sessionProviderService.getSystemSessionProvider(null);
155       ManageableRepository repository = repoService.getCurrentRepository();
156       Session session = sessionProvider.getSession(repository.getConfiguration().getDefaultWorkspaceName(), repository);
157       //add symlink to space destination
158       nodeHierarchyCreator.getJcrPath(BasePath.CMS_GROUPS_PATH);
159       rootNode = (Node) session.getItem(nodeHierarchyCreator.getJcrPath(BasePath.CMS_GROUPS_PATH) + spaceId);
160       String sharedLink = getSharedLink(currentNode, rootNode);
161       return sharedLink;
162     } catch (Exception e) {
163       LOG.error(e.getMessage(), e);
164       return "";
165     }
166   }
167 
168   /**
169    * Get the short link to display a document in the Documents app by its id.
170    * @param workspaceName The workspace of the node
171    * @param nodeId The id of the node
172    * @return The link to open the document
173    * @throws Exception
174    */
175   @Override
176   public String getShortLinkInDocumentsApp(String workspaceName, String nodeId) throws Exception {
177     StringBuilder url = new StringBuilder();
178     String containerName = portalContainerInfo.getContainerName();
179     url.append("/")
180             .append(containerName)
181             .append("/private/")
182             .append(CommonsUtils.getRestContextName())
183             .append("/documents/view/")
184             .append(workspaceName)
185             .append("/")
186             .append(nodeId);
187     return url.toString();
188   }
189 
190   /**
191    * Get link to open a document in the Documents application.
192    * This method will try to guess what is the best drive to use based on the node path.
193    * @param nodePath path of the nt:file node to open
194    * @return Link to open the document
195    * @throws Exception
196    */
197   @Override
198   public String getLinkInDocumentsApp(String nodePath) throws Exception {
199     if(nodePath == null) {
200       return null;
201     }
202 
203     // find the best matching drive to display the document
204     DriveData drive = this.getDriveOfNode(nodePath);
205 
206     return getLinkInDocumentsApp(nodePath, drive);
207   }
208 
209   /**
210    * Get link to open a document in the Documents application with the given drive
211    * @param nodePath path of the nt:file node to open
212    * @param drive drive to use to open the nt:file node
213    * @return Link to open the document
214    * @throws Exception
215    */
216   @Override
217   public String getLinkInDocumentsApp(String nodePath, DriveData drive) throws Exception {
218     if(nodePath == null) {
219       return null;
220     }
221 
222     String containerName = portalContainerInfo.getContainerName();
223     StringBuffer url = new StringBuffer();
224     url.append("/").append(containerName);
225     if (drive == null) {
226       SiteKey siteKey = getDefaultSiteKey();
227       url.append("/").append(siteKey.getName()).append("/").append(DOCUMENTS_APP_NAVIGATION_NODE_NAME)
228           .append(DOCUMENT_NOT_FOUND);
229       return url.toString();
230     }
231 
232     String encodedDriveName = URLEncoder.encode(drive.getName(), "UTF-8");
233     String encodedNodePath = URLEncoder.encode(nodePath, "UTF-8");
234     if(drive.getName().equals(ManageDriveServiceImpl.GROUPS_DRIVE_NAME)) {
235       // handle group drive case
236       String groupId = drive.getParameters().get(ManageDriveServiceImpl.DRIVE_PARAMATER_GROUP_ID);
237       if(groupId != null) {
238         String groupPageName;
239         String[] splitedGroupId = groupId.split("/");
240         if (splitedGroupId != null && splitedGroupId.length == 3 && splitedGroupId[1].equals("spaces")) {
241           // the doc is in a space -> we use the documents application of the space
242 
243           // we need to retrieve the root navigation URI of the space since it can differ from
244           // the group id if the space has been renamed
245           String rootNavigation = getSpaceRootNavigationNodeURI(groupId);
246 
247           groupPageName = rootNavigation + "/" + DOCUMENTS_APP_NAVIGATION_NODE_NAME;
248         } else {
249           // otherwise we use the portal documents application
250           groupPageName = DOCUMENTS_APP_NAVIGATION_NODE_NAME;
251         }
252         url.append("/g/").append(groupId.replaceAll("/", ":")).append("/").append(groupPageName)
253                 .append("?path=").append(encodedDriveName).append(encodedNodePath)
254                 .append("&").append(ManageDriveServiceImpl.DRIVE_PARAMATER_GROUP_ID).append("=").append(groupId);
255       } else {
256         throw new Exception("Cannot get group id from node path " + nodePath);
257       }
258     } else if(drive.getName().equals(ManageDriveServiceImpl.USER_DRIVE_NAME)
259             || drive.getName().equals(ManageDriveServiceImpl.PERSONAL_DRIVE_NAME)) {
260       // handle personal drive case
261       SiteKey siteKey = getDefaultSiteKey();
262       url.append("/").append(siteKey.getName()).append("/").append(DOCUMENTS_APP_NAVIGATION_NODE_NAME)
263               .append("?path=" + encodedDriveName + encodedNodePath);
264       String[] splitedNodePath = nodePath.split("/");
265       if(splitedNodePath != null && splitedNodePath.length >= 6) {
266         String userId = splitedNodePath[5];
267         url.append("&").append(ManageDriveServiceImpl.DRIVE_PARAMATER_USER_ID).append("=").append(userId);
268       }
269     } else {
270       // default case
271       SiteKey siteKey = getDefaultSiteKey();
272       url.append("/").append(siteKey.getName()).append("/").append(DOCUMENTS_APP_NAVIGATION_NODE_NAME)
273               .append("?path=" + encodedDriveName + encodedNodePath);
274     }
275 
276     return url.toString();
277   }
278 
279   /**
280    * Retrieve the root navigation node URi of a space
281    * This method uses the Portal Navigation API and not the Space API to avoid making ECMS depends on Social
282    * @param spaceGroupId The groupId of the space
283    * @return The URI of the root navigation node of the space
284    */
285   protected String getSpaceRootNavigationNodeURI(String spaceGroupId) throws Exception {
286     String navigationName = null;
287 
288     Navigation spaceNavigation = portal.getNavigation(new SiteId(org.gatein.api.site.SiteType.SPACE, spaceGroupId));
289     if(spaceNavigation != null) {
290       org.gatein.api.navigation.Node navigationRootNode = spaceNavigation.getRootNode(Nodes.visitChildren());
291       if(navigationRootNode != null && navigationRootNode.iterator().hasNext()) {
292         // we assume there is only one root navigation node, that's how spaces work
293         org.gatein.api.navigation.Node node = navigationRootNode.iterator().next();
294         navigationName = node.getName();
295       }
296     }
297 
298     return navigationName;
299   }
300 
301   @Override
302   public DriveData getDriveOfNode(String nodePath) throws Exception {
303     return getDriveOfNode(nodePath, ConversationState.getCurrent().getIdentity().getUserId(), Utils.getMemberships());
304   }
305 
306   @Override
307   public DriveData getDriveOfNode(String nodePath, String userId, List<String> memberships) throws Exception {
308     DriveData nodeDrive = null;
309     List<DriveData> drives = manageDriveService.getDriveByUserRoles(userId, memberships);
310 
311     // Manage special cases
312     String[] splitedPath = nodePath.split("/");
313     if (splitedPath != null && splitedPath.length >= 2 && splitedPath.length >= 6
314         && splitedPath[1].equals(ManageDriveServiceImpl.PERSONAL_DRIVE_ROOT_NODE)) {
315       if (splitedPath[5].equals(userId)) {
316         nodeDrive = manageDriveService.getDriveByName(ManageDriveServiceImpl.PERSONAL_DRIVE_NAME);
317       } else {
318         nodeDrive = manageDriveService.getDriveByName(ManageDriveServiceImpl.USER_DRIVE_NAME);
319       }
320       if (nodeDrive != null) {
321         nodeDrive = nodeDrive.clone();
322         nodeDrive.getParameters().put(ManageDriveServiceImpl.DRIVE_PARAMATER_USER_ID,
323                                       splitedPath[2] + "/" + splitedPath[3] + "/" + splitedPath[4] + "/" + splitedPath[5]);
324       }
325     }
326     if (splitedPath != null && splitedPath.length >= 2 && splitedPath[1].equals(ManageDriveServiceImpl.GROUPS_DRIVE_ROOT_NODE)) {
327       int groupDocumentsRootNodeName = nodePath.indexOf("/Documents");
328       if(groupDocumentsRootNodeName >= 0) {
329         // extract group id for doc path
330         String groupId = nodePath.substring(ManageDriveServiceImpl.GROUPS_DRIVE_ROOT_NODE.length() + 1, groupDocumentsRootNodeName);
331         nodeDrive = manageDriveService.getDriveByName(groupId.replaceAll("/", "."));
332       }
333     }
334     if (nodeDrive == null) {
335       for (DriveData drive : drives) {
336         if (nodePath.startsWith(drive.getResolvedHomePath())) {
337           if (nodeDrive == null || nodeDrive.getResolvedHomePath().length() < drive.getResolvedHomePath().length()) {
338             nodeDrive = drive;
339           }
340         }
341       }
342     }
343     return nodeDrive;
344   }
345 
346 
347   protected UserPortalConfig getDefaultUserPortalConfig() throws Exception {
348     UserPortalConfigService userPortalConfigSer = WCMCoreUtils.getService(UserPortalConfigService.class);
349     UserPortalContext NULL_CONTEXT = new UserPortalContext() {
350       public ResourceBundle getBundle(UserNavigation navigation) {
351         return null;
352       }
353       public Locale getUserLocale() {
354         return Locale.ENGLISH;
355       }
356     };
357     String remoteId = ConversationState.getCurrent().getIdentity().getUserId() ;
358     UserPortalConfig userPortalCfg = userPortalConfigSer.
359             getUserPortalConfig(userPortalConfigSer.getDefaultPortal(), remoteId, NULL_CONTEXT);
360     return userPortalCfg;
361   }
362 
363   protected SiteKey getDefaultSiteKey() throws Exception {
364     UserPortalConfig prc = getDefaultUserPortalConfig();
365     if (prc == null) {
366       return null;
367     }
368     SiteKey siteKey = SiteKey.portal(prc.getPortalConfig().getName());
369     return siteKey;
370   }
371 
372   private String getPrivatePath(String user) {
373     return "/" + user.substring(0, 1) + "___/" + user.substring(0, 2) + "___/" + user.substring(0, 3) + "___/" + user + "/Private";
374   }
375 
376   private String getSharedLink(Node currentNode, Node rootNode) {
377     Node shared = null;
378     Node link = null;
379     try {
380       rootNode = rootNode.getNode(DOCUMENTS_NODE);
381       if (!rootNode.hasNode(SHARED_NODE)) {
382         shared = rootNode.addNode(SHARED_NODE);
383       } else {
384         shared = rootNode.getNode(SHARED_NODE);
385       }
386       if (currentNode.isNodeType(NodetypeConstant.EXO_SYMLINK)) {
387         currentNode = linkManager.getTarget(currentNode);
388       }
389       List<String> path = new ArrayList<>();
390       Node targetNode = null;
391       boolean existingSymlink = false;
392       for (NodeIterator it = shared.getNodes(); it.hasNext(); ) {
393         Node node = it.nextNode();
394         path.add(((NodeImpl) node).getInternalPath().getAsString());
395         if (path.contains(((NodeImpl) shared).getInternalPath().getAsString() + "[]" + currentNode.getName() + ":1")) {
396           existingSymlink = true;
397           targetNode = node;
398           break;
399         }
400       }
401       if (existingSymlink) {
402         link = targetNode;
403       } else {
404         link = linkManager.createLink(shared, currentNode);
405       }
406       return CommonsUtils.getCurrentDomain() + getShortLinkInDocumentsApp(link.getSession().getWorkspace().getName(), ((NodeImpl) link).getInternalIdentifier());
407     } catch (Exception e) {
408       LOG.error(e.getMessage(), e);
409       return "";
410     }
411   }
412 
413 }