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.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.HashMap;
31  import java.util.LinkedList;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Set;
35  import java.util.concurrent.locks.ReadWriteLock;
36  import java.util.concurrent.locks.ReentrantReadWriteLock;
37  import javax.jcr.PropertyType;
38  import javax.jcr.RepositoryException;
39  import javax.jcr.UnsupportedRepositoryOperationException;
40  import javax.jcr.Value;
41  import javax.jcr.ValueFormatException;
42  import javax.jcr.nodetype.NoSuchNodeTypeException;
43  import javax.jcr.nodetype.NodeDefinition;
44  import javax.jcr.nodetype.NodeType;
45  import javax.jcr.nodetype.NodeTypeDefinition;
46  import javax.jcr.nodetype.PropertyDefinition;
47  import javax.jcr.query.InvalidQueryException;
48  import javax.jcr.version.OnParentVersionAction;
49  import net.jcip.annotations.GuardedBy;
50  import net.jcip.annotations.ThreadSafe;
51  import org.modeshape.common.collection.Problems;
52  import org.modeshape.common.i18n.I18n;
53  import org.modeshape.common.text.TextEncoder;
54  import org.modeshape.common.text.XmlNameEncoder;
55  import org.modeshape.common.util.CheckArg;
56  import org.modeshape.common.util.Logger;
57  import org.modeshape.graph.ExecutionContext;
58  import org.modeshape.graph.Graph;
59  import org.modeshape.graph.Location;
60  import org.modeshape.graph.Subgraph;
61  import org.modeshape.graph.observe.Changes;
62  import org.modeshape.graph.property.Name;
63  import org.modeshape.graph.property.NameFactory;
64  import org.modeshape.graph.property.Path;
65  import org.modeshape.graph.property.PathFactory;
66  import org.modeshape.graph.property.PathNotFoundException;
67  import org.modeshape.graph.property.Property;
68  import org.modeshape.graph.property.PropertyFactory;
69  import org.modeshape.graph.query.QueryResults;
70  import org.modeshape.graph.query.model.QueryCommand;
71  import org.modeshape.graph.query.model.TypeSystem;
72  import org.modeshape.graph.query.parse.QueryParser;
73  import org.modeshape.graph.query.parse.SqlQueryParser;
74  import org.modeshape.graph.query.validate.Schemata;
75  import org.modeshape.graph.request.ChangeRequest;
76  import org.modeshape.jcr.nodetype.InvalidNodeTypeDefinitionException;
77  import org.modeshape.jcr.nodetype.NodeTypeExistsException;
78  
79  /**
80   * The {@link RepositoryNodeTypeManager} is the maintainer of node type information for the entire repository at run-time. The
81   * repository manager maintains a list of all node types and the ability to retrieve node types by {@link Name}.
82   * <p>
83   * The JCR 1.0 and 2.0 specifications both require that node type information be shared across all sessions within a repository
84   * and that the {@link javax.jcr.nodetype.NodeTypeManager} perform operations based on the string versions of {@link Name}s based
85   * on the permanent (workspace-scoped) and transient (session-scoped) namespace mappings. ModeShape achieves this by maintaining a
86   * single master repository of all node type information (the {@link RepositoryNodeTypeManager}) and per-session wrappers (
87   * {@link JcrNodeTypeManager}) for this master repository that perform {@link String} to {@link Name} translation based on the
88   * {@link javax.jcr.Session}'s transient mappings and then delegating node type lookups to the repository manager.
89   * </p>
90   */
91  @ThreadSafe
92  class RepositoryNodeTypeManager implements JcrSystemObserver {
93      private static final Logger LOGGER = Logger.getLogger(RepositoryNodeTypeManager.class);
94  
95      private static final TextEncoder NAME_ENCODER = new XmlNameEncoder();
96  
97      private final JcrRepository repository;
98      private final QueryParser queryParser;
99      private final ExecutionContext context;
100     private final Path nodeTypesPath;
101 
102     @GuardedBy( "nodeTypeManagerLock" )
103     private final Map<Name, JcrNodeType> nodeTypes;
104     @GuardedBy( "nodeTypeManagerLock" )
105     private final Map<PropertyDefinitionId, JcrPropertyDefinition> propertyDefinitions;
106     @GuardedBy( "nodeTypeManagerLock" )
107     private final Map<NodeDefinitionId, JcrNodeDefinition> childNodeDefinitions;
108     @GuardedBy( "nodeTypeManagerLock" )
109     private NodeTypeSchemata schemata;
110     private final PropertyFactory propertyFactory;
111     private final PathFactory pathFactory;
112     private final NameFactory nameFactory;
113     private final ReadWriteLock nodeTypeManagerLock = new ReentrantReadWriteLock();
114     private final boolean includeColumnsForInheritedProperties;
115 
116     /**
117      * List of ways to filter the returned property definitions
118      * 
119      * @see RepositoryNodeTypeManager#findPropertyDefinitions(List, Name, PropertyCardinality, List)
120      */
121     private enum PropertyCardinality {
122         SINGLE_VALUED_ONLY,
123         MULTI_VALUED_ONLY,
124         ANY
125     }
126 
127     /**
128      * List of ways to filter the returned node definitions
129      * 
130      * @see RepositoryNodeTypeManager#findChildNodeDefinitions(List, Name, NodeCardinality, List)
131      */
132     private enum NodeCardinality {
133         NO_SAME_NAME_SIBLINGS,
134         SAME_NAME_SIBLINGS,
135         ANY
136     }
137 
138     RepositoryNodeTypeManager( JcrRepository repository,
139                                Path nodeTypesPath,
140                                boolean includeColumnsForInheritedProperties ) {
141         this.repository = repository;
142         this.nodeTypesPath = nodeTypesPath;
143         this.context = repository.getExecutionContext();
144         this.includeColumnsForInheritedProperties = includeColumnsForInheritedProperties;
145         this.propertyFactory = context.getPropertyFactory();
146         this.pathFactory = context.getValueFactories().getPathFactory();
147         this.nameFactory = context.getValueFactories().getNameFactory();
148 
149         propertyDefinitions = new HashMap<PropertyDefinitionId, JcrPropertyDefinition>();
150         childNodeDefinitions = new HashMap<NodeDefinitionId, JcrNodeDefinition>();
151         nodeTypes = new HashMap<Name, JcrNodeType>(50);
152         queryParser = new SqlQueryParser();
153 
154         if (nodeTypesPath != null) {
155             Graph systemGraph = repository.createSystemGraph(context);
156             try {
157                 systemGraph.getNodeAt(nodeTypesPath);
158             } catch (PathNotFoundException pnfe) {
159                 systemGraph.create(nodeTypesPath).with(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.NODE_TYPES).and();
160             }
161         }
162     }
163 
164     /**
165      * Return an immutable snapshot of the node types that are currently registered in this node type manager.
166      * 
167      * @return the immutable collection of (immutable) node types; never null
168      */
169     public Collection<JcrNodeType> getAllNodeTypes() {
170         try {
171             nodeTypeManagerLock.readLock().lock();
172             return Collections.unmodifiableCollection(new ArrayList<JcrNodeType>(nodeTypes.values()));
173         } finally {
174             nodeTypeManagerLock.readLock().unlock();
175         }
176     }
177 
178     /**
179      * Return an immutable snapshot of the mixin node types that are currently registered in this node type manager.
180      * 
181      * @return the immutable collection of (immutable) mixin node types; never null
182      * @see #getPrimaryNodeTypes()
183      */
184     public Collection<JcrNodeType> getMixinNodeTypes() {
185         try {
186             nodeTypeManagerLock.readLock().lock();
187 
188             List<JcrNodeType> types = new ArrayList<JcrNodeType>(nodeTypes.size());
189 
190             for (JcrNodeType nodeType : nodeTypes.values()) {
191                 if (nodeType.isMixin()) types.add(nodeType);
192             }
193 
194             return types;
195         } finally {
196             nodeTypeManagerLock.readLock().unlock();
197         }
198     }
199 
200     /**
201      * Return an immutable snapshot of the primary node types that are currently registered in this node type manager.
202      * 
203      * @return the immutable collection of (immutable) primary node types; never null
204      * @see #getMixinNodeTypes()
205      */
206     public Collection<JcrNodeType> getPrimaryNodeTypes() {
207         try {
208             nodeTypeManagerLock.readLock().lock();
209             List<JcrNodeType> types = new ArrayList<JcrNodeType>(nodeTypes.size());
210 
211             for (JcrNodeType nodeType : nodeTypes.values()) {
212                 if (!nodeType.isMixin()) types.add(nodeType);
213             }
214 
215             return types;
216         } finally {
217             nodeTypeManagerLock.readLock().unlock();
218         }
219     }
220 
221     public JcrPropertyDefinition getPropertyDefinition( PropertyDefinitionId id ) {
222         try {
223             nodeTypeManagerLock.readLock().lock();
224             return propertyDefinitions.get(id);
225         } finally {
226             nodeTypeManagerLock.readLock().unlock();
227         }
228     }
229 
230     public JcrNodeDefinition getChildNodeDefinition( NodeDefinitionId id ) {
231         try {
232             nodeTypeManagerLock.readLock().lock();
233             return childNodeDefinitions.get(id);
234         } finally {
235             nodeTypeManagerLock.readLock().unlock();
236         }
237     }
238 
239     NodeTypeSchemata getRepositorySchemata() {
240         try {
241             nodeTypeManagerLock.writeLock().lock();
242             if (schemata == null) {
243                 schemata = new NodeTypeSchemata(context, nodeTypes, propertyDefinitions.values(),
244                                                 includeColumnsForInheritedProperties);
245             }
246             return schemata;
247         } finally {
248             nodeTypeManagerLock.writeLock().unlock();
249         }
250     }
251 
252     void signalNamespaceChanges() {
253         try {
254             nodeTypeManagerLock.writeLock().lock();
255             schemata = null;
256         } finally {
257             nodeTypeManagerLock.writeLock().unlock();
258         }
259         this.schemata = null;
260     }
261 
262     JcrNodeType getNodeType( Name nodeTypeName ) {
263         try {
264             nodeTypeManagerLock.readLock().lock();
265             return nodeTypes.get(nodeTypeName);
266         } finally {
267             nodeTypeManagerLock.readLock().unlock();
268         }
269     }
270 
271     /**
272      * Tests if the named node type is registered.
273      * <p>
274      * The return value of this method is equivalent to {@code getNodeType(nodeTypeName) != null}, although the implementation is
275      * marginally more efficient that this approach.
276      * </p>
277      * 
278      * @param nodeTypeName the name of the node type to check
279      * @return true if a node type with the given name is registered, false otherwise
280      */
281     boolean hasNodeType( Name nodeTypeName ) {
282         try {
283             nodeTypeManagerLock.readLock().lock();
284             return nodeTypes.containsKey(nodeTypeName);
285         } finally {
286             nodeTypeManagerLock.readLock().unlock();
287         }
288     }
289 
290     /**
291      * Searches the supplied primary node type and the mixin node types for a property definition that is the best match for the
292      * given property name, property type, and value.
293      * <p>
294      * This method first attempts to find a single-valued property definition with the supplied property name and
295      * {@link Value#getType() value's property type} in the primary type, skipping any property definitions that are protected.
296      * The property definition is returned if it has a matching type (or has an {@link PropertyType#UNDEFINED undefined property
297      * type}) and the value satisfies the {@link PropertyDefinition#getValueConstraints() definition's constraints}. Otherwise,
298      * the process continues with each of the mixin types, in the order they are named.
299      * </p>
300      * <p>
301      * If no matching property definition could be found (and <code>checkMultiValuedDefinitions</code> parameter is
302      * <code>true</code>), the process is repeated except with multi-valued property definitions with the same name, property
303      * type, and compatible constraints, starting with the primary type and continuing with each mixin type.
304      * </p>
305      * <p>
306      * If no matching property definition could be found, and the process repeats by searching the primary type (and then mixin
307      * types) for single-valued property definitions with a compatible type, where the values can be safely cast to the
308      * definition's property type and still satisfy the definition's constraints.
309      * </p>
310      * <p>
311      * If no matching property definition could be found, the previous step is repeated with multi-valued property definitions.
312      * </p>
313      * <p>
314      * If no matching property definition could be found (and the supplied property name is not the residual name), the whole
315      * process repeats for residual property definitions (e.g., those that are defined with a {@link JcrNodeType#RESIDUAL_NAME "*"
316      * name}).
317      * </p>
318      * <p>
319      * Finally, if no satisfactory property definition could be found, this method returns null.
320      * </p>
321      * 
322      * @param primaryTypeName the name of the primary type; may not be null
323      * @param mixinTypeNames the names of the mixin types; may be null or empty if there are no mixins to include in the search
324      * @param propertyName the name of the property for which the definition should be retrieved. This method will automatically
325      *        look for residual definitions, but you can use {@link JcrNodeType#RESIDUAL_ITEM_NAME} to retrieve only the best
326      *        residual property definition (if any).
327      * @param value the value, or null if the property is being removed
328      * @param checkMultiValuedDefinitions true if the type's multi-valued property definitions should be considered, or false if
329      *        only single-value property definitions should be considered
330      * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if
331      *        this operation is being done from within internal implementations
332      * @return the best property definition, or <code>null</code> if no property definition allows the property with the supplied
333      *         name, type and number of values
334      */
335     JcrPropertyDefinition findPropertyDefinition( Name primaryTypeName,
336                                                   List<Name> mixinTypeNames,
337                                                   Name propertyName,
338                                                   Value value,
339                                                   boolean checkMultiValuedDefinitions,
340                                                   boolean skipProtected ) {
341         boolean setToEmpty = value == null;
342 
343         /*
344          * We use this flag to indicate that there was a definition encountered with the same name.  If
345          * a named definition (or definitions - for example the same node type could define a LONG and BOOLEAN
346          * version of the same property) is encountered and no match is found for the name, then processing should not
347          * proceed.  If processing did proceed, a residual definition might be found and matched.  This would 
348          * lead to a situation where a node defined a type for a named property, but contained a property with 
349          * the same name and the wrong type. 
350          */
351         boolean matchedOnName = false;
352 
353         // Look for a single-value property definition on the primary type that matches by name and type ...
354         JcrNodeType primaryType = getNodeType(primaryTypeName);
355         if (primaryType != null) {
356             for (JcrPropertyDefinition definition : primaryType.allSingleValuePropertyDefinitions(propertyName)) {
357                 matchedOnName = true;
358                 // See if the definition allows the value ...
359                 if (skipProtected && definition.isProtected()) return null;
360                 if (setToEmpty) {
361                     if (!definition.isMandatory()) return definition;
362                     // Otherwise this definition doesn't work, so continue with the next ...
363                     continue;
364                 }
365                 assert value != null;
366                 // We can use the definition if it matches the type and satisfies the constraints ...
367                 int type = definition.getRequiredType();
368                 // Don't check constraints on reference properties
369                 if (type == PropertyType.REFERENCE && type == value.getType()) return definition;
370                 if ((type == PropertyType.UNDEFINED || type == value.getType()) && definition.satisfiesConstraints(value)) return definition;
371             }
372 
373             if (matchedOnName) {
374                 if (value != null) {
375                     for (JcrPropertyDefinition definition : primaryType.allSingleValuePropertyDefinitions(propertyName)) {
376                         // See if the definition allows the value ...
377                         if (skipProtected && definition.isProtected()) return null;
378                         // Don't check constraints on reference properties
379                         int type = definition.getRequiredType();
380                         if (type == PropertyType.REFERENCE && definition.canCastToType(value)) {
381                             return definition;
382                         }
383                         if (definition.canCastToTypeAndSatisfyConstraints(value)) return definition;
384                     }
385                 }
386 
387                 if (checkMultiValuedDefinitions) {
388                     // Look for a multi-value property definition on the primary type that matches by name and type ...
389                     for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) {
390                         // See if the definition allows the value ...
391                         if (skipProtected && definition.isProtected()) return null;
392                         if (setToEmpty) {
393                             if (!definition.isMandatory()) return definition;
394                             // Otherwise this definition doesn't work, so continue with the next ...
395                             continue;
396                         }
397                         assert value != null;
398                         // We can use the definition if it matches the type and satisfies the constraints ...
399                         int type = definition.getRequiredType();
400                         // Don't check constraints on reference properties
401                         if (type == PropertyType.REFERENCE && type == value.getType()) return definition;
402                         if ((type == PropertyType.UNDEFINED || type == value.getType()) && definition.satisfiesConstraints(value)) return definition;
403                     }
404                     if (value != null) {
405                         for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) {
406                             // See if the definition allows the value ...
407                             if (skipProtected && definition.isProtected()) return null;
408                             assert definition.getRequiredType() != PropertyType.UNDEFINED;
409                             // Don't check constraints on reference properties
410                             int type = definition.getRequiredType();
411                             if (type == PropertyType.REFERENCE && definition.canCastToType(value)) {
412                                 return definition;
413                             }
414                             if (definition.canCastToTypeAndSatisfyConstraints(value)) return definition;
415                         }
416                     }
417                 }
418                 return null;
419             }
420         }
421 
422         // Look for a single-value property definition on the mixin types that matches by name and type ...
423         List<JcrNodeType> mixinTypes = null;
424         if (mixinTypeNames != null && !mixinTypeNames.isEmpty()) {
425             mixinTypes = new LinkedList<JcrNodeType>();
426             for (Name mixinTypeName : mixinTypeNames) {
427                 JcrNodeType mixinType = getNodeType(mixinTypeName);
428                 if (mixinType == null) continue;
429                 mixinTypes.add(mixinType);
430                 for (JcrPropertyDefinition definition : mixinType.allSingleValuePropertyDefinitions(propertyName)) {
431                     matchedOnName = true;
432                     // See if the definition allows the value ...
433                     if (skipProtected && definition.isProtected()) return null;
434                     if (setToEmpty) {
435                         if (!definition.isMandatory()) return definition;
436                         // Otherwise this definition doesn't work, so continue with the next ...
437                         continue;
438                     }
439                     assert value != null;
440                     // We can use the definition if it matches the type and satisfies the constraints ...
441                     int type = definition.getRequiredType();
442                     // Don't check constraints on reference properties
443                     if (type == PropertyType.REFERENCE && type == value.getType()) return definition;
444                     if ((type == PropertyType.UNDEFINED || type == value.getType()) && definition.satisfiesConstraints(value)) return definition;
445                 }
446                 if (matchedOnName) {
447                     if (value != null) {
448                         for (JcrPropertyDefinition definition : mixinType.allSingleValuePropertyDefinitions(propertyName)) {
449                             // See if the definition allows the value ...
450                             if (skipProtected && definition.isProtected()) return null;
451                             assert definition.getRequiredType() != PropertyType.UNDEFINED;
452                             // Don't check constraints on reference properties
453                             int type = definition.getRequiredType();
454                             if (type == PropertyType.REFERENCE && definition.canCastToType(value)) {
455                                 return definition;
456                             }
457                             if (definition.canCastToTypeAndSatisfyConstraints(value)) return definition;
458                         }
459                     }
460 
461                     if (checkMultiValuedDefinitions) {
462                         for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) {
463                             // See if the definition allows the value ...
464                             if (skipProtected && definition.isProtected()) return null;
465                             if (setToEmpty) {
466                                 if (!definition.isMandatory()) return definition;
467                                 // Otherwise this definition doesn't work, so continue with the next ...
468                                 continue;
469                             }
470                             assert value != null;
471                             // We can use the definition if it matches the type and satisfies the constraints ...
472                             int type = definition.getRequiredType();
473                             // Don't check constraints on reference properties
474                             if (type == PropertyType.REFERENCE && type == value.getType()) return definition;
475                             if ((type == PropertyType.UNDEFINED || type == value.getType())
476                                 && definition.satisfiesConstraints(value)) return definition;
477                         }
478                         if (value != null) {
479                             for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) {
480                                 matchedOnName = true;
481                                 // See if the definition allows the value ...
482                                 if (skipProtected && definition.isProtected()) return null;
483                                 assert definition.getRequiredType() != PropertyType.UNDEFINED;
484                                 // Don't check constraints on reference properties
485                                 int type = definition.getRequiredType();
486                                 if (type == PropertyType.REFERENCE && definition.canCastToType(value)) {
487                                     return definition;
488                                 }
489                                 if (definition.canCastToTypeAndSatisfyConstraints(value)) return definition;
490 
491                             }
492                         }
493                     }
494 
495                     return null;
496                 }
497             }
498         }
499 
500         if (checkMultiValuedDefinitions) {
501             if (primaryType != null) {
502                 // Look for a multi-value property definition on the primary type that matches by name and type ...
503                 for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) {
504                     matchedOnName = true;
505                     // See if the definition allows the value ...
506                     if (skipProtected && definition.isProtected()) return null;
507                     if (setToEmpty) {
508                         if (!definition.isMandatory()) return definition;
509                         // Otherwise this definition doesn't work, so continue with the next ...
510                         continue;
511                     }
512                     assert value != null;
513                     // We can use the definition if it matches the type and satisfies the constraints ...
514                     int type = definition.getRequiredType();
515                     // Don't check constraints on reference properties
516                     if (type == PropertyType.REFERENCE && type == value.getType()) return definition;
517                     if ((type == PropertyType.UNDEFINED || type == value.getType()) && definition.satisfiesConstraints(value)) return definition;
518                 }
519                 if (value != null) {
520                     for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) {
521                         matchedOnName = true;
522                         // See if the definition allows the value ...
523                         if (skipProtected && definition.isProtected()) return null;
524                         assert definition.getRequiredType() != PropertyType.UNDEFINED;
525                         // Don't check constraints on reference properties
526                         int type = definition.getRequiredType();
527                         if (type == PropertyType.REFERENCE && definition.canCastToType(value)) {
528                             return definition;
529                         }
530                         if (definition.canCastToTypeAndSatisfyConstraints(value)) return definition;
531                     }
532                 }
533             }
534 
535             if (matchedOnName) return null;
536 
537             if (mixinTypeNames != null && !mixinTypeNames.isEmpty()) {
538                 mixinTypes = new LinkedList<JcrNodeType>();
539                 for (Name mixinTypeName : mixinTypeNames) {
540                     JcrNodeType mixinType = getNodeType(mixinTypeName);
541                     if (mixinType == null) continue;
542                     mixinTypes.add(mixinType);
543                     for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) {
544                         matchedOnName = true;
545                         // See if the definition allows the value ...
546                         if (skipProtected && definition.isProtected()) return null;
547                         if (setToEmpty) {
548                             if (!definition.isMandatory()) return definition;
549                             // Otherwise this definition doesn't work, so continue with the next ...
550                             continue;
551                         }
552                         assert value != null;
553                         // We can use the definition if it matches the type and satisfies the constraints ...
554                         int type = definition.getRequiredType();
555                         // Don't check constraints on reference properties
556                         if (type == PropertyType.REFERENCE && type == value.getType()) return definition;
557                         if ((type == PropertyType.UNDEFINED || type == value.getType()) && definition.satisfiesConstraints(value)) return definition;
558                     }
559                     if (value != null) {
560                         for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) {
561                             matchedOnName = true;
562                             // See if the definition allows the value ...
563                             if (skipProtected && definition.isProtected()) return null;
564                             assert definition.getRequiredType() != PropertyType.UNDEFINED;
565                             // Don't check constraints on reference properties
566                             int type = definition.getRequiredType();
567                             if (type == PropertyType.REFERENCE && definition.canCastToType(value)) {
568                                 return definition;
569                             }
570                             if (definition.canCastToTypeAndSatisfyConstraints(value)) return definition;
571                         }
572                     }
573                 }
574             }
575             if (matchedOnName) return null;
576 
577         }
578 
579         // Nothing was found, so look for residual property definitions ...
580         if (!propertyName.equals(JcrNodeType.RESIDUAL_NAME)) return findPropertyDefinition(primaryTypeName,
581                                                                                            mixinTypeNames,
582                                                                                            JcrNodeType.RESIDUAL_NAME,
583                                                                                            value,
584                                                                                            checkMultiValuedDefinitions,
585                                                                                            skipProtected);
586         return null;
587     }
588 
589     /**
590      * Searches the supplied primary node type and the mixin node types for a property definition that is the best match for the
591      * given property name, property type, and value.
592      * <p>
593      * This method first attempts to find a single-valued property definition with the supplied property name and
594      * {@link Value#getType() value's property type} in the primary type, skipping any property definitions that are protected.
595      * The property definition is returned if it has a matching type (or has an {@link PropertyType#UNDEFINED undefined property
596      * type}) and the value satisfies the {@link PropertyDefinition#getValueConstraints() definition's constraints}. Otherwise,
597      * the process continues with each of the mixin types, in the order they are named.
598      * </p>
599      * <p>
600      * If no matching property definition could be found (and <code>checkMultiValuedDefinitions</code> parameter is
601      * <code>true</code>), the process is repeated except with multi-valued property definitions with the same name, property
602      * type, and compatible constraints, starting with the primary type and continuing with each mixin type.
603      * </p>
604      * <p>
605      * If no matching property definition could be found, and the process repeats by searching the primary type (and then mixin
606      * types) for single-valued property definitions with a compatible type, where the values can be safely cast to the
607      * definition's property type and still satisfy the definition's constraints.
608      * </p>
609      * <p>
610      * If no matching property definition could be found, the previous step is repeated with multi-valued property definitions.
611      * </p>
612      * <p>
613      * If no matching property definition could be found (and the supplied property name is not the residual name), the whole
614      * process repeats for residual property definitions (e.g., those that are defined with a {@link JcrNodeType#RESIDUAL_NAME "*"
615      * name}).
616      * </p>
617      * <p>
618      * Finally, if no satisfactory property definition could be found, this method returns null.
619      * </p>
620      * 
621      * @param primaryTypeName the name of the primary type; may not be null
622      * @param mixinTypeNames the names of the mixin types; may be null or empty if there are no mixins to include in the search
623      * @param propertyName the name of the property for which the definition should be retrieved. This method will automatically
624      *        look for residual definitions, but you can use {@link JcrNodeType#RESIDUAL_ITEM_NAME} to retrieve only the best
625      *        residual property definition (if any).
626      * @param values the values
627      * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if
628      *        this operation is being done from within internal implementations
629      * @return the best property definition, or <code>null</code> if no property definition allows the property with the supplied
630      *         name, type and number of values
631      */
632     JcrPropertyDefinition findPropertyDefinition( Name primaryTypeName,
633                                                   List<Name> mixinTypeNames,
634                                                   Name propertyName,
635                                                   Value[] values,
636                                                   boolean skipProtected ) {
637         boolean setToEmpty = values == null;
638         int propertyType = values == null || values.length == 0 ? PropertyType.STRING : values[0].getType();
639 
640         /*
641          * We use this flag to indicate that there was a definition encountered with the same name.  If
642          * a named definition (or definitions - for example the same node type could define a LONG and BOOLEAN
643          * version of the same property) is encountered and no match is found for the name, then processing should not
644          * proceed.  If processing did proceed, a residual definition might be found and matched.  This would 
645          * lead to a situation where a node defined a type for a named property, but contained a property with 
646          * the same name and the wrong type. 
647          */
648         boolean matchedOnName = false;
649 
650         // Look for a multi-value property definition on the primary type that matches by name and type ...
651         JcrNodeType primaryType = getNodeType(primaryTypeName);
652         if (primaryType != null) {
653             for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) {
654                 matchedOnName = true;
655                 // See if the definition allows the value ...
656                 if (skipProtected && definition.isProtected()) return null;
657                 if (setToEmpty) {
658                     if (!definition.isMandatory()) return definition;
659                     // Otherwise this definition doesn't work, so continue with the next ...
660                     continue;
661                 }
662                 assert values != null;
663                 // We can use the definition if it matches the type and satisfies the constraints ...
664                 int type = definition.getRequiredType();
665                 boolean typeMatches = values.length == 0 || type == PropertyType.UNDEFINED || type == propertyType;
666                 // Don't check constraints on reference properties
667                 if (typeMatches && type == PropertyType.REFERENCE) return definition;
668                 if (typeMatches && definition.satisfiesConstraints(values)) return definition;
669             }
670 
671             if (matchedOnName) {
672                 if (values != null && values.length != 0) {
673                     // Nothing was found with matching name and type, so look for definitions with
674                     // matching name and an undefined or castable type ...
675 
676                     // Look for a multi-value property definition on the primary type that matches by name and type ...
677                     for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) {
678                         // See if the definition allows the value ...
679                         if (skipProtected && definition.isProtected()) return null;
680                         assert definition.getRequiredType() != PropertyType.UNDEFINED;
681                         // Don't check constraints on reference properties
682                         if (definition.getRequiredType() == PropertyType.REFERENCE && definition.canCastToType(values)) return definition;
683                         if (definition.canCastToTypeAndSatisfyConstraints(values)) return definition;
684                     }
685                 }
686 
687                 return null;
688             }
689         }
690 
691         // Look for a multi-value property definition on the mixin types that matches by name and type ...
692         List<JcrNodeType> mixinTypes = null;
693         if (mixinTypeNames != null && !mixinTypeNames.isEmpty()) {
694             mixinTypes = new LinkedList<JcrNodeType>();
695             for (Name mixinTypeName : mixinTypeNames) {
696                 JcrNodeType mixinType = getNodeType(mixinTypeName);
697                 if (mixinType == null) continue;
698                 mixinTypes.add(mixinType);
699                 for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) {
700                     matchedOnName = true;
701                     // See if the definition allows the value ...
702                     if (skipProtected && definition.isProtected()) return null;
703                     if (setToEmpty) {
704                         if (!definition.isMandatory()) return definition;
705                         // Otherwise this definition doesn't work, so continue with the next ...
706                         continue;
707                     }
708                     assert values != null;
709                     // We can use the definition if it matches the type and satisfies the constraints ...
710                     int type = definition.getRequiredType();
711                     boolean typeMatches = values.length == 0 || type == PropertyType.UNDEFINED || type == propertyType;
712                     // Don't check constraints on reference properties
713                     if (typeMatches && type == PropertyType.REFERENCE) return definition;
714                     if (typeMatches && definition.satisfiesConstraints(values)) return definition;
715                 }
716                 if (matchedOnName) {
717                     if (values != null && values.length != 0) {
718                         // Nothing was found with matching name and type, so look for definitions with
719                         // matching name and an undefined or castable type ...
720 
721                         // Look for a multi-value property definition on the mixin type that matches by name and type ...
722                         for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) {
723                             // See if the definition allows the value ...
724                             if (skipProtected && definition.isProtected()) return null;
725                             assert definition.getRequiredType() != PropertyType.UNDEFINED;
726                             // Don't check constraints on reference properties
727                             if (definition.getRequiredType() == PropertyType.REFERENCE && definition.canCastToType(values)) return definition;
728                             if (definition.canCastToTypeAndSatisfyConstraints(values)) return definition;
729                         }
730                     }
731 
732                     return null;
733                 }
734 
735             }
736         }
737 
738         // Nothing was found, so look for residual property definitions ...
739         if (!propertyName.equals(JcrNodeType.RESIDUAL_NAME)) return findPropertyDefinition(primaryTypeName,
740                                                                                            mixinTypeNames,
741                                                                                            JcrNodeType.RESIDUAL_NAME,
742                                                                                            values,
743                                                                                            skipProtected);
744         return null;
745     }
746 
747     /**
748      * Searches the supplied primary and mixin node types for all valid property definitions that match the given property name
749      * and cardinality.
750      * <p>
751      * If no satisfactory property definition could be found, this method returns an empty list.
752      * </p>
753      * 
754      * @param typeNamesToCheck the name of the types to check; may not be null
755      * @param propertyName the name of the property for which the definitions should be retrieved
756      * @param typeToCheck the type of definitions to consider (single-valued only, multi-valued only, or all)
757      * @param pendingTypes a list of types that have been created during type registration but not yet registered in the type map
758      * @return a list of all valid property definitions that match the given property name and cardinality
759      */
760     private List<JcrPropertyDefinition> findPropertyDefinitions( List<Name> typeNamesToCheck,
761                                                                  Name propertyName,
762                                                                  PropertyCardinality typeToCheck,
763                                                                  List<JcrNodeType> pendingTypes ) {
764         assert typeNamesToCheck != null;
765 
766         Collection<JcrPropertyDefinition> propDefs = null;
767         List<JcrPropertyDefinition> matchingDefs = new ArrayList<JcrPropertyDefinition>();
768 
769         // Look for a single-value property definition on the mixin types that matches by name and type ...
770         for (Name typeNameToCheck : typeNamesToCheck) {
771             JcrNodeType typeName = findTypeInMapOrList(typeNameToCheck, pendingTypes);
772             if (typeName == null) continue;
773 
774             switch (typeToCheck) {
775                 case SINGLE_VALUED_ONLY:
776                     propDefs = typeName.allSingleValuePropertyDefinitions(propertyName);
777                     break;
778                 case MULTI_VALUED_ONLY:
779                     propDefs = typeName.allMultiValuePropertyDefinitions(propertyName);
780                     break;
781                 case ANY:
782                     propDefs = typeName.allPropertyDefinitions(propertyName);
783                     break;
784                 default:
785                     throw new IllegalStateException("Should be unreachable: " + typeToCheck);
786             }
787 
788             matchingDefs.addAll(propDefs);
789         }
790 
791         return matchingDefs;
792     }
793 
794     /**
795      * Determine if the property definitions of the supplied primary type and mixin types allow the property with the supplied
796      * name to be removed.
797      * 
798      * @param primaryTypeNameOfParent the name of the primary type for the parent node; may not be null
799      * @param mixinTypeNamesOfParent the names of the mixin types for the parent node; may be null or empty if there are no mixins
800      *        to include in the search
801      * @param propertyName the name of the property to be removed; may not be null
802      * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if
803      *        this operation is being done from within internal implementations
804      * @return true if at least one child node definition does not require children with the supplied name to exist, or false
805      *         otherwise
806      */
807     boolean canRemoveProperty( Name primaryTypeNameOfParent,
808                                List<Name> mixinTypeNamesOfParent,
809                                Name propertyName,
810                                boolean skipProtected ) {
811         // First look in the primary type ...
812         JcrNodeType primaryType = getNodeType(primaryTypeNameOfParent);
813         if (primaryType != null) {
814             for (JcrPropertyDefinition definition : primaryType.allPropertyDefinitions(propertyName)) {
815                 // Skip protected definitions ...
816                 if (skipProtected && definition.isProtected()) continue;
817                 // If this definition is not mandatory, then we have found that we CAN remove the property ...
818                 return !definition.isMandatory();
819             }
820         }
821 
822         // Then, look in the mixin types ...
823         if (mixinTypeNamesOfParent != null && !mixinTypeNamesOfParent.isEmpty()) {
824             for (Name mixinTypeName : mixinTypeNamesOfParent) {
825                 JcrNodeType mixinType = getNodeType(mixinTypeName);
826                 if (mixinType == null) continue;
827                 for (JcrPropertyDefinition definition : mixinType.allPropertyDefinitions(propertyName)) {
828                     // Skip protected definitions ...
829                     if (skipProtected && definition.isProtected()) continue;
830                     // If this definition is not mandatory, then we have found that we CAN remove the property ...
831                     return !definition.isMandatory();
832                 }
833             }
834         }
835 
836         // Nothing was found, so look for residual node definitions ...
837         if (!propertyName.equals(JcrNodeType.RESIDUAL_NAME)) return canRemoveProperty(primaryTypeNameOfParent,
838                                                                                       mixinTypeNamesOfParent,
839                                                                                       JcrNodeType.RESIDUAL_NAME,
840                                                                                       skipProtected);
841         return false;
842     }
843 
844     /**
845      * Determine if the node and property definitions of the supplied primary type and mixin types allow the item with the
846      * supplied name to be removed.
847      * 
848      * @param primaryTypeNameOfParent the name of the primary type for the parent node; may not be null
849      * @param mixinTypeNamesOfParent the names of the mixin types for the parent node; may be null or empty if there are no mixins
850      *        to include in the search
851      * @param itemName the name of the item to be removed; may not be null
852      * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if
853      *        this operation is being done from within internal implementations
854      * @return true if at least one child node definition does not require children with the supplied name to exist, or false
855      *         otherwise
856      */
857     boolean canRemoveItem( Name primaryTypeNameOfParent,
858                            List<Name> mixinTypeNamesOfParent,
859                            Name itemName,
860                            boolean skipProtected ) {
861         // First look in the primary type for a matching property definition...
862         JcrNodeType primaryType = getNodeType(primaryTypeNameOfParent);
863         if (primaryType != null) {
864             for (JcrPropertyDefinition definition : primaryType.allPropertyDefinitions(itemName)) {
865                 // Skip protected definitions ...
866                 if (skipProtected && definition.isProtected()) continue;
867                 // If this definition is not mandatory, then we have found that we CAN remove the property ...
868                 return !definition.isMandatory();
869             }
870         }
871 
872         // Then, look in the primary type for a matching child node definition...
873         if (primaryType != null) {
874             for (JcrNodeDefinition definition : primaryType.allChildNodeDefinitions(itemName)) {
875                 // Skip protected definitions ...
876                 if (skipProtected && definition.isProtected()) continue;
877                 // If this definition is not mandatory, then we have found that we CAN remove all children ...
878                 return !definition.isMandatory();
879             }
880         }
881 
882         // Then, look in the mixin types for a matching property definition...
883         if (mixinTypeNamesOfParent != null && !mixinTypeNamesOfParent.isEmpty()) {
884             for (Name mixinTypeName : mixinTypeNamesOfParent) {
885                 JcrNodeType mixinType = getNodeType(mixinTypeName);
886                 if (mixinType == null) continue;
887                 for (JcrPropertyDefinition definition : mixinType.allPropertyDefinitions(itemName)) {
888                     // Skip protected definitions ...
889                     if (skipProtected && definition.isProtected()) continue;
890                     // If this definition is not mandatory, then we have found that we CAN remove the property ...
891                     return !definition.isMandatory();
892                 }
893             }
894         }
895 
896         // Then, look in the mixin types for a matching child node definition...
897         if (mixinTypeNamesOfParent != null && !mixinTypeNamesOfParent.isEmpty()) {
898             for (Name mixinTypeName : mixinTypeNamesOfParent) {
899                 JcrNodeType mixinType = getNodeType(mixinTypeName);
900                 if (mixinType == null) continue;
901                 for (JcrNodeDefinition definition : mixinType.allChildNodeDefinitions(itemName)) {
902                     // Skip protected definitions ...
903                     if (skipProtected && definition.isProtected()) continue;
904                     // If this definition is not mandatory, then we have found that we CAN remove all children ...
905                     return !definition.isMandatory();
906                 }
907             }
908         }
909 
910         // Nothing was found, so look for residual item definitions ...
911         if (!itemName.equals(JcrNodeType.RESIDUAL_NAME)) return canRemoveItem(primaryTypeNameOfParent,
912                                                                               mixinTypeNamesOfParent,
913                                                                               JcrNodeType.RESIDUAL_NAME,
914                                                                               skipProtected);
915         return false;
916     }
917 
918     /**
919      * Searches the supplied primary node type and the mixin node types of a parent node for a child node definition that is the
920      * best match for a new child with the given name, primary node type name, and whether there are existing children with the
921      * same name.
922      * 
923      * @param primaryTypeNameOfParent the name of the primary type for the parent node; may not be null
924      * @param mixinTypeNamesOfParent the names of the mixin types for the parent node; may be null or empty if there are no mixins
925      *        to include in the search
926      * @param childName the name of the child to be added to the parent; may not be null
927      * @param childPrimaryNodeType the name of the primary node type for the child node, or null if the primary type is not known
928      *        and the {@link NodeDefinition#getDefaultPrimaryType() definition's default primary type} will be used
929      * @param numberOfExistingChildrenWithSameName the number of existing children with the same name as the child to be added, or
930      *        0 if this new child will be the first child with this name (or if the number of children is not known)
931      * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if
932      *        this operation is being done from within internal implementations
933      * @return the best child node definition, or <code>null</code> if no node definition allows a new child with the supplied
934      *         name, primary type, and whether there are already children with the same name
935      */
936     JcrNodeDefinition findChildNodeDefinition( Name primaryTypeNameOfParent,
937                                                List<Name> mixinTypeNamesOfParent,
938                                                Name childName,
939                                                Name childPrimaryNodeType,
940                                                int numberOfExistingChildrenWithSameName,
941                                                boolean skipProtected ) {
942         JcrNodeType childType = childPrimaryNodeType != null ? getNodeType(childPrimaryNodeType) : null;
943         boolean requireSns = numberOfExistingChildrenWithSameName > 1;
944 
945         // First look in the primary type ...
946         JcrNodeType primaryType = getNodeType(primaryTypeNameOfParent);
947         if (primaryType != null) {
948             for (JcrNodeDefinition definition : primaryType.allChildNodeDefinitions(childName, requireSns)) {
949                 // Skip protected definitions ...
950                 if (skipProtected && definition.isProtected()) return null;
951                 // See if the definition allows a child with the supplied primary type ...
952                 if (definition.allowsChildWithType(childType)) return definition;
953             }
954         }
955 
956         // Then, look in the mixin types ...
957         if (mixinTypeNamesOfParent != null && !mixinTypeNamesOfParent.isEmpty()) {
958             for (Name mixinTypeName : mixinTypeNamesOfParent) {
959                 JcrNodeType mixinType = getNodeType(mixinTypeName);
960                 if (mixinType == null) continue;
961                 for (JcrNodeDefinition definition : mixinType.allChildNodeDefinitions(childName, requireSns)) {
962                     // Skip protected definitions ...
963                     if (skipProtected && definition.isProtected()) return null;
964                     // See if the definition allows a child with the supplied primary type ...
965                     if (definition.allowsChildWithType(childType)) return definition;
966                 }
967             }
968         }
969 
970         // Nothing was found, so look for residual node definitions ...
971         if (!childName.equals(JcrNodeType.RESIDUAL_NAME)) return findChildNodeDefinition(primaryTypeNameOfParent,
972                                                                                          mixinTypeNamesOfParent,
973                                                                                          JcrNodeType.RESIDUAL_NAME,
974                                                                                          childPrimaryNodeType,
975                                                                                          numberOfExistingChildrenWithSameName,
976                                                                                          skipProtected);
977         return null;
978     }
979 
980     /**
981      * Searches the supplied primary and mixin node types for all valid child node definitions that match the given child node
982      * name and cardinality.
983      * <p>
984      * If no satisfactory child node definition could be found, this method returns an empty list.
985      * </p>
986      * 
987      * @param typeNamesToCheck the name of the types to check; may not be null
988      * @param childNodeName the name of the child node for which the definitions should be retrieved
989      * @param typesToCheck the type of definitions to consider (allows SNS or does not allow SNS)
990      * @param pendingTypes a list of types that have been created during type registration but not yet registered in the type map
991      * @return a list of all valid chlid node definitions that match the given child node name and cardinality
992      */
993     private List<JcrNodeDefinition> findChildNodeDefinitions( List<Name> typeNamesToCheck,
994                                                               Name childNodeName,
995                                                               NodeCardinality typesToCheck,
996                                                               List<JcrNodeType> pendingTypes ) {
997         assert typeNamesToCheck != null;
998         Collection<JcrNodeDefinition> nodeDefs = null;
999         List<JcrNodeDefinition> matchingDefs = new ArrayList<JcrNodeDefinition>();
1000 
1001         for (Name typeNameToCheck : typeNamesToCheck) {
1002             JcrNodeType typeName = findTypeInMapOrList(typeNameToCheck, pendingTypes);
1003             if (typeName == null) continue;
1004 
1005             switch (typesToCheck) {
1006                 case NO_SAME_NAME_SIBLINGS:
1007                     nodeDefs = typeName.allChildNodeDefinitions(childNodeName, false);
1008                     break;
1009                 case SAME_NAME_SIBLINGS:
1010                     nodeDefs = typeName.allChildNodeDefinitions(childNodeName, true);
1011                     break;
1012                 case ANY:
1013                     nodeDefs = typeName.allChildNodeDefinitions(childNodeName);
1014                     break;
1015             }
1016 
1017             assert nodeDefs != null;
1018             for (JcrNodeDefinition definition : nodeDefs) {
1019                 if (NodeCardinality.NO_SAME_NAME_SIBLINGS == typesToCheck && definition.allowsSameNameSiblings()) continue;
1020                 matchingDefs.add(definition);
1021             }
1022         }
1023 
1024         return matchingDefs;
1025     }
1026 
1027     /**
1028      * Determine if the child node definitions of the supplied primary type and mixin types of a parent node allow all of the
1029      * children with the supplied name to be removed.
1030      * 
1031      * @param primaryTypeNameOfParent the name of the primary type for the parent node; may not be null
1032      * @param mixinTypeNamesOfParent the names of the mixin types for the parent node; may be null or empty if there are no mixins
1033      *        to include in the search
1034      * @param childName the name of the child to be added to the parent; may not be null
1035      * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if
1036      *        this operation is being done from within internal implementations
1037      * @return true if at least one child node definition does not require children with the supplied name to exist, or false
1038      *         otherwise
1039      */
1040     boolean canRemoveAllChildren( Name primaryTypeNameOfParent,
1041                                   List<Name> mixinTypeNamesOfParent,
1042                                   Name childName,
1043                                   boolean skipProtected ) {
1044         // First look in the primary type ...
1045         JcrNodeType primaryType = getNodeType(primaryTypeNameOfParent);
1046         if (primaryType != null) {
1047             for (JcrNodeDefinition definition : primaryType.allChildNodeDefinitions(childName)) {
1048                 // Skip protected definitions ...
1049                 if (skipProtected && definition.isProtected()) continue;
1050                 // If this definition is not mandatory, then we have found that we CAN remove all children ...
1051                 return !definition.isMandatory();
1052             }
1053         }
1054 
1055         // Then, look in the mixin types ...
1056         if (mixinTypeNamesOfParent != null && !mixinTypeNamesOfParent.isEmpty()) {
1057             for (Name mixinTypeName : mixinTypeNamesOfParent) {
1058                 JcrNodeType mixinType = getNodeType(mixinTypeName);
1059                 if (mixinType == null) continue;
1060                 for (JcrNodeDefinition definition : mixinType.allChildNodeDefinitions(childName)) {
1061                     // Skip protected definitions ...
1062                     if (skipProtected && definition.isProtected()) continue;
1063                     // If this definition is not mandatory, then we have found that we CAN remove all children ...
1064                     return !definition.isMandatory();
1065                 }
1066             }
1067         }
1068 
1069         // Nothing was found, so look for residual node definitions ...
1070         if (!childName.equals(JcrNodeType.RESIDUAL_NAME)) return canRemoveAllChildren(primaryTypeNameOfParent,
1071                                                                                       mixinTypeNamesOfParent,
1072                                                                                       JcrNodeType.RESIDUAL_NAME,
1073                                                                                       skipProtected);
1074         return false;
1075     }
1076 
1077     /**
1078      * Projects the node types onto the provided graph under the location of <code>parentOfTypeNodes</code>. If no node currently
1079      * exists at that path, one will be created and assigned a primary type of {@code ModeShapeLexicon.NODE_TYPES}.
1080      * <p>
1081      * All node creation is performed through the graph layer. If the primary type of the node at <code>parentOfPathNodes</code>
1082      * does not contain a residual definition that allows child nodes of type <code>nt:nodeType</code>, this method will create
1083      * nodes for which the JCR layer cannot determine the corresponding node definition. This WILL corrupt the graph from a JCR
1084      * standpoint and make it unusable through the ModeShape JCR layer.
1085      * </p>
1086      * <p>
1087      * For each node type, a node is created as a child node of <code>parentOfPathNodes</code>. The created node has a name that
1088      * corresponds to the node types name and a primary type of <code>nt:nodeType</code>. All other properties and child nodes for
1089      * the newly created node are added in a manner consistent with the guidance provided in section 6.7.22 of the JCR 1.0
1090      * specification and section 4.7.24 of the JCR 2.0 specification where possible.
1091      * </p>
1092      * 
1093      * @param graph the graph onto which the type information should be projected
1094      * @param parentOfTypeNodes the path under which the type information should be projected
1095      */
1096     void projectOnto( Graph graph,
1097                       Path parentOfTypeNodes ) {
1098         assert graph != null;
1099         assert parentOfTypeNodes != null;
1100 
1101         // Make sure that the parent of the type nodes exists in the graph.
1102         try {
1103             graph.getNodeAt(parentOfTypeNodes);
1104         } catch (PathNotFoundException pnfe) {
1105             PropertyFactory propertyFactory = context.getPropertyFactory();
1106             graph.create(parentOfTypeNodes,
1107                          propertyFactory.create(JcrLexicon.PRIMARY_TYPE,
1108                                                 ModeShapeLexicon.NODE_TYPES.getString(context.getNamespaceRegistry()))).and();
1109         }
1110 
1111         Graph.Batch batch = graph.batch();
1112 
1113         for (JcrNodeType nodeType : getAllNodeTypes()) {
1114             projectNodeTypeOnto(nodeType, parentOfTypeNodes, batch);
1115         }
1116 
1117         batch.execute();
1118     }
1119 
1120     /**
1121      * Projects the node types onto the provided graph under the location of <code>parentOfTypeNodes</code>. The operations needed
1122      * to create the node (and any child nodes or properties) will be added to the batch specified in <code>batch</code>.
1123      * 
1124      * @param nodeType the node type to be projected
1125      * @param parentOfTypeNodes the path under which the type information should be projected
1126      * @param batch the batch to which any required graph modification operations should be added
1127      * @see #projectOnto(Graph, Path)
1128      */
1129     private void projectNodeTypeOnto( JcrNodeType nodeType,
1130                                       Path parentOfTypeNodes,
1131                                       Graph.Batch batch ) {
1132         assert nodeType != null;
1133         assert parentOfTypeNodes != null;
1134         assert batch != null;
1135 
1136         Path nodeTypePath = pathFactory.create(parentOfTypeNodes, nodeType.getInternalName());
1137 
1138         NodeType[] supertypes = nodeType.getDeclaredSupertypes();
1139         List<Name> supertypeNames = new ArrayList<Name>(supertypes.length);
1140         for (int i = 0; i < supertypes.length; i++) {
1141             supertypeNames.add(((JcrNodeType)supertypes[i]).getInternalName());
1142         }
1143 
1144         List<Property> propsList = new ArrayList<Property>();
1145         propsList.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.NODE_TYPE));
1146         propsList.add(propertyFactory.create(JcrLexicon.IS_MIXIN, nodeType.isMixin()));
1147         propsList.add(propertyFactory.create(JcrLexicon.IS_ABSTRACT, nodeType.isAbstract()));
1148         propsList.add(propertyFactory.create(JcrLexicon.IS_QUERYABLE, nodeType.isQueryable()));
1149 
1150         if (nodeType.getPrimaryItemName() != null) {
1151             propsList.add(propertyFactory.create(JcrLexicon.PRIMARY_ITEM_NAME, nodeType.getPrimaryItemName()));
1152         }
1153 
1154         propsList.add(propertyFactory.create(JcrLexicon.NODE_TYPE_NAME, nodeType.getName()));
1155         propsList.add(propertyFactory.create(JcrLexicon.HAS_ORDERABLE_CHILD_NODES, nodeType.hasOrderableChildNodes()));
1156         propsList.add(propertyFactory.create(JcrLexicon.SUPERTYPES, supertypeNames));
1157 
1158         batch.create(nodeTypePath).with(propsList).orReplace().and();
1159 
1160         PropertyDefinition[] propertyDefs = nodeType.getDeclaredPropertyDefinitions();
1161         for (int i = 0; i < propertyDefs.length; i++) {
1162             projectPropertyDefinitionOnto(propertyDefs[i], nodeTypePath, batch);
1163         }
1164 
1165         NodeDefinition[] childNodeDefs = nodeType.getDeclaredChildNodeDefinitions();
1166         for (int i = 0; i < childNodeDefs.length; i++) {
1167             projectChildNodeDefinitionOnto(childNodeDefs[i], nodeTypePath, batch);
1168         }
1169 
1170     }
1171 
1172     /**
1173      * Projects a single property definition onto the provided graph under the location of <code>nodeTypePath</code>. The
1174      * operations needed to create the property definition and any of its properties will be added to the batch specified in
1175      * <code>batch</code>.
1176      * <p>
1177      * All node creation is performed through the graph layer. If the primary type of the node at <code>nodeTypePath</code> does
1178      * not contain a residual definition that allows child nodes of type <code>nt:propertyDefinition</code>, this method creates
1179      * nodes for which the JCR layer cannot determine the corresponding node definition. This WILL corrupt the graph from a JCR
1180      * standpoint and make it unusable through the ModeShape JCR layer.
1181      * </p>
1182      * 
1183      * @param propertyDef the property definition to be projected
1184      * @param nodeTypePath the path under which the property definition should be projected
1185      * @param batch the batch to which any required graph modification operations should be added
1186      * @see #projectOnto(Graph, Path)
1187      */
1188     private void projectPropertyDefinitionOnto( PropertyDefinition propertyDef,
1189                                                 Path nodeTypePath,
1190                                                 Graph.Batch batch ) {
1191         assert propertyDef != null;
1192         assert nodeTypePath != null;
1193         assert batch != null;
1194 
1195         JcrPropertyDefinition jcrPropDef = (JcrPropertyDefinition)propertyDef;
1196         String propName = jcrPropDef.getInternalName().getString(context.getNamespaceRegistry(), NAME_ENCODER);
1197         Path propDefPath = pathFactory.create(nodeTypePath, JcrLexicon.PROPERTY_DEFINITION);
1198 
1199         List<Property> propsList = new ArrayList<Property>();
1200         propsList.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.PROPERTY_DEFINITION));
1201 
1202         if (!JcrNodeType.RESIDUAL_ITEM_NAME.equals(jcrPropDef.getName())) {
1203             propsList.add(propertyFactory.create(JcrLexicon.NAME, propName));
1204         }
1205         propsList.add(propertyFactory.create(JcrLexicon.AUTO_CREATED, jcrPropDef.isAutoCreated()));
1206         propsList.add(propertyFactory.create(JcrLexicon.MANDATORY, jcrPropDef.isMandatory()));
1207         propsList.add(propertyFactory.create(JcrLexicon.MULTIPLE, jcrPropDef.isMultiple()));
1208         propsList.add(propertyFactory.create(JcrLexicon.PROTECTED, jcrPropDef.isProtected()));
1209         propsList.add(propertyFactory.create(JcrLexicon.ON_PARENT_VERSION,
1210                                              OnParentVersionAction.nameFromValue(jcrPropDef.getOnParentVersion())));
1211         propsList.add(propertyFactory.create(JcrLexicon.REQUIRED_TYPE,
1212                                              PropertyType.nameFromValue(jcrPropDef.getRequiredType()).toUpperCase()));
1213 
1214         Value[] defaultValues = jcrPropDef.getDefaultValues();
1215         if (defaultValues.length > 0) {
1216             String[] defaultsAsString = new String[defaultValues.length];
1217 
1218             for (int i = 0; i < defaultValues.length; i++) {
1219                 try {
1220                     defaultsAsString[i] = defaultValues[i].getString();
1221                 } catch (RepositoryException re) {
1222                     // Really shouldn't get here as all values are convertible to string
1223                     throw new IllegalStateException(re);
1224                 }
1225             }
1226             propsList.add(propertyFactory.create(JcrLexicon.DEFAULT_VALUES, (Object[])defaultsAsString));
1227         }
1228 
1229         String[] valueConstraints = jcrPropDef.getValueConstraints();
1230         if (valueConstraints.length > 0) {
1231             propsList.add(propertyFactory.create(JcrLexicon.VALUE_CONSTRAINTS, (Object[])valueConstraints));
1232         }
1233         batch.create(propDefPath).with(propsList).and();
1234     }
1235 
1236     /**
1237      * Projects a single child node definition onto the provided graph under the location of <code>nodeTypePath</code>. The
1238      * operations needed to create the child node definition and any of its properties will be added to the batch specified in
1239      * <code>batch</code>.
1240      * <p>
1241      * All node creation is performed through the graph layer. If the primary type of the node at <code>nodeTypePath</code> does
1242      * not contain a residual definition that allows child nodes of type <code>nt:childNodeDefinition</code>, this method creates
1243      * nodes for which the JCR layer cannot determine the corresponding node definition. This WILL corrupt the graph from a JCR
1244      * standpoint and make it unusable through the ModeShape JCR layer.
1245      * </p>
1246      * 
1247      * @param childNodeDef the child node definition to be projected
1248      * @param nodeTypePath the path under which the child node definition should be projected
1249      * @param batch the batch to which any required graph modification operations should be added
1250      * @see #projectOnto(Graph, Path)
1251      */
1252     private void projectChildNodeDefinitionOnto( NodeDefinition childNodeDef,
1253                                                  Path nodeTypePath,
1254                                                  Graph.Batch batch ) {
1255         assert childNodeDef != null;
1256         assert nodeTypePath != null;
1257         assert batch != null;
1258 
1259         JcrNodeDefinition jcrNodeDef = (JcrNodeDefinition)childNodeDef;
1260         String nodeName = jcrNodeDef.getInternalName().getString(context.getNamespaceRegistry(), NAME_ENCODER);
1261         Path nodeDefPath = pathFactory.create(nodeTypePath, JcrLexicon.CHILD_NODE_DEFINITION);
1262 
1263         List<Property> propsList = new ArrayList<Property>();
1264         propsList.add(propertyFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.CHILD_NODE_DEFINITION));
1265 
1266         if (!JcrNodeType.RESIDUAL_ITEM_NAME.equals(jcrNodeDef.getName())) {
1267             propsList.add(propertyFactory.create(JcrLexicon.NAME, nodeName));
1268         }
1269 
1270         if (jcrNodeDef.getDefaultPrimaryType() != null) {
1271             propsList.add(propertyFactory.create(JcrLexicon.DEFAULT_PRIMARY_TYPE, jcrNodeDef.getDefaultPrimaryType().getName()));
1272         }
1273 
1274         propsList.add(propertyFactory.create(JcrLexicon.REQUIRED_PRIMARY_TYPES, (Object[])jcrNodeDef.requiredPrimaryTypeNames()));
1275         propsList.add(propertyFactory.create(JcrLexicon.SAME_NAME_SIBLINGS, jcrNodeDef.allowsSameNameSiblings()));
1276         propsList.add(propertyFactory.create(JcrLexicon.ON_PARENT_VERSION,
1277                                              OnParentVersionAction.nameFromValue(jcrNodeDef.getOnParentVersion())));
1278         propsList.add(propertyFactory.create(JcrLexicon.AUTO_CREATED, jcrNodeDef.isAutoCreated()));
1279         propsList.add(propertyFactory.create(JcrLexicon.MANDATORY, jcrNodeDef.isMandatory()));
1280         propsList.add(propertyFactory.create(JcrLexicon.PROTECTED, jcrNodeDef.isProtected()));
1281 
1282         batch.create(nodeDefPath).with(propsList).and();
1283     }
1284 
1285     /**
1286      * Allows the collection of node types to be unregistered if they are not referenced by other node types as supertypes,
1287      * default primary types of child nodes, or required primary types of child nodes.
1288      * <p>
1289      * <b>NOTE: This method does not check to see if any of the node types are currently being used. Unregistering a node type
1290      * that is being used will cause the system to become unstable</b>
1291      * </p>
1292      * 
1293      * @param nodeTypeNames the names of the node types to be unregistered
1294      * @throws NoSuchNodeTypeException if any of the node type names do not correspond to a registered node type
1295      * @throws InvalidNodeTypeDefinitionException if any of the node types with the given names cannot be unregistered because
1296      *         they are the supertype, one of the required primary types, or a default primary type of a node type that is not
1297      *         being unregistered.
1298      * @throws RepositoryException if any other error occurs
1299      */
1300     void unregisterNodeType( Collection<Name> nodeTypeNames )
1301         throws NoSuchNodeTypeException, InvalidNodeTypeDefinitionException, RepositoryException {
1302         CheckArg.isNotNull(nodeTypeNames, "nodeTypeNames");
1303         try {
1304             /*
1305              * Grab an exclusive lock on this data to keep other nodes from being added/saved while the unregistration checks are occurring
1306              */
1307             nodeTypeManagerLock.writeLock().lock();
1308 
1309             for (Name nodeTypeName : nodeTypeNames) {
1310                 /*
1311                  * Check that the type names are valid
1312                  */
1313                 if (nodeTypeName == null) {
1314                     throw new NoSuchNodeTypeException(JcrI18n.invalidNodeTypeName.text());
1315                 }
1316                 String name = nodeTypeName.getString(context.getNamespaceRegistry());
1317 
1318                 if (!this.nodeTypes.containsKey(nodeTypeName)) {
1319                     throw new NoSuchNodeTypeException(JcrI18n.noSuchNodeType.text(name));
1320                 }
1321 
1322                 /*
1323                  * Check that no other node definitions have dependencies on any of the named types
1324                  */
1325                 for (JcrNodeType nodeType : nodeTypes.values()) {
1326                     // If this node is also being unregistered, don't run checks against it
1327                     if (nodeTypeNames.contains(nodeType.getInternalName())) {
1328                         continue;
1329                     }
1330 
1331                     for (JcrNodeType supertype : nodeType.supertypes()) {
1332                         if (nodeTypeName.equals(supertype.getInternalName())) {
1333                             throw new InvalidNodeTypeDefinitionException(
1334                                                                          JcrI18n.cannotUnregisterSupertype.text(name,
1335                                                                                                                 supertype.getName()));
1336                         }
1337                     }
1338 
1339                     for (JcrNodeDefinition childNode : nodeType.childNodeDefinitions()) {
1340                         NodeType defaultPrimaryType = childNode.getDefaultPrimaryType();
1341                         if (defaultPrimaryType != null && name.equals(defaultPrimaryType.getName())) {
1342                             throw new InvalidNodeTypeDefinitionException(
1343                                                                          JcrI18n.cannotUnregisterDefaultPrimaryType.text(name,
1344                                                                                                                          nodeType.getName(),
1345                                                                                                                          childNode.getName()));
1346                         }
1347                         if (childNode.requiredPrimaryTypeNameSet().contains(nodeTypeName)) {
1348                             throw new InvalidNodeTypeDefinitionException(
1349                                                                          JcrI18n.cannotUnregisterRequiredPrimaryType.text(name,
1350                                                                                                                           nodeType.getName(),
1351                                                                                                                           childNode.getName()));
1352                         }
1353                     }
1354 
1355                     /*
1356                      * Search the content graph to make sure that this type isn't being used
1357                      */
1358                     if (isNodeTypeInUse(nodeTypeName)) {
1359                         throw new InvalidNodeTypeDefinitionException(JcrI18n.cannotUnregisterInUseType.text(name));
1360 
1361                     }
1362 
1363                 }
1364             }
1365 
1366             this.nodeTypes.keySet().removeAll(nodeTypeNames);
1367 
1368             if (nodeTypesPath != null) {
1369                 Graph.Batch batch = repository.createSystemGraph(context).batch();
1370 
1371                 for (Name nodeTypeName : nodeTypeNames) {
1372                     Path nodeTypePath = pathFactory.create(nodeTypesPath, nodeTypeName);
1373                     batch.delete(nodeTypePath).and();
1374                 }
1375 
1376                 batch.execute();
1377             }
1378             this.schemata = null;
1379 
1380         } finally {
1381             nodeTypeManagerLock.writeLock().unlock();
1382         }
1383     }
1384 
1385     /**
1386      * Check if the named node type is in use in any workspace in the repository
1387      * 
1388      * @param nodeTypeName the name of the node type to check
1389      * @return true if at least one node is using that type; false otherwise
1390      * @throws InvalidQueryException if there is an error searching for uses of the named node type
1391      */
1392     boolean isNodeTypeInUse( Name nodeTypeName ) throws InvalidQueryException {
1393         String nodeTypeString = nodeTypeName.getString(context.getNamespaceRegistry());
1394         String expression = "SELECT * from [" + nodeTypeString + "] LIMIT 1";
1395 
1396         TypeSystem typeSystem = context.getValueFactories().getTypeSystem();
1397         // Parsing must be done now ...
1398         QueryCommand command = queryParser.parseQuery(expression, typeSystem);
1399         assert command != null : "Could not parse " + expression;
1400 
1401         Schemata schemata = getRepositorySchemata();
1402 
1403         Set<String> workspaceNames = repository.workspaceNames();
1404         for (String workspaceName : workspaceNames) {
1405             QueryResults result = repository.queryManager().query(workspaceName, command, schemata, null, null);
1406 
1407             if (result.getRowCount() > 0) {
1408                 return true;
1409             }
1410         }
1411 
1412         return false;
1413     }
1414 
1415     /**
1416      * Registers a new node type or updates an existing node type using the specified definition and returns the resulting {@code
1417      * NodeType} object.
1418      * <p>
1419      * For details, see {@link #registerNodeTypes(Iterable)}.
1420      * </p>
1421      * 
1422      * @param ntd the {@code NodeTypeDefinition} to register
1423      * @return the newly registered (or updated) {@code NodeType}
1424      * @throws InvalidNodeTypeDefinitionException if the {@code NodeTypeDefinition} is invalid
1425      * @throws NodeTypeExistsException if <code>allowUpdate</code> is false and the {@code NodeTypeDefinition} specifies a node
1426      *         type name that is already registered
1427      * @throws RepositoryException if another error occurs
1428      */
1429     JcrNodeType registerNodeType( NodeTypeDefinition ntd )
1430         throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, RepositoryException {
1431         assert ntd != null;
1432         List<JcrNodeType> result = registerNodeTypes(Collections.singletonList(ntd));
1433         return result.isEmpty() ? null : result.get(0);
1434     }
1435 
1436     /**
1437      * Registers or updates the specified {@link NodeTypeDefinition} objects.
1438      * <p>
1439      * For details, see {@link #registerNodeTypes(Iterable)}.
1440      * </p>
1441      * 
1442      * @param ntds the node type definitions
1443      * @return the newly registered (or updated) {@link NodeType NodeTypes}
1444      * @throws InvalidNodeTypeDefinitionException
1445      * @throws NodeTypeExistsException
1446      * @throws RepositoryException
1447      */
1448     List<JcrNodeType> registerNodeTypes( NodeTypeDefinition[] ntds )
1449         throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, RepositoryException {
1450         assert ntds != null;
1451         return registerNodeTypes(Arrays.asList(ntds));
1452     }
1453 
1454     /**
1455      * Registers or updates the specified {@code Collection} of {@link NodeTypeDefinition} objects.
1456      * <p>
1457      * This method is used to register or update a set of node types with mutual dependencies.
1458      * </p>
1459      * <p>
1460      * The effect of this method is &quot;all or nothing&quot;; if an error occurs, no node types are registered or updated.
1461      * </p>
1462      * <p>
1463      * <b>ModeShape Implementation Notes</b>
1464      * </p>
1465      * <p>
1466      * ModeShape currently supports registration of batches of types with some constraints. ModeShape will allow types to be
1467      * registered if they meet the following criteria:
1468      * <ol>
1469      * <li>The batch must consist of {@code NodeTypeDefinitionTemplate node type definition templates} created through the user's
1470      * JCR session.</li>
1471      * <li>Existing types cannot be modified in-place - They must be unregistered and re-registered</li>
1472      * <li>Types must have a non-null, non-empty name</li>
1473      * <li>If a primary item name is specified for the node type, it must match the name of a property OR a child node, not both</li>
1474      * <li>Each type must have a valid set of supertypes - that is, the type's supertypes must meet the following criteria:
1475      * <ol>
1476      * <li>The type must have at least one supertype (unless the type is {@code nt:base}.</li>
1477      * <li>No two supertypes {@code t1} and {@code t2} can declare each declare a property ({@code p1} and {@code p2}) with the
1478      * same name and cardinality ({@code p1.isMultiple() == p2.isMultiple()}). Note that this does prohibit each {@code t1} and
1479      * {@code t2} from having a common supertype (or super-supertype, etc.) that declares a property).</li>
1480      * <li>No two supertypes {@code t1} and {@code t2} can declare each declare a child node ({@code n1} and {@code n2}) with the
1481      * same name and SNS status ({@code p1.allowsSameNameSiblings() == p2.allowsSameNameSiblings()}). Note that this does prohibit
1482      * each {@code t1} and {@code t2} from having a common supertype (or super-supertype, etc.) that declares a child node).</li>
1483      * </ol>
1484      * </li>
1485      * <li>Each type must have a valid set of properties - that is, the type's properties must meet the following criteria:
1486      * <ol>
1487      * <li>Residual property definitions cannot be mandatory</li>
1488      * <li>If the property is auto-created, it must specify a default value</li>
1489      * <li>If the property is single-valued, it can only specify a single default value</li>
1490      * <li>If the property overrides an existing property definition from a supertype, the new definition must be mandatory if the
1491      * old definition was mandatory</li>
1492      * <li>The property cannot override an existing property definition from a supertype if the ancestor definition is protected</li>
1493      * <li>If the property overrides an existing property definition from a supertype that specifies value constraints, the new
1494      * definition must have the same value constraints as the old definition. <i>This requirement may be relaxed in a future
1495      * version of ModeShape.</i></li>
1496      * <li>If the property overrides an existing property definition from a supertype, the new definition must have the same
1497      * required type as the old definition or a required type that can ALWAYS be cast to the required type of the ancestor (see
1498      * section 3.6.4 of the JCR 2.0 specification)</li>
1499      * </ol>
1500      * Note that an empty set of properties would meet the above criteria.</li>
1501      * <li>The type must have a valid set of child nodes - that is, the types's child nodes must meet the following criteria:
1502      * <ol>
1503      * <li>Residual child node definitions cannot be mandatory</li>
1504      * <li>If the child node is auto-created, it must specify a default primary type name</li>
1505      * <li>If the child node overrides an existing child node definition from a supertype, the new definition must be mandatory if
1506      * the old definition was mandatory</li>
1507      * <li>The child node cannot override an existing child node definition from a supertype if the ancestor definition is
1508      * protected</li>
1509      * <li>If the child node overrides an existing child node definition from a supertype, the required primary types of the new
1510      * definition must be more restrictive than the required primary types of the old definition - that is, the new primary types
1511      * must defined such that any type that satisfies all of the required primary types for the new definition must also satisfy
1512      * all of the required primary types for the old definition. This requirement is analogous to the requirement that overriding
1513      * property definitions have a required type that is always convertible to the required type of the overridden definition.</li>
1514      * </ol>
1515      * Note that an empty set of child nodes would meet the above criteria.</li>
1516      * </p>
1517      * 
1518      * @param nodeTypeDefns the {@link NodeTypeDefinition node type definitions} to register
1519      * @return the newly registered (or updated) {@link NodeType NodeTypes}
1520      * @throws UnsupportedRepositoryOperationException if {@code allowUpdates == true}. ModeShape does not support this capability
1521      *         at this time but the parameter has been retained for API compatibility.
1522      * @throws InvalidNodeTypeDefinitionException if the {@link NodeTypeDefinition} is invalid
1523      * @throws NodeTypeExistsException if <code>allowUpdate</code> is false and the {@link NodeTypeDefinition} specifies a node
1524      *         type name that is already registered
1525      * @throws RepositoryException if another error occurs
1526      */
1527     List<JcrNodeType> registerNodeTypes( Iterable<NodeTypeDefinition> nodeTypeDefns )
1528         throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, RepositoryException {
1529 
1530         if (nodeTypeDefns == null) {
1531             return Collections.emptyList();
1532         }
1533 
1534         List<JcrNodeType> typesPendingRegistration = new ArrayList<JcrNodeType>();
1535 
1536         try {
1537             nodeTypeManagerLock.writeLock().lock();
1538             for (NodeTypeDefinition nodeTypeDefn : nodeTypeDefns) {
1539                 if (nodeTypeDefn instanceof JcrNodeTypeTemplate) {
1540                     // Switch to use this context, so names are properly prefixed ...
1541                     nodeTypeDefn = ((JcrNodeTypeTemplate)nodeTypeDefn).with(context);
1542                 }
1543                 Name internalName = nameFactory.create(nodeTypeDefn.getName());
1544                 if (internalName == null || internalName.getLocalName().length() == 0) {
1545                     throw new InvalidNodeTypeDefinitionException(JcrI18n.invalidNodeTypeName.text());
1546                 }
1547 
1548                 if (nodeTypes.containsKey(internalName)) {
1549                     String name = nodeTypeDefn.getName();
1550                     throw new NodeTypeExistsException(internalName, JcrI18n.nodeTypeAlreadyExists.text(name));
1551                 }
1552 
1553                 List<JcrNodeType> supertypes = supertypesFor(nodeTypeDefn, typesPendingRegistration);
1554                 JcrNodeType nodeType = nodeTypeFrom(nodeTypeDefn, supertypes);
1555                 validate(nodeType, supertypes, typesPendingRegistration);
1556 
1557                 typesPendingRegistration.add(nodeType);
1558             }
1559 
1560             // Make sure the nodes have primary types that are either already registered, or pending registration ...
1561             for (JcrNodeType nodeType : typesPendingRegistration) {
1562                 for (JcrNodeDefinition nodeDef : nodeType.getDeclaredChildNodeDefinitions()) {
1563                     JcrNodeType[] requiredPrimaryTypes = new JcrNodeType[nodeDef.requiredPrimaryTypeNames().length];
1564                     int i = 0;
1565                     for (Name primaryTypeName : nodeDef.requiredPrimaryTypeNames()) {
1566                         requiredPrimaryTypes[i] = findTypeInMapOrList(primaryTypeName, typesPendingRegistration);
1567 
1568                         if (requiredPrimaryTypes[i] == null) {
1569                             throw new RepositoryException(
1570                                                           JcrI18n.invalidPrimaryTypeName.text(primaryTypeName, nodeType.getName()));
1571                         }
1572                         i++;
1573                     }
1574                 }
1575             }
1576 
1577             Graph.Batch batch = null;
1578             if (nodeTypesPath != null) batch = repository.createSystemGraph(context).batch();
1579 
1580             for (JcrNodeType nodeType : typesPendingRegistration) {
1581                 /*
1582                  * See comment in constructor.  Using a ConcurrentHashMap seems to be to weak of a
1583                  * solution (even it were also used for childNodeDefinitions and propertyDefinitions).
1584                  * Probably need to block all read access to these maps during this phase of registration.
1585                  */
1586                 Name name = nodeType.getInternalName();
1587                 nodeTypes.put(name, nodeType);
1588                 for (JcrNodeDefinition childDefinition : nodeType.childNodeDefinitions()) {
1589                     childNodeDefinitions.put(childDefinition.getId(), childDefinition);
1590                 }
1591                 for (JcrPropertyDefinition propertyDefinition : nodeType.propertyDefinitions()) {
1592                     propertyDefinitions.put(propertyDefinition.getId(), propertyDefinition);
1593                 }
1594 
1595                 if (nodeTypesPath != null) projectNodeTypeOnto(nodeType, nodeTypesPath, batch);
1596             }
1597 
1598             // Throw away the schemata, since the node types have changed ...
1599             this.schemata = null;
1600             if (nodeTypesPath != null) {
1601                 assert batch != null;
1602                 batch.execute();
1603             }
1604         } finally {
1605             nodeTypeManagerLock.writeLock().unlock();
1606         }
1607 
1608         return typesPendingRegistration;
1609     }
1610 
1611     /**
1612      * Registers the node types from the given subgraph containing the node type definitions in the standard ModeShape format.
1613      * <p>
1614      * The effect of this method is &quot;all or nothing&quot;; if an error occurs, no node types are registered or updated.
1615      * </p>
1616      * <p>
1617      * <b>ModeShape Implementation Notes</b>
1618      * </p>
1619      * <p>
1620      * ModeShape currently supports registration of batches of types with some constraints. ModeShape will allow types to be
1621      * registered if they meet the following criteria:
1622      * <ol>
1623      * <li>Existing types cannot be modified in-place - They must be unregistered and re-registered</li>
1624      * <li>Types must have a non-null, non-empty name</li>
1625      * <li>If a primary item name is specified for the node type, it must match the name of a property OR a child node, not both</li>
1626      * <li>Each type must have a valid set of supertypes - that is, the type's supertypes must meet the following criteria:
1627      * <ol>
1628      * <li>The type must have at least one supertype (unless the type is {@code nt:base}.</li>
1629      * <li>No two supertypes {@code t1} and {@code t2} can declare each declare a property ({@code p1} and {@code p2}) with the
1630      * same name and cardinality ({@code p1.isMultiple() == p2.isMultiple()}). Note that this does prohibit each {@code t1} and
1631      * {@code t2} from having a common supertype (or super-supertype, etc.) that declares a property).</li>
1632      * <li>No two supertypes {@code t1} and {@code t2} can declare each declare a child node ({@code n1} and {@code n2}) with the
1633      * same name and SNS status ({@code p1.allowsSameNameSiblings() == p2.allowsSameNameSiblings()}). Note that this does prohibit
1634      * each {@code t1} and {@code t2} from having a common supertype (or super-supertype, etc.) that declares a child node).</li>
1635      * </ol>
1636      * </li>
1637      * <li>Each type must have a valid set of properties - that is, the type's properties must meet the following criteria:
1638      * <ol>
1639      * <li>Residual property definitions cannot be mandatory</li>
1640      * <li>If the property is auto-created, it must specify a default value</li>
1641      * <li>If the property is single-valued, it can only specify a single default value</li>
1642      * <li>If the property overrides an existing property definition from a supertype, the new definition must be mandatory if the
1643      * old definition was mandatory</li>
1644      * <li>The property cannot override an existing property definition from a supertype if the ancestor definition is protected</li>
1645      * <li>If the property overrides an existing property definition from a supertype that specifies value constraints, the new
1646      * definition must have the same value constraints as the old definition. <i>This requirement may be relaxed in a future
1647      * version of ModeShape.</i></li>
1648      * <li>If the property overrides an existing property definition from a supertype, the new definition must have the same
1649      * required type as the old definition or a required type that can ALWAYS be cast to the required type of the ancestor (see
1650      * section 3.6.4 of the JCR 2.0 specification)</li>
1651      * </ol>
1652      * Note that an empty set of properties would meet the above criteria.</li>
1653      * <li>The type must have a valid set of child nodes - that is, the types's child nodes must meet the following criteria:
1654      * <ol>
1655      * <li>Residual child node definitions cannot be mandatory</li>
1656      * <li>If the child node is auto-created, it must specify a default primary type name</li>
1657      * <li>If the child node overrides an existing child node definition from a supertype, the new definition must be mandatory if
1658      * the old definition was mandatory</li>
1659      * <li>The child node cannot override an existing child node definition from a supertype if the ancestor definition is
1660      * protected</li>
1661      * <li>If the child node overrides an existing child node definition from a supertype, the required primary types of the new
1662      * definition must be more restrictive than the required primary types of the old definition - that is, the new primary types
1663      * must defined such that any type that satisfies all of the required primary types for the new definition must also satisfy
1664      * all of the required primary types for the old definition. This requirement is analogous to the requirement that overriding
1665      * property definitions have a required type that is always convertible to the required type of the overridden definition.</li>
1666      * </ol>
1667      * Note that an empty set of child nodes would meet the above criteria.</li>
1668      * </p>
1669      * 
1670      * @param nodeTypeSubgraph the subgraph containing the of {@link NodeType node types} to register
1671      * @param locationOfParentOfNodeTypes the location of the parent node under which the node types are found
1672      * @return the newly registered (or updated) {@link NodeType NodeTypes}
1673      * @throws UnsupportedRepositoryOperationException if {@code allowUpdates == true}. ModeShape does not support this capability
1674      *         at this time but the parameter has been retained for API compatibility.
1675      * @throws InvalidNodeTypeDefinitionException if the {@link NodeTypeDefinition} is invalid
1676      * @throws NodeTypeExistsException if <code>allowUpdate</code> is false and the {@link NodeTypeDefinition} specifies a node
1677      *         type name that is already registered
1678      * @throws RepositoryException if another error occurs
1679      */
1680     List<JcrNodeType> registerNodeTypes( Subgraph nodeTypeSubgraph,
1681                                          Location locationOfParentOfNodeTypes )
1682         throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, RepositoryException {
1683         assert nodeTypeSubgraph != null;
1684         assert locationOfParentOfNodeTypes != null;
1685         CndNodeTypeReader factory = new CndNodeTypeReader(this.context);
1686         factory.read(nodeTypeSubgraph, locationOfParentOfNodeTypes, nodeTypeSubgraph.getGraph().getSourceName());
1687         return registerNodeTypes(factory);
1688     }
1689 
1690     private JcrNodeType nodeTypeFrom( NodeTypeDefinition nodeType,
1691                                       List<JcrNodeType> supertypes ) throws RepositoryException {
1692         PropertyDefinition[] propDefns = nodeType.getDeclaredPropertyDefinitions();
1693         NodeDefinition[] childDefns = nodeType.getDeclaredChildNodeDefinitions();
1694         List<JcrPropertyDefinition> properties = new ArrayList<JcrPropertyDefinition>();
1695         List<JcrNodeDefinition> childNodes = new ArrayList<JcrNodeDefinition>();
1696 
1697         if (propDefns != null) {
1698             for (PropertyDefinition propDefn : propDefns) {
1699                 properties.add(propertyDefinitionFrom(propDefn));
1700             }
1701         }
1702         if (childDefns != null) {
1703             for (NodeDefinition childNodeDefn : childDefns) {
1704                 childNodes.add(childNodeDefinitionFrom(childNodeDefn));
1705             }
1706         }
1707 
1708         Name name = nameFactory.create(nodeType.getName());
1709         Name primaryItemName = nameFactory.create(nodeType.getPrimaryItemName());
1710         boolean mixin = nodeType.isMixin();
1711         boolean isAbstract = nodeType.isAbstract();
1712         boolean queryable = nodeType.isQueryable();
1713         boolean orderableChildNodes = nodeType.hasOrderableChildNodes();
1714 
1715         return new JcrNodeType(this.context, this, name, supertypes, primaryItemName, childNodes, properties, mixin, isAbstract,
1716                                queryable, orderableChildNodes);
1717     }
1718 
1719     private JcrPropertyDefinition propertyDefinitionFrom( PropertyDefinition propDefn ) throws RepositoryException {
1720         Name propertyName = nameFactory.create(propDefn.getName());
1721         int onParentVersionBehavior = propDefn.getOnParentVersion();
1722         int requiredType = propDefn.getRequiredType();
1723         boolean mandatory = propDefn.isMandatory();
1724         boolean multiple = propDefn.isMultiple();
1725         boolean autoCreated = propDefn.isAutoCreated();
1726         boolean isProtected = propDefn.isProtected();
1727         boolean fullTextSearchable = propDefn.isFullTextSearchable();
1728         boolean queryOrderable = propDefn.isQueryOrderable();
1729 
1730         Value[] defaultValues = propDefn.getDefaultValues();
1731         if (defaultValues != null) {
1732             for (int i = 0; i != defaultValues.length; ++i) {
1733                 Value value = defaultValues[i];
1734                 defaultValues[i] = new JcrValue(this.context.getValueFactories(), null, value);
1735             }
1736         } else {
1737             defaultValues = new Value[0];
1738         }
1739 
1740         String[] valueConstraints = propDefn.getValueConstraints();
1741         String[] queryOperators = propDefn.getAvailableQueryOperators();
1742         if (valueConstraints == null) valueConstraints = new String[0];
1743         if (queryOperators == null) queryOperators = new String[0];
1744         return new JcrPropertyDefinition(this.context, null, propertyName, onParentVersionBehavior, autoCreated, mandatory,
1745                                          isProtected, defaultValues, requiredType, valueConstraints, multiple,
1746                                          fullTextSearchable, queryOrderable, queryOperators);
1747     }
1748 
1749     private JcrNodeDefinition childNodeDefinitionFrom( NodeDefinition childNodeDefn ) {
1750         Name childNodeName = nameFactory.create(childNodeDefn.getName());
1751         Name defaultPrimaryTypeName = nameFactory.create(childNodeDefn.getDefaultPrimaryTypeName());
1752         int onParentVersion = childNodeDefn.getOnParentVersion();
1753 
1754         boolean mandatory = childNodeDefn.isMandatory();
1755         boolean allowsSns = childNodeDefn.allowsSameNameSiblings();
1756         boolean autoCreated = childNodeDefn.isAutoCreated();
1757         boolean isProtected = childNodeDefn.isProtected();
1758 
1759         Name[] requiredTypes;
1760         String[] requiredTypeNames = childNodeDefn.getRequiredPrimaryTypeNames();
1761         if (requiredTypeNames != null) {
1762             List<Name> names = new ArrayList<Name>(requiredTypeNames.length);
1763             for (String typeName : requiredTypeNames) {
1764                 names.add(nameFactory.create(typeName));
1765             }
1766             requiredTypes = names.toArray(new Name[names.size()]);
1767         } else {
1768             requiredTypes = new Name[0];
1769         }
1770 
1771         return new JcrNodeDefinition(this.context, null, childNodeName, onParentVersion, autoCreated, mandatory, isProtected,
1772                                      allowsSns, defaultPrimaryTypeName, requiredTypes);
1773     }
1774 
1775     /**
1776      * Finds the named type in the given collection of types pending registration if it exists, else returns the type definition
1777      * from the repository
1778      * 
1779      * @param typeName the name of the type to retrieve
1780      * @param pendingList a collection of types that have passed validation but have not yet been committed to the repository
1781      * @return the node type with the given name from {@code pendingList} if it exists in the collection or from the
1782      *         {@link #nodeTypes registered types} if it exists there; may be null
1783      */
1784     private JcrNodeType findTypeInMapOrList( Name typeName,
1785                                              Collection<JcrNodeType> pendingList ) {
1786         for (JcrNodeType pendingNodeType : pendingList) {
1787             if (pendingNodeType.getInternalName().equals(typeName)) {
1788                 return pendingNodeType;
1789             }
1790         }
1791 
1792         return nodeTypes.get(typeName);
1793     }
1794 
1795     /**
1796      * Returns the list of node types for the supertypes defined in the given node type.
1797      * 
1798      * @param nodeType a node type with a non-null array of supertypes
1799      * @param pendingTypes the list of types that have been processed in this type batch but not yet committed to the repository's
1800      *        set of types
1801      * @return a list of node types where each element is the node type for the corresponding element of the array of supertype
1802      *         names
1803      * @throws RepositoryException if any of the names in the array of supertype names does not correspond to an
1804      *         already-registered node type or a node type that is pending registration
1805      */
1806     private List<JcrNodeType> supertypesFor( NodeTypeDefinition nodeType,
1807                                              Collection<JcrNodeType> pendingTypes ) throws RepositoryException {
1808         assert nodeType != null;
1809 
1810         List<JcrNodeType> supertypes = new LinkedList<JcrNodeType>();
1811 
1812         boolean isMixin = nodeType.isMixin();
1813         boolean needsPrimaryAncestor = !isMixin;
1814         String nodeTypeName = nodeType.getName();
1815 
1816         for (String supertypeNameStr : nodeType.getDeclaredSupertypeNames()) {
1817             Name supertypeName = nameFactory.create(supertypeNameStr);
1818             JcrNodeType supertype = findTypeInMapOrList(supertypeName, pendingTypes);
1819             if (supertype == null) {
1820                 throw new InvalidNodeTypeDefinitionException(JcrI18n.invalidSupertypeName.text(supertypeNameStr, nodeTypeName));
1821             }
1822             needsPrimaryAncestor &= supertype.isMixin();
1823             supertypes.add(supertype);
1824         }
1825 
1826         // primary types (other than nt:base) always have at least one ancestor that's a primary type - nt:base
1827         if (needsPrimaryAncestor) {
1828             Name nodeName = nameFactory.create(nodeTypeName);
1829             if (!JcrNtLexicon.BASE.equals(nodeName)) {
1830                 JcrNodeType ntBase = findTypeInMapOrList(JcrNtLexicon.BASE, pendingTypes);
1831                 assert ntBase != null;
1832                 supertypes.add(0, ntBase);
1833             }
1834         }
1835         return supertypes;
1836     }
1837 
1838     /**
1839      * Returns the list of subtypes for the given node.
1840      * 
1841      * @param nodeType the node type for which subtypes should be returned; may not be null
1842      * @return the subtypes for the node
1843      */
1844     final Collection<JcrNodeType> subtypesFor( JcrNodeType nodeType ) {
1845         CheckArg.isNotNull(nodeType, "nodeType");
1846 
1847         try {
1848             nodeTypeManagerLock.readLock().lock();
1849 
1850             List<JcrNodeType> subtypes = new LinkedList<JcrNodeType>();
1851             for (JcrNodeType type : this.nodeTypes.values()) {
1852                 if (type.supertypes().contains(nodeType)) {
1853                     subtypes.add(type);
1854                 }
1855             }
1856 
1857             return subtypes;
1858         } finally {
1859             nodeTypeManagerLock.readLock().unlock();
1860         }
1861     }
1862 
1863     /**
1864      * Returns the list of declared subtypes for the given node.
1865      * 
1866      * @param nodeType the node type for which declared subtypes should be returned; may not be null
1867      * @return the subtypes for the node
1868      */
1869     final Collection<JcrNodeType> declaredSubtypesFor( JcrNodeType nodeType ) {
1870         CheckArg.isNotNull(nodeType, "nodeType");
1871 
1872         try {
1873             nodeTypeManagerLock.readLock().lock();
1874 
1875             String nodeTypeName = nodeType.getName();
1876             List<JcrNodeType> subtypes = new LinkedList<JcrNodeType>();
1877             for (JcrNodeType type : this.nodeTypes.values()) {
1878                 if (Arrays.asList(type.getDeclaredSupertypeNames()).contains(nodeTypeName)) {
1879                     subtypes.add(type);
1880                 }
1881             }
1882 
1883             return subtypes;
1884         } finally {
1885             nodeTypeManagerLock.readLock().unlock();
1886         }
1887     }
1888 
1889     /**
1890      * Validates that the supertypes are compatible under ModeShape restrictions.
1891      * <p>
1892      * ModeShape imposes the following rules on the supertypes of a type:
1893      * <ol>
1894      * <li>The type must have at least one supertype (unless the type is {@code nt:base}.</li>
1895      * <li>No two supertypes {@code t1} and {@code t2} can declare each declare a property ({@code p1} and {@code p2}) with the
1896      * same name and cardinality ({@code p1.isMultiple() == p2.isMultiple()}). Note that this does prohibit each {@code t1} and
1897      * {@code t2} from having a common supertype (or super-supertype, etc.) that declares a property).</li>
1898      * <li>No two supertypes {@code t1} and {@code t2} can declare each declare a child node ({@code n1} and {@code n2}) with the
1899      * same name and SNS status ({@code p1.allowsSameNameSiblings() == p2.allowsSameNameSiblings()}). Note that this does prohibit
1900      * each {@code t1} and {@code t2} from having a common supertype (or super-supertype, etc.) that declares a child node).</li>
1901      * </ol>
1902      * </p>
1903      * <p>
1904      * If any of these rules are violated, a {@link RepositoryException} is thrown.
1905      * </p>
1906      * 
1907      * @param supertypes the supertypes of this node type
1908      * @param nodeName the name of the node for which the supertypes are being validated.
1909      * @throws RepositoryException if any of the rules described above are violated
1910      */
1911     private void validate( List<JcrNodeType> supertypes,
1912                            String nodeName ) throws RepositoryException {
1913         assert supertypes != null;
1914 
1915         Map<PropertyDefinitionId, JcrPropertyDefinition> props = new HashMap<PropertyDefinitionId, JcrPropertyDefinition>();
1916 
1917         for (JcrNodeType supertype : supertypes) {
1918             for (JcrPropertyDefinition property : supertype.propertyDefinitions()) {
1919                 JcrPropertyDefinition oldProp = props.put(new PropertyDefinitionId(property.getInternalName(),
1920                                                                                    property.getInternalName(),
1921                                                                                    PropertyType.UNDEFINED, property.isMultiple()),
1922                                                           property);
1923                 if (oldProp != null) {
1924                     String oldPropTypeName = oldProp.getDeclaringNodeType().getName();
1925                     String propTypeName = property.getDeclaringNodeType().getName();
1926                     if (!oldPropTypeName.equals(propTypeName)) {
1927                         throw new InvalidNodeTypeDefinitionException(JcrI18n.supertypesConflict.text(oldPropTypeName,
1928                                                                                                      propTypeName,
1929                                                                                                      "property",
1930                                                                                                      property.getName()));
1931                     }
1932                 }
1933             }
1934         }
1935 
1936         Map<NodeDefinitionId, JcrNodeDefinition> childNodes = new HashMap<NodeDefinitionId, JcrNodeDefinition>();
1937 
1938         for (JcrNodeType supertype : supertypes) {
1939             for (JcrNodeDefinition childNode : supertype.childNodeDefinitions()) {
1940                 JcrNodeDefinition oldNode = childNodes.put(new NodeDefinitionId(childNode.getInternalName(),
1941                                                                                 childNode.getInternalName(), new Name[0]),
1942                                                            childNode);
1943                 if (oldNode != null) {
1944                     String oldNodeTypeName = oldNode.getDeclaringNodeType().getName();
1945                     String childNodeTypeName = childNode.getDeclaringNodeType().getName();
1946                     if (!oldNodeTypeName.equals(childNodeTypeName)) {
1947                         throw new InvalidNodeTypeDefinitionException(JcrI18n.supertypesConflict.text(oldNodeTypeName,
1948                                                                                                      childNodeTypeName,
1949                                                                                                      "child node",
1950                                                                                                      childNode.getName()));
1951                     }
1952                 }
1953             }
1954         }
1955     }
1956 
1957     /**
1958      * Validates that the given node type definition is valid under the ModeShape and JCR type rules within the given context.
1959      * <p>
1960      * See {@link #registerNodeTypes(Iterable)} for the list of criteria that determine whether a node type definition is valid.
1961      * </p>
1962      * 
1963      * @param nodeType the node type to attempt to validate
1964      * @param supertypes the names of the supertypes of the node type to which this child node belongs
1965      * @param pendingTypes the list of types previously registered in this batch but not yet committed to the repository
1966      * @throws RepositoryException if the given node type template is not valid
1967      */
1968     private void validate( JcrNodeType nodeType,
1969                            List<JcrNodeType> supertypes,
1970                            List<JcrNodeType> pendingTypes ) throws RepositoryException {
1971         Name nodeTypeName = nodeType.getInternalName();
1972         validate(supertypes, nodeTypeName.getString(this.context.getNamespaceRegistry()));
1973 
1974         List<Name> supertypeNames = new ArrayList<Name>(supertypes.size());
1975         for (JcrNodeType supertype : supertypes) {
1976             supertypeNames.add(supertype.getInternalName());
1977         }
1978 
1979         boolean foundExact = false;
1980         boolean foundResidual = false;
1981         Name primaryItemName = nodeType.getInternalPrimaryItemName();
1982 
1983         for (JcrNodeDefinition node : nodeType.getDeclaredChildNodeDefinitions()) {
1984             validate(node, supertypeNames, pendingTypes);
1985             if (node.isResidual()) foundResidual = true;
1986 
1987             if (primaryItemName != null && primaryItemName.equals(node.getInternalName())) {
1988                 foundExact = true;
1989             }
1990         }
1991 
1992         for (JcrPropertyDefinition prop : nodeType.getDeclaredPropertyDefinitions()) {
1993             validate(prop, supertypeNames, pendingTypes);
1994             if (prop.isResidual()) foundResidual = true;
1995             if (primaryItemName != null && primaryItemName.equals(prop.getInternalName())) {
1996                 if (foundExact) {
1997                     throw new RepositoryException(JcrI18n.ambiguousPrimaryItemName.text(primaryItemName));
1998                 }
1999                 foundExact = true;
2000             }
2001         }
2002 
2003         if (primaryItemName != null && !foundExact && !foundResidual) {
2004             throw new RepositoryException(JcrI18n.invalidPrimaryItemName.text(primaryItemName));
2005         }
2006     }
2007 
2008     /**
2009      * Validates that the given child node definition is valid under the ModeShape and JCR type rules within the given context.
2010      * <p>
2011      * ModeShape considers a child node definition valid if it meets these criteria:
2012      * <ol>
2013      * <li>Residual child node definitions cannot be mandatory</li>
2014      * <li>If the child node is auto-created, it must specify a default primary type name</li>
2015      * <li>If the child node overrides an existing child node definition from a supertype, the new definition must be mandatory if
2016      * the old definition was mandatory</li>
2017      * <li>The child node cannot override an existing child node definition from a supertype if the ancestor definition is
2018      * protected</li>
2019      * <li>If the child node overrides an existing child node definition from a supertype, the required primary types of the new
2020      * definition must be more restrictive than the required primary types of the old definition - that is, the new primary types
2021      * must defined such that any type that satisfies all of the required primary types for the new definition must also satisfy
2022      * all of the required primary types for the old definition. This requirement is analogous to the requirement that overriding
2023      * property definitions have a required type that is always convertible to the required type of the overridden definition.</li>
2024      * </ol>
2025      * </p>
2026      * 
2027      * @param node the child node definition to be validated
2028      * @param supertypes the names of the supertypes of the node type to which this child node belongs
2029      * @param pendingTypes the list of types previously registered in this batch but not yet committed to the repository
2030      * @throws RepositoryException if the child node definition is not valid
2031      */
2032     private void validate( JcrNodeDefinition node,
2033                            List<Name> supertypes,
2034                            List<JcrNodeType> pendingTypes ) throws RepositoryException {
2035         if (node.isAutoCreated() && !node.isProtected() && node.defaultPrimaryTypeName() == null) {
2036             throw new InvalidNodeTypeDefinitionException(JcrI18n.autocreatedNodesNeedDefaults.text(node.getName()));
2037         }
2038         if (node.isMandatory() && JcrNodeType.RESIDUAL_ITEM_NAME.equals(node.getName())) {
2039             throw new InvalidNodeTypeDefinitionException(JcrI18n.residualDefinitionsCannotBeMandatory.text("child nodes"));
2040         }
2041 
2042         Name nodeName = context.getValueFactories().getNameFactory().create(node.getName());
2043         nodeName = nodeName == null ? JcrNodeType.RESIDUAL_NAME : nodeName;
2044 
2045         List<JcrNodeDefinition> ancestors = findChildNodeDefinitions(supertypes, nodeName, NodeCardinality.ANY, pendingTypes);
2046 
2047         for (JcrNodeDefinition ancestor : ancestors) {
2048             if (ancestor.isProtected()) {
2049                 throw new InvalidNodeTypeDefinitionException(
2050                                                              JcrI18n.cannotOverrideProtectedDefinition.text(ancestor.getDeclaringNodeType()
2051                                                                                                                     .getName(),
2052                                                                                                             "child node"));
2053             }
2054 
2055             if (ancestor.isMandatory() && !node.isMandatory()) {
2056                 throw new InvalidNodeTypeDefinitionException(
2057                                                              JcrI18n.cannotMakeMandatoryDefinitionOptional.text(ancestor.getDeclaringNodeType()
2058                                                                                                                         .getName(),
2059                                                                                                                 "child node"));
2060 
2061             }
2062 
2063             Name[] requiredPrimaryTypeNames = ancestor.requiredPrimaryTypeNames();
2064             for (int i = 0; i < requiredPrimaryTypeNames.length; i++) {
2065                 NodeType apt = findTypeInMapOrList(requiredPrimaryTypeNames[i], pendingTypes);
2066 
2067                 if (apt == null) {
2068                     I18n msg = JcrI18n.couldNotFindDefinitionOfRequiredPrimaryType;
2069                     throw new InvalidNodeTypeDefinitionException(msg.text(requiredPrimaryTypeNames[i],
2070                                                                           node.getName(),
2071                                                                           node.getDeclaringNodeType()));
2072 
2073                 }
2074 
2075                 boolean found = false;
2076 
2077                 for (Name name : node.requiredPrimaryTypeNames()) {
2078                     JcrNodeType npt = findTypeInMapOrList(name, pendingTypes);
2079 
2080                     if (npt.isNodeType(apt.getName())) {
2081                         found = true;
2082                         break;
2083                     }
2084                 }
2085 
2086                 // Allow side-by-side definitions of residual child nodes per JCR 1.0.1 spec 6.7.8
2087                 if (!found && !JcrNodeType.RESIDUAL_NAME.equals(node.name)) {
2088                     I18n msg = JcrI18n.cannotRedefineChildNodeWithIncompatibleDefinition;
2089                     throw new InvalidNodeTypeDefinitionException(msg.text(nodeName, apt.getName(), node.getDeclaringNodeType()));
2090 
2091                 }
2092             }
2093         }
2094     }
2095 
2096     /**
2097      * Validates that the given property definition is valid under the ModeShape and JCR type rules within the given context.
2098      * <p>
2099      * ModeShape considers a property definition valid if it meets these criteria:
2100      * <ol>
2101      * <li>Residual properties cannot be mandatory</li>
2102      * <li>If the property is auto-created, it must specify a default value</li>
2103      * <li>If the property is single-valued, it can only specify a single default value</li>
2104      * <li>If the property overrides an existing property definition from a supertype, the new definition must be mandatory if the
2105      * old definition was mandatory</li>
2106      * <li>The property cannot override an existing property definition from a supertype if the ancestor definition is protected</li>
2107      * <li>If the property overrides an existing property definition from a supertype, the new definition must have the same
2108      * required type as the old definition or a required type that can ALWAYS be cast to the required type of the ancestor (see
2109      * section 3.6.4 of the JCR 2.0 specification)</li>
2110      * </ol>
2111      * Note that an empty set of properties would meet the criteria above.
2112      * </p>
2113      * 
2114      * @param prop the property definition to be validated
2115      * @param supertypes the names of the supertypes of the node type to which this property belongs
2116      * @param pendingTypes the list of types previously registered in this batch but not yet committed to the repository
2117      * @throws RepositoryException if the property definition is not valid
2118      */
2119     private void validate( JcrPropertyDefinition prop,
2120                            List<Name> supertypes,
2121                            List<JcrNodeType> pendingTypes ) throws RepositoryException {
2122         assert prop != null;
2123         assert supertypes != null;
2124         assert pendingTypes != null;
2125 
2126         if (prop.isMandatory() && !prop.isProtected() && JcrNodeType.RESIDUAL_ITEM_NAME.equals(prop.getName())) {
2127             throw new InvalidNodeTypeDefinitionException(JcrI18n.residualDefinitionsCannotBeMandatory.text("properties"));
2128         }
2129 
2130         Value[] defaultValues = prop.getDefaultValues();
2131         if (prop.isAutoCreated() && !prop.isProtected() && (defaultValues == null || defaultValues.length == 0)) {
2132             throw new InvalidNodeTypeDefinitionException(JcrI18n.autocreatedPropertyNeedsDefault.text(prop.getName(),
2133                                                                                                       prop.getDeclaringNodeType()
2134                                                                                                           .getName()));
2135         }
2136 
2137         if (!prop.isMultiple() && (defaultValues != null && defaultValues.length > 1)) {
2138             throw new InvalidNodeTypeDefinitionException(
2139                                                          JcrI18n.singleValuedPropertyNeedsSingleValuedDefault.text(prop.getName(),
2140                                                                                                                    prop.getDeclaringNodeType()
2141                                                                                                                        .getName()));
2142         }
2143 
2144         Name propName = context.getValueFactories().getNameFactory().create(prop.getName());
2145         propName = propName == null ? JcrNodeType.RESIDUAL_NAME : propName;
2146 
2147         List<JcrPropertyDefinition> ancestors = findPropertyDefinitions(supertypes,
2148                                                                         propName,
2149                                                                         prop.isMultiple() ? PropertyCardinality.MULTI_VALUED_ONLY : PropertyCardinality.SINGLE_VALUED_ONLY,
2150                                                                         pendingTypes);
2151 
2152         for (JcrPropertyDefinition ancestor : ancestors) {
2153             if (ancestor.isProtected()) {
2154                 throw new InvalidNodeTypeDefinitionException(
2155                                                              JcrI18n.cannotOverrideProtectedDefinition.text(ancestor.getDeclaringNodeType()
2156                                                                                                                     .getName(),
2157                                                                                                             "property"));
2158             }
2159 
2160             if (ancestor.isMandatory() && !prop.isMandatory()) {
2161                 throw new InvalidNodeTypeDefinitionException(
2162                                                              JcrI18n.cannotMakeMandatoryDefinitionOptional.text(ancestor.getDeclaringNodeType()
2163                                                                                                                         .getName(),
2164                                                                                                                 "property"));
2165 
2166             }
2167 
2168             // TODO: It would be nice if we could allow modification of constraints if the new constraints were more strict than
2169             // the old
2170             if (ancestor.getValueConstraints() != null
2171                 && !Arrays.equals(ancestor.getValueConstraints(), prop.getValueConstraints())) {
2172                 throw new InvalidNodeTypeDefinitionException(
2173                                                              JcrI18n.constraintsChangedInSubtype.text(propName,
2174                                                                                                       ancestor.getDeclaringNodeType()
2175                                                                                                               .getName()));
2176             }
2177 
2178             if (!isAlwaysSafeConversion(prop.getRequiredType(), ancestor.getRequiredType())) {
2179                 throw new InvalidNodeTypeDefinitionException(
2180                                                              JcrI18n.cannotRedefineProperty.text(propName,
2181                                                                                                  PropertyType.nameFromValue(prop.getRequiredType()),
2182                                                                                                  ancestor.getDeclaringNodeType()
2183                                                                                                          .getName(),
2184                                                                                                  PropertyType.nameFromValue(ancestor.getRequiredType())));
2185 
2186             }
2187         }
2188     }
2189 
2190     /**
2191      * Returns whether it is always possible to convert a value with JCR property type {@code fromType} to {@code toType}.
2192      * <p>
2193      * This method is based on the conversions which can never throw an exception in the chart in section 3.6.4 of the JCR 2.0
2194      * specification.
2195      * </p>
2196      * 
2197      * @param fromType the type to be converted from
2198      * @param toType the type to convert to
2199      * @return true if any value with type {@code fromType} can be converted to a type of {@code toType} without a
2200      *         {@link ValueFormatException} being thrown.
2201      * @see PropertyType
2202      */
2203     private boolean isAlwaysSafeConversion( int fromType,
2204                                             int toType ) {
2205 
2206         if (fromType == toType) return true;
2207 
2208         switch (toType) {
2209             case PropertyType.BOOLEAN:
2210                 return fromType == PropertyType.BINARY || fromType == PropertyType.STRING;
2211 
2212             case PropertyType.DATE:
2213                 return fromType == PropertyType.DOUBLE || fromType == PropertyType.LONG;
2214 
2215             case PropertyType.DOUBLE:
2216                 // Conversion from DATE could result in out-of-range value
2217                 return fromType == PropertyType.LONG;
2218             case PropertyType.LONG:
2219                 // Conversion from DATE could result in out-of-range value
2220                 return fromType == PropertyType.DOUBLE;
2221 
2222             case PropertyType.PATH:
2223                 return fromType == PropertyType.NAME;
2224 
2225                 // Values of any type MAY fail when converting to these types
2226             case PropertyType.NAME:
2227             case PropertyType.REFERENCE:
2228                 return false;
2229 
2230                 // Any type can be converted to these types
2231             case PropertyType.BINARY:
2232             case PropertyType.STRING:
2233             case PropertyType.UNDEFINED:
2234                 return true;
2235 
2236             default:
2237                 throw new IllegalStateException("Unexpected state: " + toType);
2238         }
2239     }
2240 
2241     @Override
2242     public Path getObservedPath() {
2243         return this.nodeTypesPath;
2244     }
2245 
2246     @Override
2247     public void notify( Changes changes ) {
2248         boolean needsReload = false;
2249 
2250         for (ChangeRequest change : changes.getChangeRequests()) {
2251             assert change.changedLocation().hasPath();
2252 
2253             Path changedPath = change.changedLocation().getPath();
2254             if (changedPath.equals(nodeTypesPath)) {
2255                 // nothing to do with the "/jcr:system/jcr:nodeTypes" node ...
2256                 continue;
2257             }
2258             assert nodeTypesPath.isAncestorOf(changedPath);
2259 
2260             switch (change.getType()) {
2261                 case CREATE_NODE:
2262                 case DELETE_BRANCH:
2263                     needsReload = true;
2264 
2265                     break;
2266                 default:
2267                     assert false : "Unexpected change request: " + change;
2268             }
2269         }
2270 
2271         if (!needsReload) return;
2272 
2273         this.nodeTypeManagerLock.writeLock().lock();
2274         try {
2275             GraphNodeTypeReader reader = new GraphNodeTypeReader(this.context);
2276             Graph systemGraph = repository.createSystemGraph(this.context);
2277 
2278             reader.read(systemGraph, nodeTypesPath, null);
2279 
2280             Problems readerProblems = reader.getProblems();
2281             if (readerProblems.hasProblems()) {
2282                 if (readerProblems.hasErrors()) {
2283                     LOGGER.error(JcrI18n.errorReadingNodeTypesFromRemote, reader.getProblems());
2284                     return;
2285                 }
2286 
2287                 LOGGER.warn(JcrI18n.problemReadingNodeTypesFromRemote, reader.getProblems());
2288             }
2289             
2290             Map<Name, JcrNodeType> newNodeTypeMap = new HashMap<Name, JcrNodeType>();
2291             try {
2292                 for (NodeTypeDefinition nodeTypeDefn : reader.getNodeTypeDefinitions()) {
2293                     List<JcrNodeType> supertypes = supertypesFor(nodeTypeDefn, newNodeTypeMap.values());
2294                     JcrNodeType nodeType = nodeTypeFrom(nodeTypeDefn, supertypes);
2295 
2296                     newNodeTypeMap.put(nodeType.getInternalName(), nodeType);
2297                 }
2298             } catch (Throwable re) {
2299                 LOGGER.error(JcrI18n.errorSynchronizingNodeTypes, re);
2300             }
2301 
2302             this.nodeTypes.clear();
2303             this.nodeTypes.putAll(newNodeTypeMap);
2304 
2305             assert this.nodeTypes.get(ModeShapeLexicon.ROOT) != null;
2306 
2307             for (JcrSession activeSession : repository.activeSessions()) {
2308                 JcrWorkspace workspace = activeSession.workspace();
2309                 if (workspace == null) continue;
2310 
2311                 JcrNodeTypeManager nodeTypeManager = workspace.nodeTypeManager();
2312                 if (nodeTypeManager == null) continue;
2313 
2314                 nodeTypeManager.signalExternalNodeTypeChanges();
2315             }
2316 
2317         } finally {
2318             this.schemata = null;
2319             this.nodeTypeManagerLock.writeLock().unlock();
2320         }
2321 
2322     }
2323 }