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.Content;
37  import info.magnolia.cms.security.JCRSessionOp;
38  import info.magnolia.cms.security.PermissionUtil;
39  import info.magnolia.cms.util.Rule;
40  import info.magnolia.context.MgnlContext;
41  import info.magnolia.context.SystemContext;
42  import info.magnolia.jcr.predicate.RuleBasedNodePredicate;
43  import info.magnolia.jcr.util.NodeTypes;
44  import info.magnolia.jcr.util.NodeUtil;
45  import info.magnolia.jcr.util.VersionUtil;
46  import info.magnolia.repository.RepositoryConstants;
47  import info.magnolia.repository.RepositoryManager;
48  import info.magnolia.repository.definition.RepositoryDefinition;
49  
50  import java.io.ByteArrayInputStream;
51  import java.io.ByteArrayOutputStream;
52  import java.io.IOException;
53  import java.io.InvalidClassException;
54  import java.io.ObjectInput;
55  import java.io.ObjectInputStream;
56  import java.io.ObjectOutput;
57  import java.io.ObjectOutputStream;
58  import java.util.ArrayList;
59  import java.util.Collection;
60  import java.util.Iterator;
61  import java.util.List;
62  
63  import javax.inject.Inject;
64  import javax.jcr.ItemNotFoundException;
65  import javax.jcr.Node;
66  import javax.jcr.NodeIterator;
67  import javax.jcr.PathNotFoundException;
68  import javax.jcr.RepositoryException;
69  import javax.jcr.Session;
70  import javax.jcr.UnsupportedRepositoryOperationException;
71  import javax.jcr.Value;
72  import javax.jcr.nodetype.NodeType;
73  import javax.jcr.version.Version;
74  import javax.jcr.version.VersionHistory;
75  import javax.jcr.version.VersionIterator;
76  import javax.jcr.version.VersionManager;
77  
78  import org.apache.commons.codec.binary.Base64;
79  import org.apache.commons.io.IOUtils;
80  import org.apache.jackrabbit.JcrConstants;
81  import org.slf4j.Logger;
82  import org.slf4j.LoggerFactory;
83  
84  /**
85   * This version manager uses an extra workspace to manage the versions. The
86   * workspace maintains a flat hierarchy. The content is then finally versioned
87   * using JCR versioning which also copies the sub-nodes.
88   *
89   * The mix:versionable is only added on the top level nodes.
90   */
91  public abstract class BaseVersionManager {
92  
93      private static final Logger log = LoggerFactory.getLogger(BaseVersionManager.class);
94  
95      /**
96       * Name of the workspace.
97       */
98      public static final String VERSION_WORKSPACE = "mgnlVersion";
99  
100     /**
101      * Node which contains stubs for referenced nodes. We have to copy them to the workspace as well.
102      */
103     public static final String TMP_REFERENCED_NODES = "mgnl:tmpReferencedNodes";
104 
105     /**
106      * Sub-node containing the data used for the version/restore process.
107      */
108     public static final String SYSTEM_NODE = "mgnl:versionMetaData";
109 
110     /**
111      * Property name for collection rule. The rule defines which sub-nodes belong to a node: page and paragraphs.
112      */
113     public static final String PROPERTY_RULE = "Rule";
114 
115     /**
116      * JCR version store root.
117      */
118     protected static final String ROOT_VERSION = "jcr:rootVersion";
119 
120     /**
121      * Workspace of the source node.
122      */
123     public static final String SOURCE_WORKSPACE = "sourceWorkspace";
124 
125     private Collection<String> versionWorkspaces = new ArrayList<>();
126 
127     private final SystemContext systemContext;
128     private final RepositoryManager repositoryManager;
129     private final CopyUtil copyUtil;
130 
131     @Inject
132     public BaseVersionManager(SystemContext systemContext, RepositoryManager repositoryManager, CopyUtil copyUtil) {
133         this.systemContext = systemContext;
134         this.repositoryManager = repositoryManager;
135         this.copyUtil = copyUtil;
136         for (RepositoryDefinition repositoryDefinition : repositoryManager.getRepositoryDefinitions()) {
137             String repositoryId = repositoryDefinition.getName();
138             String workspaceName = repositoryId + "-" + RepositoryConstants.VERSION_STORE;
139             if (repositoryManager.getWorkspaceMapping(workspaceName) != null) {
140                 versionWorkspaces.add(workspaceName);
141             } else {
142                 throw new RuntimeException(String.format("Something went wrong, version workspace for repository %s does not exist.", repositoryId));
143             }
144         }
145     }
146 
147     /**
148      * Create structure needed for version store workspace.
149      *
150      * @throws RepositoryException if unable to create magnolia system structure
151      */
152     protected void createInitialStructure() throws RepositoryException {
153         for (String workspaceName : versionWorkspaces) {
154             Session session = systemContext.getJCRSession(workspaceName);
155             try {
156                 Node tmp = session.getNode("/" + TMP_REFERENCED_NODES);
157                 // remove nodes if they are no longer referenced within this workspace
158                 NodeIterator children = tmp.getNodes();
159                 while (children.hasNext()) {
160                     Node child = children.nextNode();
161                     if (child.getReferences().getSize() < 1) {
162                         child.remove();
163                     }
164                 }
165             } catch (PathNotFoundException e) {
166                 session.getRootNode().addNode(TMP_REFERENCED_NODES, NodeTypes.System.NAME);
167             }
168             session.save();
169         }
170     }
171 
172     /**
173      * Add version of the specified node and all child nodes while ignoring the same node type.
174      */
175     public synchronized Version addVersion(Node node) throws RepositoryException {
176         Rule rule = new Rule(VersionUtil.getNodeTypeName(node) + "," + NodeTypes.System.NAME, ",");
177         rule.reverse();
178         return this.addVersion(node, rule);
179     }
180 
181     /**
182      * Add version of the specified node and all child nodes based on the given <code>Rule</code>. Reads the <code>userName</code>
183      * from the current context.
184      */
185     public synchronized Version addVersion(final Node node, final Rule rule) throws RepositoryException {
186         final String userName = getSafelyUserNameFromMgnlContext();
187         return addVersion(node, rule, userName);
188     }
189 
190     public synchronized Version addVersion(final Node node, final Rule rule, final String userName) throws RepositoryException {
191         return addVersion(node, rule, userName, null);
192     }
193 
194     /**
195      * Add version of the specified node and all child nodes based on the given <code>Rule</code> and <code>userName</code>.
196      */
197     public synchronized Version addVersion(final Node node, final Rule rule, final String userName, final String comment) throws RepositoryException {
198         final String workspaceName = node.getSession().getWorkspace().getName();
199         Version version = MgnlContext.doInSystemContext(new JCRSessionOp<Version>(workspaceName) {
200 
201             @Override
202             public Version exec(Session session) throws RepositoryException {
203                 try {
204                     return createVersion(session.getNodeByIdentifier(node.getIdentifier()), rule, userName, comment);
205                 } catch (RepositoryException re) {
206                     // since add version is synchronized on a singleton object, its safe to revert all changes made in
207                     // the session attached to workspace - mgnlVersion
208                     log.error("failed to copy versionable node to version store, reverting all changes made in this session", re);
209                     String versionWorkspaceName = VersionUtil.getVersionWorkspaceForNode(repositoryManager, node);
210                     // We're in SystemContext
211                     MgnlContext.getJCRSession(versionWorkspaceName).refresh(false);
212                     throw re;
213                 }
214 
215             }
216         });
217         return version;
218     }
219 
220     private String getSafelyUserNameFromMgnlContext() {
221         String userName = "";
222         if (MgnlContext.getUser() != null) {
223             userName = MgnlContext.getUser().getName();
224         }
225         return userName;
226     }
227 
228     /**
229      * Create version of the specified node and all child nodes based on the given <code>Rule</code>.
230      * This will return null if can't create new version in case when max version index is lower then one
231      * or when content is marked for deletion.
232      *
233      * @param node to be versioned
234      * @return newly created version node
235      * @throws UnsupportedOperationException if repository implementation does not support Versions API
236      * @throws javax.jcr.RepositoryException if any repository error occurs
237      */
238     protected Version createVersion(Node node, Rule rule, String userName) throws RepositoryException {
239         return createVersion(node, rule, userName, "");
240     }
241 
242     private Version createVersion(Node node, Rule rule, String userName, String comment) throws RepositoryException {
243         if (isInvalidMaxVersions()) {
244             log.debug("Ignore create version, MaxVersionIndex < 1");
245             return null;
246         }
247         if (node.isNodeType(NodeTypes.Deleted.NAME)) {
248             log.debug("Don't create version for content marked as deleted");
249             return null;
250         }
251         if (!node.isNodeType(NodeTypes.HasVersion.NAME)) {
252             node.addMixin(NodeTypes.HasVersion.NAME); //in case of restoring first created version the mixin has to be present
253             node.getSession().save();
254         }
255         copyUtil.copyToVersion(node, new RuleBasedNodePredicate(rule));
256         Node versionedNode = this.getVersionedNode(node);
257         versionedNode.setProperty(NodeTypes.Versionable.COMMENT, comment);
258 
259         checkAndAddMixin(versionedNode);
260         Node systemInfo = this.getSystemNode(versionedNode);
261         // add serialized rule which was used to create this version
262         ByteArrayOutputStream out = new ByteArrayOutputStream();
263         try {
264             ObjectOutput objectOut = new ObjectOutputStream(out);
265             objectOut.writeObject(rule);
266             objectOut.flush();
267             objectOut.close();
268             // PROPERTY_RULE is not a part of MetaData to allow versioning of node types which does NOT support MetaData
269             systemInfo.setProperty(PROPERTY_RULE, new String(Base64.encodeBase64(out.toByteArray())));
270         } catch (IOException e) {
271             throw new RepositoryException("Unable to add serialized Rule to the versioned content");
272         }
273         // add all system properties for this version
274         systemInfo.setProperty(ContentVersion.VERSION_USER, userName);
275         systemInfo.setProperty(ContentVersion.NAME, node.getName());
276         systemInfo.setProperty(SOURCE_WORKSPACE, node.getSession().getWorkspace().getName());
277 
278         versionedNode.save();
279 
280         // add version
281         VersionManager versionManager = versionedNode.getSession().getWorkspace().getVersionManager();
282         Version newVersion = versionManager.checkin(versionedNode.getPath());
283         versionManager.checkout(versionedNode.getPath());
284 
285         try {
286             this.setMaxVersionHistory(versionedNode);
287         } catch (RepositoryException re) {
288             log.error("Failed to limit version history to the maximum configured", re);
289             log.error("New version has already been created");
290         }
291 
292 
293         return newVersion;
294     }
295 
296     /**
297      * Check if max version index is lower then one.
298      */
299     public abstract boolean isInvalidMaxVersions();
300 
301     /**
302      * Get node from version store.
303      */
304     public synchronized Node getVersionedNode(Node node) throws RepositoryException {
305         try {
306             final String versionWorkspace = VersionUtil.getVersionWorkspaceForNode(repositoryManager, node);
307             final Session versionSession = new MgnlVersionSessionDecorator(node.getSession().getWorkspace().getName()).wrapSession(systemContext.getJCRSession(versionWorkspace));
308             return versionSession.getNodeByIdentifier(node.getIdentifier());
309         } catch (ItemNotFoundException e) {
310             // node is not versioned yet
311             return null;
312         }
313     }
314 
315     /**
316      * Get node from version store.
317      */
318     protected Node getVersionedNode(Session session, String uuid) throws RepositoryException {
319         try {
320             return session.getNodeByIdentifier(uuid);
321         } catch (ItemNotFoundException e) {
322             // node is not versioned yet
323             return null;
324         }
325     }
326 
327     /**
328      * Set version history to max version possible.
329      *
330      * @throws RepositoryException if failed to get VersionHistory or fail to remove
331      */
332     public abstract void setMaxVersionHistory(Node node) throws RepositoryException;
333 
334     /**
335      * Get history of this node as recorded in the version store.
336      *
337      * @return version history of the given node
338      * @throws UnsupportedOperationException if repository implementation does not support Versions API
339      * @throws javax.jcr.RepositoryException if any repository error occurs
340      */
341     public synchronized VersionHistory getVersionHistory(Node node) throws RepositoryException {
342         try {
343             Node versionedNode = this.getVersionedNode(node);
344             if (versionedNode == null) {
345                 // node does not exist in version store so no version history
346                 log.debug("No VersionHistory found for {} node.", node);
347                 return null;
348             }
349             VersionHistory versionHistory = versionedNode.getVersionHistory();
350             VersionIterator versionIterator = versionHistory.getAllVersions();
351             long filteredSize = versionIterator.getSize();
352             long unfilteredSize = ((MgnlVersionSessionDecorator.MgnlVersionSessionVersionIteratorWrapper) versionIterator).getUnfilteredSize();
353             // we have just root version for the passed node, but there are other versions from one or more workspaces
354             // so to preserve expected behavior, return null
355             if (filteredSize == 1 && unfilteredSize > filteredSize) {
356                 return null;
357             }
358             return versionHistory;
359         } catch (UnsupportedRepositoryOperationException e) {
360             log.debug("Node {} is not versionable.", node);
361             // node is not versionable or underlying repo doesn't support versioning.
362             return null;
363         }
364     }
365 
366     /**
367      * Get named version.
368      *
369      * @throws UnsupportedOperationException if repository implementation does not support Versions API
370      * @throws javax.jcr.RepositoryException if any repository error occurs
371      */
372     public synchronized Version getVersion(Node node, String name) throws RepositoryException {
373         VersionHistory history = this.getVersionHistory(node);
374         if (history != null) {
375             return new VersionedNode(history.getVersion(name), node);
376         }
377         log.error("Node {} was never versioned", node.getPath());
378         return null;
379     }
380 
381     /**
382      * Returns the current base version of given node.
383      */
384     public Version getBaseVersion(Node node) throws UnsupportedOperationException, RepositoryException {
385         VersionIterator versionIterator = getAllVersions(node);
386         Version baseVersion = null;
387         if (versionIterator != null) {
388             while (versionIterator.hasNext()) {
389                 Version version = versionIterator.nextVersion();
390                 if (baseVersion == null) {
391                     baseVersion = version;
392                 } else if (baseVersion.getCreated().compareTo(version.getCreated()) < 0) {
393                     baseVersion = version;
394                 }
395             }
396         }
397         if (baseVersion == null) {
398             throw new RepositoryException("Node " + node.getPath() + " was never versioned");
399         }
400         return baseVersion;
401     }
402 
403     /**
404      * Get all versions.
405      *
406      * @return Version iterator retrieved from version history
407      * @throws UnsupportedOperationException if repository implementation does not support Versions API
408      * @throws javax.jcr.RepositoryException if any repository error occurs
409      */
410     public synchronized VersionIterator getAllVersions(Node node) throws RepositoryException {
411         Node versionedNode = this.getVersionedNode(node);
412         if (versionedNode == null) {
413             // node does not exist in version store so no versions
414             return null;
415         }
416         VersionHistory versionHistory = getVersionHistory(node);
417         if (versionHistory == null) {
418             // does not have any versions in version store
419             return null;
420         }
421         return versionHistory.getAllVersions();
422     }
423 
424     /**
425      * Restore specified version.
426      *
427      * @param node to be restored
428      * @param version to be used
429      * @throws javax.jcr.version.VersionException if the specified <code>versionName</code> does not exist in this
430      * node's version history
431      * @throws javax.jcr.RepositoryException if an error occurs
432      * @deprecated since 4.5 use {@link #restore(Node, Version, boolean)} instead
433      */
434     @Deprecated
435     public synchronized void restore(Content node, Version version, boolean removeExisting) throws RepositoryException {
436         restore(node.getJCRNode(), version, removeExisting);
437     }
438 
439     /**
440      * Restore specified version.
441      *
442      * @param node to be restored
443      * @param version to be used
444      * @throws javax.jcr.version.VersionException if the specified <code>versionName</code> does not exist in this
445      * node's version history
446      * @throws javax.jcr.RepositoryException if an error occurs
447      */
448     public synchronized void restore(final Node node, Version version, boolean removeExisting) throws RepositoryException {
449         // FYI: restore is done in SC!!! Check permissions manually
450         PermissionUtil.verifyIsGrantedOrThrowException(node.getSession(), node.getPath(), Session.ACTION_SET_PROPERTY + "," + Session.ACTION_REMOVE + "," + Session.ACTION_ADD_NODE);
451 
452         // get the cloned node from version store
453         final Node versionedNode = this.getVersionedNode(node);
454 
455         final Version unwrappedVersion;
456         if (version instanceof VersionedNode) {
457             unwrappedVersion = VersionUtil.unwrap(((VersionedNode) version).unwrap());
458         } else {
459             unwrappedVersion = VersionUtil.unwrap(version);
460         }
461 
462         final String lastActivatedVersion = NodeTypes.Activatable.getLastActivatedVersion(node);
463 
464         VersionManager versionManager = versionedNode.getSession().getWorkspace().getVersionManager();
465         versionManager.restore(unwrappedVersion, removeExisting);
466         versionManager.checkout(versionedNode.getPath());
467         MgnlContext.doInSystemContext(new JCRSessionOp<Void>(versionedNode.getSession().getWorkspace().getName()) {
468 
469             @Override
470             public Void exec(Session session) throws RepositoryException {
471                 //mixins are NOT restored automatically
472                 List<String> mixins = new ArrayList<>();
473                 for (Value v : unwrappedVersion.getNode(JcrConstants.JCR_FROZENNODE).getProperty("jcr:frozenMixinTypes").getValues()) {
474                     mixins.add(v.getString());
475                 }
476 
477                 final Node systemVersionedNode = session.getNodeByIdentifier(versionedNode.getUUID());
478                 for (NodeType nt : versionedNode.getMixinNodeTypes()) {
479                     if (!mixins.remove(nt.getName())) {
480                         systemVersionedNode.removeMixin(nt.getName());
481                     }
482                 }
483                 for (String mix : mixins) {
484                     systemVersionedNode.addMixin(mix);
485                 }
486                 systemVersionedNode.save();
487 
488                 try {
489                     // using system context here is forced by the fact that JR will use same context to also check source (mgnlVersion) repo READ permission and ordinary user has no rights in this workspace
490                     Session sysDestinationSession = MgnlContext.getJCRSession(node.getSession().getWorkspace().getName());
491                     log.debug("restoring info:{}:{}", sysDestinationSession.getWorkspace().getName(), node.getPath());
492                     Node destinationNode = sysDestinationSession.getNode(node.getPath());
493                     // if restored, update original node with the restored node and its subtree
494                     Rule rule = getUsedFilter(versionedNode);
495                     try {
496                         copyUtil.copyFromVersion(versionedNode, destinationNode, new RuleBasedNodePredicate(rule));
497                         if (NodeUtil.hasMixin(destinationNode, NodeTypes.Deleted.NAME)) {
498                             destinationNode.removeMixin(NodeTypes.Deleted.NAME);
499                         }
500                         if (lastActivatedVersion != null) {
501                             // we need to keep last activated version and don't override it with old value from version
502                             destinationNode.setProperty(NodeTypes.Activatable.LAST_ACTIVATED_VERSION, lastActivatedVersion);
503                         }
504                         // next line is required because restore is done via clone op that preserves last modification date otherwise.
505                         NodeTypes.LastModified.update(destinationNode);
506                         destinationNode.save();
507                         // node was updated in system context and we should make sure it is notified of the changes
508                         node.refresh(false);
509                     } catch (RepositoryException re) {
510                         log.debug("error during restore: {}", re.getMessage(), re);
511                         log.error("failed to restore versioned node, reverting all changes make to this node");
512                         destinationNode.refresh(false);
513                         throw re;
514                     }
515                 } catch (IOException e) {
516                     throw new RepositoryException(e);
517                 } catch (ClassNotFoundException e) {
518                     throw new RepositoryException(e);
519                 }
520                 return null;
521             }
522         });
523     }
524 
525     /**
526      * Removes all versions of the node associated with given UUID.
527      *
528      * @throws RepositoryException if fails to remove versioned node from the version store
529      */
530     public synchronized void removeVersionHistory(final Node node) throws RepositoryException {
531         final String nodePath = node.getPath();
532         final Session session = node.getSession();
533         final String workspaceName = session.getWorkspace().getName();
534         log.debug("removing {} from {}", nodePath, workspaceName);
535         PermissionUtil.verifyIsGrantedOrThrowException(session, nodePath, Session.ACTION_REMOVE);
536 
537         if (!(workspaceName.endsWith("-" + RepositoryConstants.VERSION_STORE) || workspaceName.endsWith("-" + RepositoryConstants.SYSTEM))) {
538             VersionHistory versionHistory = getVersionHistory(node);
539             if (versionHistory != null) {
540                 VersionIterator versionIterator = versionHistory.getAllVersions();
541                 long filteredSize = versionIterator.getSize();
542                 long unfilteredSize = ((MgnlVersionSessionDecorator.MgnlVersionSessionVersionIteratorWrapper) versionIterator).getUnfilteredSize();
543                 if (filteredSize == unfilteredSize) {
544                     Node versioned = getVersionedNode(node);
545                     versioned.remove();
546                     versioned.getSession().save();
547                 } else {
548                     Iterator<Version> iterator = ((MgnlVersionSessionDecorator.MgnlVersionSessionVersionIteratorWrapper) versionIterator).getAllVersionsUnfiltered();
549                     while (iterator.hasNext()) {
550                         Version version = iterator.next();
551                         if (version.getFrozenNode().hasNode(SYSTEM_NODE) && version.getFrozenNode().getNode(SYSTEM_NODE).hasProperty(SOURCE_WORKSPACE)) {
552                             String sourceWorkspace = version.getFrozenNode().getNode(SYSTEM_NODE).getProperty(SOURCE_WORKSPACE).getString();
553                             if (!sourceWorkspace.equals(node.getSession().getWorkspace().getName())) {
554                                 Node nodeToRestore = systemContext.getJCRSession(sourceWorkspace).getNodeByIdentifier(node.getIdentifier());
555                                 Version base = getBaseVersion(nodeToRestore);
556                                 try {
557                                     nodeToRestore.setProperty(NodeTypes.Versionable.COMMENT, "Created by system in order to release base version.");
558                                     nodeToRestore.getSession().save();
559                                     addVersion(nodeToRestore, getUsedFilter(getVersionedNode(base.getFrozenNode())));
560                                 } catch (IOException | ClassNotFoundException e) {
561                                     throw new RepositoryException(e);
562                                 }
563                                 break;
564                             }
565                         }
566                     }
567                 }
568                 versionIterator.nextVersion(); // skip root version
569                 while (versionIterator.hasNext()) {
570                     Version version = versionIterator.nextVersion();
571                     if (version.getName().equals(JcrConstants.JCR_ROOTVERSION)) {
572                         continue;
573                     }
574                     versionHistory.removeVersion(version.getName());
575                 }
576             }
577             session.save();
578         }
579     }
580 
581     public boolean hasVersion(Node node) throws RepositoryException {
582         return NodeUtil.hasMixin(node, NodeTypes.HasVersion.NAME);
583     }
584 
585     /**
586      * Verifies the existence of the mix:versionable and adds it if not.
587      */
588     protected void checkAndAddMixin(Node node) throws RepositoryException {
589         if (!node.isNodeType("mix:versionable")) {
590             log.debug("Add mix:versionable");
591             node.addMixin("mix:versionable");
592         }
593     }
594 
595     /**
596      * Get Rule used for this version.
597      */
598     protected Rule getUsedFilter(Node versionedNode) throws IOException, ClassNotFoundException, RepositoryException {
599         // if restored, update original node with the restored node and its subtree
600         ByteArrayInputStream inStream = null;
601         try {
602             String ruleString = this.getSystemNode(versionedNode).getProperty(PROPERTY_RULE).getString();
603             inStream = new ByteArrayInputStream(Base64.decodeBase64(ruleString.getBytes()));
604             ObjectInput objectInput = new ObjectInputStream(inStream);
605             return (Rule) objectInput.readObject();
606         } catch (InvalidClassException e) {
607             log.debug(e.getMessage());
608             log.debug("Will return default rule -> all child nodes while ignoring versionedNode nodeType.");
609             Rule rule = new Rule(VersionUtil.getNodeTypeName(versionedNode) + "," + NodeTypes.System.NAME, ",");
610             rule.reverse();
611             return rule;
612         } catch (IOException e) {
613             throw e;
614         } catch (ClassNotFoundException e) {
615             throw e;
616         } finally {
617             IOUtils.closeQuietly(inStream);
618         }
619     }
620 
621     /**
622      * Get the Magnolia system node created under the given node.
623      *
624      * @throws RepositoryException if failed to create system node
625      */
626     protected synchronized Node getSystemNode(Node node) throws RepositoryException {
627         if (node.hasNode(SYSTEM_NODE)) {
628             return node.getNode(SYSTEM_NODE);
629         }
630         return node.addNode(SYSTEM_NODE, NodeTypes.System.NAME);
631     }
632 }