View Javadoc

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