View Javadoc

1   /**
2    * This file Copyright (c) 2003-2010 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,
105                                    Session jcrSession,
106                                    AccessManager aManager)
107             throws RepositoryException {
108         this.userId = userId;
109         this.jcrSession = jcrSession;
110         this.rootNode = jcrSession.getRootNode();
111         this.workspace = jcrSession.getWorkspace();
112         this.workspaceName = this.workspace.getName();
113         this.repositoryName = ContentRepository.getParentRepositoryName(this.workspaceName);
114         this.accessManager = aManager;
115     }
116 
117     /**
118      * Reinitialize itself with the partial deserialized data.
119      * */
120     private void reInitialize() {
121         WorkspaceAccessUtil util = WorkspaceAccessUtil.getInstance();
122         try {
123             this.jcrSession = util.createRepositorySession(util.getDefaultCredentials(), this.repositoryName, this.workspaceName);
124             this.queryManager = util.createQueryManager(this.jcrSession, this);
125             this.rootNode = this.jcrSession.getRootNode();
126             this.workspace = this.jcrSession.getWorkspace();
127         } catch (RepositoryException re) {
128             log.error("Failed to load HierarchyManager from persistent storage", re);
129         }
130     }
131 
132     /**
133      * Set access manager for this hierarchy.
134      * @param accessManager
135      */
136     protected void setAccessManager(AccessManager accessManager) {
137         this.accessManager = accessManager;
138     }
139 
140     /**
141      * Get access manager.
142      * @return accessmanager attached to this hierarchy
143      */
144     public AccessManager getAccessManager() {
145         return this.accessManager;
146     }
147 
148     /**
149      * Set query manager for this hierarchy.
150      * @param queryManager
151      */
152     protected void setQueryManager(QueryManager queryManager) {
153         this.queryManager = queryManager;
154     }
155 
156     public QueryManager getQueryManager() {
157         if (null == this.queryManager) {
158             WorkspaceAccessUtil util = WorkspaceAccessUtil.getInstance();
159             try {
160                 this.queryManager = util.createQueryManager(this.jcrSession, this);
161             } catch (RepositoryException e) {
162                 reInitialize();
163             }
164         }
165         return this.queryManager;
166     }
167 
168     private Node getRootNode() {
169         if (null == this.rootNode) {
170             reInitialize();
171         }
172         return this.rootNode;
173     }
174 
175     private Session getJcrSession() {
176         if (null == this.jcrSession) {
177             reInitialize();
178         }
179         return this.jcrSession;
180     }
181 
182     /**
183      * Creates contentNode of type <b>contentType</b>. contentType must be defined in item type definition of Magnolia
184      * as well as JCR implementation.
185      * @param path absolute (primary) path to this <code>Node</code>
186      * @param label page name
187      * @param contentType , JCR node type as configured
188      * @throws PathNotFoundException
189      * @throws RepositoryException
190      * @throws AccessDeniedException
191      */
192     public Content createContent(String path, String label, String contentType) throws PathNotFoundException,
193             RepositoryException, AccessDeniedException {
194         Content content = new DefaultContent(this.getRootNode(), this.getNodePath(path, label), contentType, this);
195         setMetaData(content.getMetaData());
196         AuditLoggingUtil.log( AuditLoggingUtil.ACTION_CREATE, workspaceName, content.getItemType(), content.getHandle());
197         return content;
198     }
199 
200     private String getNodePath(String parent, String label) {
201         if (StringUtils.isEmpty(parent) || (parent.equals("/"))) { //$NON-NLS-1$
202             return label;
203         }
204         if (!parent.endsWith("/")) {
205             parent = parent + "/";
206         }
207         return getNodePath(parent + label); //$NON-NLS-1$
208     }
209 
210     private String getNodePath(String path) {
211         if (path != null && path.startsWith("/")) { //$NON-NLS-1$
212             return path.replaceFirst("/", StringUtils.EMPTY); //$NON-NLS-1$
213         }
214         return path;
215     }
216 
217     /**
218      * Helper method to set page properties, create page calls this method. you could call this method anytime to create
219      * working page properties.
220      */
221     protected void setMetaData(MetaData md) throws RepositoryException, AccessDeniedException {
222         md.setCreationDate();
223         md.setModificationDate();
224         md.setAuthorId(this.userId);
225         md.setTitle(StringUtils.EMPTY);
226     }
227 
228     /**
229      * get content object of the requested URI.
230      * @param path of the content to be initialized
231      * @return Content
232      * @throws javax.jcr.PathNotFoundException
233      * @throws javax.jcr.RepositoryException
234      */
235     public Content getContent(String path) throws PathNotFoundException, RepositoryException, AccessDeniedException {
236         if (path.equals("/")) { //$NON-NLS-1$
237             return this.getRoot();
238         }
239         return (new DefaultContent(this.getRootNode(), getNodePath(path), this));
240     }
241 
242     /**
243      * Like getContent() but creates the node if not yet existing. Attention save is not called!
244      * @param path the path of the node
245      * @param create true if the node should get created
246      * @param type the node type of the created node
247      * @return the node
248      * @throws AccessDeniedException
249      * @throws RepositoryException
250      */
251     public Content getContent(String path, boolean create, ItemType type) throws AccessDeniedException,
252         RepositoryException {
253         Content node;
254         try {
255             node = getContent(path);
256         }
257         catch (PathNotFoundException e) {
258             if (create) {
259                 node = this.createContent(StringUtils.substringBeforeLast(path, "/"), StringUtils.substringAfterLast(
260                     path,
261                     "/"), type.toString());
262                 AuditLoggingUtil.log( AuditLoggingUtil.ACTION_CREATE, workspaceName, node.getItemType(), node.getHandle());
263             }
264             else {
265                 throw e;
266             }
267         }
268         return node;
269     }
270 
271 
272     /**
273      * get NodeData object of the requested URI.
274      * @param path of the atom to be initialized
275      * @return NodeData
276      * @throws javax.jcr.PathNotFoundException
277      * @throws javax.jcr.RepositoryException
278      */
279     public NodeData getNodeData(String path) throws PathNotFoundException, RepositoryException, AccessDeniedException {
280         if (StringUtils.isEmpty(path)) {
281             return null;
282         }
283         final String nodePath = StringUtils.substringBeforeLast(path, "/");
284         final String nodeDataName = StringUtils.substringAfterLast(path, "/");
285         return getContent(nodePath).getNodeData(nodeDataName);
286     }
287 
288     /**
289      * returns the first page with a given template name that is found in tree that starts from the page given py the
290      * path (including this page).
291      * @param path handle of the page from where the search should start
292      * @param templateName template name to search for
293      * @return first Content hierarchy node that has the specified template name assigned
294      * @throws javax.jcr.PathNotFoundException
295      * @throws javax.jcr.RepositoryException
296      *
297      * @deprecated since 4.0 - only used by taglibs - should go/move.
298      */
299     public Content getPage(String path, String templateName) throws PathNotFoundException, RepositoryException,
300         AccessDeniedException {
301         Content page = getContent(path);
302         if (page.getTemplate().equals(templateName)) {
303             return page;
304         }
305         Content pageToBeFound = null;
306         try {
307             if (page.hasChildren()) {
308                 Collection<Content> children = page.getChildren(ItemType.CONTENT.getSystemName());
309                 for (Content child : children) {
310                     if (child.getTemplate().equals(templateName)) {
311                         return child;
312                     }
313                     if (child.hasChildren()) {
314                         pageToBeFound = getPage(child.getHandle(), templateName);
315                     }
316                     if (pageToBeFound != null) {
317                         return pageToBeFound;
318                     }
319                 }
320             }
321         }
322         catch (Exception e) {
323             log.error("Failed to get - " + path + " : " + e.getMessage(), e);
324         }
325         return pageToBeFound;
326     }
327 
328     /**
329      * removes specified path, it can be either node or property.
330      * @param path to be removed
331      * @throws javax.jcr.PathNotFoundException
332      * @throws javax.jcr.RepositoryException
333      * @throws AccessDeniedException
334      */
335     public void delete(String path) throws PathNotFoundException, RepositoryException, AccessDeniedException {
336         Access.isGranted(this.accessManager, path, Permission.REMOVE);
337         ItemType type = null;
338         if (this.isNodeData(path)) {
339             this.getNodeData(makeRelative(path)).delete();
340         }
341         else {
342             Node aNode = this.getRootNode().getNode(makeRelative(path));
343             if (aNode.hasProperty(ItemType.JCR_FROZEN_PRIMARY_TYPE)) {
344                 type = new ItemType(aNode.getProperty(ItemType.JCR_FROZEN_PRIMARY_TYPE).getString());
345             }
346             type = new ItemType(aNode.getProperty(ItemType.JCR_PRIMARY_TYPE).getString());
347             aNode.remove();
348         }
349         AuditLoggingUtil.log( AuditLoggingUtil.ACTION_DELETE, workspaceName, type, path);
350     }
351 
352     private String makeRelative(String path) {
353         return StringUtils.stripStart(path, "/"); //$NON-NLS-1$
354     }
355 
356     /**
357      * @return rootNode of the current working repository-workspace
358      */
359     public Content getRoot() throws RepositoryException, AccessDeniedException {
360         return (new DefaultContent(this.getRootNode(), this));
361     }
362 
363     /**
364      * Checks if the requested resource is a page (hierarchy Node).
365      * @param path of the requested content
366      * @return boolean true is the requested content is a Hierarchy Node todo remove this method, instead use
367      * (getContent(PATH) is NodeType)
368      * @deprecated since 4.0 - use getContent().isNodeType() instead. (not used currently)
369      */
370     public boolean isPage(String path) throws AccessDeniedException {
371         Access.isGranted(this.accessManager, path, Permission.READ);
372 
373         String nodePath = getNodePath(path);
374         if (StringUtils.isEmpty(nodePath)) {
375             return false;
376         }
377 
378         try {
379             Node n = this.getRootNode().getNode(nodePath);
380             return (n.isNodeType(ItemType.CONTENT.getSystemName()));
381         }
382         catch (RepositoryException re) {
383             // ignore, not existing?
384         }
385         return false;
386     }
387 
388     /**
389      * 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
390      * granted or not running in SystemContext, the method will return false even if the node in question exists.
391      * @param path
392      */
393     public boolean isExist(String path) {
394         try {
395             Access.isGranted(this.accessManager, path, Permission.READ);
396         } catch (AccessDeniedException e) {
397             log.error(e.getMessage());
398             return false;
399         }
400         try {
401             return this.getJcrSession().itemExists(path);
402         }
403         catch (RepositoryException re) {
404             log.error("Exception caught", re);
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     public boolean isNodeType(String path, String type) {
423         try {
424             Node n = this.getRootNode().getNode(getNodePath(path));
425             return n.isNodeType(type);
426         }
427         catch (RepositoryException re) {
428             log.error(re.getMessage());
429             log.debug(re.getMessage(), re);
430         }
431         return false;
432     }
433 
434     /**
435      * Evaluate primary node type of the node at the given path.
436      * @deprecated since 4.0 - use getContent().isNodeType() instead. (not used currently)
437      */
438     public boolean isNodeType(String path, ItemType type) {
439         return isNodeType(path, type.getSystemName());
440     }
441 
442     /**
443      * checks if the requested resource is an NodeData (Property).
444      * @param path of the requested NodeData
445      * @return boolean true is the requested content is an NodeData
446      */
447     public boolean isNodeData(String path) throws AccessDeniedException {
448         Access.isGranted(this.accessManager, path, Permission.READ);
449         boolean result = false;
450         String nodePath = getNodePath(path);
451         if (StringUtils.isEmpty(nodePath)) {
452             return false;
453         }
454         try {
455             result = this.getRootNode().hasProperty(nodePath);
456             if (!result) {
457                 // check if its a nt:resource
458                 result = this.getRootNode().hasProperty(nodePath + "/" + ItemType.JCR_DATA);
459             }
460         }
461         catch (RepositoryException e) {
462             // ignore, no property
463         }
464         return result;
465     }
466 
467     /**
468      * This method can be used to retrieve Content which has UUID assigned to it, in other words only those nodes which
469      * has mixin type mix:referenceable.
470      * @param uuid
471      */
472     public Content getContentByUUID(String uuid) throws ItemNotFoundException, RepositoryException,
473         AccessDeniedException {
474         return new DefaultContent(this.getJcrSession().getNodeByUUID(uuid), this);
475     }
476 
477     /**
478      * gets currently used workspace for this hierarchy manager.
479      */
480     public Workspace getWorkspace() {
481         if (null == this.workspace) {
482             reInitialize();
483         }
484         return this.workspace;
485     }
486 
487     /**
488      * move content to the specified location.
489      * @param source source node path
490      * @param destination node where the node has to be moved
491      * @throws javax.jcr.PathNotFoundException
492      * @throws javax.jcr.RepositoryException
493      */
494     public void moveTo(String source, String destination) throws PathNotFoundException, RepositoryException,
495         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,
510         AccessDeniedException {
511         Access.isGranted(this.accessManager, source, Permission.READ);
512         Access.isGranted(this.accessManager, destination, Permission.WRITE);
513         this.getWorkspace().copy(source, destination);
514         AuditLoggingUtil.log( AuditLoggingUtil.ACTION_COPY, workspaceName, source, destination);
515     }
516 
517     /**
518      * Persists all changes to the repository if validation succeeds.
519      * @throws RepositoryException
520      */
521     public void save() throws RepositoryException {
522         try {
523             this.getJcrSession().save();
524         }
525         catch (RepositoryException re) {
526             log.error(re.getMessage(), re);
527             throw re;
528         }
529     }
530 
531     /**
532      * Returns true if the session has pending (unsaved) changes.
533      */
534     public boolean hasPendingChanges() throws RepositoryException {
535         return this.getJcrSession().hasPendingChanges();
536     }
537 
538     /**
539      * Refreshes this session.
540      * @param keepChanges
541      * @throws RepositoryException
542      * @see javax.jcr.Session#refresh(boolean)
543      */
544     public void refresh(boolean keepChanges) throws RepositoryException {
545         this.getJcrSession().refresh(keepChanges);
546     }
547 
548     public String getName() {
549         return this.workspaceName;
550     }
551 
552 }