Author: rhauch
Date: 2009-04-07 18:28:53 -0400 (Tue, 07 Apr 2009)
New Revision: 812
Added:
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/MixinTest.java
Modified:
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeManager.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyTypeUtil.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java
Log:
DNA-343 TCK Tests for Node Mixin Modification Methods Fail
Applied the patch that takes a stab the missing functionality.
In addition to the usual rules required by the JCR 1.0 specification, the canAddMixin and
addMixin methods enforce internal consistency by requiring that:
* No properties defined by the mixin type can have the same name as any property
defined by the node's primary type or any of its existing mixin types.
* No child nodes defined by the mixin type can have the same name as any child node
defined by the node's primary type or any of its existing mixin types.
* If the node has a current residual definition for child nodes and/or properties,
all nodes and properties that share a name with a child node definition or property
definition from the new mixin type must be compatible with the definition provided by the
new mixin type.
The removeMixin imposes the following rule in addition to those required by the JCR 1.0
specification:
* A mixin type can be removed if and only if all of the node's existing child
nodes and properties would still have a valid definition from the node's primary type
or other mixin types. In practice, this means that either the node must have a residual
definition compatible with any of the remaining child nodes or properties that currently
use a definition from the to-be-removed mixin type or all of the child nodes and
properties that use a definition from the to-be-removed mixin type must be removed prior
to calling this method.
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java 2009-04-07 22:22:56
UTC (rev 811)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java 2009-04-07 22:28:53
UTC (rev 812)
@@ -27,9 +27,12 @@
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;
import javax.jcr.InvalidItemStateException;
@@ -49,6 +52,7 @@
import javax.jcr.lock.Lock;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.nodetype.NodeDefinition;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.NodeTypeManager;
@@ -224,7 +228,7 @@
* @throws UnsupportedOperationException always
* @see javax.jcr.Node#getPrimaryNodeType()
*/
- public NodeType getPrimaryNodeType() throws RepositoryException {
+ public JcrNodeType getPrimaryNodeType() throws RepositoryException {
Name primaryTypeName = nodeInfo().getPrimaryTypeName();
return session().nodeTypeManager().getNodeType(primaryTypeName);
}
@@ -251,26 +255,6 @@
/**
* {@inheritDoc}
*
- * @return <code>false</code>
- * @see javax.jcr.Node#canAddMixin(java.lang.String)
- */
- public final boolean canAddMixin( String mixinName ) {
- return false;
- }
-
- /**
- * {@inheritDoc}
- *
- * @throws UnsupportedOperationException always
- * @see javax.jcr.Node#removeMixin(java.lang.String)
- */
- public final void removeMixin( String mixinName ) {
- throw new UnsupportedOperationException();
- }
-
- /**
- * {@inheritDoc}
- *
* @see javax.jcr.Node#getPrimaryItem()
*/
public final Item getPrimaryItem() throws RepositoryException {
@@ -604,17 +588,300 @@
/**
* {@inheritDoc}
+ * <p>
+ * <b>DNA Implementation Notes</b>
+ * </p>
+ * <p>
+ * DNA imposes the following additional restrictions on the addition of mixin types
in addition to the restrictions provided
+ * by the JCR 1.0 specification:
+ * <ol>
+ * <li>No properties defined by the mixin type can have the same name as any
property defined by the node's primary type or
+ * any of its existing mixin types.</li>
+ * <li>No child nodes defined by the mixin type can have the same name as any
child node defined by the node's primary type or
+ * any of its existing mixin types.</li>
+ * <li>If the node has a current residual definition for child nodes and/or
properties, all nodes and properties that share a
+ * name with a child node definition or property definition from the new mixin type
must be compatible with the definition
+ * provided by the new mixin type.</li>
+ * </ol>
+ * </p>
*
- * @throws UnsupportedOperationException always
+ * @see javax.jcr.Node#canAddMixin(java.lang.String)
+ */
+ public final boolean canAddMixin( String mixinName ) throws NoSuchNodeTypeException,
RepositoryException {
+ CheckArg.isNotNull(mixinName, "mixinName");
+ CheckArg.isNotZeroLength(mixinName, "mixinName");
+
+ JcrNodeType mixinCandidateType = cache.nodeTypes().getNodeType(mixinName);
+
+ if (this.isLocked()) {
+ return false;
+ }
+
+ if (this.getDefinition().isProtected()) {
+ return false;
+ }
+
+ // TODO: Check access control when that support is added
+ // TODO: Throw VersionException if this node is versionable and checked in or
unversionable and the nearest versionable
+ // ancestor is checked in
+
+ NodeType primaryType = this.getPrimaryNodeType();
+ NodeType[] mixinTypes = this.getMixinNodeTypes();
+
+ if (!mixinCandidateType.isMixin()) {
+ return false;
+ }
+
+ if (mixinCandidateType.conflictsWith(primaryType, mixinTypes)) {
+ return false;
+ }
+
+ //
------------------------------------------------------------------------------
+ // Check for any existing properties based on residual definitions that conflict
+ //
------------------------------------------------------------------------------
+ for (JcrPropertyDefinition propertyDefinition :
mixinCandidateType.propertyDefinitions()) {
+ AbstractJcrProperty existingProp = cache.findJcrProperty(new
PropertyId(nodeUuid,
+
propertyDefinition.getInternalName()));
+ if (existingProp != null) {
+ if (propertyDefinition.isMultiple()) {
+ if
(!propertyDefinition.canCastToTypeAndSatisfyConstraints(existingProp.getValues())) {
+ return false;
+ }
+ } else {
+ if
(!propertyDefinition.canCastToTypeAndSatisfyConstraints(existingProp.getValue())) {
+ return false;
+ }
+ }
+ }
+ }
+
+ //
------------------------------------------------------------------------------
+ // Check for any existing child nodes based on residual definitions that
conflict
+ //
------------------------------------------------------------------------------
+ Set<Name> mixinChildNodeNames = new HashSet<Name>();
+ for (JcrNodeDefinition nodeDefinition :
mixinCandidateType.childNodeDefinitions()) {
+ mixinChildNodeNames.add(nodeDefinition.getInternalName());
+ }
+
+ for (Name nodeName : mixinChildNodeNames) {
+ // Need to figure out if the child node requires an SNS definition
+ int snsCount =
nodeInfo().getChildren().getCountOfSameNameSiblingsWithName(nodeName);
+
+ for (Iterator<ChildNode> iter =
nodeInfo().getChildren().getChildren(nodeName); iter.hasNext();) {
+ AbstractJcrNode childNode = cache.findJcrNode(iter.next().getUuid());
+ JcrNodeDefinition match =
this.cache.nodeTypes().findChildNodeDefinition(mixinCandidateType.getInternalName(),
+
Collections.<Name>emptyList(),
+
nodeName,
+
childNode.getPrimaryNodeType().getInternalName(),
+
snsCount,
+
false);
+
+ if (match == null) {
+ return false;
+ }
+ }
+
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * <b>DNA Implementation Notes</b>
+ * </p>
+ * <p>
+ * The criteria noted in {@link #canAddMixin(String)} must be satisifed in addition
to the criteria defined in the JCR 1.0
+ * specification.
+ * </p>
+ *
* @see javax.jcr.Node#addMixin(java.lang.String)
*/
- public final void addMixin( String mixinName ) {
- throw new UnsupportedOperationException();
+ public final void addMixin( String mixinName ) throws RepositoryException {
+ CheckArg.isNotNull(mixinName, "mixinName");
+ CheckArg.isNotZeroLength(mixinName, "mixinName");
+
+ JcrNodeType mixinCandidateType = cache.nodeTypes().getNodeType(mixinName);
+
+ // Check this separately since it throws a different type of exception
+ if (this.isLocked()) {
+ throw new LockException();
+ }
+
+ if (!canAddMixin(mixinName)) {
+ throw new ConstraintViolationException();
+ }
+
+ Property existingMixinProperty = getProperty(JcrLexicon.MIXIN_TYPES);
+ Value[] existingMixinValues;
+ if (existingMixinProperty != null) {
+ existingMixinValues = existingMixinProperty.getValues();
+ } else {
+ existingMixinValues = new Value[0];
+ }
+
+ Value[] newMixinValues = new Value[existingMixinValues.length + 1];
+ System.arraycopy(existingMixinValues, 0, newMixinValues, 0,
existingMixinValues.length);
+ newMixinValues[newMixinValues.length - 1] = valueFrom(PropertyType.NAME,
mixinCandidateType.getInternalName());
+
+ cache.findJcrProperty(editor().setProperty(JcrLexicon.MIXIN_TYPES,
newMixinValues, false));
+
+ //
------------------------------------------------------------------------------
+ // Create any auto-created properties/nodes from new type
+ //
------------------------------------------------------------------------------
+
+ for (JcrPropertyDefinition propertyDefinition :
mixinCandidateType.propertyDefinitions()) {
+ if (propertyDefinition.isAutoCreated() &&
!propertyDefinition.isProtected()) {
+ if (null == cache.findJcrProperty(new PropertyId(nodeUuid,
propertyDefinition.getInternalName()))) {
+ assert propertyDefinition.getDefaultValues() != null;
+ if (propertyDefinition.isMultiple()) {
+ setProperty(propertyDefinition.getName(),
propertyDefinition.getDefaultValues());
+ } else {
+ assert propertyDefinition.getDefaultValues().length == 1;
+ setProperty(propertyDefinition.getName(),
propertyDefinition.getDefaultValues()[0]);
+ }
+ }
+ }
+ }
+
+ for (JcrNodeDefinition nodeDefinition :
mixinCandidateType.childNodeDefinitions()) {
+ if (nodeDefinition.isAutoCreated() && !nodeDefinition.isProtected())
{
+ Name nodeName = nodeDefinition.getInternalName();
+ if (!nodeInfo().getChildren().getChildren(nodeName).hasNext()) {
+ assert nodeDefinition.getDefaultPrimaryType() != null;
+ editor().createChild(nodeName,
+ (UUID)null,
+
((JcrNodeType)nodeDefinition.getDefaultPrimaryType()).getInternalName());
+ }
+ }
+ }
+
}
/**
* {@inheritDoc}
+ * <p>
+ * <b>DNA Implementation Notes</b>
+ * </p>
+ * <p>
+ * DNA allows the removal of a mixin type if and only if all of the node's
existing child nodes and properties would still
+ * have a valid definition from the node's primary type or other mixin types. In
practice, this means that either the node
+ * must have a residual definition compatible with any of the remaining child nodes
or properties that currently use a
+ * definition from the to-be-removed mixin type or all of the child nodes and
properties that use a definition from the
+ * to-be-removed mixin type must be removed prior to calling this method.
+ * </p>
+ * *
*
+ * @see javax.jcr.Node#removeMixin(java.lang.String)
+ */
+ public final void removeMixin( String mixinName ) throws RepositoryException {
+
+ if (this.isLocked()) {
+ throw new LockException();
+ }
+
+ // TODO: Check access control when that support is added
+ // TODO: Throw VersionException if this node is versionable and checked in or
unversionable and the nearest versionable
+ // ancestor is checked in
+
+ Property existingMixinProperty = getProperty(JcrLexicon.MIXIN_TYPES);
+
+ if (existingMixinProperty == null) {
+ throw new NoSuchNodeTypeException();
+ }
+
+ Value[] existingMixinValues = existingMixinProperty.getValues();
+
+ if (existingMixinValues.length == 0) {
+ throw new NoSuchNodeTypeException();
+ }
+
+ //
------------------------------------------------------------------------------
+ // Build the new list of mixin types
+ //
------------------------------------------------------------------------------
+
+ int newMixinValuesCount = existingMixinValues.length - 1;
+ Value[] newMixinValues = new Value[newMixinValuesCount];
+ List<Name> newMixinNames = new ArrayList<Name>(newMixinValuesCount);
+ Name primaryTypeName = getPrimaryNodeType().getInternalName();
+
+ int j = 0;
+ for (int i = 0; i < existingMixinValues.length; i++) {
+ if (!existingMixinValues[i].getString().equals(mixinName)) {
+ if (j < newMixinValuesCount) {
+ newMixinValues[j++] = existingMixinValues[i];
+
newMixinNames.add(cache.nameFactory.create(existingMixinValues[i].getString()));
+ } else {
+ throw new NoSuchNodeTypeException();
+ }
+ }
+ }
+
+ //
------------------------------------------------------------------------------
+ // Check that any remaining properties that use the mixin type to be removed
+ // match the residual definition for the node.
+ //
------------------------------------------------------------------------------
+
+ for (PropertyIterator iter = getProperties(); iter.hasNext();) {
+ Property property = iter.nextProperty();
+ if
(mixinName.equals(property.getDefinition().getDeclaringNodeType().getName())) {
+ JcrPropertyDefinition match;
+
+ // Only the residual definition would work - if there were any other
definition for this name,
+ // the mixin type would not have been added due to the conflict
+ if (property.getDefinition().isMultiple()) {
+ match = cache.nodeTypes().findPropertyDefinition(primaryTypeName,
+ newMixinNames,
+
JcrNodeType.RESIDUAL_NAME,
+
property.getValues(),
+ true);
+ } else {
+ match = cache.nodeTypes().findPropertyDefinition(primaryTypeName,
+ newMixinNames,
+
JcrNodeType.RESIDUAL_NAME,
+
property.getValue(),
+ true,
+ true);
+ }
+
+ if (match == null) {
+ throw new ConstraintViolationException();
+ }
+ }
+ }
+
+ //
------------------------------------------------------------------------------
+ // Check that any remaining child nodes that use the mixin type to be removed
+ // match the residual definition for the node.
+ //
------------------------------------------------------------------------------
+ for (NodeIterator iter = getNodes(); iter.hasNext();) {
+ AbstractJcrNode node = (AbstractJcrNode)iter.nextNode();
+ Name childNodeName = cache.nameFactory.create(node.getName());
+ int snsCount =
node.nodeInfo().getChildren().getCountOfSameNameSiblingsWithName(childNodeName);
+ if (mixinName.equals(node.getDefinition().getDeclaringNodeType().getName()))
{
+ // Only the residual definition would work - if there were any other
definition for this name,
+ // the mixin type would not have been added due to the conflict
+ JcrNodeDefinition match =
cache.nodeTypes().findChildNodeDefinition(primaryTypeName,
+
newMixinNames,
+
JcrNodeType.RESIDUAL_NAME,
+
node.getPrimaryNodeType().getInternalName(),
+
snsCount,
+
true);
+
+ if (match == null) {
+ throw new ConstraintViolationException();
+ }
+ }
+ }
+
+ cache.findJcrProperty(editor().setProperty(JcrLexicon.MIXIN_TYPES,
newMixinValues, false));
+
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see javax.jcr.Node#addNode(java.lang.String)
*/
public final Node addNode( String relPath )
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-07 22:22:56 UTC
(rev 811)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java 2009-04-07 22:28:53 UTC
(rev 812)
@@ -26,9 +26,11 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import javax.jcr.PropertyType;
import javax.jcr.Value;
@@ -488,4 +490,128 @@
final RepositoryNodeTypeManager nodeTypeManager() {
return nodeTypeManager;
}
+
+ /**
+ * Returns whether this node type is in conflict with the provided primary node type
or mixin types.
+ * <p>
+ * A node type is in conflict with another set of node types if either of the
following is true:
+ * <ol>
+ * <li>This node type has the same name as any of the other node
types</li>
+ * <li>This node type defines a property (or inherits the definition of a
property) with the same name as a property defined
+ * in any of the other node types <i>unless</i> this node type and the
other node type both inherited the property definition
+ * from the same ancestor node type</li>
+ * <li>This node type defines a child node (or inherits the definition of a
child node) with the same name as a child node
+ * defined in any of the other node types <i>unless</i> this node type
and the other node type both inherited the child node
+ * definition from the same ancestor node type</li>
+ * </ol>
+ * </p>
+ *
+ * @param primaryNodeType the primary node type to check
+ * @param mixinNodeTypes the mixin node types to check
+ * @return true if this node type conflicts with the provided primary or mixin node
types as defined below
+ */
+ final boolean conflictsWith( NodeType primaryNodeType,
+ NodeType[] mixinNodeTypes ) {
+ Map<PropertyDefinitionId, JcrPropertyDefinition> props = new
HashMap<PropertyDefinitionId, JcrPropertyDefinition>();
+ /*
+ * Need to have the same parent name for all PropertyDefinitionIds and
NodeDefinitionIds as we're checking for conflicts on the definition name
+ */
+ final Name DEFAULT_NAME = this.name;
+
+ for (JcrPropertyDefinition property : propertyDefinitions()) {
+ /*
+ * I'm trying really hard to reuse existing code, but it's a stretch
in this case. I don't care about the property
+ * types or where they were declared... if more than one definition with the
given name exists (not counting definitions
+ * inherited from the same root definition), then there is a conflict.
+ */
+ PropertyDefinitionId pid = new PropertyDefinitionId(DEFAULT_NAME,
property.name, PropertyType.UNDEFINED,
+ property.isMultiple());
+ props.put(pid, property);
+ }
+
+ /*
+ * The specification does not mandate whether this should or should not be
consider a conflict. However, the Apache
+ * TCK canRemoveMixin test cases assume that this will generate a conflict.
+ */
+ if (primaryNodeType.getName().equals(getName())) {
+ // This node type has already been applied to the node
+ return true;
+ }
+
+ for (JcrPropertyDefinition property :
((JcrNodeType)primaryNodeType).propertyDefinitions()) {
+ PropertyDefinitionId pid = new PropertyDefinitionId(DEFAULT_NAME,
property.name, PropertyType.UNDEFINED,
+ property.isMultiple());
+ JcrPropertyDefinition oldProp = props.put(pid, property);
+ if (oldProp != null) {
+ String oldPropTypeName = oldProp.getDeclaringNodeType().getName();
+ String propTypeName = property.getDeclaringNodeType().getName();
+ if (!oldPropTypeName.equals(propTypeName)) {
+ // The two types conflict as both separately declare a property with
the same name
+ return true;
+ }
+ }
+ }
+
+ for (NodeType mixinNodeType : mixinNodeTypes) {
+ /*
+ * The specification does not mandate whether this should or should not be
consider a conflict. However, the Apache
+ * TCK canRemoveMixin test cases assume that this will generate a conflict.
+ */
+ if (mixinNodeType.getName().equals(getName())) {
+ // This node type has already been applied to the node
+ return true;
+ }
+
+ for (JcrPropertyDefinition property :
((JcrNodeType)mixinNodeType).propertyDefinitions()) {
+ PropertyDefinitionId pid = new PropertyDefinitionId(DEFAULT_NAME,
property.name, PropertyType.UNDEFINED,
+
property.isMultiple());
+ JcrPropertyDefinition oldProp = props.put(pid, property);
+ if (oldProp != null) {
+ String oldPropTypeName = oldProp.getDeclaringNodeType().getName();
+ String propTypeName = property.getDeclaringNodeType().getName();
+ if (!oldPropTypeName.equals(propTypeName)) {
+ // The two types conflict as both separately declare a property
with the same name
+ return true;
+ }
+ }
+ }
+ }
+
+ Map<NodeDefinitionId, JcrNodeDefinition> childNodes = new
HashMap<NodeDefinitionId, JcrNodeDefinition>();
+
+ for (JcrNodeDefinition childNode : childNodeDefinitions()) {
+ NodeDefinitionId nid = new NodeDefinitionId(DEFAULT_NAME, childNode.name, new
Name[0]);
+ childNodes.put(nid, childNode);
+ }
+
+ for (JcrNodeDefinition childNode :
((JcrNodeType)primaryNodeType).childNodeDefinitions()) {
+ NodeDefinitionId nid = new NodeDefinitionId(DEFAULT_NAME, childNode.name, new
Name[0]);
+ JcrNodeDefinition oldNode = childNodes.put(nid, childNode);
+ if (oldNode != null) {
+ String oldNodeTypeName = oldNode.getDeclaringNodeType().getName();
+ String childNodeTypeName = childNode.getDeclaringNodeType().getName();
+ if (!oldNodeTypeName.equals(childNodeTypeName)) {
+ // The two types conflict as both separately declare a child node
with the same name
+ return true;
+ }
+ }
+ }
+
+ for (NodeType mixinNodeType : mixinNodeTypes) {
+ for (JcrNodeDefinition childNode :
((JcrNodeType)mixinNodeType).childNodeDefinitions()) {
+ NodeDefinitionId nid = new NodeDefinitionId(DEFAULT_NAME, childNode.name,
new Name[0]);
+ JcrNodeDefinition oldNode = childNodes.put(nid, childNode);
+ if (oldNode != null) {
+ String oldNodeTypeName = oldNode.getDeclaringNodeType().getName();
+ String childNodeTypeName =
childNode.getDeclaringNodeType().getName();
+ if (!oldNodeTypeName.equals(childNodeTypeName)) {
+ // The two types conflict as both separately declare a child node
with the same name
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
}
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeManager.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeManager.java 2009-04-07
22:22:56 UTC (rev 811)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeManager.java 2009-04-07
22:28:53 UTC (rev 812)
@@ -90,7 +90,7 @@
*
* @see javax.jcr.nodetype.NodeTypeManager#getNodeType(java.lang.String)
*/
- public NodeType getNodeType( String nodeTypeName ) throws NoSuchNodeTypeException,
RepositoryException {
+ public JcrNodeType getNodeType( String nodeTypeName ) throws NoSuchNodeTypeException,
RepositoryException {
Name ntName = context.getValueFactories().getNameFactory().create(nodeTypeName);
JcrNodeType type = repositoryTypeManager.getNodeType(ntName);
if (type != null) {
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyTypeUtil.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyTypeUtil.java 2009-04-07
22:22:56 UTC (rev 811)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyTypeUtil.java 2009-04-07
22:28:53 UTC (rev 812)
@@ -43,6 +43,7 @@
*/
static final int jcrPropertyTypeFor( org.jboss.dna.graph.property.Property property )
{
Object value = property.getFirstValue();
+ if (value == null) return PropertyType.UNDEFINED;
// Get the DNA property type for this ...
switch (org.jboss.dna.graph.property.PropertyType.discoverType(value)) {
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java 2009-04-07 22:22:56
UTC (rev 811)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java 2009-04-07 22:28:53
UTC (rev 812)
@@ -539,6 +539,12 @@
*/
public PropertyId setProperty( Name name,
JcrValue value ) throws
ConstraintViolationException {
+ return setProperty(name, value, true);
+ }
+
+ public PropertyId setProperty( Name name,
+ JcrValue value,
+ boolean skipProtected ) throws
ConstraintViolationException {
assert name != null;
assert value != null;
JcrPropertyDefinition definition = null;
@@ -576,7 +582,7 @@
name,
value,
true,
- true);
+ skipProtected);
if (definition == null) {
throw new ConstraintViolationException();
}
@@ -607,6 +613,9 @@
/**
* Set the values for the property. If the property does not exist, it will be
added. If the property does exist, the
* existing values will be replaced with those that are supplied.
+ * <p>
+ * This method will not set protected property definitions and should be used in
almost all cases.
+ * </p>
*
* @param name the property name; may not be null
* @param values new property values, all of which must have the same {@link
Value#getType() property type}; may not be
@@ -617,6 +626,25 @@
*/
public PropertyId setProperty( Name name,
Value[] values ) throws
ConstraintViolationException {
+ return setProperty(name, values, true);
+ }
+
+ /**
+ * Set the values for the property. If the property does not exist, it will be
added. If the property does exist, the
+ * existing values will be replaced with those that are supplied.
+ *
+ * @param name the property name; may not be null
+ * @param values new property values, all of which must have the same {@link
Value#getType() property type}; may not be
+ * null but may be empty
+ * @param skipProtected if true, attempts to set protected properties will fail.
If false, attempts to set protected
+ * properties will be allowed.
+ * @return the identifier for the property; never null
+ * @throws ConstraintViolationException if the property could not be set because
of a node type constraint or property
+ * definition constraint
+ */
+ public PropertyId setProperty( Name name,
+ Value[] values,
+ boolean skipProtected ) throws
ConstraintViolationException {
assert name != null;
assert values != null;
int numValues = values.length;
@@ -660,7 +688,7 @@
node.getMixinTypeNames(),
name,
values,
- true);
+ skipProtected);
if (definition == null) {
throw new ConstraintViolationException();
}
@@ -757,8 +785,8 @@
if (!definition.getId().equals(node.getDefinitionId())) {
// The node definition changed, so try to set the property ...
try {
- JcrValue value = new JcrValue(factories(), SessionCache.this,
PropertyType.STRING, definition.getId()
-
.getString());
+ JcrValue value = new JcrValue(factories(), SessionCache.this,
PropertyType.STRING,
+ definition.getId().getString());
setProperty(DnaLexicon.NODE_DEFINITON, value);
} catch (ConstraintViolationException e) {
// We can't set this property on the node (according to the node
definition).
@@ -887,10 +915,7 @@
// ---------------------------------------
// Now record the changes to the store ...
// ---------------------------------------
- Graph.Create<Graph.Batch> create =
operations.createUnder(currentLocation)
- .nodeNamed(name)
- .with(desiredUuid)
- .with(primaryTypeProp);
+ Graph.Create<Graph.Batch> create =
operations.createUnder(currentLocation).nodeNamed(name).with(desiredUuid).with(primaryTypeProp);
if (nodeDefnDefn != null) {
create = create.with(nodeDefinitionProp);
}
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java 2009-04-07
22:22:56 UTC (rev 811)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java 2009-04-07
22:28:53 UTC (rev 812)
@@ -229,16 +229,11 @@
node.getProperty("../good/path");
}
- @Test( expected = UnsupportedOperationException.class )
- public void shoudNotAllowAddMixin() throws Exception {
- node.addMixin(null);
- }
+ /*
+ * More comprehensive tests of addMixin, removeMixin, and canAddMixin require
additional setup
+ * and are in MixinTest
+ */
- @Test
- public void shoudNotAllowCanAddMixin() throws Exception {
- assertThat(node.canAddMixin(null), is(false));
- }
-
@Test( expected = UnsupportedOperationException.class )
public void shoudNotAllowCancelMerge() throws Exception {
node.cancelMerge(null);
@@ -702,11 +697,6 @@
node.orderBefore(null, null);
}
- @Test( expected = UnsupportedOperationException.class )
- public void shouldNotAllowRemoveMixin() throws Exception {
- node.removeMixin(null);
- }
-
@Test( expected = UnsupportedRepositoryOperationException.class )
public void shouldNotAllowRestoreVersionName() throws Exception {
node.restore((String)null, false);
Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java 2009-04-07 22:22:56 UTC
(rev 811)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java 2009-04-07 22:28:53 UTC
(rev 812)
@@ -38,6 +38,9 @@
import org.apache.jackrabbit.test.RepositoryStub;
import org.apache.jackrabbit.test.api.AddNodeTest;
import org.apache.jackrabbit.test.api.NamespaceRegistryTest;
+import org.apache.jackrabbit.test.api.NodeAddMixinTest;
+import org.apache.jackrabbit.test.api.NodeCanAddMixinTest;
+import org.apache.jackrabbit.test.api.NodeRemoveMixinTest;
import org.apache.jackrabbit.test.api.PropertyTest;
import org.apache.jackrabbit.test.api.RepositoryLoginTest;
import org.apache.jackrabbit.test.api.SessionUUIDTest;
@@ -200,9 +203,9 @@
// addTestSuite(PropertyItemIsModifiedTest.class);
// addTestSuite(PropertyItemIsNewTest.class);
//
- // addTestSuite(NodeAddMixinTest.class);
- // addTestSuite(NodeCanAddMixinTest.class);
- // addTestSuite(NodeRemoveMixinTest.class);
+ addTestSuite(NodeAddMixinTest.class);
+ addTestSuite(NodeCanAddMixinTest.class);
+ addTestSuite(NodeRemoveMixinTest.class);
//
// addTestSuite(WorkspaceCloneReferenceableTest.class);
// addTestSuite(WorkspaceCloneSameNameSibsTest.class);
Added: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/MixinTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/MixinTest.java
(rev 0)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/MixinTest.java 2009-04-07 22:28:53 UTC
(rev 812)
@@ -0,0 +1,675 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.jcr;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.stub;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.Value;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.version.OnParentVersionAction;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Graph;
+import org.jboss.dna.graph.connector.RepositoryConnection;
+import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
+import org.jboss.dna.graph.connector.RepositorySourceException;
+import org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.NamespaceRegistry;
+import org.jboss.dna.graph.property.basic.BasicName;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoAnnotations.Mock;
+
+public class MixinTest {
+
+ /*
+ * Declares the following node types:
+ * mixinTypeA with child node nodeA and property propertyA
+ * mixinTypeB with child node nodeB and property propertyB
+ * mixinTypeC with child node nodeA and property propertyB
+ * mixinTypeWithAutoCreatedProperty with auto-created property propertyB
+ * mixinTypeWithAutoCreatedChildNode with auto-created child node nodeB
+ * mixinTypeC with child node nodeA and property propertyB
+ * primaryTypeA with child node nodeA and property propertyA
+ */
+ static final Name MIXIN_TYPE_A = new BasicName("",
"mixinTypeA");
+ static final Name MIXIN_TYPE_B = new BasicName("",
"mixinTypeB");
+ static final Name MIXIN_TYPE_C = new BasicName("",
"mixinTypeC");
+ static final Name MIXIN_TYPE_WITH_AUTO_PROP = new BasicName("",
"mixinTypeWithAutoCreatedProperty");
+ static final Name MIXIN_TYPE_WITH_AUTO_CHILD = new BasicName("",
"mixinTypeWithAutoCreatedChildNode");
+ static final Name PRIMARY_TYPE_A = new BasicName("",
"mixinTypeA");
+
+ static final Name PROPERTY_A = new BasicName("", "propertyA");
+ static final Name PROPERTY_B = new BasicName("", "propertyB");
+
+ static final Name CHILD_NODE_A = new BasicName("", "nodeA");
+ static final Name CHILD_NODE_B = new BasicName("", "nodeB");
+
+ private String workspaceName;
+ private ExecutionContext context;
+ private InMemoryRepositorySource source;
+ private JcrWorkspace workspace;
+ private JcrSession session;
+ private Graph graph;
+ private RepositoryConnectionFactory connectionFactory;
+ private RepositoryNodeTypeManager repoTypeManager;
+ private Map<String, Object> sessionAttributes;
+ private Map<JcrRepository.Options, String> options;
+ private NamespaceRegistry registry;
+ @Mock
+ private JcrRepository repository;
+
+ @Before
+ public void beforeEach() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ workspaceName = "workspace1";
+ final String repositorySourceName = "repository";
+
+ // Set up the source ...
+ source = new InMemoryRepositorySource();
+ source.setName(workspaceName);
+ source.setDefaultWorkspaceName(workspaceName);
+
+ // Set up the execution context ...
+ context = new ExecutionContext();
+ // Register the test namespace
+ context.getNamespaceRegistry().register(TestLexicon.Namespace.PREFIX,
TestLexicon.Namespace.URI);
+
+ // Set up the initial content ...
+ graph = Graph.create(source, context);
+
+ // Make sure the path to the namespaces exists ...
+ graph.create("/jcr:system"); //
.and().create("/jcr:system/dna:namespaces");
+
graph.set("jcr:primaryType").on("/jcr:system").to(DnaLexicon.SYSTEM);
+
+ // Stub out the connection factory ...
+ connectionFactory = new RepositoryConnectionFactory() {
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.connector.RepositoryConnectionFactory#createConnection(java.lang.String)
+ */
+ @SuppressWarnings( "synthetic-access" )
+ public RepositoryConnection createConnection( String sourceName ) throws
RepositorySourceException {
+ return repositorySourceName.equals(sourceName) ? source.getConnection() :
null;
+ }
+ };
+
+ // Stub out the repository, since we only need a few methods ...
+ JcrNodeTypeSource source = null;
+ source = new JcrBuiltinNodeTypeSource(this.context, source);
+ source = new DnaBuiltinNodeTypeSource(this.context, source);
+ source = new TestNodeTypeSource(this.context, source);
+ repoTypeManager = new RepositoryNodeTypeManager(context, source);
+ stub(repository.getRepositoryTypeManager()).toReturn(repoTypeManager);
+ stub(repository.getRepositorySourceName()).toReturn(repositorySourceName);
+ stub(repository.getConnectionFactory()).toReturn(connectionFactory);
+
+ // Stub out the repository options ...
+ options = new EnumMap<JcrRepository.Options,
String>(JcrRepository.Options.class);
+ options.put(JcrRepository.Options.PROJECT_NODE_TYPES, Boolean.FALSE.toString());
+ stub(repository.getOptions()).toReturn(options);
+
+ // Set up the session attributes ...
+ sessionAttributes = new HashMap<String, Object>();
+
+ // Now create the workspace ...
+ workspace = new JcrWorkspace(repository, workspaceName, context,
sessionAttributes);
+
+ // Create the session and log in ...
+ session = (JcrSession)workspace.getSession();
+ registry = session.getExecutionContext().getNamespaceRegistry();
+ }
+
+ @After
+ public void after() throws Exception {
+ if (session != null && session.isLive()) {
+ session.logout();
+ }
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowNullMixinTypeName() throws Exception {
+ graph.create("/a");
+ graph.set("jcr:primaryType").on("/a").to(PRIMARY_TYPE_A);
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+
+ nodeA.canAddMixin(null);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowEmptyMixinTypeName() throws Exception {
+ graph.create("/a");
+ graph.set("jcr:primaryType").on("/a").to(PRIMARY_TYPE_A);
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+
+ nodeA.canAddMixin("");
+ }
+
+ @Test( expected = NoSuchNodeTypeException.class )
+ public void shouldNotAllowInvalidMixinTypeName() throws Exception {
+ graph.create("/a");
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(PRIMARY_TYPE_A);
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+
+ nodeA.canAddMixin("foo");
+ }
+
+ @Test
+ public void shouldNotAllowAddingMixinToProtectedNodes() throws Exception {
+ Node rootNode = session.getRootNode();
+ Node systemNode = rootNode.getNode(JcrLexicon.SYSTEM.getString(registry));
+
+ assertThat(systemNode.getDefinition().isProtected(), is(true));
+ assertThat(systemNode.canAddMixin(JcrMixLexicon.VERSIONABLE.getString(registry)),
is(false));
+ }
+
+ @Test
+ public void shouldAllowAddingMixinIfNoConflict() throws Exception {
+ graph.create("/a");
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(PRIMARY_TYPE_A);
+
graph.set(JcrLexicon.MIXIN_TYPES.getString(registry)).on("/a").to(JcrMixLexicon.REFERENCEABLE.getString(registry));
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+
+ assertThat(nodeA.canAddMixin(MIXIN_TYPE_B.getString(registry)), is(true));
+ }
+
+ @Test
+ public void shouldNotAllowAddingMixinIfPrimaryTypeConflicts() throws Exception {
+ graph.create("/a");
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(PRIMARY_TYPE_A);
+
graph.set(JcrLexicon.MIXIN_TYPES.getString(registry)).on("/a").to(JcrMixLexicon.REFERENCEABLE.getString(registry));
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+
+ assertThat(nodeA.canAddMixin(MIXIN_TYPE_C.getString(registry)), is(false));
+ }
+
+ @Test
+ public void shouldNotAllowAddingMixinIfMixinTypeConflicts() throws Exception {
+ graph.create("/a");
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(JcrNtLexicon.BASE.getString(registry));
+
graph.set(JcrLexicon.MIXIN_TYPES.getString(registry)).on("/a").to(MIXIN_TYPE_B);
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+
+ assertThat(nodeA.canAddMixin(MIXIN_TYPE_C.getString(registry)), is(false));
+ }
+
+ @Test
+ public void shouldAutoCreateAutoCreatedPropertiesOnAddition() throws Exception {
+ graph.create("/a");
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(JcrNtLexicon.BASE.getString(registry));
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+
+ assertThat(nodeA.canAddMixin(MIXIN_TYPE_WITH_AUTO_PROP.getString(registry)),
is(true));
+ nodeA.addMixin(MIXIN_TYPE_WITH_AUTO_PROP.getString(registry));
+ Property prop = nodeA.getProperty(PROPERTY_B.getString(registry));
+
+ assertThat(prop, is(notNullValue()));
+ assertThat(prop.getLong(), is(10L));
+ }
+
+ @Test
+ public void shouldAutoCreateAutoCreatedChildNodesOnAddition() throws Exception {
+ graph.create("/a");
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(JcrNtLexicon.BASE.getString(registry));
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+
+ assertThat(nodeA.canAddMixin(MIXIN_TYPE_WITH_AUTO_CHILD.getString(registry)),
is(true));
+ nodeA.addMixin(MIXIN_TYPE_WITH_AUTO_CHILD.getString(registry));
+ Node childNode = nodeA.getNode(CHILD_NODE_B.getString(registry));
+
+ assertThat(childNode, is(notNullValue()));
+ assertThat(childNode.getPrimaryNodeType().getName(),
is(JcrNtLexicon.UNSTRUCTURED.getString(registry)));
+ }
+
+ @Test( expected = ConstraintViolationException.class )
+ public void shouldNotAllowAdditionIfResidualPropertyConflicts() throws Exception {
+ graph.create("/a");
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(JcrNtLexicon.UNSTRUCTURED.getString(registry));
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+ nodeA.setProperty(PROPERTY_B.getString(registry), "Not a boolean");
+
+ assertThat(nodeA.canAddMixin(MIXIN_TYPE_WITH_AUTO_PROP.getString(registry)),
is(false));
+ nodeA.addMixin(MIXIN_TYPE_WITH_AUTO_PROP.getString(registry));
+ }
+
+ @Test
+ public void shouldAllowAdditionIfResidualPropertyDoesNotConflict() throws Exception
{
+ graph.create("/a");
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(JcrNtLexicon.UNSTRUCTURED.getString(registry));
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+ nodeA.setProperty(PROPERTY_B.getString(registry), 10L);
+
+ assertThat(nodeA.canAddMixin(MIXIN_TYPE_WITH_AUTO_PROP.getString(registry)),
is(true));
+ nodeA.addMixin(MIXIN_TYPE_WITH_AUTO_PROP.getString(registry));
+ }
+
+ @Test( expected = ConstraintViolationException.class )
+ public void shouldNotAllowAdditionIfResidualChildNodeConflicts() throws Exception {
+ graph.create("/a").and().create("/a/" + CHILD_NODE_B);
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(JcrNtLexicon.UNSTRUCTURED.getString(registry));
+ graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a/" +
CHILD_NODE_B).to(JcrNtLexicon.BASE.getString(registry));
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+
+ assertThat(nodeA.canAddMixin(MIXIN_TYPE_WITH_AUTO_CHILD.getString(registry)),
is(false));
+ nodeA.addMixin(MIXIN_TYPE_WITH_AUTO_CHILD.getString(registry));
+ }
+
+ @Test
+ public void shouldAllowAdditionIfResidualChildNodeDoesNotConflict() throws Exception
{
+ graph.create("/a").and().create("/a/" + CHILD_NODE_B);
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(JcrNtLexicon.UNSTRUCTURED.getString(registry));
+ graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a/" +
CHILD_NODE_B).to(JcrNtLexicon.UNSTRUCTURED.getString(registry));
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+
+ assertThat(nodeA.canAddMixin(MIXIN_TYPE_WITH_AUTO_CHILD.getString(registry)),
is(true));
+ nodeA.addMixin(MIXIN_TYPE_WITH_AUTO_CHILD.getString(registry));
+ nodeA.save();
+
+ Node newRootNode = session.getRootNode();
+ Node newNodeA = newRootNode.getNode("a");
+
+ Node newNodeB = newNodeA.getNode(CHILD_NODE_B.getString(registry));
+
+ assertThat(newNodeA.isNodeType(MIXIN_TYPE_WITH_AUTO_CHILD.getLocalName()),
is(true));
+ assertThat(newNodeB.isNodeType("nt:unstructured"), is(true));
+ assertThat(newNodeB, is(notNullValue()));
+
+ // Uncomment this to see the problem with the session cache not refreshing the
immutable info for /a after a save
+ // assertThat(newNodeB.getDefinition().getDeclaringNodeType().getName(),
is(MIXIN_TYPE_B.getString(registry)));
+
+ }
+
+ @Test
+ public void shouldAllowSettingNewPropertyAfterAddingMixin() throws Exception {
+ graph.create("/a");
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(PRIMARY_TYPE_A);
+
graph.set(JcrLexicon.MIXIN_TYPES.getString(registry)).on("/a").to(JcrMixLexicon.REFERENCEABLE.getString(registry));
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+
+ assertThat(nodeA.canAddMixin(MIXIN_TYPE_B.getString(registry)), is(true));
+ nodeA.addMixin(MIXIN_TYPE_B.getString(registry));
+
+ nodeA.setProperty(PROPERTY_B.getString(registry), "some string");
+ nodeA.save();
+
+ rootNode = session.getRootNode();
+ nodeA = rootNode.getNode("a");
+ Property propB = nodeA.getProperty(PROPERTY_B.getString(registry));
+
+ assertThat(propB, is(notNullValue()));
+ assertThat(propB.getValue().getString(), is("some string"));
+ }
+
+ @Test
+ public void shouldAllowAddingNewChildNodeAfterAddingMixin() throws Exception {
+ graph.create("/a");
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(PRIMARY_TYPE_A);
+
graph.set(JcrLexicon.MIXIN_TYPES.getString(registry)).on("/a").to(JcrMixLexicon.REFERENCEABLE.getString(registry));
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+
+ assertThat(nodeA.canAddMixin(MIXIN_TYPE_B.getString(registry)), is(true));
+ nodeA.addMixin(MIXIN_TYPE_B.getString(registry));
+
+ nodeA.addNode(CHILD_NODE_B.getString(registry));
+ nodeA.save();
+
+ Node newRootNode = session.getRootNode();
+ Node newNodeA = newRootNode.getNode("a");
+ Node newNodeB = newNodeA.getNode(CHILD_NODE_B.getString(registry));
+
+ assertThat(newNodeB, is(notNullValue()));
+ assertThat(newNodeB.getDefinition().getDeclaringNodeType().getName(),
is(MIXIN_TYPE_B.getString(registry)));
+ }
+
+ @Test
+ public void shouldAllowRemovalIfNoConflict() throws Exception {
+ graph.create("/a").and().create("/a/nodeB");
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(JcrNtLexicon.UNSTRUCTURED);
+
graph.set(JcrLexicon.MIXIN_TYPES.getString(registry)).on("/a").to(MIXIN_TYPE_B);
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+ nodeA.removeMixin(MIXIN_TYPE_B.getString(registry));
+ nodeA.save();
+
+ rootNode = session.getRootNode();
+ nodeA = rootNode.getNode("a");
+
+ assertThat(nodeA.getMixinNodeTypes().length, is(0));
+ }
+
+ @Test
+ public void shouldAllowRemovalIfExistingPropertyWouldHaveDefinition() throws
Exception {
+ graph.create("/a").and().create("/a");
+ graph.set(PROPERTY_B).on("/a").to("true");
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(JcrNtLexicon.UNSTRUCTURED);
+
graph.set(JcrLexicon.MIXIN_TYPES.getString(registry)).on("/a").to(MIXIN_TYPE_B);
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+ nodeA.removeMixin(MIXIN_TYPE_B.getString(registry));
+ nodeA.save();
+
+ rootNode = session.getRootNode();
+ nodeA = rootNode.getNode("a");
+
+ assertThat(nodeA.getMixinNodeTypes().length, is(0));
+ }
+
+ @Test( expected = ConstraintViolationException.class )
+ public void shouldNotAllowRemovalIfExistingPropertyWouldHaveNoDefinition() throws
Exception {
+ graph.create("/a").and().create("/a");
+ graph.set(PROPERTY_B).on("/a").to("true");
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(PRIMARY_TYPE_A);
+
graph.set(JcrLexicon.MIXIN_TYPES.getString(registry)).on("/a").to(MIXIN_TYPE_B);
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+ nodeA.removeMixin(MIXIN_TYPE_B.getString(registry));
+ nodeA.save();
+
+ rootNode = session.getRootNode();
+ nodeA = rootNode.getNode("a");
+
+ assertThat(nodeA.getMixinNodeTypes().length, is(0));
+ }
+
+ @Test
+ public void shouldAllowRemovalIfExistingChildNodeWouldHaveDefinition() throws
Exception {
+ graph.create("/a").and().create("/a/nodeB");
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(JcrNtLexicon.UNSTRUCTURED);
+
graph.set(JcrLexicon.MIXIN_TYPES.getString(registry)).on("/a").to(MIXIN_TYPE_B);
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+ nodeA.removeMixin(MIXIN_TYPE_B.getString(registry));
+ }
+
+ @Test( expected = ConstraintViolationException.class )
+ public void shouldNotAllowRemovalIfExistingChildNodeWouldHaveNoDefinition() throws
Exception {
+ graph.create("/a").and().create("/a/nodeB");
+
graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(PRIMARY_TYPE_A);
+
graph.set(JcrLexicon.MIXIN_TYPES.getString(registry)).on("/a").to(MIXIN_TYPE_B);
+
+ Node rootNode = session.getRootNode();
+ Node nodeA = rootNode.getNode("a");
+ nodeA.removeMixin(MIXIN_TYPE_B.getString(registry));
+ }
+
+ class TestNodeTypeSource extends AbstractJcrNodeTypeSource {
+
+ /** The list of primary node types. */
+ private final List<JcrNodeType> nodeTypes;
+
+ TestNodeTypeSource( ExecutionContext context,
+ JcrNodeTypeSource predecessor ) {
+ super(predecessor);
+
+ nodeTypes = new ArrayList<JcrNodeType>();
+
+ JcrNodeType base = findType(JcrNtLexicon.BASE);
+
+ if (base == null) {
+ String baseTypeName =
JcrNtLexicon.BASE.getString(context.getNamespaceRegistry());
+ String namespaceTypeName =
DnaLexicon.NAMESPACE.getString(context.getNamespaceRegistry());
+ throw new
IllegalStateException(JcrI18n.supertypeNotFound.text(baseTypeName, namespaceTypeName));
+ }
+
+ JcrNodeType unstructured = findType(JcrNtLexicon.UNSTRUCTURED);
+
+ if (unstructured == null) {
+ String baseTypeName =
JcrNtLexicon.UNSTRUCTURED.getString(context.getNamespaceRegistry());
+ String namespaceTypeName =
DnaLexicon.NAMESPACE.getString(context.getNamespaceRegistry());
+ throw new
IllegalStateException(JcrI18n.supertypeNotFound.text(baseTypeName, namespaceTypeName));
+ }
+
+ Value tenValue = new JcrValue(context.getValueFactories(), null,
PropertyType.LONG, 10);
+
+ // Stubbing in child node and property definitions for now
+ JcrNodeType mixinTypeA = new JcrNodeType(
+ context,
+ NO_NODE_TYPE_MANAGER,
+ MIXIN_TYPE_A,
+ Arrays.asList(new JcrNodeType[]
{base}),
+ NO_PRIMARY_ITEM_NAME,
+ Arrays.asList(new
JcrNodeDefinition[] {new JcrNodeDefinition(
+
context,
+
null,
+
CHILD_NODE_A,
+
OnParentVersionAction.IGNORE,
+
false,
+
false,
+
false,
+
false,
+
JcrNtLexicon.BASE,
+
new JcrNodeType[] {base}),}),
+ Arrays.asList(new
JcrPropertyDefinition[] {new JcrPropertyDefinition(
+
context,
+
null,
+
PROPERTY_A,
+
OnParentVersionBehavior.IGNORE.getJcrValue(),
+
false,
+
false,
+
false,
+
NO_DEFAULT_VALUES,
+
PropertyType.STRING,
+
NO_CONSTRAINTS,
+
false),}),
+ IS_A_MIXIN,
UNORDERABLE_CHILD_NODES);
+
+ JcrNodeType mixinTypeB = new JcrNodeType(
+ context,
+ NO_NODE_TYPE_MANAGER,
+ MIXIN_TYPE_B,
+ Arrays.asList(new JcrNodeType[]
{base}),
+ NO_PRIMARY_ITEM_NAME,
+ Arrays.asList(new
JcrNodeDefinition[] {new JcrNodeDefinition(
+
context,
+
null,
+
CHILD_NODE_B,
+
OnParentVersionAction.IGNORE,
+
false,
+
false,
+
false,
+
false,
+
JcrNtLexicon.BASE,
+
new JcrNodeType[] {base}),}),
+ Arrays.asList(new
JcrPropertyDefinition[] {new JcrPropertyDefinition(
+
context,
+
null,
+
PROPERTY_B,
+
OnParentVersionBehavior.IGNORE.getJcrValue(),
+
false,
+
false,
+
false,
+
NO_DEFAULT_VALUES,
+
PropertyType.BINARY,
+
NO_CONSTRAINTS,
+
false),}),
+ IS_A_MIXIN,
UNORDERABLE_CHILD_NODES);
+
+ JcrNodeType mixinTypeC = new JcrNodeType(
+ context,
+ NO_NODE_TYPE_MANAGER,
+ MIXIN_TYPE_C,
+ Arrays.asList(new JcrNodeType[]
{base}),
+ NO_PRIMARY_ITEM_NAME,
+ Arrays.asList(new
JcrNodeDefinition[] {new JcrNodeDefinition(
+
context,
+
null,
+
CHILD_NODE_A,
+
OnParentVersionAction.IGNORE,
+
false,
+
false,
+
false,
+
false,
+
JcrNtLexicon.BASE,
+
new JcrNodeType[] {base}),}),
+ Arrays.asList(new
JcrPropertyDefinition[] {new JcrPropertyDefinition(
+
context,
+
null,
+
PROPERTY_B,
+
OnParentVersionBehavior.IGNORE.getJcrValue(),
+
false,
+
false,
+
false,
+
NO_DEFAULT_VALUES,
+
PropertyType.STRING,
+
NO_CONSTRAINTS,
+
false),}),
+ IS_A_MIXIN,
UNORDERABLE_CHILD_NODES);
+
+ JcrNodeType mixinTypeWithAutoChild = new JcrNodeType(
+ context,
+ NO_NODE_TYPE_MANAGER,
+
MIXIN_TYPE_WITH_AUTO_CHILD,
+ Arrays.asList(new
JcrNodeType[] {base}),
+ NO_PRIMARY_ITEM_NAME,
+ Arrays.asList(new
JcrNodeDefinition[] {new JcrNodeDefinition(
+
context,
+
null,
+
CHILD_NODE_B,
+
OnParentVersionAction.IGNORE,
+
true,
+
true,
+
false,
+
false,
+
JcrNtLexicon.UNSTRUCTURED,
+
new JcrNodeType[] {unstructured}),}),
+ NO_PROPERTIES,
IS_A_MIXIN, UNORDERABLE_CHILD_NODES);
+
+ JcrNodeType mixinTypeWithAutoProperty = new JcrNodeType(
+ context,
+
NO_NODE_TYPE_MANAGER,
+
MIXIN_TYPE_WITH_AUTO_PROP,
+ Arrays.asList(new
JcrNodeType[] {base}),
+
NO_PRIMARY_ITEM_NAME,
+ NO_CHILD_NODES,
+ Arrays.asList(new
JcrPropertyDefinition[] {new JcrPropertyDefinition(
+
context,
+
null,
+
PROPERTY_B,
+
OnParentVersionBehavior.IGNORE.getJcrValue(),
+
true,
+
true,
+
false,
+
new Value[] {tenValue},
+
PropertyType.LONG,
+
NO_CONSTRAINTS,
+
false),}),
+ IS_A_MIXIN,
UNORDERABLE_CHILD_NODES);
+
+ JcrNodeType primaryTypeA = new JcrNodeType(
+ context,
+ NO_NODE_TYPE_MANAGER,
+ PRIMARY_TYPE_A,
+ Arrays.asList(new JcrNodeType[]
{base}),
+ NO_PRIMARY_ITEM_NAME,
+ Arrays.asList(new
JcrNodeDefinition[] {new JcrNodeDefinition(
+
context,
+
null,
+
CHILD_NODE_A,
+
OnParentVersionAction.IGNORE,
+
false,
+
false,
+
false,
+
false,
+
JcrNtLexicon.BASE,
+
new JcrNodeType[] {base}),}),
+ Arrays.asList(new
JcrPropertyDefinition[] {new JcrPropertyDefinition(
+
context,
+
null,
+
PROPERTY_A,
+
OnParentVersionBehavior.IGNORE.getJcrValue(),
+
false,
+
false,
+
false,
+
NO_DEFAULT_VALUES,
+
PropertyType.STRING,
+
NO_CONSTRAINTS,
+
false),}),
+ NOT_MIXIN,
UNORDERABLE_CHILD_NODES);
+
+ nodeTypes.addAll(Arrays.asList(new JcrNodeType[] {mixinTypeA, mixinTypeB,
mixinTypeC, mixinTypeWithAutoChild,
+ mixinTypeWithAutoProperty, primaryTypeA,}));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.jcr.AbstractJcrNodeTypeSource#getDeclaredNodeTypes()
+ */
+ @Override
+ public Collection<JcrNodeType> getDeclaredNodeTypes() {
+ return nodeTypes;
+ }
+
+ }
+
+}
\ No newline at end of file
Property changes on: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/MixinTest.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain