View Javadoc
1   /**
2    * This file Copyright (c) 2013-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.setup.for5_0;
35  
36  import info.magnolia.module.InstallContext;
37  import info.magnolia.module.delta.AbstractRepositoryTask;
38  import info.magnolia.module.delta.TaskExecutionException;
39  
40  import java.util.ArrayList;
41  import java.util.HashMap;
42  import java.util.Iterator;
43  import java.util.LinkedHashMap;
44  import java.util.List;
45  import java.util.Set;
46  
47  import javax.jcr.NamespaceException;
48  import javax.jcr.RepositoryException;
49  import javax.jcr.nodetype.NodeTypeDefinition;
50  import javax.jcr.nodetype.NodeTypeManager;
51  import javax.jcr.nodetype.NodeTypeTemplate;
52  
53  import org.apache.commons.lang3.StringUtils;
54  import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
55  import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
56  import org.apache.jackrabbit.spi.Name;
57  import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
58  import org.slf4j.Logger;
59  import org.slf4j.LoggerFactory;
60  
61  /**
62   * Task allowing to register or un-register nodeTypes.
63   * <br>
64   * <br>
65   * <b>For Registration</b>
66   * <ul>
67   *     <li>override {@link #getNodeTypesToRegister(NodeTypeManager)}.</li>
68   *     <li>Every {@link NodeTypeDefinition} in the list will either be created if not present yet, otherwise the existing one will be updated.</li>
69   * </ul>
70   * <b>Warning:</b> Be careful with the order of the list of {@link NodeTypeDefinition}:<br>
71   * Always put the parent NodeType or newly referenced NodeTypes first. For example, you may want to create a new NodeType A
72   * that is referenced as super by an updated NodeType B, the list should be [A,B] so A is created before B. Otherwise,
73   * A will not be updated. In this case, the NodeType A will not be registered and a warn log is displayed.
74   * <br>
75   * <br>
76   * <b>For Un-registration</b>
77   * <ul>
78   *     <li>override {@link #getNodeTypesToUnregister(NodeTypeManager)}.</li>
79   * </ul>
80   * <b>Warning:</b> A NodeType will be unregistered only if no dependency issue is found<br>
81   * Potential dependency issues:<br>
82   * <ul>
83   *     <li>NodeType is referenced as child node by one ore more other NodeType definitions</li>
84   *     <li>NodeType is defined as super of one ore more other NodeType definitions</li>
85   * </ul>
86   * In this case, the NodeType will not be unregistered and a warn log is displayed.
87   * <br>
88   * <br>
89   * <b>Note:</b>  Most of the custom implementation used in this task is due to jackrabbit not being able to update
90   * NodeTypeDefinitions with non-trivial changes. Originally the intention was to keep it independent from jackrabbits API
91   * as well, but in reality we are already heavily dependant on jackrabbit API.
92   *
93   */
94  public abstract class AbstractNodeTypeRegistrationTask extends AbstractRepositoryTask {
95  
96      private final Logger log = LoggerFactory.getLogger(AbstractNodeTypeRegistrationTask.class);
97  
98      private String workspaceName;
99  
100     public AbstractNodeTypeRegistrationTask(String name, String description, String workspaceName) {
101         super(name, description);
102         this.workspaceName = workspaceName;
103     }
104 
105     @Override
106     protected void doExecute(InstallContext installContext) throws RepositoryException, TaskExecutionException {
107         if (StringUtils.isBlank(workspaceName)) {
108             log.warn("No workspace defined. This task will resume");
109             return;
110         }
111 
112         NodeTypeManager nodeTypeManager = installContext.getJCRSession(workspaceName).getWorkspace().getNodeTypeManager();
113 
114         // Register nodeTypes
115         List<NodeTypeDefinition> nodeTypeToRegister = getNodeTypesToRegister(nodeTypeManager);
116         if (nodeTypeToRegister == null || nodeTypeToRegister.isEmpty()) {
117             log.info("No NodeType defined to register for the following workspace '{}'", workspaceName);
118         } else {
119             // exception not caught, installation will bail
120             registerNodeTypes(nodeTypeManager, nodeTypeToRegister, installContext);
121         }
122         // Un-register nodeTypes
123         List<String> nodeTypeToUnregister = getNodeTypesToUnregister(nodeTypeManager);
124         if (nodeTypeToUnregister == null || nodeTypeToUnregister.isEmpty()) {
125             log.info("No NodeType defined to unregister for the following workspace '{}'", workspaceName);
126         } else {
127             try {
128                 unregisterNodeTypes(nodeTypeManager, nodeTypeToUnregister);
129             } catch (RepositoryException e) {
130                 installContext.warn("Un-registration of NodeTypes generated exceptions. NodeType will not be Unregistered. Please check the NodeType dependency.");
131                 log.warn("Not able to un-register the following node type {}. The original definition is kept.", nodeTypeToUnregister, e);
132             }
133         }
134 
135     }
136 
137     /**
138      * Get a List of {@link NodeTypeDefinition} to register or update (in case they are already created).
139      */
140     public abstract List<NodeTypeDefinition> getNodeTypesToRegister(NodeTypeManager nodeTypeManager) throws RepositoryException;
141 
142     /**
143      * Get a List of {@link NodeTypeDefinition#getName()} to un-register.
144      */
145     public abstract List<String> getNodeTypesToUnregister(NodeTypeManager nodeTypeManager) throws RepositoryException;
146 
147     /**
148      * Register a list of NodeTypes.
149      *   <ul>
150      *     <li>In case of new NodeType definition, simply register it.</li>
151      *     <li>In case the NodeType definition already exist:
152      *       <ul>
153      *         <li>Get all NodeType definitions that have a dependency to the current definition.</li>
154      *         <li>Un-register the complete list of dependent NodeTypes (including the current one).</li>
155      *         <li>Re-Register the complete list of dependent NodeTypes, including the new NodeType definition.</li>
156      *         <li>In case of exception, re register the original NodeType definition and including the dependencies  andlog the exception.</li>
157      *       </ul>
158      *     </li>
159      *   </ul>
160      */
161     private void registerNodeTypes(NodeTypeManager nodeTypeManager, List<NodeTypeDefinition> nodeTypesToRegister, InstallContext installContext) throws RepositoryException {
162         NodeTypeRegistry registry = ((NodeTypeManagerImpl) nodeTypeManager).getNodeTypeRegistry();
163         NamePathResolver namePathResolver = ((NodeTypeManagerImpl) nodeTypeManager).getNamePathResolver();
164         // Iterate the nodeTypeToRegister List
165         for (NodeTypeDefinition nodeType : nodeTypesToRegister) {
166             // NodeType already registered, Update (unregister, register)
167             String nodeTypeName = nodeType.getName();
168 
169             if (nodeTypeManager.hasNodeType(nodeTypeName)) {
170 
171                 // Get the original NodeType definition. Useful in case of dependency issue (the original NodeType is re-registered in case of exception).
172                 NodeTypeDefinition originalNodeType = nodeTypeManager.getNodeType(nodeTypeName);
173 
174                 // Get the list of NodeType that reference the current nodeType
175                 Set<Name> dependentNodeTypeQNames = registry.getDependentNodeTypes(namePathResolver.getQName(nodeTypeName));
176 
177                 // we need the names
178                 List<String> dependentNodeTypeNames = getNodeTypeNames(namePathResolver, dependentNodeTypeQNames);
179 
180                 // get definitions before we un-register them, to re-register them afterwards
181                 List<NodeTypeDefinition> dependentNodeTypeDefinitions = getNodeTypeDefinitions(nodeTypeManager, dependentNodeTypeNames);
182 
183                 // UN-REGISTRATION
184                 log.info("In order to register '{}', the following depending node types have to be unregistered first: {}.", nodeTypeName, dependentNodeTypeNames);
185                 // the order does not matter
186                 dependentNodeTypeNames.add(nodeTypeName);
187                 unregisterNodeTypes(nodeTypeManager, dependentNodeTypeNames);
188 
189 
190                 // RE-REGISTRATION
191                 log.info("Registering {} and the following depending nodeTypes: {}", nodeTypeName, dependentNodeTypeQNames);
192                 try {
193                     registerNodeType(nodeTypeManager, nodeType);
194                 } catch (RepositoryException e) {
195                     // Register the previous version of the NodeType, if this fails the task will bail
196                     registerNodeType(nodeTypeManager, originalNodeType);
197                     String error = String.format("Registration (update of an existing NodeType) of the following NodeType generated exceptions '%s'. " +
198                             "The original version of the definition is kept. Please check the NodeType dependency and the input list order.", nodeTypeName);
199                     installContext.warn(error);
200                     log.warn("Not able to register the following node type {}. The original definition is kept.", nodeTypeName, e);
201                 }
202 
203                 for (NodeTypeDefinition nodeTypeDefinition : dependentNodeTypeDefinitions) {
204                     try {
205                         registerNodeType(nodeTypeManager, nodeTypeDefinition);
206                     } catch (RepositoryException e) {
207                         String error = String.format("Re-registration of the NodeType '%s' as a dependent nodeType of '%s' failed.", nodeTypeDefinition.getName(), nodeTypeName);
208                         installContext.warn(error);
209                         log.error(error, e);
210                     }
211                 }
212 
213             } else {
214                 // NodeType is new, Create (register)
215                 nodeTypeManager.registerNodeType(nodeType, false);
216                 log.info("Registering new '{}' node type.", nodeTypeName);
217             }
218         }
219     }
220 
221     /**
222      * Register the {@link NodeTypeDefinition}.
223      */
224     private void registerNodeType(NodeTypeManager nodeTypeManager, NodeTypeDefinition nodeTypeDefinition) throws RepositoryException {
225         // "transforming" each definition into a template, so that nodeTypeManager.registerNodeType doesn't barf unnecessary exceptions.
226         final NodeTypeTemplate nodeTypeTemplate = nodeTypeManager.createNodeTypeTemplate(nodeTypeDefinition);
227         log.debug("Registering the following '{}' node type.", nodeTypeTemplate.getName());
228         nodeTypeManager.registerNodeType(nodeTypeTemplate, true);
229     }
230 
231     /**
232      * Un-register all nodeTypes passed as {@code nodeTypeNames}. Filter the list as the {@link NodeTypeManager#unregisterNodeTypes(String[])}
233      * throws Exception if the nodeType is not registered.
234      */
235     private void unregisterNodeTypes(final NodeTypeManager nodeTypeManager, List<String> nodeTypeNames) throws RepositoryException {
236         Iterator<String> it = nodeTypeNames.iterator();
237         while(it.hasNext()) {
238             String nodeTypeName = it.next();
239             if (!nodeTypeManager.hasNodeType(nodeTypeName)) {
240                 log.warn("Node type '{}' is not registered . No un-registration will be performed for this nodeType.", nodeTypeName);
241                 it.remove();
242             }
243         }
244 
245         NodeTypeRegistry.disableCheckForReferencesInContentException = true;
246         nodeTypeManager.unregisterNodeTypes(nodeTypeNames.toArray(new String[nodeTypeNames.size()]));
247         log.info("Unregistered the following NodeTypes '{}'.", nodeTypeNames);
248         NodeTypeRegistry.disableCheckForReferencesInContentException = false;
249     }
250 
251     private List<NodeTypeDefinition> getNodeTypeDefinitions(NodeTypeManager nodeTypeManager, List<String> nodeTypeNames) throws RepositoryException {
252         List<NodeTypeDefinition> nodeTypeDefinitions = new ArrayList<>();
253         for (String ntn : nodeTypeNames) {
254             nodeTypeDefinitions.add(nodeTypeManager.getNodeType(ntn));
255         }
256         return nodeTypeDefinitions;
257     }
258 
259     /**
260      * Transform a list of {@link NodeTypeDefinition}s to an array-list of their names.
261      */
262     private List<String> getNodeTypeNames(NamePathResolver namePathResolver, Set<Name> nodeTypeQNames) throws NamespaceException {
263         List<String> names = new ArrayList<>();
264 
265         for (Name qName : nodeTypeQNames) {
266             names.add(namePathResolver.getJCRName(qName));
267         }
268         return names;
269     }
270 
271     /**
272      * Create a Map containing all child NodeType of.<br>
273      * - key = NodeType name.<br>
274      * - value = {@link NodeTypeDefinition}.
275      *
276      * @deprecated since 5.4.3, use {@link NodeTypeRegistry#getDependentNodeTypes(Name)}. This function was added to keep
277      * the implementation jackrabbit-API agnostic. But we already have jackrabbit dependencies in the passed params, so
278      * it essentially is useless. Apart from that it was causing the migration to fail, as the returned Map did not retain
279      * the order of the nodeTypes.
280      */
281     @Deprecated
282     protected HashMap<String, NodeTypeDefinition> getDependentNodeTypes(NodeTypeDefinition nodeType, NodeTypeRegistry registry, NamePathResolver namePathResolver, NodeTypeManager nodeTypeManager) throws RepositoryException {
283 
284         HashMap<String, NodeTypeDefinition> dependentNodeType = new LinkedHashMap<String, NodeTypeDefinition>();
285         // Get the Name in order to get the Dependent Node type
286         Name qName = namePathResolver.getQName(nodeType.getName());
287         Set<Name> dependentNodesName = registry.getDependentNodeTypes(qName);
288         // for every dependent node type, call recursively in order to get all the dependency tree.
289         if (dependentNodesName != null && !dependentNodesName.isEmpty()) {
290             for (Name name : dependentNodesName) {
291                 dependentNodeType.putAll(getDependentNodeTypes(nodeTypeManager.getNodeType(namePathResolver.getJCRName(name)), registry, namePathResolver, nodeTypeManager));
292             }
293         }
294 
295         dependentNodeType.put(nodeType.getName(), nodeType);
296         return dependentNodeType;
297 
298     }
299 }