View Javadoc

1   /**
2    * This file Copyright (c) 2003-2010 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.version;
35  
36  import info.magnolia.cms.core.AbstractContent;
37  import info.magnolia.cms.core.Content;
38  import info.magnolia.cms.core.HierarchyManager;
39  import info.magnolia.cms.core.ItemType;
40  import info.magnolia.cms.core.NodeData;
41  import info.magnolia.cms.core.DefaultContent;
42  import info.magnolia.cms.security.AccessDeniedException;
43  import info.magnolia.cms.security.AccessManager;
44  import info.magnolia.cms.security.AccessManagerImpl;
45  import info.magnolia.cms.security.Permission;
46  import info.magnolia.cms.security.PermissionImpl;
47  import info.magnolia.cms.util.ContentWrapper;
48  import info.magnolia.cms.util.HierarchyManagerWrapper;
49  import info.magnolia.cms.util.Rule;
50  import info.magnolia.cms.util.SimpleUrlPattern;
51  
52  import java.util.ArrayList;
53  import java.util.Calendar;
54  import java.util.Collection;
55  import java.util.List;
56  
57  import javax.jcr.PathNotFoundException;
58  import javax.jcr.PropertyType;
59  import javax.jcr.RepositoryException;
60  import javax.jcr.Value;
61  import javax.jcr.Workspace;
62  import javax.jcr.lock.Lock;
63  import javax.jcr.lock.LockException;
64  import javax.jcr.nodetype.NodeType;
65  import javax.jcr.version.Version;
66  import javax.jcr.version.VersionHistory;
67  import javax.jcr.version.VersionIterator;
68  
69  import org.apache.commons.lang.StringUtils;
70  import org.slf4j.Logger;
71  import org.slf4j.LoggerFactory;
72  
73  
74  /**
75   * Wraps a versioned node (frozen node) and allows traversing the hierarchy as if the node where in the original place.
76   * @author Sameer Charles
77   * $Id: ContentVersion.java 37186 2010-09-13 12:08:27Z ochytil $
78   */
79  public class ContentVersion extends DefaultContent {
80  
81      private final class FixParentContentWrapper extends ContentWrapper {
82          private final Content parent;
83  
84          private FixParentContentWrapper(Content wrappedContent, Content parent) {
85              super(wrappedContent);
86              this.parent = parent;
87          }
88  
89          @Override
90          public Content getParent() throws RepositoryException {
91              return parent;
92          }
93  
94          @Override
95          protected Content wrap(Content node) {
96              return new FixParentContentWrapper(node, this);
97          }
98      }
99  
100     private static Logger log = LoggerFactory.getLogger(ContentVersion.class);
101 
102     /**
103      * User who created this version.
104      */
105     public static final String VERSION_USER = "versionUser"; //$NON-NLS-1$
106 
107     /**
108      * Name of the base node.
109      */
110     public static final String NAME = "name";
111 
112     /**
113      * Version node (nt:version).
114      */
115     private Version state;
116 
117     /**
118      * The node as existing in the workspace. Not the version node.
119      */
120     private AbstractContent base;
121 
122     /**
123      * Rule used to create this version.
124      */
125     private Rule rule;
126 
127     public ContentVersion(Version thisVersion, AbstractContent base) throws RepositoryException {
128         if (thisVersion == null) {
129             throw new RepositoryException("Failed to get ContentVersion, version does not exist");
130         }
131         this.state = thisVersion;
132         this.base = base;
133 
134         this.hierarchyManager = new HierarchyManagerWrapper(base.getHierarchyManager()) {
135             private AccessManagerImpl accessManager;
136 
137             {
138                 // child nodes (and metaData if nothing else) depends on this to have access when root access is restricted for given user
139                 List<Permission> permissions = new ArrayList<Permission>(getWrappedHierarchyManager().getAccessManager().getPermissionList());
140                 PermissionImpl p = new PermissionImpl();
141                 p.setPattern(new SimpleUrlPattern("/jcr:system/jcr:versionStorage/*"));
142                 // read only
143                 p.setPermissions(8);
144                 permissions.add(p);
145                 // use dedicated AM and not the one base share with its parent
146                 accessManager = new AccessManagerImpl();
147                 accessManager.setPermissionList(permissions);
148             }
149 
150             @Override
151             public AccessManager getAccessManager() {
152                 return accessManager;
153             }
154         };
155         this.init();
156     }
157 
158     /**
159      * Set frozen node of this version as working node.
160      * @throws RepositoryException
161      */
162     private void init() throws RepositoryException {
163         this.setNode(this.state.getNode(ItemType.JCR_FROZENNODE));
164         try {
165             if (!StringUtils.equalsIgnoreCase(this.state.getName(), VersionManager.ROOT_VERSION)) {
166                 this.rule = VersionManager.getInstance().getUsedFilter(this);
167             }
168         }
169         catch (Exception e) {
170             log.error(e.getMessage(), e);
171         }
172         if (this.rule == null) {
173             log.info("failed to get filter used for creating this version, use open filter");
174             this.rule = new Rule();
175         }
176     }
177 
178     /**
179      * Get creation date of this version.
180      * @throws RepositoryException
181      * @return creation date as calendar
182      */
183     public Calendar getCreated() throws RepositoryException {
184         return this.state.getCreated();
185     }
186 
187     /**
188      * Return the name of the version represented by this object.
189      * @return the versions name
190      * @throws RepositoryException
191      */
192     public String getVersionLabel() throws RepositoryException {
193         return this.state.getName();
194     }
195 
196     /**
197      * Get containing version history.
198      * @throws RepositoryException
199      * @return version history associated to this version
200      */
201     public VersionHistory getContainingHistory() throws RepositoryException {
202         return this.state.getContainingHistory();
203     }
204 
205     /**
206      * The original name of the node.
207      */
208     public String getName() {
209         try {
210             return VersionManager.getInstance().getSystemNode(this).getNodeData(NAME).getString();
211         }
212         catch (RepositoryException re) {
213             log.error("Failed to retrieve name from version system node", re);
214             return "";
215         }
216     }
217 
218     /**
219      * The name of the user who created this version.
220      */
221     public String getUserName() {
222         try {
223             return VersionManager.getInstance().getSystemNode(this).getNodeData(VERSION_USER).getString();
224         }
225         catch (RepositoryException re) {
226             log.error("Failed to retrieve user from version system node", re);
227             return "";
228         }
229     }
230 
231     /**
232      * Get original path of this versioned content.
233      */
234     public String getHandle() {
235         return this.base.getHandle();
236     }
237 
238     public Content getContent(String name) throws PathNotFoundException, RepositoryException, AccessDeniedException {
239         return new FixParentContentWrapper(super.getContent(name), this);
240     }
241 
242     /**
243      * Throws an {@link AccessDeniedException} as versions are read only.
244      */
245     public Content createContent(String name) throws AccessDeniedException {
246         throw new AccessDeniedException("Not allowed to write on version preview");
247     }
248 
249     /**
250      * Throws an {@link AccessDeniedException} as versions are read only.
251      */
252     public Content createContent(String name, String contentType) throws AccessDeniedException {
253         throw new AccessDeniedException("Not allowed to write on version preview");
254     }
255 
256     /**
257      * Throws an {@link AccessDeniedException} as versions are read only.
258      */
259     public Content createContent(String name, ItemType contentType) throws AccessDeniedException {
260         throw new AccessDeniedException("Not allowed to write on version preview");
261     }
262 
263     /**
264      * Throws an {@link AccessDeniedException} as versions are read only.
265      */
266     public NodeData createNodeData(String name) throws AccessDeniedException {
267         throw new AccessDeniedException("Not allowed to write on version preview");
268     }
269 
270     /**
271      * Throws an {@link AccessDeniedException} as versions are read only.
272      */
273     public NodeData createNodeData(String name, Value value, int type) throws AccessDeniedException {
274         throw new AccessDeniedException("Not allowed to write on version preview");
275     }
276 
277     /**
278      * Throws an {@link AccessDeniedException} as versions are read only.
279      */
280     public NodeData createNodeData(String name, Value value) throws AccessDeniedException {
281         throw new AccessDeniedException("Not allowed to write on version preview");
282     }
283 
284     /**
285      *  Throws an {@link AccessDeniedException} as versions are read only.
286      */
287     public NodeData createNodeData(String name, int type) throws AccessDeniedException {
288         throw new AccessDeniedException("Not allowed to write on version preview");
289     }
290 
291     /**
292      * Throws an {@link AccessDeniedException} as versions are read only.
293      */
294     public void deleteNodeData(String name) throws RepositoryException {
295         throw new AccessDeniedException("Not allowed to write on version preview");
296     }
297 
298     /**
299      * Throws an {@link AccessDeniedException} as versions are read only.
300      */
301     public void updateMetaData() throws AccessDeniedException {
302         throw new AccessDeniedException("Not allowed to write on version preview");
303     }
304 
305     /**
306      * gets a Collection containing all child nodes of the same NodeType as "this" object.
307      * @return Collection of content objects
308      */
309     public Collection<Content> getChildren() {
310         try {
311             if (this.rule.isAllowed(this.base.getNodeTypeName())) {
312                 return wrap(super.getChildren());
313             }
314         }
315         catch (RepositoryException re) {
316             log.error(re.getMessage(), re);
317         }
318         return this.base.getChildren();
319     }
320 
321     /**
322      * Get collection of specified content type.
323      * @param contentType JCR node type as configured
324      * @return Collection of content nodes
325      */
326     public Collection<Content> getChildren(String contentType) {
327         if (this.rule.isAllowed(contentType)) {
328             return wrap(super.getChildren(contentType));
329         }
330         return this.base.getChildren(contentType);
331     }
332 
333     /**
334      * Get collection of specified content type.
335      * @param contentType ItemType
336      * @return Collection of content nodes
337      */
338     public Collection<Content> getChildren(ItemType contentType) {
339         return this.getChildren(contentType.getSystemName());
340     }
341 
342     /**
343      * Get collection of specified content type.
344      * @param contentType JCR node type as configured
345      * @param namePattern
346      * @return Collection of content nodes
347      */
348     public Collection<Content> getChildren(String contentType, String namePattern) {
349         if (this.rule.isAllowed(contentType)) {
350             return wrap(super.getChildren(contentType, namePattern));
351         }
352         return this.base.getChildren(contentType, namePattern);
353     }
354 
355     private Collection<Content> wrap(Collection<Content> children) {
356         List<Content> transformed = new ArrayList<Content>();
357         for (Content child : children) {
358             transformed.add(new FixParentContentWrapper(child, this));
359         }
360         return transformed;
361     }
362 
363     /**
364      * @return Boolean, if sub node(s) exists
365      */
366     public boolean hasChildren() {
367         return (this.getChildren().size() > 0);
368     }
369 
370     /**
371      * @param contentType JCR node type as configured
372      * @return Boolean, if sub <code>collectionType</code> exists
373      */
374     public boolean hasChildren(String contentType) {
375         return (this.getChildren(contentType).size() > 0);
376     }
377 
378     /**
379      * Returns the parent of the base node.
380      */
381     public Content getParent() throws PathNotFoundException, RepositoryException, AccessDeniedException {
382         return this.base.getParent();
383     }
384 
385     public Content getAncestor(int level) throws PathNotFoundException, RepositoryException, AccessDeniedException {
386         return this.base.getAncestor(level);
387     }
388 
389     /**
390      * Convenience method for taglib.
391      * @return Content representing node on level 0
392      * @throws javax.jcr.RepositoryException if an error occurs
393      */
394     public Collection<Content> getAncestors() throws PathNotFoundException, RepositoryException {
395         return this.base.getAncestors();
396     }
397 
398     /**
399      * Get node level from the ROOT node : FIXME implement getDepth in javax.jcr.
400      * @return level at which current node exist, relative to the ROOT node
401      * @throws javax.jcr.PathNotFoundException
402      * @throws javax.jcr.RepositoryException if an error occurs
403      */
404     public int getLevel() throws PathNotFoundException, RepositoryException {
405         return this.base.getLevel();
406     }
407 
408     /**
409      * Throws an {@link AccessDeniedException} as versions are read only.
410      */
411     public void orderBefore(String srcName, String beforeName) throws RepositoryException {
412         throw new AccessDeniedException("Not allowed to write on version preview");
413     }
414 
415     /**
416      * This method returns the index of this node within the ordered set of its same-name sibling nodes. This index is
417      * the one used to address same-name siblings using the square-bracket notation, e.g., /a[3]/b[4]. Note that the
418      * index always starts at 1 (not 0), for compatibility with XPath. As a result, for nodes that do not have
419      * same-name-siblings, this method will always return 1.
420      * @return The index of this node within the ordered set of its same-name sibling nodes.
421      * @throws javax.jcr.RepositoryException if an error occurs
422      */
423     public int getIndex() throws RepositoryException {
424         return this.base.getIndex();
425     }
426 
427     /**
428      * Returns primary node type definition of the associated Node of this object.
429      * @throws RepositoryException if an error occurs
430      */
431     public NodeType getNodeType() throws RepositoryException {
432         log.warn("This is a Version node, it will always return NT_FROZEN as node type.");
433         log.warn("Use getNodeTypeName to retrieve base node primary type");
434         return super.getNodeType();
435     }
436 
437     /**
438      * Throws an {@link AccessDeniedException} as versions are read only.
439      */
440     public void restore(String versionName, boolean removeExisting) throws RepositoryException {
441         throw new AccessDeniedException("Not allowed to write on version preview");
442     }
443 
444     /**
445      * Throws an {@link AccessDeniedException} as versions are read only.
446      */
447     public void restore(Version version, boolean removeExisting) throws RepositoryException {
448         throw new AccessDeniedException("Not allowed to write on version preview");
449     }
450 
451     /**
452      * Throws an {@link AccessDeniedException} as versions are read only.
453      */
454     public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException {
455         throw new AccessDeniedException("Not allowed to write on version preview");
456     }
457 
458     /**
459      * Throws an {@link AccessDeniedException} as versions are read only.
460      */
461     public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException {
462         throw new AccessDeniedException("Not allowed to write on version preview");
463     }
464 
465     /**
466      * Throws an {@link AccessDeniedException} as versions are read only.
467      */
468     public Version addVersion() throws RepositoryException {
469         throw new AccessDeniedException("Not allowed to add version on version preview");
470     }
471 
472     /**
473      * Throws an {@link AccessDeniedException} as versions are read only.
474      */
475     public Version addVersion(Rule rule) throws RepositoryException {
476         throw new AccessDeniedException("Not allowed to add version on version preview");
477     }
478 
479     /**
480      * Returns always false as verions are read only.
481      */
482     public boolean isModified() {
483         log.error("Not valid for version");
484         return false;
485     }
486 
487     /**
488      * Throws an {@link AccessDeniedException} as versions are read only.
489      */
490     public VersionHistory getVersionHistory() throws RepositoryException {
491         throw new AccessDeniedException("Not allowed to read VersionHistory of Version");
492     }
493 
494     /**
495      * Throws an {@link AccessDeniedException} as versions are read only.
496      */
497     public VersionIterator getAllVersions() throws RepositoryException {
498         throw new AccessDeniedException("Not allowed to get VersionIterator of Version");
499     }
500 
501     /**
502      * Throws an {@link AccessDeniedException} as versions are read only.
503      */
504     public ContentVersion getBaseVersion() throws RepositoryException {
505         throw new AccessDeniedException("Not allowed to get base version of Version");
506     }
507 
508     /**
509      * Throws an {@link AccessDeniedException} as versions are read only.
510      */
511     public ContentVersion getVersionedContent(Version version) throws RepositoryException {
512         throw new AccessDeniedException("Not allowed to get preview of Version itself");
513     }
514 
515     /**
516      * Throws an {@link AccessDeniedException} as versions are read only.
517      */
518     public ContentVersion getVersionedContent(String versionName) throws RepositoryException {
519         throw new AccessDeniedException("Not allowed to get preview of Version itself");
520     }
521 
522     /**
523      * Throws an {@link AccessDeniedException} as versions are read only.
524      */
525     public void save() throws RepositoryException {
526         throw new AccessDeniedException("Not allowed to write on version preview");
527     }
528 
529     /**
530      * Checks for the allowed access rights.
531      * @param permissions as defined in javax.jcr.Permission
532      * @return true is the current user has specified access on this node.
533      */
534     public boolean isGranted(long permissions) {
535         return (permissions & Permission.READ) == permissions;
536     }
537 
538     /**
539      * Throws an {@link AccessDeniedException} as versions are read only.
540      */
541     public void delete() throws RepositoryException {
542         throw new AccessDeniedException("Not allowed to write on version preview");
543     }
544 
545     /**
546      * Throws an {@link AccessDeniedException} as versions are read only.
547      */
548     public void delete(String path) throws RepositoryException {
549         throw new AccessDeniedException("Not allowed to write on version preview");
550     }
551 
552     /**
553      * UUID of the node refrenced by this object.
554      * @return uuid
555      */
556     public String getUUID() {
557         return this.base.getUUID();
558     }
559 
560     /**
561      * Throws an {@link AccessDeniedException} as versions are read only.
562      */
563     public void addMixin(String type) throws RepositoryException {
564         throw new AccessDeniedException("Not allowed to write on version preview");
565     }
566 
567     /**
568      * Throws an {@link AccessDeniedException} as versions are read only.
569      */
570     public void removeMixin(String type) throws RepositoryException {
571         throw new AccessDeniedException("Not allowed to write on version preview");
572     }
573 
574     /**
575      * Throws an {@link AccessDeniedException} as versions are read only.
576      */
577     public Lock lock(boolean isDeep, boolean isSessionScoped) throws LockException, RepositoryException {
578         throw new AccessDeniedException("Lock not supported on version preview");
579     }
580 
581     /**
582      * Throws an {@link AccessDeniedException} as versions are read only.
583      */
584     public Lock lock(boolean isDeep, boolean isSessionScoped, long yieldFor) throws LockException, RepositoryException {
585         throw new AccessDeniedException("Lock not supported on version preview");
586     }
587 
588     /**
589      * Throws an {@link AccessDeniedException} as versions are read only.
590      */
591     public Lock getLock() throws LockException, RepositoryException {
592         throw new AccessDeniedException("Lock not supported on version preview");
593     }
594 
595     /**
596      * Throws an {@link AccessDeniedException} as versions are read only.
597      */
598     public void unlock() throws LockException, RepositoryException {
599         throw new AccessDeniedException("Lock not supported on version preview");
600     }
601 
602     /**
603      * Throws an {@link AccessDeniedException} as versions are read only.
604      */
605     public boolean holdsLock() throws RepositoryException {
606         throw new AccessDeniedException("Lock not supported on version preview");
607     }
608 
609     /**
610      * Throws an {@link AccessDeniedException} as versions are read only.
611      */
612     public boolean isLocked() throws RepositoryException {
613         throw new AccessDeniedException("Lock not supported on version preview");
614     }
615 
616     /**
617      * Get hierarchy manager if previously set for this object.
618      * @return HierarchyManager
619      */
620     public HierarchyManager getHierarchyManager() {
621         return this.base.getHierarchyManager();
622     }
623 
624     /**
625      * Get access manager if previously set for this object.
626      * @return AccessManager
627      * @deprecated use getHierarchyManager instead
628      */
629     public AccessManager getAccessManager() {
630         return this.base.getAccessManager();
631     }
632 
633     @Override
634     public Workspace getWorkspace() throws RepositoryException {
635         return this.base.getWorkspace();
636     }
637     
638     @Override
639     public boolean hasNodeData(String name) throws RepositoryException {
640         if (this.node.hasProperty(name)) {
641             return true;
642         }
643         else { // check for mgnl:resource node
644             if (this.node.hasNode(name) && this.node.getNode(name).getProperty("jcr:frozenPrimaryType").getValue().getString().equals(ItemType.NT_RESOURCE)) {
645                 return true;
646             }
647         }
648         return false;
649     }
650 
651     @Override
652     protected int determineNodeDataType(String name) {
653         // FIXME: maybe delegate to NodeDataImplementations?
654         try {
655             if (this.node.hasProperty(name)) {
656                 return this.node.getProperty(name).getType();
657             }
658             else { // check for mgnl:resource node
659                 if (this.node.hasNode(name) && this.node.getNode(name).getProperty("jcr:frozenPrimaryType").getValue().getString().equals(ItemType.NT_RESOURCE)) {
660                     return PropertyType.BINARY;
661                 }
662             }
663         }
664         catch (RepositoryException e) {
665             throw new IllegalStateException("Can't determine property type of [" + getHandle() + "/" + name + "]", e);
666         }
667         return PropertyType.UNDEFINED;
668     }
669 
670 
671 }