View Javadoc

1   /**
2    * This file Copyright (c) 2003-2010 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.core.Path;
38  import info.magnolia.cms.beans.config.ContentRepository;
39  import info.magnolia.context.MgnlContext;
40  import info.magnolia.cms.core.HierarchyManager;
41  
42  import java.io.File;
43  import java.io.FileInputStream;
44  import java.io.FileOutputStream;
45  import java.io.IOException;
46  import java.util.Iterator;
47  
48  import javax.jcr.ImportUUIDBehavior;
49  import javax.jcr.ItemNotFoundException;
50  import javax.jcr.Node;
51  import javax.jcr.Property;
52  import javax.jcr.PropertyIterator;
53  import javax.jcr.PropertyType;
54  import javax.jcr.RepositoryException;
55  import javax.jcr.nodetype.ConstraintViolationException;
56  
57  import org.apache.commons.io.IOUtils;
58  import org.apache.commons.lang.StringUtils;
59  import org.slf4j.Logger;
60  import org.slf4j.LoggerFactory;
61  
62  
63  /**
64   * @author Sameer Charles
65   * $Id: CopyUtil.java 32667 2010-03-13 00:37:06Z gjoseph $
66   * Utility class to copy nodes using specified Roles to the magnolia specific version store
67   */
68  public final class CopyUtil {
69  
70      /**
71       * Logger.
72       */
73      private static Logger log = LoggerFactory.getLogger(CopyUtil.class);
74  
75      /**
76       * singleton instance
77       */
78      private static final CopyUtil thisInstance = new CopyUtil();
79  
80      /**
81       * private class
82       */
83      private CopyUtil() {
84      }
85  
86      /**
87       * get instance
88       */
89      public static CopyUtil getInstance() {
90          return thisInstance;
91      }
92  
93      /**
94       * copy given node to the version store using specified filter
95       * @param source
96       * @param filter
97       */
98      void copyToversion(Content source, Content.ContentFilter filter) throws RepositoryException {
99          // first check if the node already exist
100         Content root;
101         try {
102             root = this.getHierarchyManager().getContentByUUID(source.getUUID());
103             if (root.getParent().getName().equalsIgnoreCase(VersionManager.TMP_REFERENCED_NODES)) {
104                 root.getJCRNode().getSession().move(root.getHandle(), "/" + root.getName());
105             }
106             this.removeProperties(root);
107             // copy root properties
108             this.updateProperties(source, root);
109             root.save();
110         }
111         catch (ItemNotFoundException e) {
112             // create root for this versionable node
113             try {
114                 this.importNode(this.getHierarchyManager().getRoot(), source);
115             }
116             catch (IOException ioe) {
117                 throw new RepositoryException("Failed to import node in magnolia version store : " + ioe.getMessage());
118             }
119             root = this.getHierarchyManager().getContentByUUID(source.getUUID());
120             // copy root properties
121             // this.updateProperties(source, root);
122             // save parent node since this node is newly created
123             getHierarchyManager().getRoot().save();
124         }
125         // copy all child nodes
126         Iterator children = source.getChildren(filter).iterator();
127         while (children.hasNext()) {
128             Content child = (Content) children.next();
129             this.clone(child, root, filter, true);
130         }
131         this.removeNonExistingChildNodes(source, root, filter);
132     }
133 
134     /**
135      * copy source to destination using the provided filter
136      * @param source node in version store
137      * @param destination which needs to be restored
138      * @param filter this must be the same filter as used while creating this version
139      */
140     void copyFromVersion(Content source, Content destination, Content.ContentFilter filter) throws RepositoryException {
141         // merge top node properties
142         this.removeProperties(destination);
143         this.updateProperties(source, destination);
144         // copy all nodes from version store
145         this.copyAllChildNodes(source, destination, filter);
146         // remove all non existing nodes
147         this.removeNonExistingChildNodes(source, destination, filter);
148     }
149 
150     /**
151      * recursively removes all child nodes from node using specified filter
152      * @param source
153      * @param destination
154      * @param filter
155      */
156     private void removeNonExistingChildNodes(Content source, Content destination, Content.ContentFilter filter)
157         throws RepositoryException {
158         // collect all uuids from the source node hierarchy using the given filter
159         Iterator children = destination.getChildren(filter).iterator();
160         while (children.hasNext()) {
161             Content child = (Content) children.next();
162             // check if this child exist in source, if not remove it
163             if (child.getJCRNode().getDefinition().isAutoCreated()) {
164                 continue;
165             }
166             try {
167                 source.getJCRNode().getSession().getNodeByUUID(child.getUUID());
168                 // if exist its ok, recursively remove all sub nodes
169                 this.removeNonExistingChildNodes(source, child, filter);
170             }
171             catch (ItemNotFoundException e) {
172                 PropertyIterator referencedProperties = child.getJCRNode().getReferences();
173                 if (referencedProperties.getSize() > 0) {
174                     // remove all referenced properties, its safe since source workspace cannot have these
175                     // properties if node with this UUID does not exist
176                     while (referencedProperties.hasNext()) {
177                         referencedProperties.nextProperty().remove();
178                     }
179                 }
180                 child.delete();
181             }
182         }
183     }
184 
185     /**
186      * copy all child nodes from node1 to node2
187      * @param node1
188      * @param node2
189      * @param filter
190      */
191     private void copyAllChildNodes(Content node1, Content node2, Content.ContentFilter filter)
192         throws RepositoryException {
193         Iterator children = node1.getChildren(filter).iterator();
194         while (children.hasNext()) {
195             Content child = (Content) children.next();
196             this.clone(child, node2, filter, false);
197         }
198     }
199 
200     /**
201      * clone
202      * @param node
203      * @param parent
204      * @param filter
205      * @param removeExisting
206      */
207     public void clone(Content node, Content parent, Content.ContentFilter filter, boolean removeExisting)
208         throws RepositoryException {
209         try {
210             // it seems to be a bug in jackrabbit - cloning does not work if the node with the same uuid
211             // exist, "removeExisting" has no effect
212             // if node exist with the same UUID, simply update non propected properties
213             String workspaceName = ContentRepository.getInternalWorkspaceName(parent.getWorkspace().getName());
214             Content existingNode = getHierarchyManager(workspaceName)
215                 .getContentByUUID(node.getUUID());
216             if (removeExisting) {
217                 existingNode.delete();
218                 parent.save();
219                 this.clone(node, parent);
220                 return;
221             }
222             this.removeProperties(existingNode);
223             this.updateProperties(node, existingNode);
224             Iterator children = node.getChildren(filter).iterator();
225             while (children.hasNext()) {
226                 this.clone((Content) children.next(), existingNode, filter, removeExisting);
227             }
228         }
229         catch (ItemNotFoundException e) {
230             // its safe to clone if UUID does not exist in this workspace
231             this.clone(node, parent);
232         }
233     }
234 
235     /**
236      * clone
237      * @param node
238      * @param parent
239      */
240     private void clone(Content node, Content parent) throws RepositoryException {
241         if (node.getJCRNode().getDefinition().isAutoCreated()) {
242             Content destination = parent.getContent(node.getName());
243             this.removeProperties(destination);
244             this.updateProperties(node, destination);
245         }
246         else {
247             String parH = parent.getHandle();
248             parent.getWorkspace().clone(
249                 node.getWorkspace().getName(),
250                 node.getHandle(),
251                  parH + (parH != null && parH.endsWith("/") ? "" :"/") + node.getName(),
252                 true);
253         }
254     }
255 
256     /**
257      * remove all properties under the given node
258      * @param node
259      */
260     private void removeProperties(Content node) throws RepositoryException {
261         PropertyIterator properties = node.getJCRNode().getProperties();
262         while (properties.hasNext()) {
263             Property property = properties.nextProperty();
264             if (property.getDefinition().isProtected() || property.getDefinition().isMandatory()) {
265                 continue;
266             }
267             try {
268                 property.remove();
269             }
270             catch (ConstraintViolationException e) {
271                 if (log.isDebugEnabled()) {
272                     log.debug("Property " + property.getName() + " is a reserved property");
273                 }
274             }
275         }
276     }
277 
278     /**
279      * import while preserving UUID, parameters supplied must be from separate workspaces
280      * @param parent under which the specified node will be imported
281      * @param node
282      * @throws RepositoryException
283      * @throws IOException if failed to import or export
284      */
285     private void importNode(Content parent, Content node) throws RepositoryException, IOException {
286         File file = File.createTempFile("mgnl", null, Path.getTempDirectory());
287         FileOutputStream outStream = new FileOutputStream(file);
288         node.getWorkspace().getSession().exportSystemView(node.getHandle(), outStream, false, true);
289         outStream.flush();
290         IOUtils.closeQuietly(outStream);
291         FileInputStream inStream = new FileInputStream(file);
292         parent.getWorkspace().getSession().importXML(
293             parent.getHandle(),
294             inStream,
295             ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING);
296         IOUtils.closeQuietly(inStream);
297         file.delete();
298     }
299 
300     /**
301      * merge all non reserved properties
302      * @param source
303      * @param destination
304      */
305     private void updateProperties(Content source, Content destination) throws RepositoryException {
306         Node sourceNode = source.getJCRNode();
307         Node destinationNode = destination.getJCRNode();
308         PropertyIterator properties = sourceNode.getProperties();
309         while (properties.hasNext()) {
310             Property property = properties.nextProperty();
311             // exclude system property Rule and Version specific properties which were created on version
312             if (property.getName().equalsIgnoreCase(VersionManager.PROPERTY_RULE)) {
313                 continue;
314             }
315             try {
316                 if (property.getDefinition().isProtected()) {
317                     continue;
318                 }
319                 if ("jcr:isCheckedOut".equals(property.getName())) {
320                     // do not attempt to restore isCheckedOut property as it makes no sense to restore versioned node with
321                     // checkedOut status and value for this property might not be set even though the property itself is set.
322                     // Since JCR-1272 attempt to restore the property with no value will end up with RepositoryException instead
323                     // of ConstraintViolationException and hence will not be caught by the catch{} block below.
324                     continue;
325                 }
326                 if (property.getType() == PropertyType.REFERENCE) {
327                     // first check for the referenced node existence
328                     try {
329                         getHierarchyManager(destination.getWorkspace().getName())
330                             .getContentByUUID(property.getString());
331                     }
332                     catch (ItemNotFoundException e) {
333                         if (!StringUtils.equalsIgnoreCase(
334                             destination.getWorkspace().getName(),
335                             VersionManager.VERSION_WORKSPACE)) {
336                             throw e;
337                         }
338                         // get referenced node under temporary store
339                         // use jcr import, there is no other way to get a node without sub hierarchy
340                         Content referencedNode = getHierarchyManager(source.getWorkspace().getName()).getContentByUUID(
341                             property.getString());
342                         try {
343                             this.importNode(getTemporaryPath(), referencedNode);
344                             this.removeProperties(getHierarchyManager().getContentByUUID(property.getString()));
345                             getTemporaryPath().save();
346                         }
347                         catch (IOException ioe) {
348                             log.error("Failed to import referenced node", ioe);
349                         }
350                     }
351                 }
352                 if (property.getDefinition().isMultiple()) {
353                     destinationNode.setProperty(property.getName(), property.getValues());
354                 }
355                 else {
356                     destinationNode.setProperty(property.getName(), property.getValue());
357                 }
358             }
359             catch (ConstraintViolationException e) {
360                 if (log.isDebugEnabled()) {
361                     log.debug("Property " + property.getName() + " is a reserved property");
362                 }
363             }
364         }
365     }
366 
367     /**
368      * get version store hierarchy manager
369      */
370     private HierarchyManager getHierarchyManager() {
371         return MgnlContext.getHierarchyManager(VersionManager.VERSION_WORKSPACE);
372     }
373 
374     /**
375      * get hierarchy manager of the specified workspace
376      * @param workspaceId
377      */
378     private HierarchyManager getHierarchyManager(String workspaceId) {
379         return MgnlContext.getHierarchyManager(workspaceId);
380     }
381 
382     /**
383      * get temporary node
384      */
385     private Content getTemporaryPath() throws RepositoryException {
386         return getHierarchyManager().getContent("/" + VersionManager.TMP_REFERENCED_NODES);
387     }
388 }