Author: rhauch
Date: 2009-04-14 12:13:03 -0400 (Tue, 14 Apr 2009)
New Revision: 820
Modified:
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeDefinition.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyDefinition.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/NodeDefinitionId.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyDefinitionId.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/RepositoryNodeTypeManager.java
trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties
Log:
DNA-339 JCR Type Registration and Unregistration
Applying the patch, with some very minor modifications (particularly, some additional
JavaDoc in NodeDefinitionId and PropertyDefinitionId). The Key classes in
JcrPropertyDefinition and JcrNodeDefinition should be replaced with PropertyDefinitionId
and NodeDefinitionId, respectively, but this can be done in another change set. The
current approach works, but may actually not match the specification. (The JavaDoc added
to the *DefinitionId classes helps to explain what information exactly is used to
distinguish the definitions, and the Key classes use a different set of information.)
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java 2009-04-13 19:29:46 UTC
(rev 819)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java 2009-04-14 16:13:03 UTC
(rev 820)
@@ -76,6 +76,7 @@
public static I18n errorWhileFindingNodeWithUuid;
public static I18n errorWhileFindingNodeWithPath;
public static I18n nodeDefinitionCouldNotBeDeterminedForNode;
+ public static I18n noSnsDefinitionForNode;
public static I18n missingNodeTypeForExistingNode;
public static I18n unableToCreateNodeWithPrimaryTypeThatDoesNotExist;
public static I18n unableToCreateNodeWithNoDefaultPrimaryTypeOnChildNodeDefinition;
@@ -109,6 +110,23 @@
public static I18n notStoredQuery;
public static I18n invalidQueryLanguage;
+ // Type registration messages
+ public static I18n invalidNodeTypeName;
+ public static I18n nodeTypeAlreadyExists;
+ public static I18n invalidPrimaryTypeName;
+ public static I18n invalidSupertypeName;
+ public static I18n supertypesConflict;
+ public static I18n ambiguousPrimaryItemName;
+ public static I18n invalidPrimaryItemName;
+ public static I18n autocreatedNodesNeedDefaults;
+ public static I18n residualDefinitionsCannotBeMandatory;
+ public static I18n cannotOverrideProtectedDefinition;
+ public static I18n cannotMakeMandatoryDefinitionOptional;
+ public static I18n constraintsChangedInSubtype;
+ public static I18n cannotRedefineProperty;
+ public static I18n autocreatedPropertyNeedsDefault;
+ public static I18n singleValuedPropertyNeedsSingleValuedDefault;
+
static {
try {
I18n.initialize(JcrI18n.class);
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeDefinition.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeDefinition.java 2009-04-13
19:29:46 UTC (rev 819)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeDefinition.java 2009-04-14
16:13:03 UTC (rev 820)
@@ -27,11 +27,15 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Value;
import javax.jcr.nodetype.NodeDefinition;
import javax.jcr.nodetype.NodeType;
import net.jcip.annotations.Immutable;
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.NamespaceRegistry;
/**
* DNA implementation of the {@link NodeDefinition} class.
@@ -218,4 +222,73 @@
return getId().toString();
}
+ Key getKey(boolean includeTypes) {
+ return new Key(this, includeTypes);
+ }
+
+ /**
+ * Internal class that encapsulates composite unique identifier for child node
definitions
+ */
+ class Key {
+ String keyString;
+
+ Key( Node node,
+ NamespaceRegistry registry ) throws Exception {
+ StringBuffer buff = new StringBuffer();
+
+ try {
+ String nodeName =
node.getProperty(JcrLexicon.NAME.getString(registry)).getString();
+ buff.append(nodeName);
+ } catch (PathNotFoundException pnfe) {
+ // Ignorable, means name is not set.
+ buff.append(JcrNodeType.RESIDUAL_ITEM_NAME);
+ }
+
+ try {
+ Value[] requiredTypes =
node.getProperty(JcrLexicon.REQUIRED_PRIMARY_TYPES.getString(registry)).getValues();
+
+ for (int i = 0; i < requiredTypes.length; i++) {
+ buff.append('_').append(requiredTypes[i].getString());
+ }
+ } catch (PathNotFoundException pnfe) {
+ // No required types. Weird, but not debilitating.
+ }
+
+
buff.append('_').append(node.getProperty(JcrLexicon.SAME_NAME_SIBLINGS.getString(registry)).getBoolean());
+
+ this.keyString = buff.toString();
+ }
+
+ Key( NodeDefinition def,
+ boolean includeTypes ) {
+ StringBuffer buff = new StringBuffer();
+ buff.append(def.getName());
+
+ if (includeTypes) {
+ NodeType[] requiredTypes = def.getRequiredPrimaryTypes();
+ for (int i = 0; i < requiredTypes.length; i++) {
+ buff.append('_').append(requiredTypes[i].getName());
+ }
+ }
+
+ buff.append('_').append(def.allowsSameNameSiblings());
+
+ this.keyString = buff.toString();
+ }
+
+ @Override
+ public boolean equals( Object ob ) {
+ if (!(ob instanceof Key)) {
+ return false;
+ }
+
+ return keyString.equals(((Key)ob).keyString);
+ }
+
+ @Override
+ public int hashCode() {
+ return keyString.hashCode();
+ }
+ }
+
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java 2009-04-13 19:29:46 UTC
(rev 819)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java 2009-04-14 16:13:03 UTC
(rev 820)
@@ -34,7 +34,6 @@
import java.util.Set;
import javax.jcr.PropertyType;
import javax.jcr.Value;
-import javax.jcr.nodetype.NodeDefinition;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.PropertyDefinition;
import net.jcip.annotations.Immutable;
@@ -334,7 +333,7 @@
*
* @see javax.jcr.nodetype.NodeType#getDeclaredChildNodeDefinitions()
*/
- public NodeDefinition[] getDeclaredChildNodeDefinitions() {
+ public JcrNodeDefinition[] getDeclaredChildNodeDefinitions() {
// Always have to make a copy to prevent changes ...
return childNodeDefinitions.toArray(new
JcrNodeDefinition[childNodeDefinitions.size()]);
}
@@ -344,10 +343,10 @@
*
* @see javax.jcr.nodetype.NodeType#getChildNodeDefinitions()
*/
- public NodeDefinition[] getChildNodeDefinitions() {
+ public JcrNodeDefinition[] getChildNodeDefinitions() {
// Always have to make a copy to prevent changes ...
Collection<JcrNodeDefinition> definitions =
this.allDefinitions.allChildNodeDefinitions();
- return definitions.toArray(new NodeDefinition[definitions.size()]);
+ return definitions.toArray(new JcrNodeDefinition[definitions.size()]);
}
/**
@@ -355,10 +354,10 @@
*
* @see javax.jcr.nodetype.NodeType#getPropertyDefinitions()
*/
- public PropertyDefinition[] getPropertyDefinitions() {
+ public JcrPropertyDefinition[] getPropertyDefinitions() {
// Always have to make a copy to prevent changes ...
Collection<JcrPropertyDefinition> definitions =
this.allDefinitions.allPropertyDefinitions();
- return definitions.toArray(new PropertyDefinition[definitions.size()]);
+ return definitions.toArray(new JcrPropertyDefinition[definitions.size()]);
}
/**
@@ -366,9 +365,9 @@
*
* @see javax.jcr.nodetype.NodeType#getDeclaredSupertypes()
*/
- public NodeType[] getDeclaredSupertypes() {
+ public JcrNodeType[] getDeclaredSupertypes() {
// Always have to make a copy to prevent changes ...
- return declaredSupertypes.toArray(new NodeType[declaredSupertypes.size()]);
+ return declaredSupertypes.toArray(new JcrNodeType[declaredSupertypes.size()]);
}
/**
@@ -382,16 +381,26 @@
}
/**
- * Returns the internal {@link Name} object for the note type. This method exists
outside the JCR API and should not be
+ * Returns the internal {@link Name} object for the node type. This method exists
outside the JCR API and should not be
* exposed outside of the package.
*
- * @return the internal {@link Name} object for the note type.
+ * @return the internal {@link Name} object for the node type.
*/
Name getInternalName() {
return name;
}
/**
+ * Returns the internal {@link Name} object for the primary item of this node type.
This method exists outside the JCR API and
+ * should not be exposed outside of the package.
+ *
+ * @return the internal {@link Name} object for the primary item of this node type.
+ */
+ Name getInternalPrimaryItemName() {
+ return primaryItemName;
+ }
+
+ /**
* {@inheritDoc}
*
* @see javax.jcr.nodetype.NodeType#getPrimaryItemName()
@@ -410,7 +419,7 @@
*
* @see javax.jcr.nodetype.NodeType#getDeclaredPropertyDefinitions()
*/
- public PropertyDefinition[] getDeclaredPropertyDefinitions() {
+ public JcrPropertyDefinition[] getDeclaredPropertyDefinitions() {
return propertyDefinitions.toArray(new
JcrPropertyDefinition[propertyDefinitions.size()]);
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyDefinition.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyDefinition.java 2009-04-13
19:29:46 UTC (rev 819)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyDefinition.java 2009-04-14
16:13:03 UTC (rev 820)
@@ -26,6 +26,7 @@
import java.util.UUID;
import java.util.regex.Pattern;
import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
@@ -36,6 +37,7 @@
import org.jboss.dna.graph.property.DateTime;
import org.jboss.dna.graph.property.Name;
import org.jboss.dna.graph.property.NameFactory;
+import org.jboss.dna.graph.property.NamespaceRegistry;
import org.jboss.dna.graph.property.Path;
import org.jboss.dna.graph.property.PathFactory;
import org.jboss.dna.graph.property.ValueFactories;
@@ -139,6 +141,20 @@
}
/**
+ * Creates a new <code>JcrPropertyDefinition</code> that is identical to
the current object, but with the given
+ * <code>context</code>. Provided to support immutable pattern for this
class.
+ *
+ * @param context the {@link ExecutionContext} for the new
<code>JcrPropertyDefinition</code>
+ * @return a new <code>JcrPropertyDefinition</code> that is identical to
the current object, but with the given
+ * <code>context</code>.
+ */
+ JcrPropertyDefinition with( ExecutionContext context) {
+ return new JcrPropertyDefinition(context, this.declaringNodeType, this.name,
this.getOnParentVersion(),
+ this.isAutoCreated(), this.isMandatory(),
this.isProtected(), this.getDefaultValues(),
+ this.getRequiredType(),
this.getValueConstraints(), this.isMultiple());
+ }
+
+ /**
* {@inheritDoc}
*
* @see java.lang.Object#toString()
@@ -673,4 +689,51 @@
return false;
}
}
+
+ Key getKey(boolean includeType) {
+ return new Key(this, includeType);
+ }
+
+ /**
+ * Internal class that encapsulates composite unique identifier for property
definitions
+ */
+ class Key {
+ String keyString;
+
+ Key( Node node,
+ NamespaceRegistry registry ) throws Exception {
+ String propertyName = JcrNodeType.RESIDUAL_ITEM_NAME;
+
+ try {
+ propertyName =
node.getProperty(JcrLexicon.NAME.getString(registry)).getString();
+ } catch (PathNotFoundException pnfe) {
+ // Ignorable, means name is not set.
+ }
+ String requiredType =
node.getProperty(JcrLexicon.REQUIRED_TYPE.getString(registry)).getString();
+ boolean allowsMultiple =
node.getProperty(JcrLexicon.MULTIPLE.getString(registry)).getBoolean();
+
+ this.keyString = propertyName + "-" + requiredType + "-"
+ allowsMultiple;
+ }
+
+ Key( PropertyDefinition def, boolean includeType ) {
+ String requiredType = PropertyType.nameFromValue(def.getRequiredType());
+
+ this.keyString = def.getName() + (includeType ? "-" + requiredType
: "") + "-" + def.isMultiple();
+ }
+
+ @Override
+ public boolean equals( Object ob ) {
+ if (!(ob instanceof Key)) {
+ return false;
+ }
+
+ return keyString.equals(((Key)ob).keyString);
+ }
+
+ @Override
+ public int hashCode() {
+ return keyString.hashCode();
+ }
+ }
+
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java 2009-04-13 19:29:46 UTC
(rev 819)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java 2009-04-14 16:13:03 UTC
(rev 820)
@@ -166,6 +166,10 @@
return this.executionContext.getNamespaceRegistry();
}
+ JcrWorkspace workspace() {
+ return this.workspace;
+ }
+
/**
* {@inheritDoc}
*
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java 2009-04-13 19:29:46
UTC (rev 819)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java 2009-04-14 16:13:03
UTC (rev 820)
@@ -175,6 +175,10 @@
return this.nodeTypeManager;
}
+ final ExecutionContext context() {
+ return this.context;
+ }
+
/**
* {@inheritDoc}
*/
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/NodeDefinitionId.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/NodeDefinitionId.java 2009-04-13
19:29:46 UTC (rev 819)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/NodeDefinitionId.java 2009-04-14
16:13:03 UTC (rev 820)
@@ -24,6 +24,7 @@
package org.jboss.dna.jcr;
import java.io.Serializable;
+import java.util.HashMap;
import net.jcip.annotations.Immutable;
import org.jboss.dna.graph.property.Name;
import org.jboss.dna.graph.property.NameFactory;
@@ -34,6 +35,19 @@
* within the graph as {@link #getString() string values} on a property. These string
values can later be
* {@link #fromString(String, NameFactory) parsed} to reconstruct the identifier. Note
that this string representation does not
* use namespace prefixes, so they are long-lasting and durable.
+ * <p>
+ * What distinguishes one property definition from another is not well documented in the
JSR-170 specification. The closest this
+ * version of the spec gets is Section 6.7.15, but that merely says that more than one
property definition can have the same name.
+ * The proposed draft of the JSR-283 specification does clarify this more: Section 4.7.15
says :
+ * </p>
+ * <p>
+ * <quote>"Similarly, a node type may have two or more child node definitions
with identical name attributes as long as they are
+ * distinguishable by the required primary types attribute (the value returned by
+ * NodeDefinition.getRequiredPrimaryTypes)."</quote>
+ * </p>
+ * <p>
+ * This class is {@link Serializable} and designed to be used as a key in a {@link
HashMap}.
+ * </p>
*/
@Immutable
public final class NodeDefinitionId implements Serializable {
@@ -51,7 +65,10 @@
private final Name nodeTypeName;
private final Name childDefinitionName;
private final Name[] requiredPrimaryTypes;
- private final String stringVersion;
+ /**
+ * A cached string representation, which is used for {@link #equals(Object)} and
{@link #hashCode()} among other things.
+ */
+ private final String stringRepresentation;
/**
* Create an identifier for a node definition.
@@ -75,7 +92,7 @@
sb.append('/');
sb.append(requiredPrimaryType.getString());
}
- this.stringVersion = sb.toString();
+ this.stringRepresentation = sb.toString();
}
/**
@@ -121,7 +138,7 @@
* @return the string form
*/
public String getString() {
- return this.stringVersion;
+ return this.stringRepresentation;
}
/**
@@ -153,7 +170,7 @@
*/
@Override
public int hashCode() {
- return stringVersion.hashCode();
+ return stringRepresentation.hashCode();
}
/**
@@ -166,7 +183,7 @@
if (obj == this) return true;
if (obj instanceof NodeDefinitionId) {
NodeDefinitionId that = (NodeDefinitionId)obj;
- return this.stringVersion.equals(that.stringVersion);
+ return this.stringRepresentation.equals(that.stringRepresentation);
}
return false;
}
@@ -178,7 +195,7 @@
*/
@Override
public String toString() {
- return this.stringVersion;
+ return this.stringRepresentation;
}
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyDefinitionId.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyDefinitionId.java 2009-04-13
19:29:46 UTC (rev 819)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyDefinitionId.java 2009-04-14
16:13:03 UTC (rev 820)
@@ -24,6 +24,7 @@
package org.jboss.dna.jcr;
import java.io.Serializable;
+import java.util.HashMap;
import javax.jcr.PropertyType;
import net.jcip.annotations.Immutable;
import org.jboss.dna.graph.property.Name;
@@ -35,6 +36,20 @@
* stored within the graph as {@link #getString() string values} on a property. These
string values can later be
* {@link #fromString(String, NameFactory) parsed} to reconstruct the identifier. Note
that this string representation does not
* use namespace prefixes, so they are long-lasting and durable.
+ * <p>
+ * What distinguishes one property definition from another is not well documented in the
JSR-170 specification. The closest this
+ * version of the spec gets is Section 6.7.15, but that merely says that more than one
property definition can have the same name.
+ * The proposed draft of the JSR-283 specification does clarify this more: Section 4.7.15
says :
+ * </p>
+ * <p>
+ * <quote>"A node type may have two or more property definitions with
identical name attributes (the value returned by
+ * ItemDefinition.getName) as long as the definitions are otherwise distinguishable by
either the required type attribute (the
+ * value returned by PropertyDefinition.getRequiredType) or the multiple attribute (the
value returned by
+ * PropertyDefinition.isMultiple)."</quote>
+ * </p>
+ * <p>
+ * This class is {@link Serializable} and designed to be used as a key in a {@link
HashMap}.
+ * </p>
*/
@Immutable
public final class PropertyDefinitionId implements Serializable {
@@ -53,7 +68,10 @@
private final Name propertyDefinitionName;
private final int propertyType;
private final boolean allowsMultiple;
- private final String stringVersion;
+ /**
+ * A cached string representation, which is used for {@link #equals(Object)} and
{@link #hashCode()} among other things.
+ */
+ private final String stringRepresentation;
/**
* Create a new identifier for a propety definition.
@@ -73,8 +91,8 @@
this.propertyDefinitionName = propertyDefinitionName;
this.propertyType = propertyType;
this.allowsMultiple = allowsMultiple;
- this.stringVersion = this.nodeTypeName.getString() + '/' +
this.propertyDefinitionName.getString() + '/'
- + PropertyType.nameFromValue(propertyType) + '/' +
(allowsMultiple ? '*' : '1');
+ this.stringRepresentation = this.nodeTypeName.getString() + '/' +
this.propertyDefinitionName.getString() + '/'
+ + PropertyType.nameFromValue(propertyType) +
'/' + (allowsMultiple ? '*' : '1');
}
/**
@@ -129,7 +147,7 @@
* @return the string form
*/
public String getString() {
- return this.stringVersion;
+ return this.stringRepresentation;
}
/**
@@ -167,7 +185,7 @@
*/
@Override
public int hashCode() {
- return this.stringVersion.hashCode();
+ return this.stringRepresentation.hashCode();
}
/**
@@ -180,7 +198,7 @@
if (obj == this) return true;
if (obj instanceof PropertyDefinitionId) {
PropertyDefinitionId that = (PropertyDefinitionId)obj;
- return this.stringVersion.equals(that.stringVersion);
+ return this.stringRepresentation.equals(that.stringRepresentation);
}
return false;
}
@@ -192,7 +210,7 @@
*/
@Override
public String toString() {
- return this.stringVersion;
+ return this.stringRepresentation;
}
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/RepositoryNodeTypeManager.java
===================================================================
---
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/RepositoryNodeTypeManager.java 2009-04-13
19:29:46 UTC (rev 819)
+++
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/RepositoryNodeTypeManager.java 2009-04-14
16:13:03 UTC (rev 820)
@@ -24,7 +24,9 @@
package org.jboss.dna.jcr;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -32,6 +34,7 @@
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
import javax.jcr.nodetype.NodeDefinition;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.PropertyDefinition;
@@ -47,6 +50,7 @@
import org.jboss.dna.graph.property.PathNotFoundException;
import org.jboss.dna.graph.property.Property;
import org.jboss.dna.graph.property.PropertyFactory;
+import org.jboss.dna.jcr.JcrNodeTypeSource;
/**
* The {@link RepositoryNodeTypeManager} is the maintainer of node type information for
the entire repository at run-time. The
@@ -70,6 +74,27 @@
private final PropertyFactory propertyFactory;
private final PathFactory pathFactory;
+ /**
+ * List of ways to filter the returned property definitions
+ *
+ * @see RepositoryNodeTypeManager#findPropertyDefinitions(List, Name,
PropertyCardinality, List)
+ */
+ private enum PropertyCardinality {
+ SINGLE_VALUED_ONLY,
+ MULTI_VALUED_ONLY,
+ ANY
+ }
+
+ /**
+ * List of ways to filter the returned node definitions
+ *
+ * @see RepositoryNodeTypeManager#findChildNodeDefinitions(List, Name,
NodeCardinality, List)
+ */
+ private enum NodeCardinality {
+ NO_SAME_NAME_SIBLINGS,
+ SAME_NAME_SIBLINGS
+ }
+
RepositoryNodeTypeManager( ExecutionContext context,
JcrNodeTypeSource source ) {
this.context = context;
@@ -454,6 +479,52 @@
}
/**
+ * Searches the supplied primary and mixin node types for all valid property
definitions that match the given property name
+ * and cardinality.
+ * <p>
+ * If no satisfactory property definition could be found, this method returns an
empty list.
+ * </p>
+ *
+ * @param typeNamesToCheck the name of the types to check; may not be null
+ * @param propertyName the name of the property for which the definitions should be
retrieved
+ * @param typeToCheck the type of definitions to consider (single-valued only,
multi-valued only, or all)
+ * @param pendingTypes a list of types that have been created during type
registration but not yet registered in the type map
+ * @return a list of all valid property definitions that match the given property
name and cardinality
+ */
+ private List<JcrPropertyDefinition> findPropertyDefinitions( List<Name>
typeNamesToCheck,
+ Name propertyName,
+ PropertyCardinality
typeToCheck,
+ List<JcrNodeType>
pendingTypes ) {
+ assert typeNamesToCheck != null;
+ Collection<JcrPropertyDefinition> propDefs = null;
+ List<JcrPropertyDefinition> matchingDefs = new
ArrayList<JcrPropertyDefinition>();
+
+ // Look for a single-value property definition on the mixin types that matches by
name and type ...
+ for (Name typeNameToCheck : typeNamesToCheck) {
+ JcrNodeType typeName = findTypeInMapOrList(typeNameToCheck, pendingTypes);
+ if (typeName == null) continue;
+
+ switch (typeToCheck) {
+ case SINGLE_VALUED_ONLY:
+ propDefs = typeName.allSingleValuePropertyDefinitions(propertyName);
+ break;
+ case MULTI_VALUED_ONLY:
+ propDefs = typeName.allMultiValuePropertyDefinitions(propertyName);
+ break;
+ case ANY:
+ propDefs = typeName.allPropertyDefinitions(propertyName);
+ break;
+ default:
+ throw new IllegalStateException("Should be unreachable: " +
typeToCheck);
+ }
+
+ matchingDefs.addAll(propDefs);
+ }
+
+ return matchingDefs;
+ }
+
+ /**
* Determine if the property definitions of the supplied primary type and mixin types
allow the property with the supplied
* name to be removed.
*
@@ -566,6 +637,50 @@
}
/**
+ * Searches the supplied primary and mixin node types for all valid child node
definitions that match the given child node
+ * name and cardinality.
+ * <p>
+ * If no satisfactory child node definition could be found, this method returns an
empty list.
+ * </p>
+ *
+ * @param typeNamesToCheck the name of the types to check; may not be null
+ * @param childNodeName the name of the child node for which the definitions should
be retrieved
+ * @param typesToCheck the type of definitions to consider (allows SNS or does not
allow SNS)
+ * @param pendingTypes a list of types that have been created during type
registration but not yet registered in the type map
+ * @return a list of all valid chlid node definitions that match the given child node
name and cardinality
+ */
+ private List<JcrNodeDefinition> findChildNodeDefinitions( List<Name>
typeNamesToCheck,
+ Name childNodeName,
+ NodeCardinality
typesToCheck,
+ List<JcrNodeType>
pendingTypes ) {
+ assert typeNamesToCheck != null;
+ Collection<JcrNodeDefinition> nodeDefs = null;
+ List<JcrNodeDefinition> matchingDefs = new
ArrayList<JcrNodeDefinition>();
+
+ for (Name typeNameToCheck : typeNamesToCheck) {
+ JcrNodeType typeName = findTypeInMapOrList(typeNameToCheck, pendingTypes);
+ if (typeName == null) continue;
+
+ switch (typesToCheck) {
+ case NO_SAME_NAME_SIBLINGS:
+ nodeDefs = typeName.allChildNodeDefinitions(childNodeName);
+ break;
+ case SAME_NAME_SIBLINGS:
+ nodeDefs = typeName.allChildNodeDefinitions(childNodeName, true);
+ break;
+ }
+
+ assert nodeDefs != null;
+ for (JcrNodeDefinition definition : nodeDefs) {
+ if (NodeCardinality.NO_SAME_NAME_SIBLINGS == typesToCheck &&
definition.allowsSameNameSiblings()) continue;
+ matchingDefs.add(definition);
+ }
+ }
+
+ return matchingDefs;
+ }
+
+ /**
* Determine if the child node definitions of the supplied primary type and mixin
types of a parent node allow all of the
* children with the supplied name to be removed.
*
@@ -819,4 +934,531 @@
batch.create(nodeDefPath).with(propsList).and();
}
+
+ /**
+ * Registers the node types from the given {@link JcrNodeTypeSource}.
+ * <p>
+ * The effect of this method is "all or nothing"; if an error
occurs, no node types are registered or updated.
+ * </p>
+ * <p>
+ * <b>DNA Implementation Notes</b>
+ * </p>
+ * <p>
+ * DNA currently supports registration of batches of types with some constraints. DNA
will allow types to be registered if
+ * they meet the following criteria:
+ * <ol>
+ * <li>Existing types cannot be modified in-place - They must be unregistered
and re-registered</li>
+ * <li>Types must have a non-null, non-empty name</li>
+ * <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>
+ * <li>Each type must have a valid set of supertypes - that is, the type's
supertypes must meet the following criteria:
+ * <ol>
+ * <li>The type must have at least one supertype (unless the type is {@code
nt:base}.</li>
+ * <li>No two supertypes {@code t1} and {@code t2} can declare each declare a
property ({@code p1} and {@code p2}) with the
+ * same name and cardinality ({@code p1.isMultiple() == p2.isMultiple()}). Note that
this does prohibit each {@code t1} and
+ * {@code t2} from having a common supertype (or super-supertype, etc.) that declares
a property).</li>
+ * <li>No two supertypes {@code t1} and {@code t2} can declare each declare a
child node ({@code n1} and {@code n2}) with the
+ * same name and SNS status ({@code p1.allowsSameNameSiblings() ==
p2.allowsSameNameSiblings()}). Note that this does prohibit
+ * each {@code t1} and {@code t2} from having a common supertype (or super-supertype,
etc.) that declares a child node).</li>
+ * </ol>
+ * </li>
+ * <li>Each type must have a valid set of properties - that is, the type's
properties must meet the following criteria:
+ * <ol>
+ * <li>Residual property definitions cannot be mandatory</li>
+ * <li>If the property is auto-created, it must specify a default
value</li>
+ * <li>If the property is single-valued, it can only specify a single default
value</li>
+ * <li>If the property overrides an existing property definition from a
supertype, the new definition must be mandatory if the
+ * old definition was mandatory</li>
+ * <li>The property cannot override an existing property definition from a
supertype if the ancestor definition is protected</li>
+ * <li>If the property overrides an existing property definition from a
supertype that specifies value constraints, the new
+ * definition must have the same value constraints as the old definition.
<i>This requirement may be relaxed in a future
+ * version of DNA.</i></li>
+ * <li>If the property overrides an existing property definition from a
supertype, the new definition must have the same
+ * required type as the old definition or a required type that can ALWAYS be cast to
the required type of the ancestor (see
+ * section 6.2.6 of the JCR 1.0.1 specification)</li>
+ * </ol>
+ * Note that an empty set of properties would meet the above criteria.</li>
+ * <li>The type must have a valid set of child nodes - that is, the types's
child nodes must meet the following criteria:
+ * <ol>
+ * <li>Residual child node definitions cannot be mandatory</li>
+ * <li>If the child node is auto-created, it must specify a default primary
type name</li>
+ * <li>All required primary types must already be fully registered with the
type manager or must have been defined earlier in
+ * the batch. <i>This requirement may be relaxed in a future version of
DNA.</i></li>
+ * <li>If the child node overrides an existing child node definition from a
supertype, the new definition must be mandatory if
+ * the old definition was mandatory</li>
+ * <li>The child node cannot override an existing child node definition from a
supertype if the ancestor definition is
+ * protected</li>
+ * <li>If the child node overrides an existing child node definition from a
supertype, the required primary types of the new
+ * definition must be more restrictive than the required primary types of the old
definition - that is, the new primary types
+ * must defined such that any type that satisfies all of the required primary types
for the new definition must also satisfy
+ * all of the required primary types for the old definition. This requirement is
analogous to the requirement that overriding
+ * property definitions have a required type that is always convertible to the
required type of the overridden definition.</li>
+ * </ol>
+ * Note that an empty set of child nodes would meet the above criteria.</li>
+ * </p>
+ *
+ * @param nodeTypeSource the batch of {@link NodeType node types} to register
+ * @return the newly registered (or updated) {@link NodeType NodeTypes}
+ * @throws RepositoryException if any of the node types in the the {@link
JcrNodeTypeSource} are invalid
+ * @throws RepositoryException if another error occurs
+ */
+ List<JcrNodeType> registerNodeTypes( JcrNodeTypeSource nodeTypeSource ) throws
RepositoryException {
+
+ assert nodeTypeSource != null;
+ Collection<JcrNodeType> nodeTypeBatch = nodeTypeSource.getNodeTypes();
+
+ List<JcrNodeType> typesPendingRegistration = new
ArrayList<JcrNodeType>(nodeTypeBatch.size());
+
+ for (JcrNodeType nodeType : nodeTypeBatch) {
+ if (nodeType.getInternalName() == null || nodeType.getName().length() == 0)
{
+ throw new RepositoryException(JcrI18n.invalidNodeTypeName.text());
+ }
+
+ Name name = nodeType.getInternalName();
+
+ if (nodeTypes.containsKey(name)) {
+ throw new
RepositoryException(JcrI18n.nodeTypeAlreadyExists.text(nodeType.getName()));
+ }
+
+ List<JcrNodeType> supertypes = supertypesFor(nodeType,
typesPendingRegistration);
+
+ validate(nodeType, supertypes, typesPendingRegistration);
+
+ List<JcrPropertyDefinition> propertyDefs = new
ArrayList<JcrPropertyDefinition>(
+
nodeType.getDeclaredPropertyDefinitions().length);
+
+ for (JcrPropertyDefinition propertyDef :
nodeType.getDeclaredPropertyDefinitions()) {
+ propertyDefs.add(propertyDef.with(this.context));
+ }
+
+ List<JcrNodeDefinition> nodeDefs = new
ArrayList<JcrNodeDefinition>(nodeType.getDeclaredChildNodeDefinitions().length);
+
+ for (JcrNodeDefinition nodeDef : nodeType.getDeclaredChildNodeDefinitions())
{
+ JcrNodeType[] requiredPrimaryTypes = new
JcrNodeType[nodeDef.getRequiredPrimaryTypeNames().size()];
+
+ int i = 0;
+ for (Name primaryTypeName : nodeDef.getRequiredPrimaryTypeNames()) {
+ requiredPrimaryTypes[i] = findTypeInMapOrList(primaryTypeName,
typesPendingRegistration);
+
+ if (requiredPrimaryTypes[i] == null) {
+ throw new
RepositoryException(JcrI18n.invalidPrimaryTypeName.text(primaryTypeName,
nodeType.getName()));
+ }
+ i++;
+ }
+
+ nodeDefs.add(nodeDef.with(this.context));
+ }
+
+ JcrNodeType newNodeType = new JcrNodeType(this.context, this, name,
supertypes,
+
nodeType.getInternalPrimaryItemName(), nodeDefs, propertyDefs,
+ nodeType.isMixin(),
nodeType.hasOrderableChildNodes());
+ typesPendingRegistration.add(newNodeType);
+ }
+
+ // Graph.Batch batch = graph.batch();
+ for (JcrNodeType nodeType : typesPendingRegistration) {
+ /*
+ * See comment in constructor. Using a ConcurrentHashMap seems to be to weak
of a
+ * solution (even it were also used for childNodeDefinitions and
propertyDefinitions).
+ * Probably need to block all read access to these maps during this phase of
registration.
+ */
+ nodeTypes.put(nodeType.getInternalName(), nodeType);
+ for (JcrNodeDefinition childDefinition : nodeType.childNodeDefinitions()) {
+ childNodeDefinitions.put(childDefinition.getId(), childDefinition);
+ }
+ for (JcrPropertyDefinition propertyDefinition :
nodeType.propertyDefinitions()) {
+ propertyDefinitions.put(propertyDefinition.getId(), propertyDefinition);
+ }
+
+ // projectNodeTypeOnto(nodeType, parentOfTypeNodes, batch);
+ }
+
+ // batch.execute();
+
+ return typesPendingRegistration;
+ }
+
+ /**
+ * Finds the named type in the given list of types pending registration if it exists,
else returns the type definition from
+ * the repository
+ *
+ * @param typeName the name of the type to retrieve
+ * @param pendingList a list of types that have passed validation but have not yet
been committed to the repository
+ * @return the node type with the given name from {@code pendingList} if it exists in
the list or from the {@link #nodeTypes
+ * registered types} if it exists there; may be null
+ */
+ private JcrNodeType findTypeInMapOrList( Name typeName,
+ List<JcrNodeType> pendingList ) {
+ for (JcrNodeType pendingNodeType : pendingList) {
+ if (pendingNodeType.getInternalName().equals(typeName)) {
+ return pendingNodeType;
+ }
+ }
+
+ return nodeTypes.get(typeName);
+ }
+
+ /**
+ * Returns the list of node types for the supertypes defined in the given node type.
+ *
+ * @param nodeType a node type with a non-null array of supertypes
+ * @param pendingTypes the list of types that have been processed in this type batch
but not yet committed to the repository's
+ * set of types
+ * @return a list of node types where each element is the node type for the
corresponding element of the array of supertype
+ * names
+ * @throws RepositoryException if any of the names in the array of supertype names
does not correspond to an
+ * already-registered node type or a node type that is pending registration
+ */
+ private List<JcrNodeType> supertypesFor( JcrNodeType nodeType,
+ List<JcrNodeType> pendingTypes )
throws RepositoryException {
+ assert nodeType != null;
+
+ // If no supertypes are provided, assume nt:base as a supertype
+ if (nodeType.getDeclaredSupertypes() == null ||
nodeType.getDeclaredSupertypes().length == 0) {
+ return
Collections.<JcrNodeType>singletonList(nodeTypes.get(JcrNtLexicon.BASE));
+ }
+
+ JcrNodeType[] supertypesArray = nodeType.getDeclaredSupertypes();
+ List<JcrNodeType> supertypes = new
ArrayList<JcrNodeType>(supertypesArray.length);
+
+ for (int i = 0; i < supertypesArray.length; i++) {
+ supertypes.add(findTypeInMapOrList(supertypesArray[i].getInternalName(),
pendingTypes));
+
+ if (supertypes.get(i) == null) {
+ throw new
RepositoryException(JcrI18n.invalidSupertypeName.text(supertypesArray[i].getInternalName(),
+
nodeType.getName()));
+ }
+ }
+
+ return supertypes;
+ }
+
+ /**
+ * Validates that the supertypes are compatible under DNA restrictions.
+ * <p>
+ * DNA imposes the following rules on the supertypes of a type:
+ * <ol>
+ * <li>The type must have at least one supertype (unless the type is {@code
nt:base}.</li>
+ * <li>No two supertypes {@code t1} and {@code t2} can declare each declare a
property ({@code p1} and {@code p2}) with the
+ * same name and cardinality ({@code p1.isMultiple() == p2.isMultiple()}). Note that
this does prohibit each {@code t1} and
+ * {@code t2} from having a common supertype (or super-supertype, etc.) that declares
a property).</li>
+ * <li>No two supertypes {@code t1} and {@code t2} can declare each declare a
child node ({@code n1} and {@code n2}) with the
+ * same name and SNS status ({@code p1.allowsSameNameSiblings() ==
p2.allowsSameNameSiblings()}). Note that this does prohibit
+ * each {@code t1} and {@code t2} from having a common supertype (or super-supertype,
etc.) that declares a child node).</li>
+ * </ol>
+ * </p>
+ * <p>
+ * If any of these rules are violated, a {@link RepositoryException} is thrown.
+ * </p>
+ *
+ * @param supertypes the supertypes of this node type
+ * @param nodeName the name of the node for which the supertypes are being
validated.
+ * @throws RepositoryException if any of the rules described above are violated
+ */
+ private void validate( List<JcrNodeType> supertypes,
+ String nodeName ) throws RepositoryException {
+ assert supertypes.size() > 0; // This is reasonable now that we default to
having a supertype of nt:base
+
+ Map<JcrPropertyDefinition.Key, JcrPropertyDefinition> props = new
HashMap<JcrPropertyDefinition.Key, JcrPropertyDefinition>();
+
+ for (JcrNodeType supertype : supertypes) {
+ for (JcrPropertyDefinition property : supertype.propertyDefinitions()) {
+ JcrPropertyDefinition oldProp = props.put(property.getKey(false),
property);
+ if (oldProp != null) {
+ String oldPropTypeName = oldProp.getDeclaringNodeType().getName();
+ String propTypeName = property.getDeclaringNodeType().getName();
+ if (!oldPropTypeName.equals(propTypeName)) {
+ throw new
RepositoryException(JcrI18n.supertypesConflict.text(oldPropTypeName,
+
propTypeName,
+
"property",
+
property.getName()));
+ }
+ }
+ }
+ }
+
+ Map<JcrNodeDefinition.Key, JcrNodeDefinition> childNodes = new
HashMap<JcrNodeDefinition.Key, JcrNodeDefinition>();
+
+ for (JcrNodeType supertype : supertypes) {
+ for (JcrNodeDefinition childNode : supertype.childNodeDefinitions()) {
+ JcrNodeDefinition oldNode = childNodes.put(childNode.getKey(false),
childNode);
+ if (oldNode != null) {
+ String oldNodeTypeName = oldNode.getDeclaringNodeType().getName();
+ String childNodeTypeName =
childNode.getDeclaringNodeType().getName();
+ if (!oldNodeTypeName.equals(childNodeTypeName)) {
+ throw new
RepositoryException(JcrI18n.supertypesConflict.text(oldNodeTypeName,
+
childNodeTypeName,
+
"child node",
+
childNode.getName()));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Validates that the given node type definition is valid under the DNA and JCR type
rules within the given context.
+ * <p>
+ * See {@link #registerNodeTypes(JcrNodeTypeSource)} for the list of criteria that
determine whether a node type definition is
+ * valid.
+ * </p>
+ *
+ * @param nodeType the node type to attempt to validate
+ * @param supertypes the names of the supertypes of the node type to which this child
node belongs
+ * @param pendingTypes the list of types previously registered in this batch but not
yet committed to the repository
+ * @throws RepositoryException if the given node type template is not valid
+ */
+ private void validate( JcrNodeType nodeType,
+ List<JcrNodeType> supertypes,
+ List<JcrNodeType> pendingTypes ) throws
RepositoryException {
+ validate(supertypes, nodeType.getName());
+
+ List<Name> supertypeNames = new ArrayList<Name>(supertypes.size());
+ for (JcrNodeType supertype : supertypes)
+ supertypeNames.add(supertype.getInternalName());
+
+ boolean found = false;
+ String primaryItemName = nodeType.getPrimaryItemName();
+ for (JcrNodeDefinition node : nodeType.getDeclaredChildNodeDefinitions()) {
+ validate(node, supertypeNames, pendingTypes);
+
+ if (primaryItemName != null &&
primaryItemName.equals(node.getName())) {
+ found = true;
+ }
+ }
+
+ for (JcrPropertyDefinition prop : nodeType.getDeclaredPropertyDefinitions()) {
+ validate(prop, supertypeNames, pendingTypes);
+ if (primaryItemName != null &&
primaryItemName.equals(prop.getName())) {
+ if (found) {
+ throw new
RepositoryException(JcrI18n.ambiguousPrimaryItemName.text(primaryItemName));
+ }
+ found = true;
+ }
+ }
+
+ if (primaryItemName != null && !found) {
+ throw new
RepositoryException(JcrI18n.invalidPrimaryItemName.text(primaryItemName));
+ }
+ }
+
+ /**
+ * Validates that the given child node definition is valid under the DNA and JCR type
rules within the given context.
+ * <p>
+ * DNA considers a child node definition valid if it meets these criteria:
+ * <ol>
+ * <li>Residual child node definitions cannot be mandatory</li>
+ * <li>If the child node is auto-created, it must specify a default primary
type name</li>
+ * <li>All required primary types must already be fully registered with the
type manager or must have been defined earlier in
+ * the batch. This requirement may be relaxed in a future version of DNA.</li>
+ * <li>If the child node overrides an existing child node definition from a
supertype, the new definition must be mandatory if
+ * the old definition was mandatory</li>
+ * <li>The child node cannot override an existing child node definition from a
supertype if the ancestor definition is
+ * protected</li>
+ * <li>If the child node overrides an existing child node definition from a
supertype, the required primary types of the new
+ * definition must be more restrictive than the required primary types of the old
definition - that is, the new primary types
+ * must defined such that any type that satisfies all of the required primary types
for the new definition must also satisfy
+ * all of the required primary types for the old definition. This requirement is
analogous to the requirement that overriding
+ * property definitions have a required type that is always convertible to the
required type of the overridden definition.</li>
+ * </ol>
+ * </p>
+ *
+ * @param node the child node definition to be validated
+ * @param supertypes the names of the supertypes of the node type to which this child
node belongs
+ * @param pendingTypes the list of types previously registered in this batch but not
yet committed to the repository
+ * @throws RepositoryException if the child node definition is not valid
+ */
+ private void validate( JcrNodeDefinition node,
+ List<Name> supertypes,
+ List<JcrNodeType> pendingTypes ) throws
RepositoryException {
+ if (node.isAutoCreated() && node.getDefaultPrimaryType() == null) {
+ throw new RepositoryException(JcrI18n.autocreatedNodesNeedDefaults.text());
+ }
+ if (node.isMandatory() && node.getName() == null) {
+ throw new
RepositoryException(JcrI18n.residualDefinitionsCannotBeMandatory.text("child
nodes"));
+ }
+
+ Name nodeName =
context.getValueFactories().getNameFactory().create(node.getName());
+ nodeName = nodeName == null ? JcrNodeType.RESIDUAL_NAME : nodeName;
+
+ List<JcrNodeDefinition> ancestors = findChildNodeDefinitions(supertypes,
+ nodeName,
+
node.allowsSameNameSiblings() ? NodeCardinality.SAME_NAME_SIBLINGS :
NodeCardinality.NO_SAME_NAME_SIBLINGS,
+ pendingTypes);
+
+ for (JcrNodeDefinition ancestor : ancestors) {
+ if (ancestor.isProtected()) {
+ throw new RepositoryException(
+
JcrI18n.cannotOverrideProtectedDefinition.text(ancestor.getDeclaringNodeType().getName(),
+
"child node"));
+ }
+
+ if (ancestor.isMandatory() && !node.isMandatory()) {
+ throw new RepositoryException(
+
JcrI18n.cannotMakeMandatoryDefinitionOptional.text(ancestor.getDeclaringNodeType().getName(),
+
"child node"));
+
+ }
+
+ for (int i = 0; i < ancestor.getRequiredPrimaryTypes().length; i++) {
+ NodeType apt = ancestor.getRequiredPrimaryTypes()[i];
+ boolean found = false;
+ for (Name name : node.getRequiredPrimaryTypeNames()) {
+ JcrNodeType npt = findTypeInMapOrList(name, pendingTypes);
+
+ if (npt.isNodeType(apt.getName())) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ throw new RepositoryException(
+ "Cannot redefine child node
'"
+ + nodeName
+ + "' with required type
'"
+ + apt.getName()
+ + "' with new child node that
does not required that type or a subtype of that type.");
+
+ }
+ }
+ }
+ }
+
+ /**
+ * Validates that the given property definition is valid under the DNA and JCR type
rules within the given context.
+ * <p>
+ * DNA considers a property definition valid if it meets these criteria:
+ * <ol>
+ * <li>Residual properties cannot be mandatory</li>
+ * <li>If the property is auto-created, it must specify a default
value</li>
+ * <li>If the property is single-valued, it can only specify a single default
value</li>
+ * <li>If the property overrides an existing property definition from a
supertype, the new definition must be mandatory if the
+ * old definition was mandatory</li>
+ * <li>The property cannot override an existing property definition from a
supertype if the ancestor definition is protected</li>
+ * <li>If the property overrides an existing property definition from a
supertype, the new definition must have the same
+ * required type as the old definition or a required type that can ALWAYS be cast to
the required type of the ancestor (see
+ * section 6.2.6 of the JCR 1.0.1 specification)</li>
+ * </ol>
+ * Note that an empty set of properties would meet the criteria above.
+ * </p>
+ *
+ * @param prop the property definition to be validated
+ * @param supertypes the names of the supertypes of the node type to which this
property belongs
+ * @param pendingTypes the list of types previously registered in this batch but not
yet committed to the repository
+ * @throws RepositoryException if the property definition is not valid
+ */
+ private void validate( JcrPropertyDefinition prop,
+ List<Name> supertypes,
+ List<JcrNodeType> pendingTypes ) throws
RepositoryException {
+ assert prop != null;
+ assert supertypes != null;
+ assert pendingTypes != null;
+
+ if (prop.isMandatory() && !prop.isProtected() && prop.getName()
== null) {
+ throw new
RepositoryException(JcrI18n.residualDefinitionsCannotBeMandatory.text("properties"));
+ }
+
+ Value[] defaultValues = prop.getDefaultValues();
+ if (prop.isAutoCreated() && !prop.isProtected() && (defaultValues
== null || defaultValues.length == 0)) {
+ throw new
RepositoryException(JcrI18n.autocreatedPropertyNeedsDefault.text(prop.getName(),
+
prop.getDeclaringNodeType().getName()));
+ }
+
+ if (!prop.isMultiple() && (defaultValues != null &&
defaultValues.length > 1)) {
+ throw new RepositoryException(
+
JcrI18n.singleValuedPropertyNeedsSingleValuedDefault.text(prop.getName(),
+
prop.getDeclaringNodeType().getName()));
+ }
+
+ Name propName =
context.getValueFactories().getNameFactory().create(prop.getName());
+ propName = propName == null ? JcrNodeType.RESIDUAL_NAME : propName;
+
+ List<JcrPropertyDefinition> ancestors =
findPropertyDefinitions(supertypes,
+ propName,
+ prop.isMultiple()
? PropertyCardinality.MULTI_VALUED_ONLY : PropertyCardinality.SINGLE_VALUED_ONLY,
+ pendingTypes);
+
+ for (JcrPropertyDefinition ancestor : ancestors) {
+ if (ancestor.isProtected()) {
+ throw new RepositoryException(
+
JcrI18n.cannotOverrideProtectedDefinition.text(ancestor.getDeclaringNodeType().getName(),
+
"property"));
+ }
+
+ if (ancestor.isMandatory() && !prop.isMandatory()) {
+ throw new RepositoryException(
+
JcrI18n.cannotMakeMandatoryDefinitionOptional.text(ancestor.getDeclaringNodeType().getName(),
+
"property"));
+
+ }
+
+ // TODO: It would be nice if we could allow modification of constraints if
the new constraints were more strict than
+ // the old
+ if (ancestor.getValueConstraints() != null
+ && !Arrays.equals(ancestor.getValueConstraints(),
prop.getValueConstraints())) {
+ throw new
RepositoryException(JcrI18n.constraintsChangedInSubtype.text(propName,
+
ancestor.getDeclaringNodeType().getName()));
+ }
+
+ if (!isAlwaysSafeConversion(prop.getRequiredType(),
ancestor.getRequiredType())) {
+ throw new RepositoryException(
+
JcrI18n.cannotRedefineProperty.text(propName,
+
PropertyType.nameFromValue(prop.getRequiredType()),
+
ancestor.getDeclaringNodeType().getName(),
+
PropertyType.nameFromValue(ancestor.getRequiredType())));
+
+ }
+ }
+ }
+
+ /**
+ * Returns whether it is always possible to convert a value with JCR property type
{@code fromType} to {@code toType}.
+ * <p>
+ * This method is based on the conversions which can never throw an exception in the
chart in section 6.2.6 of the JCR 1.0.1
+ * specification.
+ * </p>
+ *
+ * @param fromType the type to be converted from
+ * @param toType the type to convert to
+ * @return true if any value with type {@code fromType} can be converted to a type of
{@code toType} without a
+ * {@link ValueFormatException} being thrown.
+ * @see PropertyType
+ */
+ private boolean isAlwaysSafeConversion( int fromType,
+ int toType ) {
+
+ if (fromType == toType) return true;
+
+ switch (toType) {
+ case PropertyType.BOOLEAN:
+ return fromType == PropertyType.BINARY || fromType ==
PropertyType.STRING;
+
+ case PropertyType.DATE:
+ return fromType == PropertyType.DOUBLE || fromType == PropertyType.LONG;
+
+ case PropertyType.DOUBLE:
+ // Conversion from DATE could result in out-of-range value
+ return fromType == PropertyType.LONG;
+ case PropertyType.LONG:
+ // Conversion from DATE could result in out-of-range value
+ return fromType == PropertyType.DOUBLE;
+
+ case PropertyType.PATH:
+ return fromType == PropertyType.NAME;
+
+ // Values of any type MAY fail when converting to these types
+ case PropertyType.NAME:
+ case PropertyType.REFERENCE:
+ return false;
+
+ // Any type can be converted to these types
+ case PropertyType.BINARY:
+ case PropertyType.STRING:
+ case PropertyType.UNDEFINED:
+ return true;
+
+ default:
+ throw new IllegalStateException("Unexpected state: " +
toType);
+ }
+ }
+
}
Modified: trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties
===================================================================
--- trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties 2009-04-13
19:29:46 UTC (rev 819)
+++ trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties 2009-04-14
16:13:03 UTC (rev 820)
@@ -66,6 +66,7 @@
errorWhileFindingNodeWithUuid = Error while finding the node with UUID "{0}" in
workspace "{1}": {2}
errorWhileFindingNodeWithPath = Error while finding the node "{0}" in workspace
"{1}"
nodeDefinitionCouldNotBeDeterminedForNode = Unable to determine a valid node definition
for the node "{0}" in workspace "{1}"
+noSnsDefinitionForNode = A node definition that allows same name siblings could not be
found for the node "{0}" in workspace "{1}"
missingNodeTypeForExistingNode = Missing primary node type "{0}" for node {1}
in workspace "{2}"
unableToCreateNodeWithPrimaryTypeThatDoesNotExist = Unable to create child
"{1}" in workspace "{2}" because the node type "{0}" does
not exist
unableToCreateNodeWithNoDefaultPrimaryTypeOnChildNodeDefinition = Unable to create child
"{2}" in workspace "{3}" because the node definition "{0}"
on the "{1}" node type has no default primary type
@@ -93,4 +94,20 @@
tooDeep=Depth parameter ({0}) cannot be greater than the result of getDepth() for this
node
notStoredQuery=This query has not been stored or loaded
-invalidQueryLanguage="{0}" is not a valid query langauge. Supported languages
are: {1}
\ No newline at end of file
+invalidQueryLanguage="{0}" is not a valid query langauge. Supported languages
are\: {1}
+
+invalidNodeTypeName=Node types cannot have a null or empty name
+nodeTypeAlreadyExists=Node type '{0}' already exists
+invalidPrimaryTypeName=Required primary type '{0}' in type '{1}' does not
exist
+invalidSupertypeName=Supertype '{0}' from type '{1}' does not exist
+supertypesConflict=Types '{0}' and '{1}' cannot both be supertypes of the
same type, as both separately declare {2} '{3}'
+ambiguousPrimaryItemName=Primary item name '{0}' matches the name of a child node
and a property
+invalidPrimaryItemName=Primary item name '{0}' does not match the name of any
child nodes or properties
+autocreatedNodesNeedDefaults=Autocreated child nodes must specify a default primary type
+residualDefinitionsCannotBeMandatory=Residual {0} cannot be mandatory
+cannotOverrideProtectedDefinition=Cannot override protected {1} definition from
'{0}'
+cannotMakeMandatoryDefinitionOptional=Cannot override mandatory {1} definition from
'{0}' with a non-mandatory definition.
+constraintsChangedInSubtype=Must use exact same constraints when overriding the property
definition for {0} from type '{1}'
+cannotRedefineProperty=Cannot redefine property '{0}' with new type '{1}'
when existing property with same name in type '{2}' has incompatible type
'{3}'
+autocreatedPropertyNeedsDefault=Auto-created property '{0}' in type '{1}'
must specify a default value
+singleValuedPropertyNeedsSingleValuedDefault=Single-valued property '{0}' in type
'{1}' cannot have multiple default values