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.version.ContentVersion;
37  import info.magnolia.cms.core.version.VersionManager;
38  import info.magnolia.cms.security.AccessDeniedException;
39  import info.magnolia.cms.util.Rule;
40  import info.magnolia.context.MgnlContext;
41  import info.magnolia.context.MgnlContext.Op;
42  import info.magnolia.jcr.RuntimeRepositoryException;
43  import info.magnolia.jcr.util.NodeUtil;
44  import info.magnolia.jcr.wrapper.JCRPropertiesFilteringNodeWrapper;
45  import info.magnolia.objectfactory.Components;
46  import info.magnolia.objectfactory.MgnlInstantiationException;
47  import info.magnolia.repository.RepositoryConstants;
48  import info.magnolia.repository.RepositoryManager;
49  
50  import java.util.ArrayList;
51  import java.util.Collection;
52  import java.util.Collections;
53  import java.util.Comparator;
54  import java.util.List;
55  
56  import javax.jcr.ItemNotFoundException;
57  import javax.jcr.Node;
58  import javax.jcr.NodeIterator;
59  import javax.jcr.PathNotFoundException;
60  import javax.jcr.Property;
61  import javax.jcr.PropertyIterator;
62  import javax.jcr.PropertyType;
63  import javax.jcr.RepositoryException;
64  import javax.jcr.Session;
65  import javax.jcr.UnsupportedRepositoryOperationException;
66  import javax.jcr.Workspace;
67  import javax.jcr.lock.Lock;
68  import javax.jcr.lock.LockException;
69  import javax.jcr.nodetype.NodeType;
70  import javax.jcr.version.Version;
71  import javax.jcr.version.VersionException;
72  import javax.jcr.version.VersionHistory;
73  import javax.jcr.version.VersionIterator;
74  
75  import org.apache.commons.lang3.StringUtils;
76  import org.slf4j.Logger;
77  import org.slf4j.LoggerFactory;
78  
79  
80  /**
81   * Default, JCR-based, implementation of {@link Content}.
82   *
83   * @deprecated since 4.5 use jcr.Node instead.
84   */
85  @Deprecated
86  public class DefaultContent extends AbstractContent {
87      private static final Logger log = LoggerFactory.getLogger(DefaultContent.class);
88  
89      private final RepositoryManager repositoryManager;
90  
91      /**
92       * Wrapped jcr node.
93       */
94      protected Node node;
95  
96      /**
97       * node metadata.
98       */
99      private MetaData metaData;
100 
101     /**
102      * Empty constructor. Should NEVER be used for standard use, test only.
103      * @deprecated since 5.4
104      */
105     @Deprecated
106     protected DefaultContent() {
107         RepositoryManager manager = null;
108         try {
109             manager = Components.getComponent(RepositoryManager.class);
110         } catch (IllegalStateException | MgnlInstantiationException e) {
111             log.error("Content is not correctly initialized and will fail if this code is executed in production mode.");
112         }
113         this.repositoryManager = manager;
114     }
115 
116     /**
117      * Should NEVER be used for standard use, test only.
118      * @deprecated since 5.4
119      */
120     @Deprecated
121     protected DefaultContent(RepositoryManager repositoryManager) {
122         this.repositoryManager = repositoryManager;
123     }
124 
125     /**
126      * Constructor to get existing node.
127      *
128      * @param rootNode node to start with
129      * @param path absolute (primary) path to this <code>Node</code>
130      * @throws PathNotFoundException if the node at <code>path</code> does not exist
131      * @throws AccessDeniedException if the current session does not have sufficient access rights to complete the
132      * operation
133      * @throws RepositoryException if an error occurs
134      */
135     protected DefaultContent(Node rootNode, String path) throws PathNotFoundException, RepositoryException, AccessDeniedException {
136         this(rootNode.getNode(path));
137     }
138 
139     /**
140      * Constructor to get existing node.
141      *
142      * @param elem initialized node object
143      * @throws AccessDeniedException if the current session does not have sufficient access rights to complete the
144      * operation
145      * @throws RepositoryException if an error occurs
146      * @deprecated since 5.4 use {@link #DefaultContent(Node, RepositoryManager)}
147      */
148     @Deprecated
149     public DefaultContent(Node node) {
150         this(node, Components.getComponent(RepositoryManager.class));
151     }
152 
153     public DefaultContent(Node node, RepositoryManager repositoryManager) {
154         this.repositoryManager = repositoryManager;
155         try {
156             this.setNode(node);
157         } catch (RepositoryException e) {
158             throw new RuntimeRepositoryException(e);
159         }
160     }
161 
162     /**
163      * Creates contentNode of type <b>contentType </b> contentType must be defined in item type definition of Magnolia
164      * as well as JCR implementation.
165      *
166      * @param rootNode node to start with
167      * @param path absolute (primary) path to this <code>Node</code>
168      * @param contentType JCR node type as configured
169      * @throws PathNotFoundException if the node at <code>path</code> does not exist
170      * @throws RepositoryException if an error occurs
171      * @throws AccessDeniedException if the current session does not have sufficient access rights to complete the
172      * operation
173      * @deprecated since 5.4
174      */
175     @Deprecated
176     protected DefaultContent(Node rootNode, String path, String contentType)
177             throws PathNotFoundException,
178             RepositoryException,
179             AccessDeniedException {
180         this(Components.getComponent(RepositoryManager.class));
181         this.setNode(rootNode.addNode(path, contentType));
182         // add mix:lockable as default for all nodes created using this manager
183         // for version 3.5 we cannot change node type definitions because of compatibility reasons
184         // MAGNOLIA-1518
185         this.addMixin(ItemType.MIX_LOCKABLE);
186     }
187 
188     protected void setNode(Node node) throws RepositoryException {
189         // Default content takes care of filtering jcr properties on its own
190         this.node = NodeUtil.deepUnwrap(node, JCRPropertiesFilteringNodeWrapper.class);
191     }
192 
193     @Override
194     public Content getContent(String name) throws PathNotFoundException, RepositoryException, AccessDeniedException {
195         return wrapAsContent(this.node, name);
196     }
197 
198     @Override
199     public Content createContent(String name, String contentType) throws PathNotFoundException, RepositoryException,
200     AccessDeniedException {
201         Content content = wrapAsContent(this.node, name, contentType);
202         MetaData metaData = content.getMetaData();
203         metaData.setCreationDate();
204         return content;
205     }
206 
207     @Override
208     public boolean hasNodeData(String name) throws RepositoryException {
209         if (this.node.hasProperty(name)) {
210             return true;
211         }
212         if (hasBinaryNode(name)) {
213             return true;
214         }
215         return false;
216     }
217 
218     @Override
219     public NodeData newNodeDataInstance(String name, int type, boolean createIfNotExisting) throws AccessDeniedException, RepositoryException {
220         // create an empty dummy
221         if (!hasNodeData(name) && !createIfNotExisting) {
222             // TODO: This could also mean that user just doesn't have permission
223             return new NonExistingNodeData(this, name);
224         }
225 
226         if (type == PropertyType.UNDEFINED) {
227             type = determineNodeDataType(name);
228         }
229 
230         if (type == PropertyType.BINARY) {
231             return addBinaryNodeData(name);
232         }
233         return new DefaultNodeData(this, name);
234     }
235 
236     protected int determineNodeDataType(String name) {
237         // FIXME: maybe delegate to NodeDataImplementations?
238         try {
239             if (this.node.hasProperty(name)) {
240                 return this.node.getProperty(name).getType();
241             }
242             if (hasBinaryNode(name)) {
243                 return PropertyType.BINARY;
244             }
245         } catch (RepositoryException e) {
246             throw new IllegalStateException("Can't determine property type of [" + getHandle() + "/" + name + "]", e);
247         }
248         return PropertyType.UNDEFINED;
249     }
250 
251 
252     @Override
253     public MetaData getMetaData() {
254         if (this.metaData == null) {
255             this.metaData = new MetaData(this.node);
256         }
257         return this.metaData;
258     }
259 
260     @Override
261     public String getName() {
262         try {
263             return this.node.getName();
264         } catch (RepositoryException e) {
265             log.error(e.getMessage(), e);
266         }
267         return StringUtils.EMPTY;
268     }
269 
270     @Override
271     public Collection<Content> getChildren(ContentFilter filter, String namePattern, Comparator<Content> orderCriteria) {
272         List<Content> children;
273         children = new ArrayList<Content>();
274 
275         try {
276             final NodeIterator nodeIterator;
277             if (namePattern == null) {
278                 nodeIterator = this.node.getNodes();
279             } else {
280                 nodeIterator = this.node.getNodes(namePattern);
281             }
282 
283             while (nodeIterator.hasNext()) {
284                 Node subNode = (Node) nodeIterator.next();
285                 Content content = wrapAsContent(subNode);
286                 if (filter.accept(content)) {
287                     children.add(content);
288                 }
289             }
290         } catch (RepositoryException re) {
291             log.error("Exception caught", re);
292         }
293 
294         if (orderCriteria != null) {
295             // stable sort - do not reorder or remove equal items
296             Collections.sort(children, orderCriteria);
297         }
298         return children;
299     }
300 
301     protected Content wrapAsContent(Node node) {
302         return new DefaultContent(node);
303     }
304 
305     protected Content wrapAsContent(Node node, String name) throws AccessDeniedException, PathNotFoundException, RepositoryException {
306         return new DefaultContent(node, name);
307     }
308 
309     protected Content wrapAsContent(Node node, String name, String contentType) throws AccessDeniedException, PathNotFoundException, RepositoryException {
310         return new DefaultContent(node, name, contentType);
311     }
312 
313     @Override
314     public Collection<NodeData> getNodeDataCollection(String namePattern) {
315         final ArrayList<NodeData> all = new ArrayList<NodeData>();
316         try {
317             all.addAll(getPrimitiveNodeDatas(namePattern));
318             all.addAll(getBinaryNodeDatas(namePattern));
319         } catch (RepositoryException e) {
320             throw new IllegalStateException("Can't read node datas of " + toString(), e);
321         }
322         return all;
323     }
324 
325     protected Collection<NodeData> getPrimitiveNodeDatas(String namePattern) throws RepositoryException {
326         final Collection<NodeData> nodeDatas = new ArrayList<NodeData>();
327         final PropertyIterator propertyIterator;
328         if (namePattern == null) {
329             propertyIterator = this.node.getProperties();
330         } else {
331             propertyIterator = this.node.getProperties(namePattern);
332         }
333         while (propertyIterator.hasNext()) {
334             Property property = (Property) propertyIterator.next();
335             try {
336                 if (!property.getName().startsWith("jcr:") && !property.getName().startsWith("mgnl:")) {
337                     nodeDatas.add(getNodeData(property.getName()));
338                 }
339             } catch (PathNotFoundException e) {
340                 log.error("Exception caught", e);
341             } catch (AccessDeniedException e) {
342                 // ignore, simply wont add content in a list
343             }
344         }
345         return nodeDatas;
346     }
347 
348 
349     @Override
350     public boolean hasContent(String name) throws RepositoryException {
351         return this.node.hasNode(name);
352     }
353 
354     @Override
355     public String getHandle() {
356         try {
357             return this.node.getPath();
358         } catch (RepositoryException e) {
359             log.error("Failed to get handle: {}", e.getMessage(), e);
360             return StringUtils.EMPTY;
361         }
362     }
363 
364     @Override
365     public Content getParent() throws PathNotFoundException, RepositoryException, AccessDeniedException {
366         return wrapAsContent(this.node.getParent());
367     }
368 
369     @Override
370     public Content getAncestor(int level) throws PathNotFoundException, RepositoryException, AccessDeniedException {
371         if (level > this.getLevel()) {
372             throw new PathNotFoundException();
373         }
374         return wrapAsContent((Node) this.node.getAncestor(level));
375     }
376 
377     @Override
378     public Collection<Content> getAncestors() throws PathNotFoundException, RepositoryException {
379         List<Content> allAncestors = new ArrayList<Content>();
380         int level = this.getLevel();
381         while (level != 0) {
382             try {
383                 allAncestors.add(getAncestor(--level));
384             } catch (AccessDeniedException e) {
385                 // valid
386             }
387         }
388         return allAncestors;
389     }
390 
391     @Override
392     public int getLevel() throws PathNotFoundException, RepositoryException {
393         return this.node.getDepth();
394     }
395 
396     @Override
397     public void orderBefore(String srcName, String beforeName) throws RepositoryException {
398         this.node.orderBefore(srcName, beforeName);
399     }
400 
401     @Override
402     public int getIndex() throws RepositoryException {
403         return this.node.getIndex();
404     }
405 
406     @Override
407     public Node getJCRNode() {
408         return this.node;
409     }
410 
411     @Override
412     public boolean isNodeType(String type) {
413         return isNodeType(this.node, type);
414     }
415 
416     /**
417      * private Helper method to evaluate primary node type of the given node.
418      */
419     protected boolean isNodeType(Node node, String type) {
420         try {
421             return NodeUtil.isNodeType(node, type);
422         } catch (RepositoryException re) {
423             log.error(re.getMessage());
424             log.debug(re.getMessage(), re);
425             return false;
426         }
427     }
428 
429     @Override
430     public NodeType getNodeType() throws RepositoryException {
431         return this.node.getPrimaryNodeType();
432     }
433 
434     @Override
435     public String getNodeTypeName() throws RepositoryException {
436 
437         if (this.node.hasProperty(ItemType.JCR_FROZEN_PRIMARY_TYPE)) {
438             return this.node.getProperty(ItemType.JCR_FROZEN_PRIMARY_TYPE).getString();
439         }
440         return this.node.getProperty(ItemType.JCR_PRIMARY_TYPE).getString();
441     }
442 
443     @Override
444     public ItemType getItemType() throws RepositoryException {
445         return new ItemType(getNodeTypeName());
446     }
447 
448     @Override
449     public void restore(String versionName, boolean removeExisting) throws VersionException, UnsupportedRepositoryOperationException, RepositoryException {
450         Version version = this.getVersionHistory().getVersion(versionName);
451         this.restore(version, removeExisting);
452     }
453 
454     @Override
455     public void restore(Version version, boolean removeExisting) throws VersionException, UnsupportedRepositoryOperationException, RepositoryException {
456         VersionManager.getInstance().restore(this.getJCRNode(), version, removeExisting);
457     }
458 
459     @Override
460     public void restore(Version version, String relPath, boolean removeExisting) throws VersionException, UnsupportedRepositoryOperationException, RepositoryException {
461         throw new UnsupportedRepositoryOperationException("Not implemented since 3.0 Beta");
462     }
463 
464     @Override
465     public void restoreByLabel(String versionLabel, boolean removeExisting) throws VersionException, UnsupportedRepositoryOperationException, RepositoryException {
466         // FIXME: !!! why does it do something and then throws exception anyway?
467         this.node.restoreByLabel(versionLabel, removeExisting);
468         throw new UnsupportedRepositoryOperationException("Not implemented since 3.0 Beta");
469     }
470 
471     @Override
472     public Version addVersion() throws UnsupportedRepositoryOperationException, RepositoryException {
473         return VersionManager.getInstance().addVersion(this.getJCRNode());
474     }
475 
476     @Override
477     public Version addVersion(Rule rule) throws UnsupportedRepositoryOperationException, RepositoryException {
478         return VersionManager.getInstance().addVersion(this.getJCRNode(), rule);
479     }
480 
481     public BinaryNodeData addBinaryNodeData(String name) {
482         return new BinaryNodeData(this, name);
483     }
484 
485     /**
486      * Returns true if this node is either
487      * <ul>
488      * <li/>versionable and currently checked-out, <li/>non-versionable and its nearest versionable ancestor is
489      * checked-out or <li/>non-versionable and it has no versionable ancestor.
490      * </ul>
491      * Returns false if this node is either
492      * <ul>
493      * <li/>versionable and currently checked-in or <li/>non-versionable and its nearest versionable ancestor is
494      * checked-in.
495      * </ul>
496      *
497      * @return true if the node is checked out
498      */
499     protected boolean isCheckedOut() throws RepositoryException {
500         return this.node.isCheckedOut();
501     }
502 
503     @Override
504     public boolean isModified() {
505         return this.node.isModified();
506     }
507 
508     @Override
509     public VersionHistory getVersionHistory() throws UnsupportedRepositoryOperationException, RepositoryException {
510         return VersionManager.getInstance().getVersionHistory(this.getJCRNode());
511     }
512 
513     @Override
514     public VersionIterator getAllVersions() throws UnsupportedRepositoryOperationException, RepositoryException {
515         return VersionManager.getInstance().getAllVersions(this.getJCRNode());
516     }
517 
518     @Override
519     public ContentVersion getBaseVersion() throws UnsupportedRepositoryOperationException, RepositoryException {
520         return new ContentVersion(VersionManager.getInstance().getBaseVersion(this.getJCRNode()), this);
521     }
522 
523     @Override
524     public ContentVersion getVersionedContent(Version version) throws RepositoryException {
525         return new ContentVersion(version, this);
526     }
527 
528     @Override
529     public ContentVersion getVersionedContent(String versionName) throws RepositoryException {
530         return new ContentVersion(VersionManager.getInstance().getVersion(this.getJCRNode(), versionName), this);
531     }
532 
533     @Override
534     public void removeVersionHistory() throws AccessDeniedException, RepositoryException {
535         VersionManager.getInstance().removeVersionHistory(this.node);
536     }
537 
538     @Override
539     public void save() throws RepositoryException {
540         this.node.save();
541     }
542 
543     @Override
544     public void delete() throws RepositoryException {
545         final String nodePath = this.node.getPath();
546         final String workspaceName = this.node.getSession().getWorkspace().getName();
547         log.debug("removing {} from {}", this.node.getPath(), workspaceName);
548         if (!(workspaceName.endsWith("-" + RepositoryConstants.VERSION_STORE) || workspaceName.endsWith("-" + RepositoryConstants.SYSTEM))) {
549             MgnlContext.doInSystemContext((Op<Void, RepositoryException>) () -> {
550                 try {
551                     final String uuid = node.getIdentifier();
552                     log.debug("Removing history for node {}({})", uuid, nodePath);
553                     VersionManager.getInstance().removeVersionHistory(node);
554                 } catch (ItemNotFoundException | UnsupportedRepositoryOperationException e) {
555                     // doesn't exist in version store or not versionable or not referenceable ... either way ignore
556                 }
557                 return null;
558             });
559         }
560         this.node.remove();
561     }
562 
563     @Override
564     public void refresh(boolean keepChanges) throws RepositoryException {
565         this.node.refresh(keepChanges);
566     }
567 
568     @Override
569     public String getUUID() {
570         try {
571             return this.node.getUUID();
572         } catch (UnsupportedOperationException e) {
573             log.error(e.getMessage());
574         } catch (RepositoryException re) {
575             log.error("Exception caught", re);
576         }
577         return StringUtils.EMPTY;
578     }
579 
580     @Override
581     public void addMixin(String type) throws RepositoryException {
582         // TODO: there seems to be bug somewhere as we are able to add mixins even when the method below returns false
583         if (!this.node.canAddMixin(type)) {
584             log.debug("Node - {} does not allow mixin type - {}", this.node.getPath(), type);
585         }
586         try {
587             this.node.addMixin(type);
588         } catch (Exception e) {
589             log.error("Failed to add  mixin type - {} to a node {}", type, this.node.getPath());
590         }
591     }
592 
593     @Override
594     public void removeMixin(String type) throws RepositoryException {
595         this.node.removeMixin(type);
596     }
597 
598     @Override
599     public NodeType[] getMixinNodeTypes() throws RepositoryException {
600         return this.node.getMixinNodeTypes();
601     }
602 
603     @Override
604     public Lock lock(boolean isDeep, boolean isSessionScoped) throws LockException, RepositoryException {
605         return this.node.lock(isDeep, isSessionScoped);
606     }
607 
608     @Override
609     public Lock lock(boolean isDeep, boolean isSessionScoped, long yieldFor) throws LockException, RepositoryException {
610         long finalTime = System.currentTimeMillis() + yieldFor;
611         LockException lockException = null;
612         while (System.currentTimeMillis() <= finalTime) {
613             try {
614                 return this.node.lock(isDeep, isSessionScoped);
615             } catch (LockException e) {
616                 // its not an exception yet, still got time
617                 lockException = e;
618             }
619             Thread.yield();
620         }
621         // could not get lock
622         throw lockException;
623     }
624 
625     @Override
626     public Lock getLock() throws LockException, RepositoryException {
627         return this.node.getLock();
628     }
629 
630     @Override
631     public void unlock() throws LockException, RepositoryException {
632         this.node.unlock();
633     }
634 
635     @Override
636     public boolean holdsLock() throws RepositoryException {
637         return this.node.holdsLock();
638     }
639 
640     @Override
641     public boolean isLocked() throws RepositoryException {
642         return this.node.isLocked();
643     }
644 
645     @Override
646     public boolean hasMetaData() {
647         return true;
648     }
649 
650     @Override
651     public boolean hasMixin(String mixinName) throws RepositoryException {
652         if (StringUtils.isBlank(mixinName)) {
653             throw new IllegalArgumentException("Mixin name can't be empty.");
654         }
655         for (NodeType type : getMixinNodeTypes()) {
656             if (mixinName.equals(type.getName())) {
657                 return true;
658             }
659         }
660         return false;
661     }
662 
663     @Override
664     public HierarchyManager getHierarchyManager() {
665         try {
666             return createHierarchyManager(node.getSession());
667         } catch (RepositoryException e) {
668             throw new RuntimeException(e);
669         }
670     }
671 
672     @Override
673     public Workspace getWorkspace() throws RepositoryException {
674         return node.getSession().getWorkspace();
675     }
676 
677     @Override
678     public boolean equals(Object obj) {
679         if (obj == null || !(obj instanceof DefaultContent)) {
680             return false;
681         }
682         DefaultContent otherContent = (DefaultContent) obj;
683         return getJCRNode().equals(otherContent.getJCRNode());
684     }
685 
686     /**
687      * @deprecated since 5.4
688      */
689     @Deprecated
690     protected HierarchyManager createHierarchyManager(Session session) {
691         return new DefaultHierarchyManager(session, repositoryManager);
692     }
693 
694     protected boolean hasBinaryNode(String name) throws RepositoryException {
695         return this.node.hasNode(name) && (this.node.getNode(name).isNodeType(ItemType.NT_RESOURCE) ||
696                 (this.node.hasProperty("jcr:frozenPrimaryType") && this.node.getNode(name).getProperty("jcr:frozenPrimaryType").getValue().getString().equals(ItemType.NT_RESOURCE)));
697     }
698 }