View Javadoc

1   /**
2    * This file Copyright (c) 2003-2011 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.cms.core;
35  
36  import info.magnolia.cms.core.search.QueryManager;
37  import info.magnolia.cms.security.AccessDeniedException;
38  import info.magnolia.cms.security.AccessManager;
39  import info.magnolia.cms.security.Permission;
40  import info.magnolia.cms.util.WorkspaceAccessUtil;
41  import info.magnolia.cms.beans.config.ContentRepository;
42  import info.magnolia.logging.AuditLoggingUtil;
43  
44  import java.util.Collection;
45  import java.io.ObjectStreamField;
46  import java.io.Serializable;
47  
48  import javax.jcr.PathNotFoundException;
49  import javax.jcr.RepositoryException;
50  import javax.jcr.Node;
51  import javax.jcr.Session;
52  import javax.jcr.Workspace;
53  import javax.jcr.ItemNotFoundException;
54  
55  import org.apache.commons.lang.StringUtils;
56  import org.slf4j.Logger;
57  import org.slf4j.LoggerFactory;
58  
59  
60  /**
61   * Default JCR-based implementation of {@link HierarchyManager}.
62   *
63   * @author Sameer Charles
64   * $Id:HierarchyManager.java 2719 2006-04-27 14:38:44Z scharles $
65   */
66  public class DefaultHierarchyManager implements HierarchyManager, Serializable {
67  
68      private static final long serialVersionUID = 223L;
69  
70      /**
71       * instead of defining each field transient, we explicitly says what needs to be
72       * serialized.
73       * */
74      private static final ObjectStreamField[] serialPersistentFields = {
75          new ObjectStreamField("userId", String.class),
76          new ObjectStreamField("repositoryName", String.class),
77          new ObjectStreamField("workspaceName", String.class),
78          new ObjectStreamField("accessManager", AccessManager.class)
79      };
80  
81      private static final Logger log = LoggerFactory.getLogger(DefaultHierarchyManager.class);
82  
83      private Node rootNode;
84  
85      private Workspace workspace;
86  
87      private Session jcrSession;
88  
89      private QueryManager queryManager;
90  
91      /**
92       * All serializable properties.
93       * */
94      private String userId;
95  
96      private String repositoryName;
97  
98      private String workspaceName;
99  
100     private AccessManager accessManager;
101 
102     protected DefaultHierarchyManager() {}
103 
104     public DefaultHierarchyManager(String userId, Session jcrSession, AccessManager aManager) throws RepositoryException {
105         this.userId = userId;
106         this.jcrSession = jcrSession;
107         this.rootNode = jcrSession.getRootNode();
108         this.workspace = jcrSession.getWorkspace();
109         this.workspaceName = this.workspace.getName();
110         this.repositoryName = ContentRepository.getParentRepositoryName(this.workspaceName);
111         this.accessManager = aManager;
112     }
113 
114     /**
115      * Reinitialize itself with the partial deserialized data.
116      * */
117     private void reInitialize() {
118         WorkspaceAccessUtil util = WorkspaceAccessUtil.getInstance();
119         try {
120             this.jcrSession = util.createRepositorySession(util.getDefaultCredentials(), this.repositoryName, this.workspaceName);
121             this.queryManager = util.createQueryManager(this.jcrSession, this);
122             this.rootNode = this.jcrSession.getRootNode();
123             this.workspace = this.jcrSession.getWorkspace();
124         } catch (RepositoryException re) {
125             log.error("Failed to load HierarchyManager from persistent storage", re);
126         }
127     }
128 
129     /**
130      * Set access manager for this hierarchy.
131      * @param accessManager
132      */
133     protected void setAccessManager(AccessManager accessManager) {
134         this.accessManager = accessManager;
135     }
136 
137     /**
138      * Get access manager.
139      * @return accessmanager attached to this hierarchy
140      */
141     public AccessManager getAccessManager() {
142         return this.accessManager;
143     }
144 
145     /**
146      * Set query manager for this hierarchy.
147      * @param queryManager
148      */
149     protected void setQueryManager(QueryManager queryManager) {
150         this.queryManager = queryManager;
151     }
152 
153     public QueryManager getQueryManager() {
154         if (null == this.queryManager) {
155             WorkspaceAccessUtil util = WorkspaceAccessUtil.getInstance();
156             try {
157                 this.queryManager = util.createQueryManager(this.jcrSession, this);
158             } catch (RepositoryException e) {
159                 reInitialize();
160             }
161         }
162         return this.queryManager;
163     }
164 
165     private Node getRootNode() {
166         if (null == this.rootNode) {
167             reInitialize();
168         }
169         return this.rootNode;
170     }
171 
172     private Session getJcrSession() {
173         log.debug("Accessing JCR session from {}", Thread.currentThread().getName());
174         if (null == this.jcrSession) {
175             reInitialize();
176         }
177         return this.jcrSession;
178     }
179 
180     /**
181      * Creates contentNode of type <b>contentType</b>. contentType must be defined in item type definition of Magnolia
182      * as well as JCR implementation.
183      * @param path absolute (primary) path to this <code>Node</code>
184      * @param label page name
185      * @param contentType , JCR node type as configured
186      * @throws PathNotFoundException
187      * @throws RepositoryException
188      * @throws AccessDeniedException
189      */
190     public Content createContent(String path, String label, String contentType) throws PathNotFoundException, RepositoryException, AccessDeniedException {
191         Content content = new DefaultContent(this.getRootNode(), this.getNodePath(path, label), contentType, this);
192         setMetaData(content.getMetaData());
193         AuditLoggingUtil.log( AuditLoggingUtil.ACTION_CREATE, workspaceName, content.getItemType(), content.getHandle());
194         return content;
195     }
196 
197     private String getNodePath(String parent, String label) {
198         if (StringUtils.isEmpty(parent) || (parent.equals("/"))) { //$NON-NLS-1$
199             return label;
200         }
201         if (!parent.endsWith("/")) {
202             parent = parent + "/";
203         }
204         return getNodePath(parent + label);
205     }
206 
207     private String getNodePath(String path) {
208         if (path != null && path.startsWith("/")) { //$NON-NLS-1$
209             return path.replaceFirst("/", StringUtils.EMPTY); //$NON-NLS-1$
210         }
211         return path;
212     }
213 
214     /**
215      * Helper method to set page properties, create page calls this method. you could call this method anytime to create
216      * working page properties.
217      */
218     protected void setMetaData(MetaData md) throws RepositoryException, AccessDeniedException {
219         md.setCreationDate();
220         md.setModificationDate();
221         md.setAuthorId(this.userId);
222         md.setTitle(StringUtils.EMPTY);
223     }
224 
225     /**
226      * get content object of the requested URI.
227      * @param path of the content to be initialized
228      * @return Content
229      * @throws javax.jcr.PathNotFoundException
230      * @throws javax.jcr.RepositoryException
231      */
232     public Content getContent(String path) throws PathNotFoundException, RepositoryException, AccessDeniedException {
233         if (path.equals("/")) { //$NON-NLS-1$
234             return this.getRoot();
235         }
236         return (new DefaultContent(this.getRootNode(), getNodePath(path), this));
237     }
238 
239     /**
240      * Like getContent() but creates the node if not yet existing. Attention save is not called!
241      * @param path the path of the node
242      * @param create true if the node should get created
243      * @param type the node type of the created node
244      * @return the node
245      * @throws AccessDeniedException
246      * @throws RepositoryException
247      */
248     public Content getContent(String path, boolean create, ItemType type) throws AccessDeniedException, RepositoryException {
249         Content node;
250         try {
251             node = getContent(path);
252         }
253         catch (PathNotFoundException e) {
254             if (create) {
255                 node = this.createContent(StringUtils.substringBeforeLast(path, "/"), StringUtils.substringAfterLast( path, "/"), type.toString());
256                 AuditLoggingUtil.log( AuditLoggingUtil.ACTION_CREATE, workspaceName, node.getItemType(), node.getHandle());
257             }
258             else {
259                 throw e;
260             }
261         }
262         return node;
263     }
264 
265 
266     /**
267      * get NodeData object of the requested URI.
268      * @param path of the atom to be initialized
269      * @return NodeData
270      * @throws javax.jcr.PathNotFoundException
271      * @throws javax.jcr.RepositoryException
272      */
273     public NodeData getNodeData(String path) throws PathNotFoundException, RepositoryException, AccessDeniedException {
274         if (StringUtils.isEmpty(path)) {
275             return null;
276         }
277         final String nodePath = StringUtils.substringBeforeLast(path, "/");
278         final String nodeDataName = StringUtils.substringAfterLast(path, "/");
279         return getContent(nodePath).getNodeData(nodeDataName);
280     }
281 
282     /**
283      * returns the first page with a given template name that is found in tree that starts from the page given py the
284      * path (including this page).
285      * @param path handle of the page from where the search should start
286      * @param templateName template name to search for
287      * @return first Content hierarchy node that has the specified template name assigned
288      * @throws javax.jcr.PathNotFoundException
289      * @throws javax.jcr.RepositoryException
290      *
291      * @deprecated since 4.0 - only used by taglibs - should go/move.
292      */
293     @Deprecated
294     public Content getPage(String path, String templateName) throws PathNotFoundException, RepositoryException,  AccessDeniedException {
295         Content page = getContent(path);
296         if (page.getTemplate().equals(templateName)) {
297             return page;
298         }
299         Content pageToBeFound = null;
300         try {
301             if (page.hasChildren()) {
302                 Collection<Content> children = page.getChildren(ItemType.CONTENT.getSystemName());
303                 for (Content child : children) {
304                     if (child.getTemplate().equals(templateName)) {
305                         return child;
306                     }
307                     if (child.hasChildren()) {
308                         pageToBeFound = getPage(child.getHandle(), templateName);
309                     }
310                     if (pageToBeFound != null) {
311                         return pageToBeFound;
312                     }
313                 }
314             }
315         }
316         catch (Exception e) {
317             log.error("Failed to get - " + path + " : " + e.getMessage(), e);
318         }
319         return pageToBeFound;
320     }
321 
322     /**
323      * removes specified path, it can be either node or property.
324      * @param path to be removed
325      * @throws javax.jcr.PathNotFoundException
326      * @throws javax.jcr.RepositoryException
327      * @throws AccessDeniedException
328      */
329     public void delete(String path) throws PathNotFoundException, RepositoryException, AccessDeniedException {
330         Access.isGranted(this.accessManager, path, Permission.REMOVE);
331         ItemType type = null;
332         if (this.isNodeData(path)) {
333             this.getNodeData(makeRelative(path)).delete();
334         }
335         else {
336             Node aNode = this.getRootNode().getNode(makeRelative(path));
337             if (aNode.hasProperty(ItemType.JCR_FROZEN_PRIMARY_TYPE)) {
338                 type = new ItemType(aNode.getProperty(ItemType.JCR_FROZEN_PRIMARY_TYPE).getString());
339             }
340             type = new ItemType(aNode.getProperty(ItemType.JCR_PRIMARY_TYPE).getString());
341             aNode.remove();
342         }
343         AuditLoggingUtil.log( AuditLoggingUtil.ACTION_DELETE, workspaceName, type, path);
344     }
345 
346     private String makeRelative(String path) {
347         return StringUtils.stripStart(path, "/"); //$NON-NLS-1$
348     }
349 
350     /**
351      * @return rootNode of the current working repository-workspace
352      */
353     public Content getRoot() throws RepositoryException, AccessDeniedException {
354         return (new DefaultContent(this.getRootNode(), this));
355     }
356 
357     /**
358      * Checks if the requested resource is a page (hierarchy Node).
359      * @param path of the requested content
360      * @return boolean true is the requested content is a Hierarchy Node todo remove this method, instead use
361      * (getContent(PATH) is NodeType)
362      * @deprecated since 4.0 - use getContent().isNodeType() instead. (not used currently)
363      */
364     @Deprecated
365     public boolean isPage(String path) throws AccessDeniedException {
366         Access.isGranted(this.accessManager, path, Permission.READ);
367 
368         String nodePath = getNodePath(path);
369         if (StringUtils.isEmpty(nodePath)) {
370             return false;
371         }
372 
373         try {
374             Node n = this.getRootNode().getNode(nodePath);
375             return (n.isNodeType(ItemType.CONTENT.getSystemName()));
376         }
377         catch (RepositoryException re) {
378             // ignore, not existing?
379         }
380         return false;
381     }
382 
383     /**
384      * check is either the node or property exists with the specified path and user has access to it. If at least READ permission is not
385      * granted or not running in SystemContext, the method will return false even if the node in question exists.
386      * @param path
387      */
388     public boolean isExist(String path) {
389         try {
390             Access.isGranted(this.accessManager, path, Permission.READ);
391         } catch (AccessDeniedException e) {
392             log.error(e.getMessage());
393             return false;
394         }
395         try {
396             return this.getJcrSession().itemExists(path);
397         } catch (RepositoryException re) {
398             // do not create hard dependency on JR API. The path validity check is not exposed via JCR API
399             if (re.getCause().getClass().getName().equals("org.apache.jackrabbit.spi.commons.conversion.MalformedPathException")) {
400                 // do not log invalid path by default
401                 log.debug("{} is not valid jcr path.", path);
402             } else {
403                 log.error("Exception caught", re);
404             }
405             return false;
406         }
407     }
408 
409     public boolean isGranted(String path, long permissions) {
410         try {
411             Access.isGranted(this.accessManager, path, permissions);
412         } catch (AccessDeniedException e) {
413             return false;
414         }
415         return true;
416     }
417 
418     /**
419      * Evaluate primary node type of the node at the given path.
420      * @deprecated since 4.0 - use getContent().isNodeType() instead. (not used currently)
421      */
422     @Deprecated
423     public boolean isNodeType(String path, String type) {
424         try {
425             Node n = this.getRootNode().getNode(getNodePath(path));
426             return n.isNodeType(type);
427         }
428         catch (RepositoryException re) {
429             log.error(re.getMessage());
430             log.debug(re.getMessage(), re);
431         }
432         return false;
433     }
434 
435     /**
436      * Evaluate primary node type of the node at the given path.
437      * @deprecated since 4.0 - use getContent().isNodeType() instead. (not used currently)
438      */
439     @Deprecated
440     public boolean isNodeType(String path, ItemType type) {
441         return isNodeType(path, type.getSystemName());
442     }
443 
444     /**
445      * checks if the requested resource is an NodeData (Property).
446      * @param path of the requested NodeData
447      * @return boolean true is the requested content is an NodeData
448      */
449     public boolean isNodeData(String path) throws AccessDeniedException {
450         Access.isGranted(this.accessManager, path, Permission.READ);
451         boolean result = false;
452         String nodePath = getNodePath(path);
453         if (StringUtils.isEmpty(nodePath)) {
454             return false;
455         }
456         try {
457             result = this.getRootNode().hasProperty(nodePath);
458             if (!result) {
459                 // check if its a nt:resource
460                 result = this.getRootNode().hasProperty(nodePath + "/" + ItemType.JCR_DATA);
461             }
462         }
463         catch (RepositoryException e) {
464             // ignore, no property
465         }
466         return result;
467     }
468 
469     /**
470      * This method can be used to retrieve Content which has UUID assigned to it, in other words only those nodes which
471      * has mixin type mix:referenceable.
472      * @param uuid
473      */
474     public Content getContentByUUID(String uuid) throws ItemNotFoundException, RepositoryException, AccessDeniedException {
475         return new DefaultContent(this.getJcrSession().getNodeByUUID(uuid), this);
476     }
477 
478     /**
479      * gets currently used workspace for this hierarchy manager.
480      */
481     public Workspace getWorkspace() {
482         if (null == this.workspace) {
483             reInitialize();
484         }
485         return this.workspace;
486     }
487 
488     /**
489      * move content to the specified location.
490      * @param source source node path
491      * @param destination node where the node has to be moved
492      * @throws javax.jcr.PathNotFoundException
493      * @throws javax.jcr.RepositoryException
494      */
495     public void moveTo(String source, String destination) throws PathNotFoundException, RepositoryException, AccessDeniedException {
496         Access.isGranted(this.accessManager, source, Permission.REMOVE);
497         Access.isGranted(this.accessManager, destination, Permission.WRITE);
498         this.getWorkspace().move(source, destination);
499         AuditLoggingUtil.log( AuditLoggingUtil.ACTION_MOVE, workspaceName, source, destination);
500     }
501 
502     /**
503      * copy content to the specified location.
504      * @param source source node path
505      * @param destination node where the node has to be copied
506      * @throws javax.jcr.PathNotFoundException
507      * @throws javax.jcr.RepositoryException
508      */
509     public void copyTo(String source, String destination) throws PathNotFoundException, RepositoryException, AccessDeniedException {
510         Access.isGranted(this.accessManager, source, Permission.READ);
511         Access.isGranted(this.accessManager, destination, Permission.WRITE);
512         this.getWorkspace().copy(source, destination);
513         AuditLoggingUtil.log( AuditLoggingUtil.ACTION_COPY, workspaceName, source, destination);
514     }
515 
516     /**
517      * Persists all changes to the repository if validation succeeds.
518      * @throws RepositoryException
519      */
520     public void save() throws RepositoryException {
521         try {
522             this.getJcrSession().save();
523         }
524         catch (RepositoryException re) {
525             log.error(re.getMessage(), re);
526             throw re;
527         }
528     }
529 
530     /**
531      * Returns true if the session has pending (unsaved) changes.
532      */
533     public boolean hasPendingChanges() throws RepositoryException {
534         return this.getJcrSession().hasPendingChanges();
535     }
536 
537     /**
538      * Refreshes this session.
539      * @param keepChanges
540      * @throws RepositoryException
541      * @see javax.jcr.Session#refresh(boolean)
542      */
543     public void refresh(boolean keepChanges) throws RepositoryException {
544         this.getJcrSession().refresh(keepChanges);
545     }
546 
547     public String getName() {
548         return this.workspaceName;
549     }
550 
551 }