View Javadoc
1   /**
2    * This file Copyright (c) 2014-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.for5_3;
35  
36  import info.magnolia.jcr.RuntimeRepositoryException;
37  import info.magnolia.jcr.predicate.AbstractPredicate;
38  import info.magnolia.jcr.util.NodeTypes;
39  import info.magnolia.module.InstallContext;
40  import info.magnolia.module.delta.NodeVisitorTask;
41  import info.magnolia.module.delta.TaskExecutionException;
42  import info.magnolia.repository.RepositoryConstants;
43  
44  import java.util.HashSet;
45  import java.util.Set;
46  
47  import javax.jcr.Node;
48  import javax.jcr.Property;
49  import javax.jcr.PropertyIterator;
50  import javax.jcr.RepositoryException;
51  import javax.jcr.Session;
52  
53  import org.apache.jackrabbit.commons.predicate.Predicate;
54  import org.slf4j.Logger;
55  import org.slf4j.LoggerFactory;
56  
57  /**
58   * A migration task to move properties <i>workspace</i>, <i>path</i>, <i>includeProperties</i>, <i>includeSystemNodes</i>, <i>defaultOrder</i> and <i>nodeTypes</i>
59   * from <i>subapp/workbench</i> or <i>subApp/editor</i> (only the property <i>workspace</i>) and adding them to a new node
60   * <i>contentConnector</i> which is added to <i>workbench</i> or <i>editor</i>.<br/>
61   * The property path is renamed to rootPath.
62   * This task normally is not meant to be used standalone.
63   *
64   * @see {@link ContentAppMigrationTask}
65   */
66  public class MigrateJcrPropertiesToContentConnectorTask extends NodeVisitorTask {
67  
68      private static final Logger log = LoggerFactory.getLogger(MigrateJcrPropertiesToContentConnectorTask.class);
69  
70      private static final String WORKBENCH_NODENAME = "workbench";
71      private static final String EDITOR_NODENAME = "editor";
72      private static final String CONTENTCONNECTOR_NODENAME = "contentConnector";
73      private static final String PATH_PROPERTY = "path";
74      private static final String ROOTPATH_PROPERTY = "rootPath";
75  
76      private final Set<Node> nodesToRemove = new HashSet<Node>();
77  
78      public MigrateJcrPropertiesToContentConnectorTask(String path) {
79          super("Migrate properties workspace, path from workbench and migrate workspace from editor and add them to a node called contentConnector and add this new node to workbench respectively editor.",
80                  "Migrating properties workspace, path from workbench and migrating workspace from editor and adding them to a node called contentConnector and adding the new node to workbench respectively editor.",
81                  RepositoryConstants.CONFIG, path);
82      }
83  
84      public MigrateJcrPropertiesToContentConnectorTask() {
85          this("/");
86      }
87  
88      @Override
89      protected void doExecute(InstallContext installContext) throws RepositoryException, TaskExecutionException {
90          super.doExecute(installContext);
91          // this task removes some nodes, so we must do this after execution, not to mess with NodeVisitor while it is running
92          if (!nodesToRemove.isEmpty()) {
93              for (Node node : nodesToRemove) {
94                  node.remove();
95              }
96          }
97      }
98  
99      @Override
100     protected boolean nodeMatches(Node node) {
101         try {
102             return node.getPrimaryNodeType().getName().equals(NodeTypes.ContentNode.NAME) &&
103                     (node.getName().equals(WORKBENCH_NODENAME) || node.getName().equals(EDITOR_NODENAME));
104         } catch (RepositoryException e) {
105             log.error("Couldn't evaluate visited node's name or node-type", e);
106         }
107         return false;
108     }
109 
110     @Override
111     protected void operateOnNode(InstallContext installContext, Node node) {
112 
113         try {
114             Node subAppNode = node.getParent();
115             if (!"subApps".equals(subAppNode.getParent().getName()) && !subAppNode.hasProperty("subAppClass")) {
116                 return;
117             }
118 
119             Node contentConnectorNode = subAppNode.hasNode(CONTENTCONNECTOR_NODENAME) ? subAppNode.getNode(CONTENTCONNECTOR_NODENAME) : subAppNode.addNode(CONTENTCONNECTOR_NODENAME, NodeTypes.ContentNode.NAME);
120 
121             // workbench
122             if (WORKBENCH_NODENAME.equals(node.getName())) {
123                 migrateProperty("workspace", node, contentConnectorNode);
124                 migrateProperty("path", node, contentConnectorNode);
125                 migrateProperty("includeProperties", node, contentConnectorNode);
126                 migrateProperty("includeSystemNodes", node, contentConnectorNode);
127                 migrateProperty("defaultOrder", node, contentConnectorNode);
128                 migrateNode("nodeTypes", node, contentConnectorNode, installContext.getJCRSession(RepositoryConstants.CONFIG));
129             }
130             // editor
131             else {
132                 migrateProperty("workspace", node, contentConnectorNode);
133             }
134 
135             boolean nodeCleared = !node.getNodes().hasNext();
136             for (final PropertyIterator it = node.getProperties(); nodeCleared && it.hasNext();) {
137                 final String propertyName = it.nextProperty().getName();
138                 nodeCleared = propertyName.startsWith(NodeTypes.MGNL_PREFIX) || propertyName.startsWith(NodeTypes.JCR_PREFIX) || propertyName.equals("extends");
139             }
140 
141             if (nodeCleared) {
142                 // don't remove node right away, otherwise node-visitor gets confused
143                 nodesToRemove.add(node);
144             }
145         } catch (RepositoryException e) {
146             throw new RuntimeRepositoryException("Failed to migrate JCR-properties to contentConnector.",e);
147         }
148     }
149 
150     private void migrateNode(String nodeName, Node sourceNode, Node destNode, Session jcrSession) throws RepositoryException {
151         if (sourceNode.hasNode(nodeName)) {
152             if (destNode.hasNode(nodeName)) {
153                 destNode.getNode(nodeName).remove();
154             }
155 
156             jcrSession.move(sourceNode.getNode(nodeName).getPath(), destNode.getPath() + "/" + nodeName);
157         }
158     }
159 
160     /*
161      * Moving a property by a given name from the source node (workbench or editor) to the destination.-node (contentConnector).
162      * If the attribute from the source-node is 'path', it will change its name to 'rootPath'.
163      */
164     private void migrateProperty(String propertyName, Node sourceNode, Node destNode) throws RepositoryException {
165         if (sourceNode.hasProperty(propertyName)) {
166             Property sourceNodeProperty = sourceNode.getProperty(propertyName);
167             if (!PATH_PROPERTY.equals(propertyName)) {
168                 destNode.setProperty(propertyName, sourceNodeProperty.getString());
169             } else {
170                 destNode.setProperty(ROOTPATH_PROPERTY, sourceNodeProperty.getString());
171             }
172             sourceNodeProperty.remove();
173         }
174     }
175 
176     @Override
177     protected Predicate getFilteringPredicate() {
178         return new AbstractPredicate<Node>() {
179 
180             @Override
181             public boolean evaluateTyped(Node node) {
182                 try {
183                     // after marking a node for removal, we interrupt visiting further down when we evaluate its direct sub-nodes.
184                     if (nodesToRemove.contains(node.getParent())) {
185                         return false;
186                     }
187                 } catch (RepositoryException e) {
188                     log.warn("Couldn't get parent of visited node, not filtering subtree for NodeVisitor.");
189                 }
190                 return true;
191             }
192         };
193     }
194 }