View Javadoc
1   /**
2    * This file Copyright (c) 2016 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.context.SystemContext;
37  import info.magnolia.jcr.decoration.AbstractContentDecorator;
38  import info.magnolia.jcr.decoration.ContentDecoratorNodeWrapper;
39  import info.magnolia.jcr.decoration.ContentDecoratorSessionWrapper;
40  import info.magnolia.jcr.decoration.ContentDecoratorWorkspaceWrapper;
41  import info.magnolia.jcr.iterator.FilteringRangeIterator;
42  import info.magnolia.jcr.util.NodeTypes;
43  import info.magnolia.jcr.util.NodeUtil;
44  import info.magnolia.objectfactory.Components;
45  
46  import java.util.ArrayList;
47  import java.util.Iterator;
48  import java.util.List;
49  
50  import javax.jcr.AccessDeniedException;
51  import javax.jcr.ItemNotFoundException;
52  import javax.jcr.Node;
53  import javax.jcr.Property;
54  import javax.jcr.RepositoryException;
55  import javax.jcr.Session;
56  import javax.jcr.Workspace;
57  import javax.jcr.lock.LockException;
58  import javax.jcr.nodetype.ConstraintViolationException;
59  import javax.jcr.version.Version;
60  import javax.jcr.version.VersionException;
61  import javax.jcr.version.VersionIterator;
62  
63  import org.slf4j.Logger;
64  import org.slf4j.LoggerFactory;
65  
66  /**
67   * Decorator that is only used in version workspaces. Ensures that if two or more nodes from different workspaces are versioned, versions are not broken.
68   */
69  public class MgnlVersionSessionDecorator extends AbstractContentDecorator {
70  
71      private static final Logger log = LoggerFactory.getLogger(MgnlVersionSessionDecorator.class);
72  
73      private String sourceWorkspace;
74  
75      public MgnlVersionSessionDecorator(String sourceWorkspace) {
76          this.sourceWorkspace = sourceWorkspace;
77      }
78  
79      @Override
80      public Session wrapSession(Session session) {
81          return new MgnlVersionSessionWrapper(session, this);
82      }
83  
84      @Override
85      public Workspace wrapWorkspace(Workspace workspace) {
86          return new MgnlVersionSessionWorkspaceWrapper(workspace, this);
87      }
88  
89      @Override
90      public Node wrapNode(Node node) {
91          return new MgnlVersionSessionNodeWrapper(node, this);
92      }
93  
94      @Override
95      public VersionIterator wrapVersionIterator(VersionIterator versionIterator) {
96          return new MgnlVersionSessionVersionIteratorWrapper(sourceWorkspace, versionIterator);
97      }
98  
99      @Override
100     public boolean isMultipleWrapEnabled() {
101         return false;
102     }
103 
104     private void removeHasVersionMixin(Node versionedNode) throws RepositoryException {
105         Node node = versionedNode;
106         while (node.getDepth() != 1) {
107             node = node.getParent();
108         }
109         String uuid = versionedNode.getIdentifier();
110         try {
111             Node original = Components.getComponent(SystemContext.class).getJCRSession(sourceWorkspace).getNodeByIdentifier(uuid);
112             if (NodeUtil.hasMixin(original, NodeTypes.HasVersion.NAME)) {
113                 original.removeMixin(NodeTypes.HasVersion.NAME);
114                 original.getSession().save();
115             }
116         } catch (ItemNotFoundException e) {
117             // in case original node does not exist, print warning
118             log.warn("Original node for version {} not found.", e.getMessage());
119         }
120     }
121 
122     /**
123      * Wrapper that returns only relevant versions for the versioned node.
124      */
125     public class MgnlVersionSessionVersionIteratorWrapper extends FilteringRangeIterator<Version> implements VersionIterator {
126 
127         private final String sourceWorkspace;
128         private final VersionIterator versionIterator;
129         private final List<Version> allVersions = new ArrayList<>();
130 
131         public MgnlVersionSessionVersionIteratorWrapper(String sourceWorkspace, VersionIterator versionIterator) {
132             super(versionIterator);
133             this.sourceWorkspace = sourceWorkspace;
134             this.versionIterator = versionIterator;
135         }
136 
137         @Override
138         protected boolean evaluate(Version version) {
139             allVersions.add(version);
140             try {
141                 if (version.getFrozenNode().hasNode(BaseVersionManager.SYSTEM_NODE) && version.getFrozenNode().getNode(BaseVersionManager.SYSTEM_NODE).hasProperty(BaseVersionManager.SOURCE_WORKSPACE)) {
142                     String workspace = version.getFrozenNode().getNode(BaseVersionManager.SYSTEM_NODE).getProperty(BaseVersionManager.SOURCE_WORKSPACE).getString();
143                     return sourceWorkspace.equals(workspace);
144                 }
145             } catch (RepositoryException e) {
146                 log.warn("Unable to determine source workspace for version {}", version, e);
147                 return false;
148             }
149             return true;
150         }
151 
152         @Override
153         public Version nextVersion() {
154             return wrapVersion(next());
155         }
156 
157         /**
158          * Returns size for all versions from all workspaces, unlike {@link #getSize()} that returns filtered results by workspace.
159          */
160         public long getUnfilteredSize() {
161             return versionIterator.getSize();
162         }
163 
164         /**
165          * Returns all versions regardless of the workspace they were versioned from.
166          */
167         public Iterator<Version> getAllVersionsUnfiltered() {
168             return allVersions.iterator();
169         }
170     }
171 
172     /**
173      * Wrapper that ensures that mgnl:hasVersion mixin is removed from source node if versioned node is being removed.
174      */
175     public class MgnlVersionSessionWrapper extends ContentDecoratorSessionWrapper<MgnlVersionSessionDecorator> {
176 
177         public MgnlVersionSessionWrapper(Session session, MgnlVersionSessionDecorator contentDecorator) {
178             super(session, contentDecorator);
179         }
180 
181         @Override
182         public void removeItem(String absPath) throws LockException, ConstraintViolationException, AccessDeniedException, VersionException, RepositoryException {
183             if (getItem(absPath) instanceof Property) {
184                 super.removeItem(absPath);
185             } else {
186                 removeHasVersionMixin(getNode(absPath));
187                 super.removeItem(absPath);
188             }
189         }
190     }
191 
192     /**
193      * Wrapper that ensures that mgnl:hasVersion mixin is removed from source node if versioned node is being removed.
194      */
195     public class MgnlVersionSessionNodeWrapper extends ContentDecoratorNodeWrapper<MgnlVersionSessionDecorator> {
196 
197         public MgnlVersionSessionNodeWrapper(Node node, MgnlVersionSessionDecorator contentDecorator) {
198             super(node, contentDecorator);
199         }
200 
201         @Override
202         public void remove() throws LockException, ConstraintViolationException, AccessDeniedException, VersionException, RepositoryException {
203             removeHasVersionMixin(this);
204             super.remove();
205         }
206     }
207 
208     /**
209      * Makes sure we don't lose wrapper.
210      */
211     public class MgnlVersionSessionWorkspaceWrapper extends ContentDecoratorWorkspaceWrapper {
212 
213         public MgnlVersionSessionWorkspaceWrapper(Workspace workspace, MgnlVersionSessionDecorator contentDecorator) {
214             super(workspace, contentDecorator);
215         }
216     }
217 }