View Javadoc

1   /**
2    * This file Copyright (c) 2003-2012 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.core.search.QueryManagerImpl;
38  import info.magnolia.cms.security.AccessDeniedException;
39  import info.magnolia.cms.security.AccessManager;
40  import info.magnolia.cms.security.PermissionUtil;
41  import info.magnolia.jcr.RuntimeRepositoryException;
42  import info.magnolia.jcr.util.NodeUtil;
43  import info.magnolia.jcr.util.SessionUtil;
44  import info.magnolia.jcr.wrapper.JCRPropertiesFilteringNodeWrapper;
45  import info.magnolia.logging.AuditLoggingUtil;
46  
47  import java.io.ObjectStreamField;
48  import java.io.Serializable;
49  import java.util.Collection;
50  
51  import javax.jcr.ItemNotFoundException;
52  import javax.jcr.Node;
53  import javax.jcr.PathNotFoundException;
54  import javax.jcr.RepositoryException;
55  import javax.jcr.Session;
56  import javax.jcr.Workspace;
57  
58  import org.apache.commons.lang.StringUtils;
59  import org.slf4j.Logger;
60  import org.slf4j.LoggerFactory;
61  
62  
63  /**
64   * Default JCR-based implementation of {@link HierarchyManager}.
65   *
66   * @author Sameer Charles
67   * @version $Id$
68   *
69   * @deprecated since 4.5. Use Session and its methods instead.
70   */
71  @Deprecated
72  public class DefaultHierarchyManager implements HierarchyManager, Serializable {
73  
74      private static final long serialVersionUID = 223L;
75  
76      /**
77       * instead of defining each field transient, we explicitly says what needs to be
78       * serialized.
79       * */
80      private static final ObjectStreamField[] serialPersistentFields = {
81          new ObjectStreamField("userId", String.class),
82          new ObjectStreamField("repositoryName", String.class),
83          new ObjectStreamField("workspaceName", String.class),
84      };
85  
86      private static final Logger log = LoggerFactory.getLogger(DefaultHierarchyManager.class);
87  
88      private Session jcrSession;
89  
90      public DefaultHierarchyManager(Session jcrSession) {
91          this.jcrSession = jcrSession;
92      }
93  
94      public DefaultHierarchyManager(String userId, Session jcrSession, AccessManager ignoredAccessManager) throws RepositoryException {
95          this(userId, jcrSession);
96      }
97  
98      public DefaultHierarchyManager(String ignoredUserId, Session jcrSession) throws RepositoryException {
99          this(jcrSession);
100     }
101 
102     /**
103      * Only used in tests.
104      */
105     public DefaultHierarchyManager(Session jcrSession, String ignored) {
106         this.jcrSession = jcrSession;
107     }
108 
109     /**
110      * Set access manager for this hierarchy.
111      * @param accessManager
112      */
113     protected void setAccessManager(AccessManager accessManager) {
114         // throw an ex because letting this fall through would be too unsecure for code relying on the old behavior
115         throw new UnsupportedOperationException("Custom access managers are no longer supported. Use Repository level security checks instead.");
116     }
117 
118     /**
119      * Get access manager.
120      * @return accessmanager attached to this hierarchy
121      */
122     @Override
123     public AccessManager getAccessManager() {
124         // throw an ex because letting this fall through would be too unsecure for code relying on the old behavior
125         throw new UnsupportedOperationException("Custom access managers are no longer supported. Use Repository level security checks instead.");
126     }
127 
128     /**
129      * Set query manager for this hierarchy.
130      * @param queryManager
131      */
132     protected void setQueryManager(QueryManager queryManager) {
133         throw new UnsupportedOperationException("This Operation is no longer available.");
134     }
135 
136     @Override
137     public QueryManager getQueryManager() {
138         try {
139             return new QueryManagerImpl(getJcrSession().getWorkspace().getQueryManager(), this);
140         } catch (RepositoryException e) {
141             throw new RuntimeRepositoryException(e);
142         }
143     }
144 
145     private Node getRootNode() throws RepositoryException {
146         return jcrSession.getRootNode();
147     }
148 
149     private String getWorkspaceName() {
150         return jcrSession.getWorkspace().getName();
151     }
152 
153     protected Session getJcrSession() {
154         return this.jcrSession;
155     }
156 
157     protected void setJcrSession(Session jcrSession) {
158         this.jcrSession = jcrSession;
159     }
160 
161     /**
162      * Creates contentNode of type <b>contentType</b>. contentType must be defined in item type definition of Magnolia
163      * as well as JCR implementation.
164      * @param path absolute (primary) path to this <code>Node</code>
165      * @param label page name
166      * @param contentType , JCR node type as configured
167      * @throws PathNotFoundException
168      * @throws RepositoryException
169      * @throws AccessDeniedException
170      */
171     @Override
172     public Content createContent(String path, String label, String contentType) throws PathNotFoundException,
173     RepositoryException, AccessDeniedException {
174         Content content = wrapAsContent(this.getRootNode(), this.getNodePath(path, label), contentType);
175         setMetaData(content.getMetaData());
176         AuditLoggingUtil.log( AuditLoggingUtil.ACTION_CREATE, getWorkspaceName(), content.getItemType(), content.getHandle());
177         return content;
178     }
179 
180     protected Content wrapAsContent(Node rootNode, String path, String contentType) throws PathNotFoundException, RepositoryException, AccessDeniedException {
181         return new DefaultContent(rootNode, path, contentType);
182     }
183 
184     private String getNodePath(String parent, String label) {
185         if (StringUtils.isEmpty(parent) || (parent.equals("/"))) {
186             return label;
187         }
188         if (!parent.endsWith("/")) {
189             parent = parent + "/";
190         }
191         return getNodePath(parent + label);
192     }
193 
194     private String getNodePath(String path) {
195         if (path != null && path.startsWith("/")) {
196             return path.replaceFirst("/", StringUtils.EMPTY);
197         }
198         return path;
199     }
200 
201     /**
202      * Helper method to set page properties, create page calls this method. you could call this method anytime to create
203      * working page properties.
204      */
205     protected void setMetaData(MetaData md) throws RepositoryException, AccessDeniedException {
206         md.setCreationDate();
207         md.setModificationDate();
208         md.setAuthorId(this.jcrSession.getUserID());
209         md.setTitle(StringUtils.EMPTY);
210     }
211 
212     /**
213      * get content object of the requested URI.
214      * @param path of the content to be initialized
215      * @return Content
216      * @throws javax.jcr.PathNotFoundException
217      * @throws javax.jcr.RepositoryException
218      */
219     @Override
220     public Content getContent(String path) throws PathNotFoundException, RepositoryException, AccessDeniedException {
221         if (path.equals("/")) {
222             return this.getRoot();
223         }
224         try {
225             return wrapAsContent(this.getRootNode(), getNodePath(path));
226         } catch (ItemNotFoundException e) {
227             this.getJcrSession().refresh(true);
228             return wrapAsContent(this.getRootNode(), getNodePath(path));
229         }
230     }
231 
232     public Content wrapAsContent(Node rootNode, String path) throws AccessDeniedException, PathNotFoundException, RepositoryException {
233         return new DefaultContent(rootNode, path);
234     }
235     /**
236      * Like getContent() but creates the node if not yet existing. Attention save is not called!
237      * @param path the path of the node
238      * @param create true if the node should get created
239      * @param type the node type of the created node
240      * @return the node
241      * @throws AccessDeniedException
242      * @throws RepositoryException
243      */
244     @Override
245     public Content getContent(String path, boolean create, ItemType type) throws AccessDeniedException,
246     RepositoryException {
247         Content node;
248         try {
249             node = getContent(path);
250         }
251         catch (PathNotFoundException e) {
252             if (create) {
253                 node = this.createContent(StringUtils.substringBeforeLast(path, "/"), StringUtils.substringAfterLast(
254                         path,
255                 "/"), type.toString());
256                 AuditLoggingUtil.log( AuditLoggingUtil.ACTION_CREATE, getWorkspaceName(), 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     @Override
274     public NodeData getNodeData(String path) throws PathNotFoundException, RepositoryException, AccessDeniedException {
275         if (StringUtils.isEmpty(path)) {
276             return null;
277         }
278         final String nodePath = StringUtils.substringBeforeLast(path, "/");
279         final String nodeDataName = StringUtils.substringAfterLast(path, "/");
280         return getContent(nodePath).getNodeData(nodeDataName);
281     }
282 
283     /**
284      * returns the first page with a given template name that is found in tree that starts from the page given py the
285      * path (including this page).
286      * @param path handle of the page from where the search should start
287      * @param templateName template name to search for
288      * @return first Content hierarchy node that has the specified template name assigned
289      * @throws javax.jcr.PathNotFoundException
290      * @throws javax.jcr.RepositoryException
291      *
292      * @deprecated since 4.0 - only used by taglibs - should go/move.
293      */
294     @Deprecated
295     public Content getPage(String path, String templateName) throws PathNotFoundException, RepositoryException,
296     AccessDeniedException {
297         Content page = getContent(path);
298         if (page.getTemplate().equals(templateName)) {
299             return page;
300         }
301         Content pageToBeFound = null;
302         try {
303             if (page.hasChildren()) {
304                 Collection<Content> children = page.getChildren(ItemType.CONTENT.getSystemName());
305                 for (Content child : children) {
306                     if (child.getTemplate().equals(templateName)) {
307                         return child;
308                     }
309                     if (child.hasChildren()) {
310                         pageToBeFound = getPage(child.getHandle(), templateName);
311                     }
312                     if (pageToBeFound != null) {
313                         return pageToBeFound;
314                     }
315                 }
316             }
317         }
318         catch (Exception e) {
319             log.error("Failed to get - " + path + " : " + e.getMessage(), e);
320         }
321         return pageToBeFound;
322     }
323 
324     /**
325      * removes specified path, it can be either node or property.
326      * @param path to be removed
327      * @throws javax.jcr.PathNotFoundException
328      * @throws javax.jcr.RepositoryException
329      * @throws AccessDeniedException
330      */
331     @Override
332     public void delete(String path) throws PathNotFoundException, RepositoryException, AccessDeniedException {
333         ItemType type = null;
334         if (this.isNodeData(path)) {
335             this.getNodeData(makeRelative(path)).delete();
336         }
337         else {
338             Node aNode = this.getRootNode().getNode(makeRelative(path));
339             aNode = NodeUtil.deepUnwrap(aNode, JCRPropertiesFilteringNodeWrapper.class);
340             if (aNode.hasProperty(ItemType.JCR_FROZEN_PRIMARY_TYPE)) {
341                 type = new ItemType(aNode.getProperty(ItemType.JCR_FROZEN_PRIMARY_TYPE).getString());
342             }
343             type = new ItemType(aNode.getProperty(ItemType.JCR_PRIMARY_TYPE).getString());
344             aNode.remove();
345         }
346         AuditLoggingUtil.log( AuditLoggingUtil.ACTION_DELETE, getWorkspaceName(), type, path);
347     }
348 
349     private String makeRelative(String path) {
350         return StringUtils.stripStart(path, "/");
351     }
352 
353     /**
354      * @return rootNode of the current working repository-workspace
355      */
356     @Override
357     public Content getRoot() throws RepositoryException, AccessDeniedException {
358         return (new DefaultContent(this.getRootNode()));
359     }
360 
361 
362     /**
363      * 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
364      * granted or not running in SystemContext, the method will return false even if the node in question exists.
365      * @param path
366      */
367     @Override
368     public boolean isExist(String path) {
369         if (!PermissionUtil.isGranted(jcrSession, path, Session.ACTION_READ)) {
370             return false;
371         }
372         try {
373             this.getJcrSession().refresh(true);
374             return this.getJcrSession().itemExists(path);
375         } catch (RepositoryException re) {
376             // do not create hard dependency on JR API. The path validity check is not exposed via JCR API
377             if (re.getCause().getClass().getName().equals("org.apache.jackrabbit.spi.commons.conversion.MalformedPathException")) {
378                 // do not log invalid path by default
379                 log.debug("{} is not valid jcr path.", path);
380             } else {
381                 log.error("Exception caught", re);
382             }
383             return false;
384         }
385     }
386 
387     @Override
388     public boolean isGranted(String path, long oldPermissions) {
389         return PermissionUtil.isGranted(jcrSession, path, oldPermissions);
390     }
391 
392     /**
393      * checks if the requested resource is an NodeData (Property).
394      * @param path of the requested NodeData
395      * @return boolean true is the requested content is an NodeData
396      */
397     @Override
398     public boolean isNodeData(String path) throws AccessDeniedException {
399         boolean result = false;
400         String nodePath = getNodePath(path);
401         if (StringUtils.isEmpty(nodePath)) {
402             return false;
403         }
404         try {
405             result = this.getRootNode().hasProperty(nodePath);
406             if (!result) {
407                 // check if its a nt:resource
408                 result = this.getRootNode().hasProperty(nodePath + "/" + ItemType.JCR_DATA);
409             }
410         }
411         catch (RepositoryException e) {
412             // ignore, no property
413         }
414         return result;
415     }
416 
417     /**
418      * This method can be used to retrieve Content which has UUID assigned to it, in other words only those nodes which
419      * has mixin type mix:referenceable.
420      * @param uuid
421      */
422     @Override
423     public Content getContentByUUID(String uuid) throws ItemNotFoundException, RepositoryException,
424     AccessDeniedException {
425         try {
426             return wrapAsContent(this.getJcrSession().getNodeByIdentifier(uuid));
427         } catch (ItemNotFoundException e) {
428             // retry in case session was not updated
429             this.getJcrSession().refresh(true);
430             return wrapAsContent(this.getJcrSession().getNodeByIdentifier(uuid));
431         }
432     }
433 
434     protected Content wrapAsContent(Node node) {
435         return new DefaultContent(node);
436     }
437 
438     /**
439      * gets currently used workspace for this hierarchy manager.
440      */
441     @Override
442     public Workspace getWorkspace() {
443         return getJcrSession().getWorkspace();
444     }
445 
446     /**
447      * move content to the specified location.
448      * @param source source node path
449      * @param destination node where the node has to be moved
450      * @throws javax.jcr.PathNotFoundException
451      * @throws javax.jcr.RepositoryException
452      */
453     @Override
454     public void moveTo(String source, String destination) throws PathNotFoundException, RepositoryException,
455     AccessDeniedException {
456         this.getWorkspace().move(source, destination);
457         AuditLoggingUtil.log( AuditLoggingUtil.ACTION_MOVE, getWorkspaceName(), source, destination);
458     }
459 
460     /**
461      * copy content to the specified location.
462      * @param source source node path
463      * @param destination node where the node has to be copied
464      * @throws javax.jcr.PathNotFoundException
465      * @throws javax.jcr.RepositoryException
466      */
467     @Override
468     public void copyTo(String source, String destination) throws PathNotFoundException, RepositoryException,
469     AccessDeniedException {
470         this.getWorkspace().copy(source, destination);
471         AuditLoggingUtil.log( AuditLoggingUtil.ACTION_COPY, getWorkspaceName(), source, destination);
472     }
473 
474     /**
475      * Persists all changes to the repository if validation succeeds.
476      * @throws RepositoryException
477      */
478     @Override
479     public void save() throws RepositoryException {
480         try {
481             this.getJcrSession().save();
482         }
483         catch (RepositoryException re) {
484             // TODO dlipp - this might end up with logging the error twice. I'd prefer to either log or rethrow - in that context the rethrow.
485             log.error(re.getMessage(), re);
486             throw re;
487         }
488     }
489 
490     /**
491      * Returns true if the session has pending (unsaved) changes.
492      */
493     @Override
494     public boolean hasPendingChanges() throws RepositoryException {
495         return this.getJcrSession().hasPendingChanges();
496     }
497 
498     /**
499      * Refreshes this session.
500      * @param keepChanges
501      * @throws RepositoryException
502      * @see javax.jcr.Session#refresh(boolean)
503      */
504     @Override
505     public void refresh(boolean keepChanges) throws RepositoryException {
506         this.getJcrSession().refresh(keepChanges);
507     }
508 
509     @Override
510     public String getName() {
511         return getWorkspaceName();
512     }
513 
514     @Override
515     public int hashCode() {
516         final int prime = 31;
517         int result = 1;
518         result = prime * result + ((jcrSession == null) ? 0 : jcrSession.hashCode());
519         return result;
520     }
521 
522     @Override
523     public boolean equals(Object obj) {
524         if (this == obj) {
525             return true;
526         }
527         if (obj == null) {
528             return false;
529         }
530         if (getClass() != obj.getClass()) {
531             return false;
532         }
533         DefaultHierarchyManager other = (DefaultHierarchyManager) obj;
534         if (jcrSession == null) {
535             if (other.jcrSession != null) {
536                 return false;
537             }
538         } else if (!SessionUtil.hasSameUnderlyingSession(jcrSession, other.jcrSession)) {
539             return false;
540         }
541         return true;
542     }
543 }