View Javadoc

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