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