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