View Javadoc

1   /**
2    * This file Copyright (c) 2003-2013 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.DefaultContent;
39  import info.magnolia.cms.core.HierarchyManager;
40  import info.magnolia.cms.core.ItemType;
41  import info.magnolia.cms.core.NodeData;
42  import info.magnolia.cms.security.AccessDeniedException;
43  import info.magnolia.cms.security.AccessManager;
44  import info.magnolia.cms.security.Permission;
45  import info.magnolia.cms.util.ContentWrapper;
46  import info.magnolia.cms.util.NodeDataWrapper;
47  import info.magnolia.cms.util.Rule;
48  
49  import java.util.ArrayList;
50  import java.util.Calendar;
51  import java.util.Collection;
52  import java.util.Comparator;
53  import java.util.List;
54  
55  import javax.jcr.Node;
56  import javax.jcr.PathNotFoundException;
57  import javax.jcr.PropertyType;
58  import javax.jcr.RepositoryException;
59  import javax.jcr.Value;
60  import javax.jcr.Workspace;
61  import javax.jcr.lock.Lock;
62  import javax.jcr.lock.LockException;
63  import javax.jcr.nodetype.NodeType;
64  import javax.jcr.nodetype.NodeTypeManager;
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   */
77  public class ContentVersion extends DefaultContent {
78  
79      /**
80       * Makes sure that the handle hides the fact that the nodes live in the version store.
81       */
82      private final class ContentVersionChildWrapper extends ContentWrapper {
83          private final Content parent;
84  
85          private ContentVersionChildWrapper(Content wrappedContent, Content parent) {
86              super(wrappedContent);
87              this.parent = parent;
88          }
89  
90          @Override
91          public Content getParent() throws RepositoryException {
92              return parent;
93          }
94  
95          /**
96           * Show the original path not the one from the version store.
97           */
98          @Override
99          public String getHandle() {
100             try {
101                 return getParent().getHandle() + "/" + getName();
102             }
103             catch (RepositoryException e) {
104                 throw new RuntimeException("Can't create handle for versioned node.", e);
105             }
106         }
107 
108         /**
109          * We have to wrap the node data to make sure that the handle is correct.
110          */
111         @Override
112         public NodeData newNodeDataInstance(String name, int type, boolean createIfNotExisting) throws AccessDeniedException, RepositoryException {
113             return new NodeDataWrapper(super.newNodeDataInstance(name, type, createIfNotExisting)) {
114                 @Override
115                 public String getHandle() {
116                     return ContentVersionChildWrapper.this.getHandle() + "/" + getName();
117                 }
118             };
119         }
120 
121         @Override
122         protected Content wrap(Content node) {
123             return new ContentVersionChildWrapper(node, this);
124         }
125     }
126 
127     private static Logger log = LoggerFactory.getLogger(ContentVersion.class);
128 
129     /**
130      * User who created this version.
131      */
132     public static final String VERSION_USER = "versionUser";
133 
134     /**
135      * Name of the base node.
136      */
137     public static final String NAME = "name";
138 
139     /**
140      * Version node (nt:version).
141      */
142     private final Version state;
143 
144     /**
145      * The node as existing in the workspace. Not the version node.
146      */
147     private final Content base;
148 
149     /**
150      * Rule used to create this version.
151      */
152     private Rule rule;
153 
154     private final Version versionedNode;
155 
156     public ContentVersion(Version thisVersion, Content base) throws RepositoryException {
157         if (thisVersion == null) {
158             throw new RepositoryException("Failed to get ContentVersion, version does not exist");
159         }
160         this.versionedNode = thisVersion;
161         if (thisVersion instanceof VersionedNode) {
162             this.state = ((VersionedNode) thisVersion).unwrap();
163         } else {
164             this.state = thisVersion;
165         }
166         this.base = base;
167 
168         //setting permissions should be no longer necessary as it is handled by jcr itself
169 
170         //        this.hierarchyManager = new HierarchyManagerWrapper(base.getHierarchyManager()) {
171         //            private AccessManagerImpl accessManager;
172         //
173         //            {
174         //                // child nodes (and metaData if nothing else) depends on this to have access when root access is restricted for given user
175         //                List<Permission> permissions = new ArrayList<Permission>(getWrappedHierarchyManager().getAccessManager().getPermissionList());
176         //                PermissionImpl p = new PermissionImpl();
177         //                p.setPattern(new SimpleUrlPattern("/jcr:system/jcr:versionStorage/*"));
178         //                // read only
179         //                p.setPermissions(8);
180         //                permissions.add(p);
181         //                // use dedicated AM and not the one base share with its parent
182         //                accessManager = new AccessManagerImpl();
183         //                accessManager.setPermissionList(permissions);
184         //            }
185         //
186         //            @Override
187         //            public AccessManager getAccessManager() {
188         //                return accessManager;
189         //            }
190         //        };
191         this.init();
192     }
193 
194     /**
195      * Set frozen node of this version as working node.
196      * @throws RepositoryException
197      */
198     private void init() throws RepositoryException {
199         this.setNode(this.state.getNode(ItemType.JCR_FROZENNODE));
200         try {
201             if (!StringUtils.equalsIgnoreCase(this.state.getName(), VersionManager.ROOT_VERSION)) {
202                 this.rule = VersionManager.getInstance().getUsedFilter(this.getJCRNode());
203             }
204         }
205         catch (Exception e) {
206             log.error(e.getMessage(), e);
207         }
208         if (this.rule == null) {
209             log.info("failed to get filter used for creating this version, use open filter");
210             this.rule = new Rule();
211         }
212     }
213 
214     /**
215      * Get creation date of this version.
216      * @throws RepositoryException
217      * @return creation date as calendar
218      */
219     public Calendar getCreated() throws RepositoryException {
220         return this.state.getCreated();
221     }
222 
223     /**
224      * Return the name of the version represented by this object.
225      * @return the versions name
226      * @throws RepositoryException
227      */
228     public String getVersionLabel() throws RepositoryException {
229         return this.state.getName();
230     }
231 
232     /**
233      * Get containing version history.
234      * @throws RepositoryException
235      * @return version history associated to this version
236      */
237     public VersionHistory getContainingHistory() throws RepositoryException {
238         return this.state.getContainingHistory();
239     }
240 
241     /**
242      * The original name of the node.
243      */
244     @Override
245     public String getName() {
246         try {
247             return VersionManager.getInstance().getSystemNode(this.getJCRNode()).getProperty(NAME).getString();
248         }
249         catch (RepositoryException re) {
250             log.error("Failed to retrieve name from version system node", re);
251             return "";
252         }
253     }
254 
255     /**
256      * The name of the user who created this version.
257      */
258     public String getUserName() {
259         try {
260             return VersionManager.getInstance().getSystemNode(this.getJCRNode()).getProperty(VERSION_USER).getString();
261         }
262         catch (RepositoryException re) {
263             log.error("Failed to retrieve user from version system node", re);
264             return "";
265         }
266     }
267 
268     /**
269      * Get original path of this versioned content.
270      */
271     @Override
272     public String getHandle() {
273         return this.base.getHandle();
274     }
275 
276     /**
277      * Returns a direct child if it was included in the version. Otherwise it tries to get the child from the original place.
278      * The versioning rule is respected.
279      */
280     @Override
281     public Content getContent(String name) throws PathNotFoundException, RepositoryException, AccessDeniedException {
282         //first we have to check if this is a direct child
283         if(super.hasContent(name)){
284             return new ContentVersionChildWrapper(super.getContent(name), this);
285         }
286         Content content = base.getContent(name);
287         // only return the node if it was excluded from the versioning, otherwise the node is new
288         if(!rule.isAllowed(content.getJCRNode())){
289             return content;
290         }
291         throw new PathNotFoundException(base.getHandle() + "/" + name);
292     }
293 
294     /**
295      * Uses the same approach as {@link #getContent(String)}.
296      */
297     @Override
298     public boolean hasContent(String name) throws RepositoryException {
299         if(super.hasContent(name)){
300             return true;
301         }
302         else if(base.hasContent(name)){
303             Content content = base.getContent(name);
304             // only return the node if it was excluded from the versioning, otherwise the node is new
305             if(!rule.isAllowed(content.getJCRNode())){
306                 return true;
307             }
308         }
309         return false;
310     }
311 
312     /**
313      * Throws an {@link AccessDeniedException} as versions are read only.
314      */
315     @Override
316     public Content createContent(String name) throws AccessDeniedException {
317         throw new AccessDeniedException("Not allowed to write on version preview");
318     }
319 
320     /**
321      * Throws an {@link AccessDeniedException} as versions are read only.
322      */
323     @Override
324     public Content createContent(String name, String contentType) throws AccessDeniedException {
325         throw new AccessDeniedException("Not allowed to write on version preview");
326     }
327 
328     /**
329      * Throws an {@link AccessDeniedException} as versions are read only.
330      */
331     @Override
332     public Content createContent(String name, ItemType contentType) throws AccessDeniedException {
333         throw new AccessDeniedException("Not allowed to write on version preview");
334     }
335 
336     /**
337      * Throws an {@link AccessDeniedException} as versions are read only.
338      */
339     @Override
340     public NodeData createNodeData(String name) throws AccessDeniedException {
341         throw new AccessDeniedException("Not allowed to write on version preview");
342     }
343 
344     /**
345      * Throws an {@link AccessDeniedException} as versions are read only.
346      */
347     public NodeData createNodeData(String name, Value value, int type) throws AccessDeniedException {
348         throw new AccessDeniedException("Not allowed to write on version preview");
349     }
350 
351     /**
352      * Throws an {@link AccessDeniedException} as versions are read only.
353      */
354     @Override
355     public NodeData createNodeData(String name, Value value) throws AccessDeniedException {
356         throw new AccessDeniedException("Not allowed to write on version preview");
357     }
358 
359     /**
360      *  Throws an {@link AccessDeniedException} as versions are read only.
361      */
362     @Override
363     public NodeData createNodeData(String name, int type) throws AccessDeniedException {
364         throw new AccessDeniedException("Not allowed to write on version preview");
365     }
366 
367     /**
368      * Throws an {@link AccessDeniedException} as versions are read only.
369      */
370     @Override
371     public void deleteNodeData(String name) throws RepositoryException {
372         throw new AccessDeniedException("Not allowed to write on version preview");
373     }
374 
375     /**
376      * Throws an {@link AccessDeniedException} as versions are read only.
377      */
378     @Override
379     public void updateMetaData() throws AccessDeniedException {
380         throw new AccessDeniedException("Not allowed to write on version preview");
381     }
382 
383     /**
384      * All {@link #getChildren()} methods delegate to this method. We combine the direct children and children
385      * from the current node which were not included by the version rule.
386      */
387     @Override
388     public Collection<Content> getChildren(ContentFilter filter, String namePattern, Comparator<Content> orderCriteria) {
389         ArrayList<Content> result = new ArrayList<Content>();
390         result.addAll(wrap(super.getChildren(filter, namePattern, orderCriteria)));
391 
392         Collection<Content> transientChildren = ((AbstractContent)this.base).getChildren(filter, namePattern, orderCriteria);
393         for (Content transientChild : transientChildren) {
394             try {
395                 if(!rule.isAllowed(transientChild.getJCRNode())){
396                     result.add(transientChild);
397                 }
398             }
399             catch (RepositoryException e) {
400                 throw new RuntimeException("Can't determine node type of " + transientChild, e);
401             }
402         }
403 
404         return result;
405     }
406 
407     private Collection<Content> wrap(Collection<Content> children) {
408         List<Content> transformed = new ArrayList<Content>();
409         for (Content child : children) {
410             transformed.add(new ContentVersionChildWrapper(child, this));
411         }
412         return transformed;
413     }
414 
415     /**
416      * @return Boolean, if sub node(s) exists
417      */
418     @Override
419     public boolean hasChildren() {
420         return (this.getChildren().size() > 0);
421     }
422 
423     /**
424      * @param contentType JCR node type as configured
425      * @return Boolean, if sub <code>collectionType</code> exists
426      */
427     @Override
428     public boolean hasChildren(String contentType) {
429         return (this.getChildren(contentType).size() > 0);
430     }
431 
432     /**
433      * Returns the parent of the base node.
434      */
435     @Override
436     public Content getParent() throws PathNotFoundException, RepositoryException, AccessDeniedException {
437         return this.base.getParent();
438     }
439 
440     @Override
441     public Content getAncestor(int level) throws PathNotFoundException, RepositoryException, AccessDeniedException {
442         return this.base.getAncestor(level);
443     }
444 
445     /**
446      * Convenience method for taglib.
447      * @return Content representing node on level 0
448      * @throws javax.jcr.RepositoryException if an error occurs
449      */
450     @Override
451     public Collection<Content> getAncestors() throws PathNotFoundException, RepositoryException {
452         return this.base.getAncestors();
453     }
454 
455     /**
456      * Get node level from the ROOT node : FIXME implement getDepth in javax.jcr.
457      * @return level at which current node exist, relative to the ROOT node
458      * @throws javax.jcr.PathNotFoundException
459      * @throws javax.jcr.RepositoryException if an error occurs
460      */
461     @Override
462     public int getLevel() throws PathNotFoundException, RepositoryException {
463         return this.base.getLevel();
464     }
465 
466     /**
467      * Throws an {@link AccessDeniedException} as versions are read only.
468      */
469     @Override
470     public void orderBefore(String srcName, String beforeName) throws RepositoryException {
471         throw new AccessDeniedException("Not allowed to write on version preview");
472     }
473 
474     /**
475      * This method returns the index of this node within the ordered set of its same-name sibling nodes. This index is
476      * the one used to address same-name siblings using the square-bracket notation, e.g., /a[3]/b[4]. Note that the
477      * index always starts at 1 (not 0), for compatibility with XPath. As a result, for nodes that do not have
478      * same-name-siblings, this method will always return 1.
479      * @return The index of this node within the ordered set of its same-name sibling nodes.
480      * @throws javax.jcr.RepositoryException if an error occurs
481      */
482     @Override
483     public int getIndex() throws RepositoryException {
484         return this.base.getIndex();
485     }
486 
487     /**
488      * Returns primary node type definition of the associated Node of this object.
489      * @throws RepositoryException if an error occurs
490      */
491     @Override
492     public NodeType getNodeType() throws RepositoryException {
493         log.warn("This is a Version node, it will always return NT_FROZEN as node type.");
494         log.warn("Use getNodeTypeName to retrieve base node primary type");
495         return super.getNodeType();
496     }
497 
498     /**
499      * Throws an {@link AccessDeniedException} as versions are read only.
500      */
501     @Override
502     public void restore(String versionName, boolean removeExisting) throws RepositoryException {
503         throw new AccessDeniedException("Not allowed to write on version preview");
504     }
505 
506     /**
507      * Throws an {@link AccessDeniedException} as versions are read only.
508      */
509     @Override
510     public void restore(Version version, boolean removeExisting) throws RepositoryException {
511         throw new AccessDeniedException("Not allowed to write on version preview");
512     }
513 
514     /**
515      * Throws an {@link AccessDeniedException} as versions are read only.
516      */
517     @Override
518     public void restore(Version version, String relPath, boolean removeExisting) throws RepositoryException {
519         throw new AccessDeniedException("Not allowed to write on version preview");
520     }
521 
522     /**
523      * Throws an {@link AccessDeniedException} as versions are read only.
524      */
525     @Override
526     public void restoreByLabel(String versionLabel, boolean removeExisting) throws RepositoryException {
527         throw new AccessDeniedException("Not allowed to write on version preview");
528     }
529 
530     /**
531      * Throws an {@link AccessDeniedException} as versions are read only.
532      */
533     @Override
534     public Version addVersion() throws RepositoryException {
535         throw new AccessDeniedException("Not allowed to add version on version preview");
536     }
537 
538     /**
539      * Throws an {@link AccessDeniedException} as versions are read only.
540      */
541     @Override
542     public Version addVersion(Rule rule) throws RepositoryException {
543         throw new AccessDeniedException("Not allowed to add version on version preview");
544     }
545 
546     /**
547      * Returns always false as verions are read only.
548      */
549     @Override
550     public boolean isModified() {
551         log.error("Not valid for version");
552         return false;
553     }
554 
555     /**
556      * Throws an {@link AccessDeniedException} as versions are read only.
557      */
558     @Override
559     public VersionHistory getVersionHistory() throws RepositoryException {
560         throw new AccessDeniedException("Not allowed to read VersionHistory of Version");
561     }
562 
563     /**
564      * Throws an {@link AccessDeniedException} as versions are read only.
565      */
566     @Override
567     public VersionIterator getAllVersions() throws RepositoryException {
568         throw new AccessDeniedException("Not allowed to get VersionIterator of Version");
569     }
570 
571     /**
572      * Throws an {@link AccessDeniedException} as versions are read only.
573      */
574     @Override
575     public ContentVersion getBaseVersion() throws RepositoryException {
576         throw new AccessDeniedException("Not allowed to get base version of Version");
577     }
578 
579     /**
580      * Throws an {@link AccessDeniedException} as versions are read only.
581      */
582     @Override
583     public ContentVersion getVersionedContent(Version version) throws RepositoryException {
584         throw new AccessDeniedException("Not allowed to get preview of Version itself");
585     }
586 
587     /**
588      * Throws an {@link AccessDeniedException} as versions are read only.
589      */
590     @Override
591     public ContentVersion getVersionedContent(String versionName) throws RepositoryException {
592         throw new AccessDeniedException("Not allowed to get preview of Version itself");
593     }
594 
595     /**
596      * Throws an {@link AccessDeniedException} as versions are read only.
597      */
598     @Override
599     public void save() throws RepositoryException {
600         throw new AccessDeniedException("Not allowed to write on version preview");
601     }
602 
603     /**
604      * Checks for the allowed access rights.
605      * @param permissions as defined in javax.jcr.Permission
606      * @return true is the current user has specified access on this node.
607      */
608     @Override
609     public boolean isGranted(long permissions) {
610         return (permissions & Permission.READ) == permissions;
611     }
612 
613     /**
614      * Throws an {@link AccessDeniedException} as versions are read only.
615      */
616     @Override
617     public void delete() throws RepositoryException {
618         throw new AccessDeniedException("Not allowed to write on version preview");
619     }
620 
621     /**
622      * Throws an {@link AccessDeniedException} as versions are read only.
623      */
624     @Override
625     public void delete(String path) throws RepositoryException {
626         throw new AccessDeniedException("Not allowed to write on version preview");
627     }
628 
629     /**
630      * UUID of the node refrenced by this object.
631      * @return uuid
632      */
633     @Override
634     public String getUUID() {
635         return this.base.getUUID();
636     }
637 
638     /**
639      * Throws an {@link AccessDeniedException} as versions are read only.
640      */
641     @Override
642     public void addMixin(String type) throws RepositoryException {
643         throw new AccessDeniedException("Not allowed to write on version preview");
644     }
645 
646     /**
647      * Throws an {@link AccessDeniedException} as versions are read only.
648      */
649     @Override
650     public void removeMixin(String type) throws RepositoryException {
651         throw new AccessDeniedException("Not allowed to write on version preview");
652     }
653 
654     /**
655      * Throws an {@link AccessDeniedException} as versions are read only.
656      */
657     @Override
658     public Lock lock(boolean isDeep, boolean isSessionScoped) throws LockException, RepositoryException {
659         throw new AccessDeniedException("Lock not supported on version preview");
660     }
661 
662     /**
663      * Throws an {@link AccessDeniedException} as versions are read only.
664      */
665     @Override
666     public Lock lock(boolean isDeep, boolean isSessionScoped, long yieldFor) throws LockException, RepositoryException {
667         throw new AccessDeniedException("Lock not supported on version preview");
668     }
669 
670     /**
671      * Throws an {@link AccessDeniedException} as versions are read only.
672      */
673     @Override
674     public Lock getLock() throws LockException, RepositoryException {
675         throw new AccessDeniedException("Lock not supported on version preview");
676     }
677 
678     /**
679      * Throws an {@link AccessDeniedException} as versions are read only.
680      */
681     @Override
682     public void unlock() throws LockException, RepositoryException {
683         throw new AccessDeniedException("Lock not supported on version preview");
684     }
685 
686     /**
687      * Throws an {@link AccessDeniedException} as versions are read only.
688      */
689     @Override
690     public boolean holdsLock() throws RepositoryException {
691         throw new AccessDeniedException("Lock not supported on version preview");
692     }
693 
694     /**
695      * Throws an {@link AccessDeniedException} as versions are read only.
696      */
697     @Override
698     public boolean isLocked() throws RepositoryException {
699         throw new AccessDeniedException("Lock not supported on version preview");
700     }
701 
702     /**
703      * Get hierarchy manager if previously set for this object.
704      * @return HierarchyManager
705      */
706     @Override
707     public HierarchyManager getHierarchyManager() {
708         return this.base.getHierarchyManager();
709     }
710 
711     /**
712      * Get access manager if previously set for this object.
713      * @return AccessManager
714      * @deprecated use getHierarchyManager instead
715      */
716     @Deprecated
717     @Override
718     public AccessManager getAccessManager() {
719         return this.base.getAccessManager();
720     }
721 
722     @Override
723     public Workspace getWorkspace() throws RepositoryException {
724         return this.base.getWorkspace();
725     }
726 
727     @Override
728     public boolean hasNodeData(String name) throws RepositoryException {
729         if (this.node.hasProperty(name)) {
730             return true;
731         }
732         if (this.node.hasNode(name) && this.node.getNode(name).getProperty("jcr:frozenPrimaryType").getValue().getString().equals(ItemType.NT_RESOURCE)) {
733             return true;
734         }
735         return false;
736     }
737 
738     @Override
739     protected int determineNodeDataType(String name) {
740         // FIXME: maybe delegate to NodeDataImplementations?
741         try {
742             if (this.node.hasProperty(name)) {
743                 return this.node.getProperty(name).getType();
744             }
745             if (this.node.hasNode(name) && this.node.getNode(name).getProperty("jcr:frozenPrimaryType").getValue().getString().equals(ItemType.NT_RESOURCE)) {
746                 return PropertyType.BINARY;
747             }
748         }
749         catch (RepositoryException e) {
750             throw new IllegalStateException("Can't determine property type of [" + getHandle() + "/" + name + "]", e);
751         }
752         return PropertyType.UNDEFINED;
753     }
754 
755     @Override
756     public NodeType[] getMixinNodeTypes() throws RepositoryException {
757         Value[] vals = this.node.getProperty("jcr:frozenMixinTypes").getValues();
758         NodeTypeManager typeMan = getJCRNode().getSession().getWorkspace().getNodeTypeManager();
759         NodeType[] types = new NodeType[vals.length];
760         int i = 0;
761         for (Value val : vals) {
762             types[i++] = typeMan.getNodeType(val.getString());
763         }
764         return types;
765     }
766 
767     //    public List<ContentVersion> getPredecessors() throws RepositoryException {
768     //        List<ContentVersion> list = new ArrayList<ContentVersion>();
769     //        for (Version v : this.state.getPredecessors()) {
770     //            list.add(new ContentVersion(v, this.base));
771     //        }
772     //        return list;
773     //    }
774     public Version[] getPredecessors() throws RepositoryException {
775         // would prefer to return the above version (List of ContentVersions), but since our other APIs return jcr Version as well ...
776         return this.state.getPredecessors();
777     }
778 
779     @Override
780     public Node getJCRNode() {
781         // we seem to build content version from 2 different types of nodes - from Version and from jcr:frozenNode
782         try {
783             if (versionedNode.hasNode("jcr:frozenNode")) {
784                 return versionedNode.getFrozenNode();
785             }
786         } catch (RepositoryException e) {
787             log.error("Failed to retrieve frozen node from version " + versionedNode, e);
788         }
789 
790         return versionedNode;
791     }
792 }