View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors.
8    *
9    * Unless otherwise indicated, all code in ModeShape is licensed
10   * to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   * 
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.jcr;
25  
26  import java.security.AccessControlException;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.Collection;
30  import java.util.Collections;
31  import java.util.List;
32  import javax.jcr.AccessDeniedException;
33  import javax.jcr.PropertyType;
34  import javax.jcr.RepositoryException;
35  import javax.jcr.UnsupportedRepositoryOperationException;
36  import javax.jcr.Value;
37  import javax.jcr.nodetype.NoSuchNodeTypeException;
38  import javax.jcr.nodetype.NodeDefinition;
39  import javax.jcr.nodetype.NodeType;
40  import javax.jcr.nodetype.NodeTypeDefinition;
41  import javax.jcr.nodetype.NodeTypeIterator;
42  import javax.jcr.nodetype.NodeTypeManager;
43  import javax.jcr.nodetype.PropertyDefinition;
44  import net.jcip.annotations.Immutable;
45  import org.modeshape.common.util.CheckArg;
46  import org.modeshape.graph.ExecutionContext;
47  import org.modeshape.graph.property.Name;
48  import org.modeshape.graph.property.NameFactory;
49  import org.modeshape.graph.property.Path;
50  import org.modeshape.graph.query.validate.Schemata;
51  import org.modeshape.jcr.nodetype.InvalidNodeTypeDefinitionException;
52  import org.modeshape.jcr.nodetype.NodeDefinitionTemplate;
53  import org.modeshape.jcr.nodetype.NodeTypeExistsException;
54  import org.modeshape.jcr.nodetype.NodeTypeTemplate;
55  import org.modeshape.jcr.nodetype.PropertyDefinitionTemplate;
56  
57  /**
58   * Local implementation of @{link NodeTypeManager}. This class handles translation between {@link Name}s and {@link String}s based
59   * on the namespace registry from the session's execution context in order to support transient namespace remappings. All
60   * {@link NodeType}s returned by this implementation are wrapped with the execution context of the session to allow proper ongoing
61   * handling of names. This implies that reference equality is not a safe test for node type equivalence.
62   * 
63   * @see RepositoryNodeTypeManager
64   */
65  @Immutable
66  public class JcrNodeTypeManager implements NodeTypeManager {
67  
68      private final JcrSession session;
69      private final RepositoryNodeTypeManager repositoryTypeManager;
70      private Schemata schemata;
71  
72      JcrNodeTypeManager( JcrSession session,
73                          RepositoryNodeTypeManager repositoryTypeManager ) {
74          this.session = session;
75          this.repositoryTypeManager = repositoryTypeManager;
76      }
77  
78      private final ExecutionContext context() {
79          return session.getExecutionContext();
80      }
81  
82      Schemata schemata() {
83          if (schemata == null) {
84              schemata = repositoryTypeManager.getRepositorySchemata().getSchemataForSession(session);
85              assert schemata != null;
86          }
87          return schemata;
88      }
89  
90      void signalNamespaceChanges() {
91          this.schemata = null;
92      }
93  
94      void signalExternalNodeTypeChanges() {
95          this.schemata = null;
96      }
97  
98      /**
99       * {@inheritDoc}
100      * 
101      * @see javax.jcr.nodetype.NodeTypeManager#getAllNodeTypes()
102      */
103     public NodeTypeIterator getAllNodeTypes() throws RepositoryException {
104         session.checkLive();
105         return new JcrNodeTypeIterator(repositoryTypeManager.getAllNodeTypes());
106     }
107 
108     /**
109      * {@inheritDoc}
110      * 
111      * @see javax.jcr.nodetype.NodeTypeManager#getMixinNodeTypes()
112      */
113     public NodeTypeIterator getMixinNodeTypes() throws RepositoryException {
114         session.checkLive();
115         Collection<JcrNodeType> rawTypes = repositoryTypeManager.getMixinNodeTypes();
116         List<JcrNodeType> types = new ArrayList<JcrNodeType>(rawTypes.size());
117 
118         // Need to return a version of the node type with the current context
119         for (JcrNodeType type : rawTypes) {
120             types.add(type.with(context()));
121         }
122 
123         return new JcrNodeTypeIterator(types);
124     }
125 
126     /**
127      * {@inheritDoc}
128      * 
129      * @see javax.jcr.nodetype.NodeTypeManager#getNodeType(java.lang.String)
130      */
131     public JcrNodeType getNodeType( String nodeTypeName ) throws NoSuchNodeTypeException, RepositoryException {
132         session.checkLive();
133         Name ntName = context().getValueFactories().getNameFactory().create(nodeTypeName);
134         JcrNodeType type = repositoryTypeManager.getNodeType(ntName);
135         if (type != null) {
136             type = type.with(context());
137             return type;
138         }
139         throw new NoSuchNodeTypeException(JcrI18n.typeNotFound.text(nodeTypeName));
140     }
141 
142     /**
143      * Returns the node type with the given name (if one exists)
144      * 
145      * @param nodeTypeName the name of the node type to be returned
146      * @return the node type with the given name (if one exists)
147      * @see RepositoryNodeTypeManager#getNodeType(Name)
148      */
149     JcrNodeType getNodeType( Name nodeTypeName ) {
150         JcrNodeType nodeType = repositoryTypeManager.getNodeType(nodeTypeName);
151 
152         if (nodeType != null) {
153             nodeType = nodeType.with(context());
154         }
155 
156         return nodeType;
157     }
158 
159     /**
160      * Returns true if and only if the node type with the given name exists.
161      * <p>
162      * This is equivalent to the following code:
163      * 
164      * <pre>
165      * try {
166      *     getNodeType(nodeTypeName);
167      *     return true;
168      * } catch (NoSuchNodeTypeException nsnte) {
169      *     return false;
170      * }
171      * </pre>
172      * 
173      * However, the implementation is more efficient that the approach listed above and does not rely upon exceptions.
174      * </p>
175      * 
176      * @param nodeTypeName the name of the node type
177      * @return true if the named node type does exist, or false otherwise
178      * @see RepositoryNodeTypeManager#hasNodeType(Name)
179      */
180     public boolean hasNodeType( String nodeTypeName ) {
181         Name ntName = context().getValueFactories().getNameFactory().create(nodeTypeName);
182         return repositoryTypeManager.hasNodeType(ntName);
183     }
184 
185     /**
186      * {@inheritDoc}
187      * 
188      * @see javax.jcr.nodetype.NodeTypeManager#getPrimaryNodeTypes()
189      */
190     public NodeTypeIterator getPrimaryNodeTypes() throws RepositoryException {
191         session.checkLive();
192         Collection<JcrNodeType> rawTypes = repositoryTypeManager.getPrimaryNodeTypes();
193         List<JcrNodeType> types = new ArrayList<JcrNodeType>(rawTypes.size());
194 
195         // Need to return a version of the node type with the current context
196         for (JcrNodeType type : rawTypes) {
197             types.add(type.with(context()));
198         }
199 
200         return new JcrNodeTypeIterator(types);
201     }
202 
203     /**
204      * Get the {@link NodeDefinition} for the root node.
205      * 
206      * @return the definition; never null
207      * @throws RepositoryException
208      * @throws NoSuchNodeTypeException
209      */
210     JcrNodeDefinition getRootNodeDefinition() throws NoSuchNodeTypeException, RepositoryException {
211         for (NodeDefinition definition : repositoryTypeManager.getNodeType(ModeShapeLexicon.ROOT).getChildNodeDefinitions()) {
212             if (definition.getName().equals(JcrNodeType.RESIDUAL_ITEM_NAME)) return (JcrNodeDefinition)definition;
213         }
214 
215         assert false; // should not get here
216         return null;
217     }
218 
219     /**
220      * Get the node definition given the supplied identifier.
221      * 
222      * @param definitionId the identifier of the node definition
223      * @return the node definition, or null if there is no such definition (or if the ID was null)
224      */
225     JcrNodeDefinition getNodeDefinition( NodeDefinitionId definitionId ) {
226         if (definitionId == null) return null;
227         return repositoryTypeManager.getChildNodeDefinition(definitionId);
228     }
229 
230     /**
231      * Get the property definition given the supplied identifier.
232      * 
233      * @param definitionId the identifier of the node definition
234      * @return the property definition, or null if there is no such definition (or if the ID was null)
235      */
236     JcrPropertyDefinition getPropertyDefinition( PropertyDefinitionId definitionId ) {
237         if (definitionId == null) return null;
238         return repositoryTypeManager.getPropertyDefinition(definitionId);
239     }
240 
241     /**
242      * Searches the supplied primary node type and the mixin node types for a property definition that is the best match for the
243      * given property name, property type, and value.
244      * <p>
245      * This method first attempts to find a single-valued property definition with the supplied property name and
246      * {@link Value#getType() value's property type} in the primary type, skipping any property definitions that are protected.
247      * The property definition is returned if it has a matching type (or has an {@link PropertyType#UNDEFINED undefined property
248      * type}) and the value satisfies the {@link PropertyDefinition#getValueConstraints() definition's constraints} . Otherwise,
249      * the process continues with each of the mixin types, in the order they are named.
250      * </p>
251      * <p>
252      * If no matching property definition could be found (and <code>checkMultiValuedDefinitions</code> parameter is
253      * <code>true</code>), the process is repeated except with multi-valued property definitions with the same name, property
254      * type, and compatible constraints, starting with the primary type and continuing with each mixin type.
255      * </p>
256      * <p>
257      * If no matching property definition could be found, and the process repeats by searching the primary type (and then mixin
258      * types) for single-valued property definitions with a compatible type, where the values can be safely cast to the
259      * definition's property type and still satisfy the definition's constraints.
260      * </p>
261      * <p>
262      * If no matching property definition could be found, the previous step is repeated with multi-valued property definitions.
263      * </p>
264      * <p>
265      * If no matching property definition could be found (and the supplied property name is not the residual name), the whole
266      * process repeats for residual property definitions (e.g., those that are defined with a {@link JcrNodeType#RESIDUAL_NAME "*"
267      * name}).
268      * </p>
269      * <p>
270      * Finally, if no satisfactory property definition could be found, this method returns null.
271      * </p>
272      * 
273      * @param primaryTypeName the name of the primary type; may not be null
274      * @param mixinTypeNames the names of the mixin types; may be null or empty if there are no mixins to include in the search
275      * @param propertyName the name of the property for which the definition should be retrieved. This method will automatically
276      *        look for residual definitions, but you can use {@link JcrNodeType#RESIDUAL_ITEM_NAME} to retrieve only the best
277      *        residual property definition (if any).
278      * @param value the value, or null if the property is being removed
279      * @param checkMultiValuedDefinitions true if the type's multi-valued property definitions should be considered, or false if
280      *        only single-value property definitions should be considered
281      * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if
282      *        this operation is being done from within internal implementations
283      * @return the best property definition, or <code>null</code> if no property definition allows the property with the supplied
284      *         name, type and number of values
285      */
286     final JcrPropertyDefinition findPropertyDefinition( Name primaryTypeName,
287                                                         List<Name> mixinTypeNames,
288                                                         Name propertyName,
289                                                         Value value,
290                                                         boolean checkMultiValuedDefinitions,
291                                                         boolean skipProtected ) {
292         return repositoryTypeManager.findPropertyDefinition(primaryTypeName,
293                                                             mixinTypeNames,
294                                                             propertyName,
295                                                             value,
296                                                             checkMultiValuedDefinitions,
297                                                             skipProtected);
298     }
299 
300     /**
301      * Searches the supplied primary node type and the mixin node types for a property definition that is the best match for the
302      * given property name, property type, and value. with the given name and property type that allows the supplied values.
303      * <p>
304      * This method first attempts to find a single-valued property definition with the supplied property name and
305      * {@link Value#getType() value's property type} in the primary type, skipping any property definitions that are protected.
306      * The property definition is returned if it has a matching type (or has an {@link PropertyType#UNDEFINED undefined property
307      * type}) and the value satisfies the {@link PropertyDefinition#getValueConstraints() definition's constraints} . Otherwise,
308      * the process continues with each of the mixin types, in the order they are named.
309      * </p>
310      * <p>
311      * If no matching property definition could be found (and <code>checkMultiValuedDefinitions</code> parameter is
312      * <code>true</code>), the process is repeated except with multi-valued property definitions with the same name, property
313      * type, and compatible constraints, starting with the primary type and continuing with each mixin type.
314      * </p>
315      * <p>
316      * If no matching property definition could be found, and the process repeats by searching the primary type (and then mixin
317      * types) for single-valued property definitions with a compatible type, where the values can be safely cast to the
318      * definition's property type and still satisfy the definition's constraints.
319      * </p>
320      * <p>
321      * If no matching property definition could be found, the previous step is repeated with multi-valued property definitions.
322      * </p>
323      * <p>
324      * If no matching property definition could be found (and the supplied property name is not the residual name), the whole
325      * process repeats for residual property definitions (e.g., those that are defined with a {@link JcrNodeType#RESIDUAL_NAME "*"
326      * name}).
327      * </p>
328      * <p>
329      * Finally, if no satisfactory property definition could be found, this method returns null.
330      * </p>
331      * 
332      * @param primaryTypeName the name of the primary type; may not be null
333      * @param mixinTypeNames the names of the mixin types; may be null or empty if there are no mixins to include in the search
334      * @param propertyName the name of the property for which the definition should be retrieved. This method will automatically
335      *        look for residual definitions, but you can use {@link JcrNodeType#RESIDUAL_ITEM_NAME} to retrieve only the best
336      *        residual property definition (if any).
337      * @param values the values
338      * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if
339      *        this operation is being done from within internal implementations
340      * @return the best property definition, or <code>null</code> if no property definition allows the property with the supplied
341      *         name, type and number of values
342      */
343     final JcrPropertyDefinition findPropertyDefinition( Name primaryTypeName,
344                                                         List<Name> mixinTypeNames,
345                                                         Name propertyName,
346                                                         Value[] values,
347                                                         boolean skipProtected ) {
348         return repositoryTypeManager.findPropertyDefinition(primaryTypeName, mixinTypeNames, propertyName, values, skipProtected);
349     }
350 
351     /**
352      * Determine if the property definitions of the supplied primary type and mixin types allow the property with the supplied
353      * name to be removed.
354      * 
355      * @param primaryTypeNameOfParent the name of the primary type for the parent node; may not be null
356      * @param mixinTypeNamesOfParent the names of the mixin types for the parent node; may be null or empty if there are no mixins
357      *        to include in the search
358      * @param propertyName the name of the property to be removed; may not be null
359      * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if
360      *        this operation is being done from within internal implementations
361      * @return true if at least one child node definition does not require children with the supplied name to exist, or false
362      *         otherwise
363      */
364     boolean canRemoveProperty( Name primaryTypeNameOfParent,
365                                List<Name> mixinTypeNamesOfParent,
366                                Name propertyName,
367                                boolean skipProtected ) {
368         return repositoryTypeManager.canRemoveProperty(primaryTypeNameOfParent,
369                                                        mixinTypeNamesOfParent,
370                                                        propertyName,
371                                                        skipProtected);
372     }
373 
374     /**
375      * Searches the supplied primary node type and the mixin node types of a parent node for a child node definition that is the
376      * best match for a new child with the given name, primary node type name, and whether there are existing children with the
377      * same name.
378      * 
379      * @param primaryTypeNameOfParent the name of the primary type for the parent node; may not be null
380      * @param mixinTypeNamesOfParent the names of the mixin types for the parent node; may be null or empty if there are no mixins
381      *        to include in the search
382      * @param childName the name of the child to be added to the parent; may not be null
383      * @param childPrimaryNodeType the name of the primary node type for the child node, or null if the primary type is not known
384      *        and the {@link NodeDefinition#getDefaultPrimaryType() definition's default primary type} will be used
385      * @param numberOfExistingChildrenWithSameName the number of existing children with the same name as the child to be added, or
386      *        0 if this new child will be the first child with this name (or if the number of children is not known)
387      * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if
388      *        this operation is being done from within internal implementations
389      * @return the best child node definition, or <code>null</code> if no node definition allows a new child with the supplied
390      *         name, primary type, and whether there are already children with the same name
391      */
392     final JcrNodeDefinition findChildNodeDefinition( Name primaryTypeNameOfParent,
393                                                      List<Name> mixinTypeNamesOfParent,
394                                                      Name childName,
395                                                      Name childPrimaryNodeType,
396                                                      int numberOfExistingChildrenWithSameName,
397                                                      boolean skipProtected ) {
398         return repositoryTypeManager.findChildNodeDefinition(primaryTypeNameOfParent,
399                                                              mixinTypeNamesOfParent,
400                                                              childName,
401                                                              childPrimaryNodeType,
402                                                              numberOfExistingChildrenWithSameName,
403                                                              skipProtected);
404     }
405 
406     /**
407      * Determine if the child node definitions of the supplied primary type and mixin types of a parent node allow all of the
408      * children with the supplied name to be removed.
409      * 
410      * @param primaryTypeNameOfParent the name of the primary type for the parent node; may not be null
411      * @param mixinTypeNamesOfParent the names of the mixin types for the parent node; may be null or empty if there are no mixins
412      *        to include in the search
413      * @param childName the name of the child to be added to the parent; may not be null
414      * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if
415      *        this operation is being done from within internal implementations
416      * @return true if at least one child node definition does not require children with the supplied name to exist, or false
417      *         otherwise
418      */
419     final boolean canRemoveAllChildren( Name primaryTypeNameOfParent,
420                                         List<Name> mixinTypeNamesOfParent,
421                                         Name childName,
422                                         boolean skipProtected ) {
423         return repositoryTypeManager.canRemoveAllChildren(primaryTypeNameOfParent,
424                                                           mixinTypeNamesOfParent,
425                                                           childName,
426                                                           skipProtected);
427     }
428 
429     /**
430      * Registers a new node type or updates an existing node type using the specified definition and returns the resulting
431      * {@link NodeType} object.
432      * <p>
433      * Typically, the object passed to this method will be a {@link NodeTypeTemplate} (a subclass of {@link NodeTypeDefinition})
434      * acquired from {@link JcrNodeTypeManager#createNodeTypeTemplate()} and then filled-in with definition information.
435      * </p>
436      * 
437      * @param template the new node type to register
438      * @param allowUpdate this flag is not used
439      * @return the {@code newly created node type}
440      * @throws InvalidNodeTypeDefinitionException if the {@code NodeTypeDefinition} is invalid
441      * @throws NodeTypeExistsException if {@code allowUpdate} is false and the {@code NodeTypeDefinition} specifies a node type
442      *         name that already exists
443      * @throws UnsupportedRepositoryOperationException if {@code allowUpdate} is true; ModeShape does not allow updating node
444      *         types at this time.
445      * @throws AccessDeniedException if the current session does not have the {@link ModeShapePermissions#REGISTER_TYPE register
446      *         type permission}.
447      * @throws RepositoryException if another error occurs
448      */
449     public NodeType registerNodeType( NodeTypeDefinition template,
450                                       boolean allowUpdate )
451         throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, UnsupportedRepositoryOperationException,
452         AccessDeniedException, RepositoryException {
453 
454         session.checkLive();
455         try {
456             session.checkPermission((Path)null, ModeShapePermissions.REGISTER_TYPE);
457         } catch (AccessControlException ace) {
458             throw new AccessDeniedException(ace);
459         }
460         try {
461             return this.repositoryTypeManager.registerNodeType(template);
462         } finally {
463             schemata = null;
464         }
465     }
466 
467     /**
468      * Registers or updates the specified Collection of {@code NodeTypeDefinition} objects. This method is used to register or
469      * update a set of node types with mutual dependencies. Returns an iterator over the resulting {@code NodeType} objects.
470      * <p>
471      * The effect of the method is "all or nothing"; if an error occurs, no node types are registered or updated.
472      * </p>
473      * 
474      * @param templates the new node types to register
475      * @param allowUpdates this flag is not used
476      * @return the {@code newly created node types}
477      * @throws InvalidNodeTypeDefinitionException if a {@code NodeTypeDefinition} within the collection is invalid
478      * @throws NodeTypeExistsException if {@code allowUpdate} is false and a {@code NodeTypeDefinition} within the collection
479      *         specifies a node type name that already exists
480      * @throws UnsupportedRepositoryOperationException if {@code allowUpdate} is true; ModeShape does not allow updating node
481      *         types at this time.
482      * @throws AccessDeniedException if the current session does not have the {@link ModeShapePermissions#REGISTER_TYPE register
483      *         type permission}.
484      * @throws RepositoryException if another error occurs
485      */
486     public NodeTypeIterator registerNodeTypes( Collection<NodeTypeDefinition> templates,
487                                                boolean allowUpdates )
488         throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, UnsupportedRepositoryOperationException,
489         AccessDeniedException, RepositoryException {
490 
491         session.checkLive();
492         try {
493             session.checkPermission((Path)null, ModeShapePermissions.REGISTER_TYPE);
494         } catch (AccessControlException ace) {
495             throw new AccessDeniedException(ace);
496         }
497         try {
498             return new JcrNodeTypeIterator(repositoryTypeManager.registerNodeTypes(templates));
499         } finally {
500             schemata = null;
501         }
502     }
503 
504     /**
505      * Registers the node types from the given {@code JcrNodeTypeSource}. This method is used to register or update a set of node
506      * types with mutual dependencies. Returns an iterator over the resulting {@code NodeType} objects.
507      * <p>
508      * The effect of the method is "all or nothing"; if an error occurs, no node types are registered or updated.
509      * </p>
510      * 
511      * @param nodeTypes the iterable object containing the new node types to register
512      * @return the {@code newly created node types}
513      * @throws InvalidNodeTypeDefinitionException if a {@code NodeTypeDefinition} within the collection is invalid
514      * @throws NodeTypeExistsException if {@code allowUpdate} is false and a {@code NodeTypeDefinition} within the collection
515      *         specifies a node type name that already exists
516      * @throws UnsupportedRepositoryOperationException if {@code allowUpdate} is true; ModeShape does not allow updating node
517      *         types at this time.
518      * @throws AccessDeniedException if the current session does not have the {@link ModeShapePermissions#REGISTER_TYPE register
519      *         type permission}.
520      * @throws RepositoryException if another error occurs
521      */
522     public NodeTypeIterator registerNodeTypes( Iterable<NodeTypeDefinition> nodeTypes )
523         throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, UnsupportedRepositoryOperationException,
524         AccessDeniedException, RepositoryException {
525 
526         try {
527             session.checkPermission((Path)null, ModeShapePermissions.REGISTER_TYPE);
528         } catch (AccessControlException ace) {
529             throw new AccessDeniedException(ace);
530         }
531 
532         try {
533             return new JcrNodeTypeIterator(this.repositoryTypeManager.registerNodeTypes(nodeTypes));
534         } finally {
535             schemata = null;
536         }
537     }
538 
539     /**
540      * Registers or updates the specified array of {@code NodeTypeDefinition} objects. This method is used to register or update a
541      * set of node types with mutual dependencies. Returns an iterator over the resulting {@code NodeType} objects.
542      * <p>
543      * The effect of the method is "all or nothing"; if an error occurs, no node types are registered or updated.
544      * </p>
545      * 
546      * @param ntds the new node types to register
547      * @param allowUpdate must be {@code false}; ModeShape does not allow updating node types at this time
548      * @return the {@code newly created node types}
549      * @throws InvalidNodeTypeDefinitionException if a {@code NodeTypeDefinition} within the collection is invalid
550      * @throws NodeTypeExistsException if {@code allowUpdate} is false and a {@code NodeTypeDefinition} within the collection
551      *         specifies a node type name that already exists
552      * @throws UnsupportedRepositoryOperationException if {@code allowUpdate} is true; ModeShape does not allow updating node
553      *         types at this time.
554      * @throws AccessDeniedException if the current session does not have the {@link ModeShapePermissions#REGISTER_TYPE register
555      *         type permission}.
556      * @throws RepositoryException if another error occurs
557      */
558     public NodeTypeIterator registerNodeTypes( NodeTypeDefinition[] ntds,
559                                                boolean allowUpdate )
560         throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, UnsupportedRepositoryOperationException,
561         RepositoryException {
562 
563         try {
564             session.checkPermission((Path)null, ModeShapePermissions.REGISTER_TYPE);
565         } catch (AccessControlException ace) {
566             throw new AccessDeniedException(ace);
567         }
568 
569         try {
570             return new JcrNodeTypeIterator(this.repositoryTypeManager.registerNodeTypes(ntds));
571         } finally {
572             schemata = null;
573         }
574     }
575 
576     /**
577      * Unregisters the named node type if it is not referenced by other node types as a supertype, a default primary type of a
578      * child node (or nodes), or a required primary type of a child node (or nodes).
579      * 
580      * @param nodeTypeName
581      * @throws NoSuchNodeTypeException if node type name does not correspond to a registered node type
582      * @throws InvalidNodeTypeDefinitionException if the node type with the given name cannot be unregistered because it is the
583      *         supertype, one of the required primary types, or a default primary type of another node type
584      * @throws AccessDeniedException if the current session does not have the {@link ModeShapePermissions#REGISTER_TYPE register
585      *         type permission}.
586      * @throws RepositoryException if any other error occurs
587      */
588     public void unregisterNodeType( String nodeTypeName )
589         throws NoSuchNodeTypeException, InvalidNodeTypeDefinitionException, RepositoryException {
590         unregisterNodeTypes(Collections.singleton(nodeTypeName));
591     }
592 
593     /**
594      * Allows the collection of node types to be unregistered if they are not referenced by other node types as supertypes,
595      * default primary types of child nodes, or required primary types of child nodes.
596      * 
597      * @param nodeTypeNames the names of the node types to be unregistered
598      * @throws NoSuchNodeTypeException if any of the node type names do not correspond to a registered node type
599      * @throws InvalidNodeTypeDefinitionException if any of the node types with the given names cannot be unregistered because
600      *         they are the supertype, one of the required primary types, or a default primary type of a node type that is not
601      *         being unregistered.
602      * @throws AccessDeniedException if the current session does not have the {@link ModeShapePermissions#REGISTER_TYPE register
603      *         type permission}.
604      * @throws RepositoryException if any other error occurs
605      */
606     public void unregisterNodeTypes( Collection<String> nodeTypeNames )
607         throws NoSuchNodeTypeException, InvalidNodeTypeDefinitionException, RepositoryException {
608         NameFactory nameFactory = context().getValueFactories().getNameFactory();
609 
610         try {
611             session.checkPermission((Path)null, ModeShapePermissions.REGISTER_TYPE);
612         } catch (AccessControlException ace) {
613             throw new AccessDeniedException(ace);
614         }
615 
616         Collection<Name> names = new ArrayList<Name>(nodeTypeNames.size());
617         for (String name : nodeTypeNames) {
618             names.add(nameFactory.create(name));
619         }
620         repositoryTypeManager.unregisterNodeType(names);
621         schemata = null;
622     }
623 
624     /**
625      * Allows the collection of node types to be unregistered if they are not referenced by other node types as supertypes,
626      * default primary types of child nodes, or required primary types of child nodes.
627      * 
628      * @param names the names of the node types to be unregistered
629      * @throws NoSuchNodeTypeException if any of the node type names do not correspond to a registered node type
630      * @throws InvalidNodeTypeDefinitionException if any of the node types with the given names cannot be unregistered because
631      *         they are the supertype, one of the required primary types, or a default primary type of a node type that is not
632      *         being unregistered.
633      * @throws AccessDeniedException if the current session does not have the {@link ModeShapePermissions#REGISTER_TYPE register
634      *         type permission}.
635      * @throws RepositoryException if any other error occurs
636      */
637     public void unregisterNodeTypes( String[] names ) throws NoSuchNodeTypeException, RepositoryException {
638         unregisterNodeTypes(Arrays.asList(names));
639     }
640 
641     /**
642      * Returns an empty {@code NodeTypeTemplate} which can then be used to define a node type and passed to
643      * {@link JcrNodeTypeManager#registerNodeType(NodeTypeDefinition, boolean)}
644      * 
645      * @return an empty {@code NodeTypeTemplate} which can then be used to define a node type and passed to
646      *         {@link JcrNodeTypeManager#registerNodeType(NodeTypeDefinition, boolean)}.
647      * @throws RepositoryException if another error occurs
648      */
649     public NodeTypeTemplate createNodeTypeTemplate() throws RepositoryException {
650         return new JcrNodeTypeTemplate(context());
651     }
652 
653     /**
654      * Returns a {@code NodeTypeTemplate} based on the definition given in {@code ntd}. This template can then be used to define a
655      * node type and passed to {@link JcrNodeTypeManager#registerNodeType(NodeTypeDefinition, boolean)}
656      * 
657      * @param ntd an existing node type definition; null values will be ignored
658      * @return an empty {@code NodeTypeTemplate} which can then be used to define a node type and passed to
659      *         {@link JcrNodeTypeManager#registerNodeType(NodeTypeDefinition, boolean)}.
660      * @throws RepositoryException if another error occurs
661      */
662     @SuppressWarnings( "unchecked" )
663     public NodeTypeTemplate createNodeTypeTemplate( NodeTypeDefinition ntd ) throws RepositoryException {
664         NodeTypeTemplate ntt = new JcrNodeTypeTemplate(context(), true);
665 
666         if (ntd != null) {
667             ntt.setName(ntd.getName());
668             ntt.setAbstract(ntd.isAbstract());
669             ntt.setDeclaredSuperTypeNames(ntd.getDeclaredSupertypeNames());
670             ntt.setMixin(ntd.isMixin());
671             ntt.setOrderableChildNodes(ntd.hasOrderableChildNodes());
672             ntt.setPrimaryItemName(ntd.getPrimaryItemName());
673             ntt.setQueryable(ntd.isQueryable());
674 
675             // copy child nodes and props
676             for (NodeDefinition nodeDefinition : ntd.getDeclaredChildNodeDefinitions()) {
677                 JcrNodeDefinitionTemplate ndt = new JcrNodeDefinitionTemplate(context());
678 
679                 ndt.setAutoCreated(nodeDefinition.isAutoCreated());
680                 ndt.setDefaultPrimaryType(nodeDefinition.getDefaultPrimaryTypeName());
681                 ndt.setMandatory(nodeDefinition.isMandatory());
682                 if (nodeDefinition.getName() != null) {
683                     ndt.setName(nodeDefinition.getName());
684                 }
685                 ndt.setOnParentVersion(nodeDefinition.getOnParentVersion());
686                 ndt.setProtected(nodeDefinition.isProtected());
687                 ndt.setRequiredPrimaryTypeNames(nodeDefinition.getRequiredPrimaryTypeNames());
688                 ndt.setSameNameSiblings(nodeDefinition.allowsSameNameSiblings());
689 
690                 ntt.getNodeDefinitionTemplates().add(ndt);
691             }
692 
693             for (PropertyDefinition propertyDefinition : ntd.getDeclaredPropertyDefinitions()) {
694                 JcrPropertyDefinitionTemplate pdt = new JcrPropertyDefinitionTemplate(context());
695 
696                 pdt.setAutoCreated(propertyDefinition.isAutoCreated());
697                 pdt.setAvailableQueryOperators(propertyDefinition.getAvailableQueryOperators());
698                 pdt.setDefaultValues(propertyDefinition.getDefaultValues());
699                 pdt.setFullTextSearchable(propertyDefinition.isFullTextSearchable());
700                 pdt.setMandatory(propertyDefinition.isMandatory());
701                 pdt.setMultiple(propertyDefinition.isMultiple());
702                 if (propertyDefinition.getName() != null) {
703                     pdt.setName(propertyDefinition.getName());
704                 }
705                 pdt.setOnParentVersion(propertyDefinition.getOnParentVersion());
706                 pdt.setProtected(propertyDefinition.isProtected());
707                 pdt.setQueryOrderable(propertyDefinition.isQueryOrderable());
708                 pdt.setRequiredType(propertyDefinition.getRequiredType());
709                 pdt.setValueConstraints(propertyDefinition.getValueConstraints());
710 
711                 ntt.getPropertyDefinitionTemplates().add(pdt);
712             }
713         }
714 
715         return ntt;
716     }
717 
718     /**
719      * Returns an empty {@code PropertyDefinitionTemplate} which can then be used to create a property definition and attached to
720      * a {@code NodeTypeTemplate}.
721      * 
722      * @return an empty {@code PropertyDefinitionTemplate} which can then be used to create a property definition and attached to
723      *         a {@code NodeTypeTemplate}.
724      * @throws RepositoryException if another error occurs
725      */
726     public NodeDefinitionTemplate createNodeDefinitionTemplate() throws RepositoryException {
727         return new JcrNodeDefinitionTemplate(context());
728     }
729 
730     /**
731      * Returns an empty {@code PropertyDefinitionTemplate} which can then be used to create a property definition and attached to
732      * a {@code NodeTypeTemplate}.
733      * 
734      * @return an empty {@code PropertyDefinitionTemplate} which can then be used to create a property definition and attached to
735      *         a {@code NodeTypeTemplate}.
736      * @throws RepositoryException if another error occurs
737      */
738     public PropertyDefinitionTemplate createPropertyDefinitionTemplate() throws RepositoryException {
739         return new JcrPropertyDefinitionTemplate(context());
740     }
741 
742     /**
743      * Determine if any of the test type names are equal to or have been derived from the primary type or any of the mixins.
744      * 
745      * @param testTypeNames the names of the types or mixins being tested against (never <code>null</code>)
746      * @param primaryTypeName the primary type name (never <code>null</code>)
747      * @param mixinNames the mixin names (may be <code>null</code>)
748      * @return <code>true</code> if at least one test type name is equal to or derived from the primary type or one of the mixins
749      * @throws RepositoryException if there is an exception obtaining node types
750      * @throws IllegalArgumentException if <code>testTypeNames</code> is <code>null</code> or empty or if
751      *         <code>primaryTypeName</code> is <code>null</code> or zero length
752      */
753     public boolean isDerivedFrom( String[] testTypeNames,
754                                   String primaryTypeName,
755                                   String[] mixinNames ) throws RepositoryException {
756         CheckArg.isNotEmpty(testTypeNames, "testTypeNames");
757         CheckArg.isNotEmpty(primaryTypeName, "primaryTypeName");
758 
759         NameFactory nameFactory = context().getValueFactories().getNameFactory();
760         Name[] typeNames = nameFactory.create(testTypeNames);
761 
762         // first check primary type
763         for (Name typeName : typeNames) {
764             JcrNodeType nodeType = getNodeType(typeName);
765 
766             if ((nodeType != null) && nodeType.isNodeType(primaryTypeName)) {
767                 return true;
768             }
769         }
770 
771         // now check mixins
772         if (mixinNames != null) {
773             for (String mixin : mixinNames) {
774                 for (Name typeName : typeNames) {
775                     JcrNodeType nodeType = getNodeType(typeName);
776 
777                     if ((nodeType != null) && nodeType.isNodeType(mixin)) {
778                         return true;
779                     }
780                 }
781             }
782         }
783 
784         return false;
785     }
786 }