View Javadoc
1   /**
2    * This file Copyright (c) 2013-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.ui.contentapp.setup;
35  
36  import info.magnolia.importexport.postprocessors.MetaDataAsMixinConversionHelper;
37  import info.magnolia.jcr.util.NodeTypes;
38  import info.magnolia.jcr.util.NodeUtil;
39  import info.magnolia.jcr.util.NodeVisitor;
40  import info.magnolia.module.InstallContext;
41  import info.magnolia.module.delta.AbstractTask;
42  import info.magnolia.module.delta.TaskExecutionException;
43  
44  import java.util.Arrays;
45  import java.util.HashMap;
46  import java.util.Map.Entry;
47  
48  import javax.jcr.Node;
49  import javax.jcr.NodeIterator;
50  import javax.jcr.RepositoryException;
51  import javax.jcr.Session;
52  import javax.jcr.Workspace;
53  
54  import org.apache.commons.lang3.StringUtils;
55  import org.slf4j.Logger;
56  import org.slf4j.LoggerFactory;
57  
58  /**
59   * Migrate a Data type repository to a new workspace.<br>
60   * This migration task: <br>
61   * - keep the current node identifier<br>
62   * - remove the metaData sub nodes<br>
63   * <br>
64   * Steps: <br>
65   * - Copy the Data repository (data/categorization) to the new workspace (categorization) <br>
66   * - For every element of the 'oldToNewNodeTypeMapping' <br>
67   * -- Change the primary NodeType (from 'category' to 'mgnl:category') <br>
68   * - Remove all metaData nodes.
69   */
70  public abstract class AbstractDataTypeMigrationTask extends AbstractTask {
71      private static final Logger log = LoggerFactory.getLogger(AbstractDataTypeMigrationTask.class);
72  
73      // data/categorization
74      private String dataRootPath;
75      private String newRootPath;
76      private HashMap<String, String> oldToNewNodeTypeMapping;
77      private String newWorkspaceName;
78      private Session dataSession;
79      private Session newSession;
80      private boolean isTargetRoot;
81  
82      /**
83       * @param dataPath path from the data to migrate ('/category'). If set to root, the task will not be executed.
84       * @param newPath new path where the content of dataPath will be copied.
85       * @param newWorkspaceName new workspace name.
86       */
87      public AbstractDataTypeMigrationTask(String taskName, String taskDescription, String dataPath, String newPath, String newWorkspaceName) {
88          super(taskName, taskDescription);
89          this.dataRootPath = dataPath;
90          this.isTargetRoot = (StringUtils.isBlank(newPath) || "/".equals(newPath));
91          this.newRootPath = (StringUtils.isNotBlank(newPath) && !"/".equals(newPath)) ? newPath : dataPath;
92          this.newWorkspaceName = newWorkspaceName;
93          this.oldToNewNodeTypeMapping = new HashMap<String, String>();
94      }
95  
96      @Override
97      public void execute(InstallContext installContext) throws TaskExecutionException {
98          try {
99              // Init session
100             dataSession = installContext.getJCRSession("data");
101             newSession = installContext.getJCRSession(newWorkspaceName);
102 
103             // Init oldToNewNodeTypeMapping
104             initOldToNewNodeTypeMappingElement(oldToNewNodeTypeMapping);
105 
106             // Check that the root Data exist
107             if (dataSession.nodeExists(dataRootPath)) {
108                 migrateData();
109             } else {
110                 installContext.warn("Data migration task cancelled. The following data type do not exist in the data workspace: " + dataRootPath);
111             }
112 
113         } catch (Exception e) {
114             installContext.error("Unable to perform Migration task " + getName(), e);
115             TaskExecutionException taskExecutionException = (e instanceof TaskExecutionException) ? (TaskExecutionException) e : new TaskExecutionException(e.getMessage());
116             throw taskExecutionException;
117         }
118 
119     }
120 
121     /**
122      * Set the HashMap of nodeType to change.<br>
123      * key: oldType ('dataFolder')<br>
124      * value: newType ('mgnl:folder')<br>
125      * <b>Order is important</b><br>
126      * Define first the simple node type and the the types with restriction<br>
127      * For example, if you have a node type for images that have a constraint for a custom data type (for binary).<br>
128      * - First define your custom data type
129      * addOldToNewNodeTypeMappingElement("dataFolder", "mgnl:folder");
130      * addOldToNewNodeTypeMappingElement("category", "mgnl:category");
131      * addOldToNewNodeTypeMappingElement("dataItemNode", "mgnl:content");
132      */
133     protected abstract void initOldToNewNodeTypeMappingElement(HashMap<String, String> oldToNewNodeTypeMapping);
134 
135     private void migrateData() throws TaskExecutionException {
136         try {
137             copyDataWorkspaceToNewWorkspace();
138             for (Entry<String, String> entry : oldToNewNodeTypeMapping.entrySet()) {
139                 convertPrimaryNodeType(entry.getKey(), entry.getValue());
140             }
141             removeMetaDataNodes();
142         } catch (RepositoryException re) {
143             throw new TaskExecutionException("Could not migrate Data folders to the new workspace :" + newWorkspaceName, re);
144         }
145     }
146 
147     /**
148      * Copy the dataRootPath to the new workspace at newRootPath.
149      */
150     private void copyDataWorkspaceToNewWorkspace() throws TaskExecutionException, RepositoryException {
151         // Init
152         final Node rootData = dataSession.getNode(dataRootPath);
153         Workspace newWorkspace = newSession.getWorkspace();
154         // Check that the data content to migrate is not the root of the Data module.
155         if (rootData.getDepth() == 0) {
156             throw new TaskExecutionException("Can not migrate the root of Data workspace. You have to choose a specific data type to migrate like 'data/category'");
157         }
158         if (!newSession.nodeExists(newRootPath) && !"/".equals(getParentPath(newRootPath)) && !newSession.nodeExists(getParentPath(newRootPath))) {
159             NodeUtil.createPath(newSession.getRootNode(), getParentPath(newRootPath), NodeTypes.Folder.NAME).getSession().save();
160         }
161         newWorkspace.clone("data", dataRootPath, newRootPath, true);
162         log.info("Following data workspace part {}: is now moved to the following workspace '{}' location '{}'", Arrays.asList(dataRootPath, newWorkspace.getName(), newRootPath).toArray());
163 
164         if (this.isTargetRoot) {
165             // move to root
166             NodeIterator nodeIterator = newSession.getNode(newRootPath).getNodes();
167             while (nodeIterator.hasNext()) {
168                 Node node = nodeIterator.nextNode();
169                 newSession.getWorkspace().move(node.getPath(), "/" + node.getName());
170             }
171             newSession.removeItem(newRootPath);
172             newRootPath = "/";
173         }
174 
175         newSession.save();
176     }
177 
178     private void convertPrimaryNodeType(String oldNodeType, String newNodetype) throws RepositoryException {
179         final Node root = newSession.getNode(newRootPath);
180         NodeUtil.visit(root, createTypeVisitor(oldNodeType, newNodetype));
181         newSession.save();
182     }
183 
184 
185     private NodeVisitor createTypeVisitor(final String oldNodeType, final String newNodetype) {
186         NodeVisitor folderVisitor = new NodeVisitor() {
187             @Override
188             public void visit(Node node) throws RepositoryException {
189                 if (NodeUtil.isNodeType(node, oldNodeType)) {
190                     node.setPrimaryType(newNodetype);
191                     log.debug("Node primary Type changed from '{}' to '{}' for '{}' ", Arrays.asList(oldNodeType, newNodetype, node.getPath()).toArray());
192                 }
193             }
194         };
195         return folderVisitor;
196     }
197 
198 
199     /**
200      * Convert metadata node.
201      */
202     private void removeMetaDataNodes() throws RepositoryException {
203         MetaDataAsMixinConversionHelper conversionHelper = new MetaDataAsMixinConversionHelper();
204         conversionHelper.setDeleteMetaDataIfEmptied(true);
205         conversionHelper.setPeriodicSaves(true);
206 
207         NodeIterator childRootNodes = newSession.getRootNode().getNodes();
208         while (childRootNodes.hasNext()) {
209             Node child = childRootNodes.nextNode();
210             if (!child.getName().startsWith(NodeTypes.JCR_PREFIX) && !child.getName().startsWith(NodeTypes.REP_PREFIX)) {
211                 conversionHelper.convertNodeAndChildren(child);
212             } else {
213                 log.debug("Node '{}' are not handled by this task", child.getName());
214             }
215         }
216         log.info("Converted MetaData in workspace '{}'", newSession.getWorkspace().getName());
217     }
218 
219     private String getParentPath(String path) {
220         int lastIndexOfSlash = path.lastIndexOf("/");
221         if (lastIndexOfSlash > 0) {
222             return StringUtils.substringBeforeLast(path, "/");
223         }
224         return "/";
225     }
226 
227 }
228 
229