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