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