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