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