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