View Javadoc

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