View Javadoc

1   /**
2    * This file Copyright (c) 2003-2013 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     }
208 
209     /**
210      * get content object of the requested URI.
211      * @param path of the content to be initialized
212      * @return Content
213      * @throws javax.jcr.PathNotFoundException
214      * @throws javax.jcr.RepositoryException
215      */
216     @Override
217     public Content getContent(String path) throws PathNotFoundException, RepositoryException, AccessDeniedException {
218         if (path.equals("/")) {
219             return this.getRoot();
220         }
221         try {
222             return wrapAsContent(this.getRootNode(), getNodePath(path));
223         } catch (ItemNotFoundException e) {
224             this.getJcrSession().refresh(true);
225             return wrapAsContent(this.getRootNode(), getNodePath(path));
226         }
227     }
228 
229     public Content wrapAsContent(Node rootNode, String path) throws AccessDeniedException, PathNotFoundException, RepositoryException {
230         return new DefaultContent(rootNode, path);
231     }
232     /**
233      * Like getContent() but creates the node if not yet existing. Attention save is not called!
234      * @param path the path of the node
235      * @param create true if the node should get created
236      * @param type the node type of the created node
237      * @return the node
238      * @throws AccessDeniedException
239      * @throws RepositoryException
240      */
241     @Override
242     public Content getContent(String path, boolean create, ItemType type) throws AccessDeniedException,
243     RepositoryException {
244         Content node;
245         try {
246             node = getContent(path);
247         }
248         catch (PathNotFoundException e) {
249             if (create) {
250                 node = this.createContent(StringUtils.substringBeforeLast(path, "/"), StringUtils.substringAfterLast(
251                         path,
252                 "/"), type.toString());
253             }
254             else {
255                 throw e;
256             }
257         }
258         return node;
259     }
260 
261 
262     /**
263      * get NodeData object of the requested URI.
264      * @param path of the atom to be initialized
265      * @return NodeData
266      * @throws javax.jcr.PathNotFoundException
267      * @throws javax.jcr.RepositoryException
268      */
269     @Override
270     public NodeData getNodeData(String path) throws PathNotFoundException, RepositoryException, AccessDeniedException {
271         if (StringUtils.isEmpty(path)) {
272             return null;
273         }
274         final String nodePath = StringUtils.substringBeforeLast(path, "/");
275         final String nodeDataName = StringUtils.substringAfterLast(path, "/");
276         return getContent(nodePath).getNodeData(nodeDataName);
277     }
278 
279     /**
280      * returns the first page with a given template name that is found in tree that starts from the page given py the
281      * path (including this page).
282      * @param path handle of the page from where the search should start
283      * @param templateName template name to search for
284      * @return first Content hierarchy node that has the specified template name assigned
285      * @throws javax.jcr.PathNotFoundException
286      * @throws javax.jcr.RepositoryException
287      *
288      * @deprecated since 4.0 - only used by taglibs - should go/move.
289      */
290     @Deprecated
291     public Content getPage(String path, String templateName) throws PathNotFoundException, RepositoryException,
292     AccessDeniedException {
293         Content page = getContent(path);
294         if (page.getTemplate().equals(templateName)) {
295             return page;
296         }
297         Content pageToBeFound = null;
298         try {
299             if (page.hasChildren()) {
300                 Collection<Content> children = page.getChildren(ItemType.CONTENT.getSystemName());
301                 for (Content child : children) {
302                     if (child.getTemplate().equals(templateName)) {
303                         return child;
304                     }
305                     if (child.hasChildren()) {
306                         pageToBeFound = getPage(child.getHandle(), templateName);
307                     }
308                     if (pageToBeFound != null) {
309                         return pageToBeFound;
310                     }
311                 }
312             }
313         }
314         catch (Exception e) {
315             log.error("Failed to get - " + path + " : " + e.getMessage(), e);
316         }
317         return pageToBeFound;
318     }
319 
320     /**
321      * removes specified path, it can be either node or property.
322      * @param path to be removed
323      * @throws javax.jcr.PathNotFoundException
324      * @throws javax.jcr.RepositoryException
325      * @throws AccessDeniedException
326      */
327     @Override
328     public void delete(String path) throws PathNotFoundException, RepositoryException, AccessDeniedException {
329         ItemType type = null;
330         if (this.isNodeData(path)) {
331             this.getNodeData(makeRelative(path)).delete();
332         }
333         else {
334             Node aNode = this.getRootNode().getNode(makeRelative(path));
335             aNode = NodeUtil.deepUnwrap(aNode, JCRPropertiesFilteringNodeWrapper.class);
336             if (aNode.hasProperty(ItemType.JCR_FROZEN_PRIMARY_TYPE)) {
337                 type = new ItemType(aNode.getProperty(ItemType.JCR_FROZEN_PRIMARY_TYPE).getString());
338             }
339             type = new ItemType(aNode.getProperty(ItemType.JCR_PRIMARY_TYPE).getString());
340             aNode.remove();
341         }
342     }
343 
344     private String makeRelative(String path) {
345         return StringUtils.stripStart(path, "/");
346     }
347 
348     /**
349      * @return rootNode of the current working repository-workspace
350      */
351     @Override
352     public Content getRoot() throws RepositoryException, AccessDeniedException {
353         return (new DefaultContent(this.getRootNode()));
354     }
355 
356 
357     /**
358      * 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
359      * granted or not running in SystemContext, the method will return false even if the node in question exists.
360      * @param path
361      */
362     @Override
363     public boolean isExist(String path) {
364         if (!PermissionUtil.isGranted(jcrSession, path, Session.ACTION_READ)) {
365             return false;
366         }
367         try {
368             this.getJcrSession().refresh(true);
369             return this.getJcrSession().itemExists(path);
370         } catch (RepositoryException re) {
371             // do not create hard dependency on JR API. The path validity check is not exposed via JCR API
372             if (re.getCause().getClass().getName().equals("org.apache.jackrabbit.spi.commons.conversion.MalformedPathException")) {
373                 // do not log invalid path by default
374                 log.debug("{} is not valid jcr path.", path);
375             } else {
376                 log.error("Exception caught", re);
377             }
378             return false;
379         }
380     }
381 
382     @Override
383     public boolean isGranted(String path, long oldPermissions) {
384         return PermissionUtil.isGranted(jcrSession, path, oldPermissions);
385     }
386 
387     /**
388      * checks if the requested resource is an NodeData (Property).
389      * @param path of the requested NodeData
390      * @return boolean true is the requested content is an NodeData
391      */
392     @Override
393     public boolean isNodeData(String path) throws AccessDeniedException {
394         boolean result = false;
395         String nodePath = getNodePath(path);
396         if (StringUtils.isEmpty(nodePath)) {
397             return false;
398         }
399         try {
400             result = this.getRootNode().hasProperty(nodePath);
401             if (!result) {
402                 // check if its a nt:resource
403                 result = this.getRootNode().hasProperty(nodePath + "/" + ItemType.JCR_DATA);
404             }
405         }
406         catch (RepositoryException e) {
407             // ignore, no property
408         }
409         return result;
410     }
411 
412     /**
413      * This method can be used to retrieve Content which has UUID assigned to it, in other words only those nodes which
414      * has mixin type mix:referenceable.
415      * @param uuid
416      */
417     @Override
418     public Content getContentByUUID(String uuid) throws ItemNotFoundException, RepositoryException,
419     AccessDeniedException {
420         try {
421             return wrapAsContent(this.getJcrSession().getNodeByIdentifier(uuid));
422         } catch (ItemNotFoundException e) {
423             // retry in case session was not updated
424             this.getJcrSession().refresh(true);
425             return wrapAsContent(this.getJcrSession().getNodeByIdentifier(uuid));
426         }
427     }
428 
429     protected Content wrapAsContent(Node node) {
430         return new DefaultContent(node);
431     }
432 
433     /**
434      * gets currently used workspace for this hierarchy manager.
435      */
436     @Override
437     public Workspace getWorkspace() {
438         return getJcrSession().getWorkspace();
439     }
440 
441     /**
442      * move content to the specified location.
443      * @param source source node path
444      * @param destination node where the node has to be moved
445      * @throws javax.jcr.PathNotFoundException
446      * @throws javax.jcr.RepositoryException
447      */
448     @Override
449     public void moveTo(String source, String destination) throws PathNotFoundException, RepositoryException,
450     AccessDeniedException {
451         this.getWorkspace().move(source, destination);
452     }
453 
454     /**
455      * copy content to the specified location.
456      * @param source source node path
457      * @param destination node where the node has to be copied
458      * @throws javax.jcr.PathNotFoundException
459      * @throws javax.jcr.RepositoryException
460      */
461     @Override
462     public void copyTo(String source, String destination) throws PathNotFoundException, RepositoryException,
463     AccessDeniedException {
464         this.getWorkspace().copy(source, destination);
465     }
466 
467     /**
468      * Persists all changes to the repository if validation succeeds.
469      * @throws RepositoryException
470      */
471     @Override
472     public void save() throws RepositoryException {
473         try {
474             this.getJcrSession().save();
475         }
476         catch (RepositoryException re) {
477             // TODO dlipp - this might end up with logging the error twice. I'd prefer to either log or rethrow - in that context the rethrow.
478             log.error(re.getMessage(), re);
479             throw re;
480         }
481     }
482 
483     /**
484      * Returns true if the session has pending (unsaved) changes.
485      */
486     @Override
487     public boolean hasPendingChanges() throws RepositoryException {
488         return this.getJcrSession().hasPendingChanges();
489     }
490 
491     /**
492      * Refreshes this session.
493      * @param keepChanges
494      * @throws RepositoryException
495      * @see javax.jcr.Session#refresh(boolean)
496      */
497     @Override
498     public void refresh(boolean keepChanges) throws RepositoryException {
499         this.getJcrSession().refresh(keepChanges);
500     }
501 
502     @Override
503     public String getName() {
504         return getWorkspaceName();
505     }
506 
507     @Override
508     public int hashCode() {
509         final int prime = 31;
510         int result = 1;
511         result = prime * result + ((jcrSession == null) ? 0 : jcrSession.hashCode());
512         return result;
513     }
514 
515     @Override
516     public boolean equals(Object obj) {
517         if (this == obj) {
518             return true;
519         }
520         if (obj == null) {
521             return false;
522         }
523         if (getClass() != obj.getClass()) {
524             return false;
525         }
526         DefaultHierarchyManager other = (DefaultHierarchyManager) obj;
527         if (jcrSession == null) {
528             if (other.jcrSession != null) {
529                 return false;
530             }
531         } else if (!SessionUtil.hasSameUnderlyingSession(jcrSession, other.jcrSession)) {
532             return false;
533         }
534         return true;
535     }
536 }