View Javadoc
1   /**
2    * This file Copyright (c) 2012-2015 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.dam.app.setup.migration;
35  
36  import info.magnolia.dam.jcr.DamConstants;
37  import info.magnolia.dam.jcr.AssetNodeTypes;
38  import info.magnolia.jcr.util.NodeTypes;
39  import info.magnolia.jcr.util.NodeUtil;
40  import info.magnolia.jcr.util.NodeVisitor;
41  import info.magnolia.module.InstallContext;
42  import info.magnolia.module.delta.AbstractRepositoryTask;
43  import info.magnolia.module.delta.TaskExecutionException;
44  
45  import java.util.List;
46  
47  import javax.jcr.Node;
48  import javax.jcr.Property;
49  import javax.jcr.RepositoryException;
50  import javax.jcr.Session;
51  import javax.jcr.Workspace;
52  
53  import org.apache.commons.lang.StringUtils;
54  import org.apache.jackrabbit.JcrConstants;
55  import org.slf4j.Logger;
56  import org.slf4j.LoggerFactory;
57  
58  import com.google.common.collect.Lists;
59  
60  /**
61   * Migrate Data from a Specified Data repository to DAM.<br>
62   * Let say that you have the following Data Structure:<br>
63   * 
64   * <pre>
65   * dms / site1 / image / video / mp4 / mp3 / audio / site2 / image / audio / video
66   * </pre>
67   * 
68   * By defining the following input arguments:<br>
69   * &nbsp;originalPathsList = ["site1/image","site1/video/mp4","site2"]<br>
70   * &nbsp;targetSubPath = "data". If null, no subPath will be added.<br>
71   * &nbsp;dataRepository = "dms"<br>
72   * The Migration task will create the following structure into the dam
73   * repository:<br>
74   * 
75   * <pre>
76   * dam / data / site1 / image / video / mp4 / site2 / image / audio / video
77   * </pre>
78   * 
79   * If a dam folder with the same name already exist, folders will be merged.
80   **/
81  public class MoveDataWorkspaceToDamMigrationTask extends AbstractRepositoryTask {
82  
83      private static final Logger log = LoggerFactory.getLogger(MoveDataWorkspaceToDamMigrationTask.class);
84  
85      private final List<String> originalPathsList;
86      private final String targetSubPath;
87      private Session damSession;
88      private final String dataRepository;
89      private Session dataSession;
90      private final String originalBinaryName = "document";
91  
92      /**
93       * Default constructor.
94       */
95      public MoveDataWorkspaceToDamMigrationTask(String taskName, String taskDescription, List<String> originalPathsList, String targetSubPath, String dataRepository) {
96          super(taskName, taskDescription);
97          this.dataRepository = dataRepository;
98          this.originalPathsList = originalPathsList;
99          this.targetSubPath = targetSubPath;
100     }
101 
102     @Override
103     public void doExecute(InstallContext ctx) throws TaskExecutionException {
104         log.info("Start to move data from '{}' to DAM repository.", dataRepository);
105         try {
106             // Init
107             dataSession = ctx.getJCRSession(dataRepository);
108             damSession = ctx.getJCRSession(DamConstants.WORKSPACE);
109 
110             for (String path : this.originalPathsList) {
111                 if (!dataSession.nodeExists(path)) {
112                     log.warn("Path '{}' does not exist for the repository '{}'. No Data migration will be performed", path, dataRepository);
113                     continue;
114                 }
115                 migrateData(path, this.targetSubPath);
116             }
117 
118         } catch (Exception e) {
119             ctx.error("Unable to perform Migration task " + getName(), e);
120             throw new TaskExecutionException(e.getMessage());
121         }
122         log.info("Successfully moved data to DAM repository: ");
123     }
124 
125     private void migrateData(String path, String targetSubPath) throws TaskExecutionException {
126         String rootDataPath = StringUtils.isNotBlank(targetSubPath) ? targetSubPath + path : path;
127         copyNodesFromDataRepositoryToDam(path, targetSubPath);
128         convertFoldersToDam(rootDataPath);
129         convertContentNodesToDam(rootDataPath);
130     }
131 
132     /**
133      * Copy all nodes from DATA to DAM workspace.
134      * Step:
135      * Copy from the DATA to DAM workspace in a tmp folder.
136      * Merge the TMP folder into the final location.
137      */
138     protected void copyNodesFromDataRepositoryToDam(String dataPath, String targetSubPath) throws TaskExecutionException {
139 
140         log.info("Trying to move Data from the following path " + dataPath + " to " + (StringUtils.isNotBlank(targetSubPath) ? targetSubPath + "/" + dataPath : dataPath));
141         try {
142             Workspace workspaceDAM = damSession.getWorkspace();
143             final Node rootData = dataSession.getNode(dataPath);
144             List<Node> rootNodeToMove = null;
145 
146             if (rootData.equals(dataSession.getRootNode())) {
147                 // We have to iterate the nodes because workspaceDAM.clone("dataRepository","/","/",true); does not work for root.
148                 rootNodeToMove = Lists.newArrayList(rootData.getNodes(NodeTypes.Content.NAME));
149             } else {
150                 rootNodeToMove = Lists.newArrayList(rootData);
151             }
152 
153             for (Node child : rootNodeToMove) {
154                 String sourcePath = child.getPath();
155                 String destPath = StringUtils.isNotBlank(targetSubPath) ? targetSubPath + sourcePath : "" + sourcePath;
156                 // Delete node if it already exists in dam workspace.
157                 if (damSession.nodeExists(destPath)) {
158                     damSession.removeItem(destPath);
159                     damSession.save();
160                     log.warn("Destination path already existing in the DAM repository : " + destPath + "- data located in the current path are now removed");
161                 }
162                 workspaceDAM.clone(dataRepository, sourcePath, destPath, true);
163                 log.info("Following " + this.dataRepository + ":" + sourcePath + " moved to dam:" + destPath);
164                 damSession.save();
165             }
166         } catch (Exception e) {
167             throw new TaskExecutionException("Could not copy nodes from dms to DAM workspace. ", e);
168         }
169     }
170 
171     /**
172      * Convert nodes with type=folder to primaryType mgnl:folder.
173      */
174     private void convertFoldersToDam(String newDAMpath) throws TaskExecutionException {
175         try {
176             final Node rootDAM = damSession.getNode(newDAMpath);
177             NodeUtil.visit(rootDAM, createFolderVisitor());
178             damSession.save();
179 
180         } catch (RepositoryException e) {
181             throw new TaskExecutionException("Could not convert folders to DAM format. ", e);
182         }
183     }
184 
185     /**
186      * Convert content nodes to primaryType mgnl:asset.
187      */
188     private void convertContentNodesToDam(String newDAMpath) throws TaskExecutionException {
189         try {
190             final Node rootDAM = damSession.getNode(newDAMpath);
191             NodeUtil.visit(rootDAM, createDataVisitor());
192             damSession.save();
193         } catch (RepositoryException e) {
194             throw new TaskExecutionException("Could not convert content nodes to DAM format. ", e);
195         }
196     }
197 
198     private NodeVisitor createFolderVisitor() {
199         NodeVisitor folderVisitor = new NodeVisitor() {
200             @Override
201             public void visit(Node node) throws RepositoryException {
202                 if (isNodeDmsFolder(node)) {
203                     if (node.hasProperty(AssetNodeTypes.Asset.TYPE)) {
204                         node.getProperty(AssetNodeTypes.Asset.TYPE).remove();
205                     }
206                     node.setPrimaryType(NodeTypes.Folder.NAME);
207                     log.debug("Node primary Type set to NT_FOLDER for dam: " + node.getPath());
208                 }
209             }
210         };
211 
212         return folderVisitor;
213     }
214 
215     private NodeVisitor createDataVisitor() {
216         NodeVisitor dataVisitor = new NodeVisitor() {
217             @Override
218             public void visit(Node node) throws RepositoryException {
219                 if (isNodeDmsContent(node)) {
220                     log.debug("Handle Node path " + node.getPath());
221                     node.setPrimaryType(AssetNodeTypes.Asset.NAME);
222                     log.debug("Handle Node path " + node.getPath() + " primary type set to " + AssetNodeTypes.Asset.NAME);
223                     log.debug("Change primary type to '" + AssetNodeTypes.Asset.NAME + "' for : " + node.getPath());
224 
225                     if (node.hasNode(originalBinaryName)) {
226                         Node nodeDocument = node.getNode(originalBinaryName);
227                         node.getSession().move(nodeDocument.getPath(), nodeDocument.getParent().getPath() + "/" + JcrConstants.JCR_CONTENT);
228                         log.debug("Handle Node path " + nodeDocument.getPath() + " renamed to type jcr:content");
229                         log.debug("Rename content node to : " + nodeDocument.getPath());
230                         if (nodeDocument.hasProperty(AssetNodeTypes.AssetResource.HEIGHT)) {
231                             nodeDocument.setProperty(AssetNodeTypes.AssetResource.HEIGHT, Long.parseLong(nodeDocument.getProperty(AssetNodeTypes.AssetResource.HEIGHT).getString()));
232                         }
233                         if (nodeDocument.hasProperty(AssetNodeTypes.AssetResource.WIDTH)) {
234                             nodeDocument.setProperty(AssetNodeTypes.AssetResource.WIDTH, Long.parseLong(nodeDocument.getProperty(AssetNodeTypes.AssetResource.WIDTH).getString()));
235                         }
236                         if (nodeDocument.hasProperty(AssetNodeTypes.AssetResource.SIZE)) {
237                             nodeDocument.setProperty(AssetNodeTypes.AssetResource.SIZE, Long.parseLong(nodeDocument.getProperty(AssetNodeTypes.AssetResource.SIZE).getString()));
238                         }
239                     }
240                 }
241             }
242         };
243 
244         return dataVisitor;
245     }
246 
247     /**
248      * Check if the folder is already a DMS folder.
249      */
250     private boolean isNodeDmsFolder(Node node) throws RepositoryException {
251         if (!NodeUtil.isNodeType(node, NodeTypes.Folder.NAME)) {
252             if (node.hasProperty("type")) {
253                 Property type = node.getProperty("type");
254                 String typeString = type.getString();
255 
256                 if ("folder".equals(typeString)) {
257                     return true;
258                 }
259             }
260         }
261         return false;
262     }
263 
264     /**
265      * Check if the node is already a DMS Content Node.
266      */
267     private boolean isNodeDmsContent(Node node) throws RepositoryException {
268         if (NodeUtil.isNodeType(node, NodeTypes.ContentNode.NAME)) {
269             String id = node.getName();
270 
271             if (!"description_files".equals(id)) {
272                 return true;
273             }
274         }
275         return false;
276     }
277 }