View Javadoc

1   /**
2    * This file Copyright (c) 2013-2014 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.HashMap;
41  import java.util.LinkedList;
42  import java.util.List;
43  import java.util.Map;
44  import java.util.Set;
45  
46  import javax.jcr.RepositoryException;
47  import javax.jcr.nodetype.NodeTypeDefinition;
48  import javax.jcr.nodetype.NodeTypeManager;
49  import javax.jcr.nodetype.NodeTypeTemplate;
50  
51  import org.apache.commons.lang.StringUtils;
52  import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
53  import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
54  import org.apache.jackrabbit.spi.Name;
55  import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
56  import org.slf4j.Logger;
57  import org.slf4j.LoggerFactory;
58  
59  /**
60   * Task allowing to register or unregister nodeTypes.<br>
61   * <br>
62   * <b>For registration</b>, override {@link AbstractNodeTypeRegistrationTask#getNodeTypesToRegister(NodeTypeManager)}. <br>
63   * - For every {@link NodeTypeDefinition}, create them if not present or update the existing one's.<br>
64   * <b>Warning:</b> Be careful of the List order of {@link NodeTypeDefinition} <br>
65   * - Always put the parent NodeType first, or newly reference NodeType.<br>
66   * -- For example, you may want to create a new NodeType A that is referenced as Super by an Updated NodeType B, the list <br>
67   * -- should be [A,B] so A is created before B. Otherwise, A will not be updated.<br>
68   * In this case, the NodeType A will not be registered and a warn log is displayed.<br>
69   * <br>
70   * <b>For unregister</b>, override {@link AbstractNodeTypeRegistrationTask#getNodeTypesToUnregister(NodeTypeManager)}. <br>
71   * - For every NodeType defined by their name, unregister them. <br>
72   * <b>Warning:</b> A NodeType will be unregister only if no dependency issue is found<br>
73   * Dependency issue:<br>
74   * - The NodeType is referenced as child node by one ore more other NodeType definition<br>
75   * - The NodeType is defined as super by one ore more other NodeType definition<br>
76   * In this case, the NodeType will not be unregistered and a warn log is displayed.
77   */
78  public abstract class AbstractNodeTypeRegistrationTask extends AbstractRepositoryTask {
79  
80      private final Logger log = LoggerFactory.getLogger(AbstractNodeTypeRegistrationTask.class);
81  
82      private String workspaceName;
83  
84      public AbstractNodeTypeRegistrationTask(String name, String description, String workspaceName) {
85          super(name, description);
86          this.workspaceName = workspaceName;
87      }
88  
89      @Override
90      protected void doExecute(InstallContext installContext) throws RepositoryException, TaskExecutionException {
91          if (StringUtils.isBlank(workspaceName)) {
92              log.warn("No workspace defined. This task will resume");
93              return;
94          }
95  
96          NodeTypeManager nodeTypeManager = installContext.getJCRSession(workspaceName).getWorkspace().getNodeTypeManager();
97  
98          // Register nodeTypes
99          List<NodeTypeDefinition> nodeTypeToRegister = getNodeTypesToRegister(nodeTypeManager);
100         if (nodeTypeToRegister == null || nodeTypeToRegister.isEmpty()) {
101             log.info("No NodeType defined to register for the following workspace '{}'", workspaceName);
102         } else {
103             registerNodeTypes(nodeTypeManager, nodeTypeToRegister, installContext);
104         }
105         // Unregister nodeTypes
106         List<String> nodeTypeToUnregister = getNodeTypesToUnregister(nodeTypeManager);
107         if (nodeTypeToUnregister == null || nodeTypeToUnregister.isEmpty()) {
108             log.info("No NodeType defined to unregister for the following workspace '{}'", workspaceName);
109         } else {
110             unregisterNodeTypes(nodeTypeManager, nodeTypeToUnregister, installContext);
111         }
112 
113     }
114 
115     /**
116      * Create a List of {@link NodeTypeDefinition} to register or update (in case they are already created).
117      */
118     public abstract List<NodeTypeDefinition> getNodeTypesToRegister(NodeTypeManager nodeTypeManager) throws RepositoryException;
119 
120     /**
121      * Create a List of {@link NodeTypeDefinition#getName()} to unregister.
122      */
123     public abstract List<String> getNodeTypesToUnregister(NodeTypeManager nodeTypeManager) throws RepositoryException;
124 
125 
126     /**
127      * Register a NodeType List.<br>
128      * - In case of new NodeType definition, simply register it.
129      * - In case of the NodeType definition already exist: <br>
130      * -- Get all NodeType definition that have a dependency to the current definition <br>
131      * -- Unregister the complete list of dependent NodeType (including the current one) <br>
132      * -- Register the complete list of dependent NodeType, including the new NodeType definition <br>
133      * -- In case of exception, re register the original NodeType definition and log the exception.
134      */
135     private void registerNodeTypes(NodeTypeManager nodeTypeManager, List<NodeTypeDefinition> nodeTypesToRegister, InstallContext installContext) throws RepositoryException {
136         NodeTypeRegistry registry = ((NodeTypeManagerImpl) nodeTypeManager).getNodeTypeRegistry();
137         NamePathResolver namePathResolver = ((NodeTypeManagerImpl) nodeTypeManager).getNamePathResolver();
138         // Iterate the nodeTypeToRegister List
139         for (NodeTypeDefinition nodeType : nodeTypesToRegister) {
140             // NodeType already registered, Update (unregister, register)
141             if (nodeTypeManager.hasNodeType(nodeType.getName())) {
142                 // Get the original NodeType definition. Useful in case of dependency issue (the original NodeType is re-registered in case of exception).
143                 NodeTypeDefinition originalNodeType = nodeTypeManager.getNodeType(nodeType.getName());
144                 // Get the list of NodeType that reference the current nodeType
145                 HashMap<String, NodeTypeDefinition> nodeTypeToUnregister = getDependentNodeTypes(nodeType, registry, namePathResolver, nodeTypeManager);
146                 // UNREGISTRATION
147                 NodeTypeRegistry.disableCheckForReferencesInContentException = true;
148                 log.info("In order to register '{}', the following depending node types have to be unregistered first: {}.", nodeType.getName(), nodeTypeToUnregister.keySet());
149                 final String[] nodeTypesToUnregisterNames = nodeTypeToUnregister.keySet().toArray(new String[nodeTypeToUnregister.keySet().size()]);
150                 nodeTypeManager.unregisterNodeTypes(nodeTypesToUnregisterNames);
151                 NodeTypeRegistry.disableCheckForReferencesInContentException = false;
152 
153                 // RE-REGISTRATION
154                 final Map<String, NodeTypeTemplate> templates = new HashMap<String, NodeTypeTemplate>();
155                 try {
156                     for (NodeTypeDefinition nodeTypeDefinition : nodeTypeToUnregister.values()) {
157                         // "transforming" each definition into a template, so that nodeTypeManager.registerNodeTypes doesn't barf unnecessary exceptions.
158                         templates.put(nodeTypeDefinition.getName(), nodeTypeManager.createNodeTypeTemplate(nodeTypeDefinition));
159                     }
160 
161                     log.info("Registering the following '{}' node type. Reregistered the following dependency: {}", nodeType.getName(), nodeTypeToUnregister.keySet());
162                     nodeTypeManager.registerNodeTypes(templates.values().toArray(new NodeTypeDefinition[nodeTypeToUnregister.values().size()]), true);
163 
164                 } catch (RepositoryException re) {
165                     // Register the previous version of the NodeType
166                     NodeTypeTemplate originalTemplate =  nodeTypeManager.createNodeTypeTemplate(originalNodeType);
167                     nodeTypeManager.registerNodeType(originalTemplate, false);
168                     // Register the dependent child
169                     templates.remove(nodeType.getName());
170                     nodeTypeManager.registerNodeTypes(templates.values().toArray(new NodeTypeDefinition[templates.values().size()]), true);
171                     installContext.warn("Registration (update of an existing NodeType) of the following NodeType generated exceptions '" + nodeType.getName() + "'.The original version of the definition is kept. Please check the NodeType dependency and the input list order");
172                     log.warn("Not able to register the following node type {}. The original definition is kept.", nodeType.getName(), re);
173                 }
174             } else {
175                 // NodeType is new, Create (register)
176                 nodeTypeManager.registerNodeType(nodeType, false);
177                 log.info("Registering new '{}' node type.", nodeType.getName());
178             }
179         }
180     }
181 
182     /**
183      * Create a Map containing all child NodeType of.<br>
184      * - key = NodeType name.<br>
185      * - value = {@link NodeTypeDefinition}.
186      */
187     protected HashMap<String, NodeTypeDefinition> getDependentNodeTypes(NodeTypeDefinition nodeType, NodeTypeRegistry registry, NamePathResolver namePathResolver, NodeTypeManager nodeTypeManager) throws RepositoryException {
188 
189         HashMap<String, NodeTypeDefinition> dependentNodeType = new HashMap<String, NodeTypeDefinition>();
190         // Get the Name in order to get the Dependent Node type
191         Name qName = namePathResolver.getQName(nodeType.getName());
192         Set<Name> dependentNodesName = registry.getDependentNodeTypes(qName);
193         // for every dependent node type, call recursively in order to get all the dependency tree.
194         if (dependentNodesName != null && !dependentNodesName.isEmpty()) {
195             for (Name name : dependentNodesName) {
196                 dependentNodeType.putAll(getDependentNodeTypes(nodeTypeManager.getNodeType(namePathResolver.getJCRName(name)), registry, namePathResolver, nodeTypeManager));
197             }
198         }
199 
200         dependentNodeType.put(nodeType.getName(), nodeType);
201         return dependentNodeType;
202 
203     }
204 
205     /**
206      * Unregister all NodeType.
207      */
208     private void unregisterNodeTypes(NodeTypeManager nodeTypeManager, List<String> nodeTypesToUnregister, InstallContext installContext) throws RepositoryException {
209         LinkedList<String> filteredNodeTypeToUnregister = getFilteredNodesToUnregister(nodeTypeManager, nodeTypesToUnregister);
210         try {
211             NodeTypeRegistry.disableCheckForReferencesInContentException = true;
212             nodeTypeManager.unregisterNodeTypes(filteredNodeTypeToUnregister.toArray(new String[filteredNodeTypeToUnregister.size()]));
213             log.info("Unregistered the following NodeTypes '{}'", filteredNodeTypeToUnregister.toArray());
214             NodeTypeRegistry.disableCheckForReferencesInContentException = false;
215         } catch (RepositoryException re) {
216             // IN CASE OF EXCEPTION:
217             installContext.warn("Unregistration of NodeTypes generated exceptions. NodeType will not be Unregistered. Please check the NodeType dependency");
218             log.warn("Not able to unregister the following node type {}. The original definition is kept.", filteredNodeTypeToUnregister.toArray(), re);
219         }
220     }
221 
222     /**
223      * Filter the user defined NodeType to unregister.<br>
224      * Only keep the currently registered NodeType.
225      */
226     private LinkedList<String> getFilteredNodesToUnregister(NodeTypeManager nodeTypeManager, List<String> nodeTypeToUnregister) throws RepositoryException {
227         LinkedList<String> filteredNodeTypes = new LinkedList<String>();
228         for (String nodeTypeName : nodeTypeToUnregister) {
229             if (nodeTypeManager.hasNodeType(nodeTypeName)) {
230                 filteredNodeTypes.add(nodeTypeName);
231             } else {
232                 log.warn("Node type '{}' is not registered . No unregistration will be performed", nodeTypeName);
233             }
234         }
235         return filteredNodeTypes;
236     }
237 }