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.security.JCRSessionOp;
37  import info.magnolia.cms.security.PermissionUtil;
38  import info.magnolia.cms.util.Rule;
39  import info.magnolia.context.MgnlContext;
40  import info.magnolia.context.SystemContext;
41  import info.magnolia.jcr.predicate.RuleBasedNodePredicate;
42  import info.magnolia.jcr.util.NodeTypes;
43  import info.magnolia.jcr.util.NodeUtil;
44  import info.magnolia.jcr.util.VersionUtil;
45  import info.magnolia.repository.RepositoryConstants;
46  import info.magnolia.repository.RepositoryManager;
47  import info.magnolia.repository.definition.RepositoryDefinition;
48  
49  import java.io.ByteArrayInputStream;
50  import java.io.ByteArrayOutputStream;
51  import java.io.IOException;
52  import java.io.InvalidClassException;
53  import java.io.ObjectInput;
54  import java.io.ObjectInputStream;
55  import java.io.ObjectOutput;
56  import java.io.ObjectOutputStream;
57  import java.util.ArrayList;
58  import java.util.Calendar;
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      * Name of the base node.
102      */
103     public static final String NAME = "name";
104 
105     /**
106      * User who created this version.
107      */
108     public static final String VERSION_USER = "versionUser";
109 
110     /**
111      * Node which contains stubs for referenced nodes. We have to copy them to the workspace as well.
112      */
113     public static final String TMP_REFERENCED_NODES = "mgnl:tmpReferencedNodes";
114 
115     /**
116      * Sub-node containing the data used for the version/restore process.
117      */
118     public static final String SYSTEM_NODE = "mgnl:versionMetaData";
119 
120     /**
121      * Property name for collection rule. The rule defines which sub-nodes belong to a node: page and paragraphs.
122      */
123     public static final String PROPERTY_RULE = "Rule";
124 
125     /**
126      * JCR version store root.
127      */
128     protected static final String ROOT_VERSION = "jcr:rootVersion";
129 
130     /**
131      * Workspace of the source node.
132      */
133     public static final String SOURCE_WORKSPACE = "sourceWorkspace";
134 
135     private Collection<String> versionWorkspaces = new ArrayList<>();
136 
137     private final SystemContext systemContext;
138     private final RepositoryManager repositoryManager;
139     private final CopyUtil copyUtil;
140 
141     @Inject
142     public BaseVersionManager(SystemContext systemContext, RepositoryManager repositoryManager, CopyUtil copyUtil) {
143         this.systemContext = systemContext;
144         this.repositoryManager = repositoryManager;
145         this.copyUtil = copyUtil;
146         for (RepositoryDefinition repositoryDefinition : repositoryManager.getRepositoryDefinitions()) {
147             String repositoryId = repositoryDefinition.getName();
148             String workspaceName = repositoryId + "-" + RepositoryConstants.VERSION_STORE;
149             if (repositoryManager.getWorkspaceMapping(workspaceName) != null) {
150                 versionWorkspaces.add(workspaceName);
151             } else {
152                 throw new RuntimeException(String.format("Something went wrong, version workspace for repository %s does not exist.", repositoryId));
153             }
154         }
155     }
156 
157     /**
158      * Create structure needed for version store workspace.
159      *
160      * @throws RepositoryException if unable to create magnolia system structure
161      */
162     protected void createInitialStructure() throws RepositoryException {
163         for (String workspaceName : versionWorkspaces) {
164             Session session = systemContext.getJCRSession(workspaceName);
165             try {
166                 Node tmp = session.getNode("/" + TMP_REFERENCED_NODES);
167                 // remove nodes if they are no longer referenced within this workspace
168                 NodeIterator children = tmp.getNodes();
169                 while (children.hasNext()) {
170                     Node child = children.nextNode();
171                     if (child.getReferences().getSize() < 1) {
172                         child.remove();
173                     }
174                 }
175             } catch (PathNotFoundException e) {
176                 session.getRootNode().addNode(TMP_REFERENCED_NODES, NodeTypes.System.NAME);
177             }
178             session.save();
179         }
180     }
181 
182     /**
183      * Add version of the specified node and all child nodes while ignoring the same node type.
184      */
185     public synchronized Version addVersion(Node node) throws RepositoryException {
186         Rulel/Rule.html#Rule">Rule rule = new Rule(VersionUtil.getNodeTypeName(node) + "," + NodeTypes.System.NAME, ",");
187         rule.reverse();
188         return this.addVersion(node, rule);
189     }
190 
191     /**
192      * Add version of the specified node and all child nodes based on the given <code>Rule</code>. Reads the <code>userName</code>
193      * from the current context.
194      */
195     public synchronized Version addVersion(final Node node, final Rule rule) throws RepositoryException {
196         final String userName = getSafelyUserNameFromMgnlContext();
197         return addVersion(node, rule, userName);
198     }
199 
200     public synchronized Version addVersion(final Node node, final Rule rule, final String userName) throws RepositoryException {
201         return addVersion(node, rule, userName, null);
202     }
203 
204     /**
205      * Add version of the specified node and all child nodes based on the given <code>Rule</code> and <code>userName</code>.
206      */
207     public synchronized Version addVersion(final Node node, final Rule rule, final String userName, final String comment) throws RepositoryException {
208         final String workspaceName = node.getSession().getWorkspace().getName();
209         Version version = MgnlContext.doInSystemContext(new JCRSessionOp<Version>(workspaceName) {
210 
211             @Override
212             public Version exec(Session session) throws RepositoryException {
213                 try {
214                     return createVersion(session.getNodeByIdentifier(node.getIdentifier()), rule, userName, comment);
215                 } catch (RepositoryException re) {
216                     // since add version is synchronized on a singleton object, its safe to revert all changes made in
217                     // the session attached to workspace - mgnlVersion
218                     log.error("failed to copy versionable node to version store, reverting all changes made in this session", re);
219                     String versionWorkspaceName = VersionUtil.getVersionWorkspaceForNode(repositoryManager, node);
220                     // We're in SystemContext
221                     MgnlContext.getJCRSession(versionWorkspaceName).refresh(false);
222                     throw re;
223                 }
224 
225             }
226         });
227         return version;
228     }
229 
230     private String getSafelyUserNameFromMgnlContext() {
231         String userName = "";
232         if (MgnlContext.getUser() != null) {
233             userName = MgnlContext.getUser().getName();
234         }
235         return userName;
236     }
237 
238     /**
239      * Create version of the specified node and all child nodes based on the given <code>Rule</code>.
240      * This will return null if can't create new version in case when max version index is lower then one
241      * or when content is marked for deletion.
242      *
243      * @param node to be versioned
244      * @return newly created version node
245      * @throws UnsupportedOperationException if repository implementation does not support Versions API
246      * @throws javax.jcr.RepositoryException if any repository error occurs
247      */
248     protected Version createVersion(Node node, Rule rule, String userName) throws RepositoryException {
249         return createVersion(node, rule, userName, "");
250     }
251 
252     private Version createVersion(Node node, Rule rule, String userName, String comment) throws RepositoryException {
253         if (isInvalidMaxVersions()) {
254             log.debug("Ignore create version, MaxVersionIndex < 1");
255             return null;
256         }
257         if (node.isNodeType(NodeTypes.Deleted.NAME)) {
258             log.debug("Don't create version for content marked as deleted");
259             return null;
260         }
261         if (!node.isNodeType(NodeTypes.HasVersion.NAME)) {
262             node.addMixin(NodeTypes.HasVersion.NAME); //in case of restoring first created version the mixin has to be present
263             node.getSession().save();
264         }
265         copyUtil.copyToVersion(node, new RuleBasedNodePredicate(rule));
266         Node versionedNode = this.getVersionedNode(node);
267         versionedNode.setProperty(NodeTypes.Versionable.COMMENT, comment);
268 
269         checkAndAddMixin(versionedNode);
270         Node systemInfo = this.getSystemNode(versionedNode);
271         // add serialized rule which was used to create this version
272         ByteArrayOutputStream out = new ByteArrayOutputStream();
273         try {
274             ObjectOutput objectOut = new ObjectOutputStream(out);
275             objectOut.writeObject(rule);
276             objectOut.flush();
277             objectOut.close();
278             // PROPERTY_RULE is not a part of MetaData to allow versioning of node types which does NOT support MetaData
279             systemInfo.setProperty(PROPERTY_RULE, new String(Base64.encodeBase64(out.toByteArray())));
280         } catch (IOException e) {
281             throw new RepositoryException("Unable to add serialized Rule to the versioned content");
282         }
283         // add all system properties for this version
284         systemInfo.setProperty(VERSION_USER, userName);
285         systemInfo.setProperty(NAME, node.getName());
286         systemInfo.setProperty(SOURCE_WORKSPACE, node.getSession().getWorkspace().getName());
287 
288         versionedNode.getSession().save();
289 
290         // add version
291         VersionManager versionManager = versionedNode.getSession().getWorkspace().getVersionManager();
292         Version newVersion = versionManager.checkin(versionedNode.getPath());
293         versionManager.checkout(versionedNode.getPath());
294 
295         try {
296             this.setMaxVersionHistory(versionedNode);
297         } catch (RepositoryException re) {
298             log.error("Failed to limit version history to the maximum configured", re);
299             log.error("New version has already been created");
300         }
301 
302 
303         return newVersion;
304     }
305 
306     /**
307      * Check if max version index is lower then one.
308      */
309     public abstract boolean isInvalidMaxVersions();
310 
311     /**
312      * Get node from version store.
313      */
314     public synchronized Node getVersionedNode(Node node) throws RepositoryException {
315         try {
316             final String versionWorkspace = VersionUtil.getVersionWorkspaceForNode(repositoryManager, node);
317             final Session versionSession = new MgnlVersionSessionDecorator(node.getSession().getWorkspace().getName()).wrapSession(systemContext.getJCRSession(versionWorkspace));
318             return versionSession.getNodeByIdentifier(node.getIdentifier());
319         } catch (ItemNotFoundException e) {
320             // node is not versioned yet
321             return null;
322         }
323     }
324 
325     /**
326      * Get node from version store.
327      */
328     protected Node getVersionedNode(Session session, String uuid) throws RepositoryException {
329         try {
330             return session.getNodeByIdentifier(uuid);
331         } catch (ItemNotFoundException e) {
332             // node is not versioned yet
333             return null;
334         }
335     }
336 
337     /**
338      * Set version history to max version possible.
339      *
340      * @throws RepositoryException if failed to get VersionHistory or fail to remove
341      */
342     public abstract void setMaxVersionHistory(Node node) throws RepositoryException;
343 
344     /**
345      * Get history of this node as recorded in the version store.
346      *
347      * @return version history of the given node
348      * @throws UnsupportedOperationException if repository implementation does not support Versions API
349      * @throws javax.jcr.RepositoryException if any repository error occurs
350      */
351     public synchronized VersionHistory getVersionHistory(Node node) throws RepositoryException {
352         try {
353             Node versionedNode = this.getVersionedNode(node);
354             if (versionedNode == null) {
355                 // node does not exist in version store so no version history
356                 log.debug("No VersionHistory found for {} node.", node);
357                 return null;
358             }
359             VersionHistory versionHistory = versionedNode.getVersionHistory();
360             VersionIterator versionIterator = versionHistory.getAllVersions();
361             long filteredSize = versionIterator.getSize();
362             long unfilteredSize = ((MgnlVersionSessionDecorator.MgnlVersionSessionVersionIteratorWrapper) versionIterator).getUnfilteredSize();
363             // we have just root version for the passed node, but there are other versions from one or more workspaces
364             // so to preserve expected behavior, return null
365             if (filteredSize == 1 && unfilteredSize > filteredSize) {
366                 return null;
367             }
368             return versionHistory;
369         } catch (UnsupportedRepositoryOperationException e) {
370             log.debug("Node {} is not versionable.", node);
371             // node is not versionable or underlying repo doesn't support versioning.
372             return null;
373         }
374     }
375 
376     /**
377      * Get named version.
378      *
379      * @throws UnsupportedOperationException if repository implementation does not support Versions API
380      * @throws javax.jcr.RepositoryException if any repository error occurs
381      */
382     public synchronized Version getVersion(Node node, String name) throws RepositoryException {
383         VersionHistory history = this.getVersionHistory(node);
384         if (history != null) {
385             return new VersionedNode(history.getVersion(name), node);
386         }
387         log.error("Node {} was never versioned", node.getPath());
388         return null;
389     }
390 
391     /**
392      * Returns the current base version of given node.
393      */
394     public Version getBaseVersion(Node node) throws UnsupportedOperationException, RepositoryException {
395         VersionIterator versionIterator = getAllVersions(node);
396         Version baseVersion = null;
397         if (versionIterator != null) {
398             while (versionIterator.hasNext()) {
399                 Version version = versionIterator.nextVersion();
400                 if (baseVersion == null) {
401                     baseVersion = version;
402                 } else if (baseVersion.getCreated().compareTo(version.getCreated()) < 0) {
403                     baseVersion = version;
404                 }
405             }
406         }
407         if (baseVersion == null) {
408             throw new RepositoryException("Node " + node.getPath() + " was never versioned");
409         }
410         return baseVersion;
411     }
412 
413     /**
414      * Get all versions.
415      *
416      * @return Version iterator retrieved from version history
417      * @throws UnsupportedOperationException if repository implementation does not support Versions API
418      * @throws javax.jcr.RepositoryException if any repository error occurs
419      */
420     public synchronized VersionIterator getAllVersions(Node node) throws RepositoryException {
421         Node versionedNode = this.getVersionedNode(node);
422         if (versionedNode == null) {
423             // node does not exist in version store so no versions
424             return null;
425         }
426         VersionHistory versionHistory = getVersionHistory(node);
427         if (versionHistory == null) {
428             // does not have any versions in version store
429             return null;
430         }
431         return versionHistory.getAllVersions();
432     }
433 
434     /**
435      * Restore specified version.
436      *
437      * @param node to be restored
438      * @param version to be used
439      * @throws javax.jcr.version.VersionException if the specified <code>versionName</code> does not exist in this
440      * node's version history
441      * @throws javax.jcr.RepositoryException if an error occurs
442      */
443     public synchronized void restore(final Node node, Version version, boolean removeExisting) throws RepositoryException {
444         // FYI: restore is done in SC!!! Check permissions manually
445         PermissionUtil.verifyIsGrantedOrThrowException(node.getSession(), node.getPath(), Session.ACTION_SET_PROPERTY + "," + Session.ACTION_REMOVE + "," + Session.ACTION_ADD_NODE);
446 
447         // get the cloned node from version store
448         final Node versionedNode = this.getVersionedNode(node);
449 
450         final Version unwrappedVersion;
451         if (version instanceof VersionedNode) {
452             unwrappedVersion = VersionUtil.unwrap(((VersionedNode) version).unwrap());
453         } else {
454             unwrappedVersion = VersionUtil.unwrap(version);
455         }
456 
457         final String lastActivatedVersion = NodeTypes.Activatable.getLastActivatedVersion(node);
458         final Calendar lastActivatedVersionCreated = NodeTypes.Activatable.getLastActivatedVersionCreated(node);
459 
460         VersionManager versionManager = versionedNode.getSession().getWorkspace().getVersionManager();
461         versionManager.restore(unwrappedVersion, removeExisting);
462         versionManager.checkout(versionedNode.getPath());
463         MgnlContext.doInSystemContext(new JCRSessionOp<Void>(versionedNode.getSession().getWorkspace().getName()) {
464 
465             @Override
466             public Void exec(Session session) throws RepositoryException {
467                 //mixins are NOT restored automatically
468                 List<String> mixins = new ArrayList<>();
469                 for (Value v : unwrappedVersion.getNode(JcrConstants.JCR_FROZENNODE).getProperty("jcr:frozenMixinTypes").getValues()) {
470                     mixins.add(v.getString());
471                 }
472 
473                 final Node systemVersionedNode = session.getNodeByIdentifier(versionedNode.getUUID());
474                 for (NodeType nt : versionedNode.getMixinNodeTypes()) {
475                     if (!mixins.remove(nt.getName())) {
476                         systemVersionedNode.removeMixin(nt.getName());
477                     }
478                 }
479                 for (String mix : mixins) {
480                     systemVersionedNode.addMixin(mix);
481                 }
482                 systemVersionedNode.save();
483 
484                 try {
485                     // 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
486                     Session sysDestinationSession = MgnlContext.getJCRSession(node.getSession().getWorkspace().getName());
487                     log.debug("restoring info:{}:{}", sysDestinationSession.getWorkspace().getName(), node.getPath());
488                     Node destinationNode = sysDestinationSession.getNode(node.getPath());
489                     // if restored, update original node with the restored node and its subtree
490                     Rule rule = getUsedFilter(versionedNode);
491                     try {
492                         copyUtil.copyFromVersion(versionedNode, destinationNode, new RuleBasedNodePredicate(rule));
493                         if (NodeUtil.hasMixin(destinationNode, NodeTypes.Deleted.NAME)) {
494                             destinationNode.removeMixin(NodeTypes.Deleted.NAME);
495                         }
496                         if (lastActivatedVersion != null) {
497                             // we need to keep last activated version and don't override it with old value from version
498                             destinationNode.setProperty(NodeTypes.Activatable.LAST_ACTIVATED_VERSION, lastActivatedVersion);
499                         }
500                         if (lastActivatedVersionCreated != 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_CREATED, lastActivatedVersionCreated);
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             Rulel/Rule.html#Rule">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 }