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