Index: dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java (revision 844) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java (working copy) @@ -63,6 +63,7 @@ import net.jcip.annotations.Immutable; import org.jboss.dna.common.i18n.I18n; import org.jboss.dna.common.util.CheckArg; +import org.jboss.dna.graph.Graph; import org.jboss.dna.graph.property.Binary; import org.jboss.dna.graph.property.DateTime; import org.jboss.dna.graph.property.Name; @@ -73,7 +74,6 @@ import org.jboss.dna.jcr.cache.ChildNode; import org.jboss.dna.jcr.cache.Children; import org.jboss.dna.jcr.cache.NodeInfo; -import org.jboss.dna.jcr.cache.PropertyInfo; /** * An abstract implementation of the JCR {@link Node} interface. Instances of this class are created and managed by the @@ -126,6 +126,18 @@ throw new RepositoryException(msg); } } + + final NodeEditor editorFor(Graph.Batch operations) throws RepositoryException { + try { + return cache.getEditorFor(nodeUuid, operations); + } catch (ItemNotFoundException err) { + String msg = JcrI18n.nodeHasAlreadyBeenRemovedFromThisSession.text(nodeUuid, cache.workspaceName()); + throw new RepositoryException(msg); + } catch (InvalidItemStateException err) { + String msg = JcrI18n.nodeHasAlreadyBeenRemovedFromThisSession.text(nodeUuid, cache.workspaceName()); + throw new RepositoryException(msg); + } + } final JcrValue valueFrom( int propertyType, Object value ) { @@ -729,50 +749,7 @@ 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()); - } - } - } - + this.editor().addMixin(mixinCandidateType); } /** @@ -891,7 +868,7 @@ } } - cache.findJcrProperty(editor().setProperty(JcrLexicon.MIXIN_TYPES, newMixinValues, false)); + cache.findJcrProperty(editor().setProperty(JcrLexicon.MIXIN_TYPES, newMixinValues, PropertyType.NAME, false)); } @@ -1022,31 +999,16 @@ throw new UnsupportedOperationException(); } - /** - * Checks whether there is an existing property with this name that does not match the given cardinality. If such a property - * exists, a {@code javax.jcr.ValueFormatException} is thrown, as per section 7.1.5 of the JCR 1.0.1 specification. - * - * @param propertyName the name of the property - * @param isMultiple whether the property must have multiple values - * @throws javax.jcr.ValueFormatException if the property exists but has the opposite cardinality - * @throws RepositoryException if any other error occurs - */ - private void checkCardinalityOfExistingProperty( Name propertyName, - boolean isMultiple ) - throws javax.jcr.ValueFormatException, RepositoryException { - // Check for existing single-valued property - can't set multiple values on single-valued property - PropertyInfo propInfo = this.nodeInfo().getProperty(propertyName); - if (propInfo != null && propInfo.isMultiValued() != isMultiple) { - if (isMultiple) { - I18n msg = JcrI18n.unableToSetSingleValuedPropertyUsingMultipleValues; - throw new ValueFormatException(msg.text(getPath(), - propertyName.getString(cache.namespaces), - cache.workspaceName())); - } - I18n msg = JcrI18n.unableToSetMultiValuedPropertyUsingSingleValue; - throw new ValueFormatException(msg.text(getPath(), propertyName, cache.workspaceName())); + protected final Property removeExistingValuedProperty( String name ) + throws ConstraintViolationException, RepositoryException { + PropertyId id = new PropertyId(nodeUuid, nameFrom(name)); + AbstractJcrProperty property = cache.findJcrProperty(id); + if (property != null) { + property.remove(); + return property; } - + // else the property doesn't exist ... + throw new RepositoryException(JcrI18n.propertyNotFoundOnNode.text(name, getPath(), cache.workspaceName())); } /** @@ -1057,10 +1019,7 @@ public final Property setProperty( String name, boolean value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { - Name propertyName = nameFrom(name); - checkCardinalityOfExistingProperty(propertyName, false); - return cache.findJcrProperty(editor().setProperty(propertyName, valueFrom(PropertyType.BOOLEAN, value))); - + return cache.findJcrProperty(editor().setProperty(nameFrom(name), valueFrom(PropertyType.BOOLEAN, value))); } /** @@ -1071,15 +1030,12 @@ public final Property setProperty( String name, Calendar value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { + if (value == null) { - // If there is an existing property, then remove it ... return removeExistingValuedProperty(name); } - Name propertyName = nameFrom(name); - checkCardinalityOfExistingProperty(propertyName, false); - return cache.findJcrProperty(editor().setProperty(propertyName, valueFrom(value))); - + return cache.findJcrProperty(editor().setProperty(nameFrom(name), valueFrom(value))); } /** @@ -1090,10 +1046,7 @@ public final Property setProperty( String name, double value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { - Name propertyName = nameFrom(name); - checkCardinalityOfExistingProperty(propertyName, false); - return cache.findJcrProperty(editor().setProperty(propertyName, valueFrom(PropertyType.DOUBLE, value))); - + return cache.findJcrProperty(editor().setProperty(nameFrom(name), valueFrom(PropertyType.DOUBLE, value))); } /** @@ -1105,12 +1058,10 @@ InputStream value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { if (value == null) { - // If there is an existing property, then remove it ... return removeExistingValuedProperty(name); } - Name propertyName = nameFrom(name); - checkCardinalityOfExistingProperty(propertyName, false); - return cache.findJcrProperty(editor().setProperty(propertyName, valueFrom(value))); + + return cache.findJcrProperty(editor().setProperty(nameFrom(name), valueFrom(value))); } /** @@ -1121,9 +1072,7 @@ public final Property setProperty( String name, long value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { - Name propertyName = nameFrom(name); - checkCardinalityOfExistingProperty(propertyName, false); - return cache.findJcrProperty(editor().setProperty(propertyName, valueFrom(PropertyType.LONG, value))); + return cache.findJcrProperty(editor().setProperty(nameFrom(name), valueFrom(PropertyType.LONG, value))); } /** @@ -1135,13 +1084,10 @@ Node value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { if (value == null) { - // If there is an existing property, then remove it ... return removeExistingValuedProperty(name); } - Name propertyName = nameFrom(name); - checkCardinalityOfExistingProperty(propertyName, false); - return cache.findJcrProperty(editor().setProperty(propertyName, valueFrom(value))); + return cache.findJcrProperty(editor().setProperty(nameFrom(name), valueFrom(value))); } /** @@ -1153,12 +1099,10 @@ String value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { if (value == null) { - // If there is an existing property, then remove it ... return removeExistingValuedProperty(name); } - Name propertyName = nameFrom(name); - checkCardinalityOfExistingProperty(propertyName, false); - return cache.findJcrProperty(editor().setProperty(propertyName, valueFrom(PropertyType.STRING, value))); + + return cache.findJcrProperty(editor().setProperty(nameFrom(name), valueFrom(PropertyType.STRING, value))); } /** @@ -1171,12 +1115,10 @@ int type ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { if (value == null) { - // If there is an existing property, then remove it ... return removeExistingValuedProperty(name); } - Name propertyName = nameFrom(name); - checkCardinalityOfExistingProperty(propertyName, false); - return cache.findJcrProperty(editor().setProperty(propertyName, valueFrom(type, value))); + + return cache.findJcrProperty(editor().setProperty(nameFrom(name), valueFrom(type, value))); } /** @@ -1188,12 +1130,10 @@ String[] values ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { if (values == null) { - // If there is an existing property, then remove it ... return removeExistingValuedProperty(name); } - Name propertyName = nameFrom(name); - checkCardinalityOfExistingProperty(propertyName, true); - return cache.findJcrProperty(editor().setProperty(propertyName, valuesFrom(PropertyType.STRING, values))); + + return cache.findJcrProperty(editor().setProperty(nameFrom(name), valuesFrom(PropertyType.STRING, values), PropertyType.UNDEFINED)); } /** @@ -1206,12 +1146,10 @@ int type ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { if (values == null) { - // If there is an existing property, then remove it ... return removeExistingValuedProperty(name); } - Name propertyName = nameFrom(name); - checkCardinalityOfExistingProperty(propertyName, true); - return cache.findJcrProperty(editor().setProperty(propertyName, valuesFrom(type, values))); + + return cache.findJcrProperty(editor().setProperty(nameFrom(name), valuesFrom(type, values), PropertyType.UNDEFINED)); } /** @@ -1223,24 +1161,10 @@ Value value ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { if (value == null) { - // If there is an existing property, then remove it ... return removeExistingValuedProperty(name); } - Name propertyName = nameFrom(name); - checkCardinalityOfExistingProperty(propertyName, false); - return cache.findJcrProperty(editor().setProperty(propertyName, (JcrValue)value)); - } - protected final Property removeExistingValuedProperty( String name ) - throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { - PropertyId id = new PropertyId(nodeUuid, nameFrom(name)); - AbstractJcrProperty property = cache.findJcrProperty(id); - if (property != null) { - property.remove(); - return property; - } - // else the property doesn't exist ... - throw new RepositoryException(JcrI18n.propertyNotFoundOnNode.text(name, getPath(), cache.workspaceName())); + return cache.findJcrProperty(editor().setProperty(nameFrom(name), (JcrValue)value)); } /** @@ -1253,12 +1177,10 @@ int type ) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { if (value == null) { - // If there is an existing property, then remove it ... return removeExistingValuedProperty(name); } - Name propertyName = nameFrom(name); - checkCardinalityOfExistingProperty(propertyName, false); - return cache.findJcrProperty(editor().setProperty(propertyName, ((JcrValue)value).asType(type))); + + return cache.findJcrProperty(editor().setProperty(nameFrom(name), ((JcrValue)value).asType(type))); } /** @@ -1273,44 +1195,8 @@ // If there is an existing property, then remove it ... return removeExistingValuedProperty(name); } - int len = values.length; - Value[] newValues = null; - if (len == 0) { - newValues = JcrMultiValueProperty.EMPTY_VALUES; - } else { - List valuesWithDesiredType = new ArrayList(len); - int expectedType = -1; - for (int i = 0; i != len; ++i) { - Value value = values[i]; - if (value == null) continue; - if (expectedType == -1) { - expectedType = value.getType(); - } else if (value.getType() != expectedType) { - // Make sure the type of each value is the same, as per Javadoc in section 7.1.5 of the JCR 1.0.1 spec - StringBuilder sb = new StringBuilder(); - sb.append('['); - for (int j = 0; j != values.length; ++j) { - if (j != 0) sb.append(","); - sb.append(values[j].toString()); - } - sb.append(']'); - String propType = PropertyType.nameFromValue(expectedType); - I18n msg = JcrI18n.allPropertyValuesMustHaveSameType; - throw new ValueFormatException(msg.text(name, values, propType, getPath(), cache.workspaceName())); - } - valuesWithDesiredType.add(value); - } - if (valuesWithDesiredType.isEmpty()) { - newValues = JcrMultiValueProperty.EMPTY_VALUES; - } else { - newValues = valuesWithDesiredType.toArray(new Value[valuesWithDesiredType.size()]); - } - } - Name propertyName = nameFrom(name); - checkCardinalityOfExistingProperty(propertyName, true); - // Set the value, perhaps to an empty array ... - return cache.findJcrProperty(editor().setProperty(propertyName, newValues)); + return setProperty(name, values, PropertyType.UNDEFINED); } /** @@ -1327,48 +1213,8 @@ return removeExistingValuedProperty(name); } - int len = values.length; - Value[] newValues = null; - if (len == 0) { - newValues = JcrMultiValueProperty.EMPTY_VALUES; - } else { - List valuesWithDesiredType = new ArrayList(len); - int expectedType = -1; - for (int i = 0; i != len; ++i) { - Value value = values[i]; - - if (value == null) continue; - if (expectedType == -1) { - expectedType = value.getType(); - } else if (value.getType() != expectedType) { - // Make sure the type of each value is the same, as per Javadoc in section 7.1.5 of the JCR 1.0.1 spec - StringBuilder sb = new StringBuilder(); - sb.append('['); - for (int j = 0; j != values.length; ++j) { - if (j != 0) sb.append(","); - sb.append(values[j].toString()); - } - sb.append(']'); - String propType = PropertyType.nameFromValue(expectedType); - I18n msg = JcrI18n.allPropertyValuesMustHaveSameType; - throw new ValueFormatException(msg.text(name, values, propType, getPath(), cache.workspaceName())); - } - if (value.getType() != type) { - value = ((JcrValue)value).asType(type); - } - valuesWithDesiredType.add(value); - } - if (valuesWithDesiredType.isEmpty()) { - newValues = JcrMultiValueProperty.EMPTY_VALUES; - } else { - newValues = valuesWithDesiredType.toArray(new Value[valuesWithDesiredType.size()]); - } - } - - Name propertyName = nameFrom(name); - checkCardinalityOfExistingProperty(propertyName, true); // Set the value, perhaps to an empty array ... - return cache.findJcrProperty(editor().setProperty(propertyName, newValues)); + return cache.findJcrProperty(editor().setProperty(nameFrom(name), values, type)); } /** Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrContentHandler.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrContentHandler.java (revision 844) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrContentHandler.java (working copy) @@ -32,16 +32,19 @@ import java.util.UUID; import javax.jcr.ImportUUIDBehavior; import javax.jcr.ItemExistsException; -import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; +import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import net.jcip.annotations.NotThreadSafe; import org.jboss.dna.common.text.TextDecoder; import org.jboss.dna.common.text.XmlNameEncoder; import org.jboss.dna.common.util.Base64; +import org.jboss.dna.graph.Graph; +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.xml.sax.Attributes; @@ -71,9 +74,10 @@ protected static final TextDecoder DOCUMENT_VIEW_NAME_DECODER = new JcrDocumentViewExporter.JcrDocumentViewPropertyEncoder(); + private final NameFactory nameFactory; + protected final JcrSession session; protected final int uuidBehavior; - protected final SaveMode saveMode; protected final String primaryTypeName; protected final String mixinTypesName; @@ -82,6 +86,8 @@ private AbstractJcrNode currentNode; private ContentHandler delegate; + private Graph.Batch pendingOperations; + enum SaveMode { WORKSPACE, SESSION @@ -99,15 +105,37 @@ || uuidBehavior == ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW; this.session = session; + this.nameFactory = session.getExecutionContext().getValueFactories().getNameFactory(); this.currentNode = session.getNode(parentPath); this.uuidBehavior = uuidBehavior; - this.saveMode = saveMode; + if (saveMode == SaveMode.WORKSPACE) { + this.pendingOperations = session.createBatch(); + } + this.primaryTypeName = JcrLexicon.PRIMARY_TYPE.getString(this.session.namespaces()); this.mixinTypesName = JcrLexicon.MIXIN_TYPES.getString(this.session.namespaces()); this.uuidName = JcrLexicon.UUID.getString(this.session.namespaces()); } + protected final Name nameFor( String name ) { + return nameFactory.create(name); + } + + protected final Value valueFor( String value, + int type ) throws ValueFormatException { + return session.getValueFactory().createValue(value, type); + // return new JcrValue(session.getExecutionContext().getValueFactories(), cache(), type, value); + } + + protected final SessionCache cache() { + return session.cache(); + } + + protected final Graph.Batch operations() { + return pendingOperations; + } + /** * {@inheritDoc} * @@ -124,6 +152,20 @@ /** * {@inheritDoc} * + * @see org.xml.sax.helpers.DefaultHandler#endDocument() + */ + @Override + public void endDocument() throws SAXException { + if (pendingOperations != null) { + pendingOperations.execute(); + } + + super.endDocument(); + } + + /** + * {@inheritDoc} + * * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String) */ @Override @@ -273,9 +315,10 @@ uuid = UUID.fromString(rawUuid.get(0).getString()); } - AbstractJcrNode newNode = parentNode.addNode(currentNodeName, currentProps.get(primaryTypeName) - .get(0) - .getString(), uuid); + String typeName = currentProps.get(primaryTypeName).get(0).getString(); + AbstractJcrNode newNode = cache().findJcrNode(parentNode.editorFor(operations()).createChild(nameFor(currentNodeName), + uuid, + nameFor(typeName)).getUuid()); for (Map.Entry> entry : currentProps.entrySet()) { if (entry.getKey().equals(primaryTypeName)) { @@ -284,7 +327,8 @@ if (entry.getKey().equals(mixinTypesName)) { for (Value value : entry.getValue()) { - newNode.addMixin(value.getString()); + JcrNodeType mixinType = session.workspace().nodeTypeManager().getNodeType(nameFor(value.getString())); + newNode.editorFor(operations()).addMixin(mixinType); } continue; } @@ -296,9 +340,11 @@ List values = entry.getValue(); if (values.size() == 1) { - newNode.setProperty(entry.getKey(), values.get(0)); + newNode.editorFor(operations()).setProperty(nameFor(entry.getKey()), (JcrValue)values.get(0)); } else { - newNode.setProperty(entry.getKey(), values.toArray(new Value[values.size()])); + newNode.editorFor(operations()).setProperty(nameFor(entry.getKey()), + values.toArray(new Value[values.size()]), + PropertyType.UNDEFINED); } } @@ -385,7 +431,7 @@ switch (uuidBehavior) { case ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING: parentNode = (AbstractJcrNode)existingNodeWithUuid.getParent(); - existingNodeWithUuid.remove(); + parentNode.editorFor(operations()).destroyChild(uuid); break; case ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW: uuid = UUID.randomUUID(); @@ -394,7 +440,8 @@ if (existingNodeWithUuid.path().isAtOrAbove(parentStack.firstElement().path())) { throw new ConstraintViolationException(); } - existingNodeWithUuid.remove(); + AbstractJcrNode temp = (AbstractJcrNode)existingNodeWithUuid.getParent(); + temp.editorFor(operations()).destroyChild(uuid); break; case ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW: throw new ItemExistsException(); @@ -403,7 +450,9 @@ } name = DOCUMENT_VIEW_NAME_DECODER.decode(name); - AbstractJcrNode currentNode = parentNode.addNode(name, primaryTypeName, uuid); + AbstractJcrNode currentNode = cache().findJcrNode(parentNode.editorFor(operations()).createChild(nameFor(name), + uuid, + nameFor(primaryTypeName)).getUuid()); for (int i = 0; i < atts.getLength(); i++) { if (JcrContentHandler.this.primaryTypeName.equals(atts.getQName(i))) { @@ -411,7 +460,8 @@ } if (mixinTypesName.equals(atts.getQName(i))) { - currentNode.addMixin(atts.getValue(i)); + JcrNodeType mixinType = session.workspace().nodeTypeManager().getNodeType(nameFor(atts.getValue(i))); + currentNode.editorFor(operations()).addMixin(mixinType); continue; } @@ -424,7 +474,8 @@ // String value = DOCUMENT_VIEW_NAME_DECODER.decode(atts.getValue(i)); String value = atts.getValue(i); String propertyName = DOCUMENT_VIEW_NAME_DECODER.decode(atts.getQName(i)); - currentNode.setProperty(propertyName, value); + currentNode.editorFor(operations()).setProperty(nameFor(propertyName), + (JcrValue)valueFor(value, PropertyType.STRING)); } parentStack.push(currentNode); @@ -451,12 +502,14 @@ int start, int length ) throws SAXException { try { - NamespaceRegistry namespaces = session.getExecutionContext().getNamespaceRegistry(); - Node parentNode = parentStack.peek(); - Node currentNode = parentNode.addNode(JcrLexicon.XMLTEXT.getString(namespaces), - JcrNtLexicon.UNSTRUCTURED.getString(namespaces)); + AbstractJcrNode parentNode = parentStack.peek(); + AbstractJcrNode currentNode = cache().findJcrNode(parentNode.editorFor(operations()).createChild(JcrLexicon.XMLTEXT, + null, + JcrNtLexicon.UNSTRUCTURED).getUuid()); + String s = new String(ch, start, length); - currentNode.setProperty(JcrLexicon.XMLCHARACTERS.getString(namespaces), s); + currentNode.editorFor(operations()).setProperty(JcrLexicon.XMLCHARACTERS, + (JcrValue)valueFor(s, PropertyType.STRING)); } catch (RepositoryException re) { throw new EnclosingSAXException(re); Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java (revision 844) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java (working copy) @@ -128,6 +128,10 @@ public static I18n autocreatedPropertyNeedsDefault; public static I18n singleValuedPropertyNeedsSingleValuedDefault; + public static I18n noDefinition; + public static I18n noSnsDefinition; + public static I18n missingMandatoryItem; + static { try { I18n.initialize(JcrI18n.class); Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrMultiValueProperty.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrMultiValueProperty.java (revision 844) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrMultiValueProperty.java (working copy) @@ -37,9 +37,7 @@ import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.version.VersionException; import net.jcip.annotations.NotThreadSafe; -import org.jboss.dna.common.i18n.I18n; import org.jboss.dna.graph.property.Property; -import org.jboss.dna.graph.property.ValueFactory; /** * @author jverhaeg @@ -191,88 +189,7 @@ return; } - Value[] newValues = null; - if (values.length != 0) { - int numValues = values.length; - int valueType = -1; - List valuesList = new ArrayList(numValues); - ValueFactory factory = null; - for (int i = 0; i != numValues; ++i) { - Value value = values[i]; - if (value == null) { - // skip null values ... - continue; - } - if (valueType == -1) { - valueType = value.getType(); - } else if (value.getType() != valueType) { - // Make sure the type of each value is the same, as per Javadoc in section 7.1.5 of the JCR 1.0.1 spec - StringBuilder sb = new StringBuilder(); - sb.append('['); - for (int j = 0; j != values.length; ++j) { - if (j != 0) sb.append(","); - sb.append(values[j].toString()); - } - sb.append(']'); - String propType = PropertyType.nameFromValue(valueType); - I18n msg = JcrI18n.allPropertyValuesMustHaveSameType; - throw new ValueFormatException(msg.text(getName(), values, propType, getPath(), cache.workspaceName())); - } - - if (value instanceof JcrValue) { - // just use the value ... - valuesList.add(value); - } else { - // This isn't our implementation, so create one for use - if (factory == null) { - int currentType = this.getType(); - factory = context().getValueFactories().getValueFactory(PropertyTypeUtil.dnaPropertyTypeFor(currentType)); - } - int type = value.getType(); - Object data = null; - switch (value.getType()) { - case PropertyType.STRING: - data = value.getString(); - break; - case PropertyType.BINARY: - data = value.getStream(); - break; - case PropertyType.BOOLEAN: - data = value.getBoolean(); - break; - case PropertyType.DATE: - data = value.getDate(); - break; - case PropertyType.DOUBLE: - data = value.getDouble(); - break; - case PropertyType.LONG: - data = value.getLong(); - break; - case PropertyType.NAME: - data = value.getString(); - break; - case PropertyType.PATH: - data = value.getString(); - break; - case PropertyType.REFERENCE: - data = value.getString(); - break; - default: - throw new RepositoryException(); - } - valuesList.add(createValue(factory.create(data), type)); - } - } - if (valuesList.isEmpty()) { - newValues = EMPTY_VALUES; - } else { - newValues = valuesList.toArray(new Value[valuesList.size()]); - } - } else { - newValues = EMPTY_VALUES; - } - cache.getEditorFor(propertyId.getNodeId()).setProperty(propertyId.getPropertyName(), newValues); + cache.getEditorFor(propertyId.getNodeId()).setProperty(propertyId.getPropertyName(), values, PropertyType.UNDEFINED); } /** @@ -305,7 +222,8 @@ } else { jcrValues = EMPTY_VALUES; } - cache.getEditorFor(propertyId.getNodeId()).setProperty(propertyId.getPropertyName(), jcrValues); + + cache.getEditorFor(propertyId.getNodeId()).setProperty(propertyId.getPropertyName(), jcrValues, PropertyType.STRING); } /** Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java (revision 844) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java (working copy) @@ -180,6 +180,10 @@ JcrWorkspace workspace() { return this.workspace; } + + Graph.Batch createBatch() { + return graph.batch(); + } /** * {@inheritDoc} Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java (revision 844) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java (working copy) @@ -32,8 +32,10 @@ import javax.jcr.ItemExistsException; import javax.jcr.NamespaceRegistry; import javax.jcr.PathNotFoundException; +import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; +import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Workspace; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; @@ -46,20 +48,26 @@ import org.jboss.dna.common.util.CheckArg; import org.jboss.dna.graph.ExecutionContext; import org.jboss.dna.graph.Graph; -import org.jboss.dna.graph.Location; import org.jboss.dna.graph.connector.RepositoryConnectionFactory; import org.jboss.dna.graph.connector.RepositorySource; import org.jboss.dna.graph.connector.RepositorySourceException; -import org.jboss.dna.graph.io.GraphImporter; import org.jboss.dna.graph.property.Name; import org.jboss.dna.graph.property.Path; import org.jboss.dna.graph.property.PathFactory; import org.jboss.dna.graph.property.Property; import org.jboss.dna.graph.property.PropertyFactory; -import org.jboss.dna.graph.property.ValueFormatException; import org.jboss.dna.graph.property.basic.GraphNamespaceRegistry; +import org.jboss.dna.jcr.JcrContentHandler.EnclosingSAXException; +import org.jboss.dna.jcr.JcrContentHandler.SaveMode; import org.jboss.dna.jcr.JcrRepository.Options; +import org.jboss.dna.jcr.SessionCache.NodeEditor; +import org.jboss.dna.jcr.cache.ChangedNodeInfo; import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; /** * @author John Verhaeg @@ -224,8 +232,8 @@ /** * {@inheritDoc} */ - public final ObservationManager getObservationManager() { - throw new UnsupportedOperationException(); + public final ObservationManager getObservationManager() throws UnsupportedRepositoryOperationException { + throw new UnsupportedRepositoryOperationException(); } /** @@ -292,17 +339,16 @@ * * @see javax.jcr.Workspace#getImportContentHandler(java.lang.String, int) */ - @SuppressWarnings( "unused" ) public ContentHandler getImportContentHandler( String parentAbsPath, int uuidBehavior ) throws PathNotFoundException, ConstraintViolationException, VersionException, LockException, AccessDeniedException, RepositoryException { - CheckArg.isNotEmpty(parentAbsPath, "parentAbsPath"); - // Create a graph importer, which can return the content handler that can be used by the caller - // to call the handler's event methods to create content... - GraphImporter importer = new GraphImporter(graph); - Path parentPath = context.getValueFactories().getPathFactory().create(parentAbsPath); - return importer.getHandlerForImportingXml(Location.create(parentPath), false); + + CheckArg.isNotNull(parentAbsPath, "parentAbsPath"); + + Path parentPath = this.context.getValueFactories().getPathFactory().create(parentAbsPath); + + return new JcrContentHandler(this.session, parentPath, uuidBehavior, SaveMode.WORKSPACE); } /** @@ -310,19 +356,33 @@ * * @see javax.jcr.Workspace#importXML(java.lang.String, java.io.InputStream, int) */ - @SuppressWarnings( "unused" ) public void importXML( String parentAbsPath, InputStream in, int uuidBehavior ) throws IOException, PathNotFoundException, ItemExistsException, ConstraintViolationException, InvalidSerializedDataException, LockException, AccessDeniedException, RepositoryException { - // try { - // graph.importXmlFrom(in).into(parentAbsPath); - // } catch (org.jboss.dna.graph.property.PathNotFoundException e) { - // throw new PathNotFoundException(e.getMessage(), e); - // } catch (SAXException err) { - // } - throw new UnsupportedOperationException(); + + CheckArg.isNotNull(parentAbsPath, "parentAbsPath"); + CheckArg.isNotNull(in, "in"); + + try { + XMLReader parser = XMLReaderFactory.createXMLReader(); + parser.setContentHandler(getImportContentHandler(parentAbsPath, uuidBehavior)); + parser.parse(new InputSource(in)); + } catch (EnclosingSAXException ese) { + Exception cause = ese.getException(); + if (cause instanceof ItemExistsException) { + throw (ItemExistsException) cause; + } else if (cause instanceof ConstraintViolationException) { + throw (ConstraintViolationException) cause; + } + throw new RepositoryException(cause); + } catch (SAXParseException se) { + throw new InvalidSerializedDataException(se); + } catch (SAXException se) { + throw new RepositoryException(se); + } + } /** Index: dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java (revision 844) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java (working copy) @@ -653,7 +858,9 @@ /** * Obtain an {@link NodeEditor editor} that can be used to manipulate the properties or children on the node identified by the * supplied UUID. The node must exist prior to this call, either as a node that exists in the workspace or as a node that was - * created within this session but not yet persited to the workspace. + * created within this session but not yet persisted to the workspace. This method returns an editor that batches all changes + * in transient storage from where they can be persisted to the repository by {@link javax.jcr.Session#save() saving the + * session} or by {@link javax.jcr.Item#save() saving an ancestor}. * * @param uuid the UUID of the node that is to be changed; may not be null and must represent an existing node * @return the editor; never null @@ -662,6 +869,26 @@ * @throws RepositoryException if any other error occurs while reading information from the repository */ public NodeEditor getEditorFor( UUID uuid ) throws ItemNotFoundException, InvalidItemStateException, RepositoryException { + return getEditorFor(uuid, this.operations); + } + + /** + * Obtain an {@link NodeEditor editor} that can be used to manipulate the properties or children on the node identified by the + * supplied UUID. The node must exist prior to this call, either as a node that exists in the workspace or as a node that was + * created within this session but not yet persisted to the workspace. + * + * @param uuid the UUID of the node that is to be changed; may not be null and must represent an existing node + * @param operationsBatch the {@link Graph.Batch} to use for batching operations. This should be populated for direct + * persistence (q.v. section 7.1.3.7 of the JCR 1.0.1 specification) and should be null to use session-based + * persistence. + * @return the editor; never null + * @throws ItemNotFoundException if no such node could be found in the session or workspace + * @throws InvalidItemStateException if the item has been marked for deletion within this session + * @throws RepositoryException if any other error occurs while reading information from the repository + */ + public NodeEditor getEditorFor( UUID uuid, + Graph.Batch operationsBatch ) + throws ItemNotFoundException, InvalidItemStateException, RepositoryException { // See if we already have something in the changed nodes ... ChangedNodeInfo info = changedNodes.get(uuid); Location currentLocation = null; @@ -679,7 +906,7 @@ // compute the current location ... currentLocation = Location.create(getPathFor(info), uuid); } - return new NodeEditor(info, currentLocation); + return new NodeEditor(info, currentLocation, operationsBatch == null ? this.operations : operationsBatch); } /** @@ -688,14 +915,49 @@ public final class NodeEditor { private final ChangedNodeInfo node; private final Location currentLocation; + private final Graph.Batch operations; protected NodeEditor( ChangedNodeInfo node, - Location currentLocation ) { + Location currentLocation, + Graph.Batch operations ) { this.node = node; this.currentLocation = currentLocation; + this.operations = operations; } /** + * Checks whether there is an existing property with this name that does not match the given cardinality. If such a + * property exists, a {@code javax.jcr.ValueFormatException} is thrown, as per section 7.1.5 of the JCR 1.0.1 + * specification. + * + * @param propertyName the name of the property + * @param isMultiple whether the property must have multiple values + * @throws javax.jcr.ValueFormatException if the property exists but has the opposite cardinality + * @throws RepositoryException if any other error occurs + */ + private void checkCardinalityOfExistingProperty( Name propertyName, + boolean isMultiple ) + throws javax.jcr.ValueFormatException, RepositoryException { + // Check for existing single-valued property - can't set multiple values on single-valued property + PropertyInfo propInfo = this.node.getProperty(propertyName); + if (propInfo != null && propInfo.isMultiValued() != isMultiple) { + NamespaceRegistry namespaces = SessionCache.this.namespaces; + String workspaceName = SessionCache.this.workspaceName(); + if (isMultiple) { + I18n msg = JcrI18n.unableToSetSingleValuedPropertyUsingMultipleValues; + throw new javax.jcr.ValueFormatException(msg.text(propertyName.getString(namespaces), + getPathFor(this.node).getString(namespaces), + workspaceName)); + } + I18n msg = JcrI18n.unableToSetMultiValuedPropertyUsingSingleValue; + throw new javax.jcr.ValueFormatException(msg.text(getPathFor(this.node).getString(namespaces), + propertyName, + workspaceName)); + } + + } + + /** * Set the value 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 the supplied value. * @@ -704,20 +966,23 @@ * @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 + * @throws RepositoryException if any other error occurs */ public PropertyId setProperty( Name name, - JcrValue value ) throws ConstraintViolationException { + JcrValue value ) throws ConstraintViolationException, RepositoryException { return setProperty(name, value, true); } public PropertyId setProperty( Name name, JcrValue value, - boolean skipProtected ) throws ConstraintViolationException { + boolean skipProtected ) throws ConstraintViolationException, RepositoryException { assert name != null; assert value != null; JcrPropertyDefinition definition = null; PropertyId id = null; + checkCardinalityOfExistingProperty(name, false); + // Look for an existing property ... PropertyInfo existing = node.getProperty(name); if (existing != null) { @@ -789,13 +1054,18 @@ * @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 valueType * @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 + * @throws javax.jcr.ValueFormatException + * @throws RepositoryException */ public PropertyId setProperty( Name name, - Value[] values ) throws ConstraintViolationException { - return setProperty(name, values, true); + Value[] values, + int valueType ) + throws ConstraintViolationException, RepositoryException, javax.jcr.ValueFormatException { + return setProperty(name, values, valueType, true); } /** @@ -807,16 +1077,66 @@ * 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. + * @param valueType * @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 + * @throws javax.jcr.ValueFormatException + * @throws RepositoryException */ public PropertyId setProperty( Name name, Value[] values, - boolean skipProtected ) throws ConstraintViolationException { + int valueType, + boolean skipProtected ) + throws ConstraintViolationException, RepositoryException, javax.jcr.ValueFormatException { assert name != null; assert values != null; - int numValues = values.length; + + // TODO: Re-add following line! + // checkCardinalityOfExistingProperty(name, true); + + int len = values.length; + Value[] newValues = null; + if (len == 0) { + newValues = JcrMultiValueProperty.EMPTY_VALUES; + } else { + List valuesWithDesiredType = new ArrayList(len); + int expectedType = -1; + for (int i = 0; i != len; ++i) { + Value value = values[i]; + + if (value == null) continue; + if (expectedType == -1) { + expectedType = value.getType(); + } else if (value.getType() != expectedType) { + // Make sure the type of each value is the same, as per Javadoc in section 7.1.5 of the JCR 1.0.1 spec + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (int j = 0; j != values.length; ++j) { + if (j != 0) sb.append(","); + sb.append(values[j].toString()); + } + sb.append(']'); + String propType = PropertyType.nameFromValue(expectedType); + I18n msg = JcrI18n.allPropertyValuesMustHaveSameType; + NamespaceRegistry namespaces = SessionCache.this.namespaces; + String path = getPathFor(node.getUuid()).getString(namespaces); + String workspaceName = SessionCache.this.workspaceName(); + throw new javax.jcr.ValueFormatException(msg.text(name, values, propType, path, workspaceName)); + } + if (value.getType() != valueType && valueType != PropertyType.UNDEFINED) { + value = ((JcrValue)value).asType(valueType); + } + valuesWithDesiredType.add(value); + } + if (valuesWithDesiredType.isEmpty()) { + newValues = JcrMultiValueProperty.EMPTY_VALUES; + } else { + newValues = valuesWithDesiredType.toArray(new Value[valuesWithDesiredType.size()]); + } + } + + int numValues = newValues.length; JcrPropertyDefinition definition = null; PropertyId id = null; @@ -836,14 +1156,14 @@ // Just use the definition as is ... } else { // Use the property type for the first non-null value ... - int type = values[0].getType(); + int type = newValues[0].getType(); if (definition.getRequiredType() != PropertyType.UNDEFINED && definition.getRequiredType() != type) { // The property type is not right, so we have to check if we can cast. // It's easier and could save more work if we just find a new property definition that works ... definition = null; } else { // The types match, so see if the value satisfies the constraints ... - if (!definition.satisfiesConstraints(values)) definition = null; + if (!definition.satisfiesConstraints(newValues)) definition = null; } } } @@ -856,21 +1176,21 @@ definition = nodeTypes().findPropertyDefinition(node.getPrimaryTypeName(), node.getMixinTypeNames(), name, - values, + newValues, skipProtected); if (definition == null) { throw new ConstraintViolationException(); } } // Create the DNA property ... - int type = values.length != 0 ? values[0].getType() : definition.getRequiredType(); - Object[] objValues = new Object[values.length]; + int type = newValues.length != 0 ? newValues[0].getType() : definition.getRequiredType(); + Object[] objValues = new Object[newValues.length]; int propertyType = definition.getRequiredType(); if (propertyType == PropertyType.UNDEFINED || propertyType == type) { // Can use the values as is ... propertyType = type; for (int i = 0; i != numValues; ++i) { - objValues[i] = ((JcrValue)values[i]).value(); + objValues[i] = ((JcrValue)newValues[i]).value(); } } else { // A cast is required ... @@ -878,7 +1198,7 @@ org.jboss.dna.graph.property.PropertyType dnaPropertyType = PropertyTypeUtil.dnaPropertyTypeFor(propertyType); ValueFactory factory = factories().getValueFactory(dnaPropertyType); for (int i = 0; i != numValues; ++i) { - objValues[i] = factory.create(((JcrValue)values[i]).value()); + objValues[i] = factory.create(((JcrValue)newValues[i]).value()); } } Property dnaProp = propertyFactory.create(name, objValues); @@ -923,6 +1243,7 @@ public ChildNode moveToBeChild( UUID nodeUuid, Name newNodeName ) throws ItemNotFoundException, InvalidItemStateException, ConstraintViolationException, RepositoryException { + //UUID nodeUuid = child.nodeUuid; if (nodeUuid.equals(node.getUuid()) || isAncestor(nodeUuid)) { Path pathOfNode = getPathFor(nodeUuid); Path thisPath = currentLocation.getPath(); @@ -931,28 +1252,14 @@ } // Is the node already a child? - ChildNode child = node.getChildren().getChild(nodeUuid); - if (child != null) return child; + ChildNode existingChild = node.getChildren().getChild(nodeUuid); + if (existingChild != null) return existingChild; + JcrNodeDefinition definition = findBestNodeDefinition(node.getUuid(), newNodeName, null); + // Get an editor for the child (in its current location) and one for its parent ... - NodeEditor existingNodeEditor = getEditorFor(nodeUuid); - ChangedNodeInfo existingNodeInfo = existingNodeEditor.node; - UUID existingParent = existingNodeInfo.getParent(); - NodeEditor existingParentEditor = getEditorFor(existingParent); - ChangedNodeInfo existingParentInfo = existingParentEditor.node; + NodeEditor newChildEditor = getEditorFor(nodeUuid); - // Verify that this node's definition allows the specified child ... - Name childName = existingParentInfo.getChildren().getChild(nodeUuid).getName(); - int numSns = node.getChildren().getCountOfSameNameSiblingsWithName(childName); - JcrNodeDefinition definition = nodeTypes().findChildNodeDefinition(node.getPrimaryTypeName(), - node.getMixinTypeNames(), - childName, - childName, - numSns, - true); - if (definition == null) { - throw new ConstraintViolationException(); - } if (!definition.getId().equals(node.getDefinitionId())) { // The node definition changed, so try to set the property ... try { @@ -967,27 +1274,85 @@ node.setDefinitionId(definition.getId()); // And remove the property from the info ... - existingNodeEditor.removeProperty(DnaLexicon.NODE_DEFINITON); + newChildEditor.removeProperty(DnaLexicon.NODE_DEFINITON); } } // Remove the node from the current parent and add it to this ... - child = existingParentInfo.removeChild(nodeUuid, pathFactory); - ChildNode newChild = node.addChild(newNodeName, child.getUuid(), pathFactory); + ChangedNodeInfo newChildInfo = newChildEditor.node; + UUID existingParent = newChildInfo.getParent(); + ChangedNodeInfo existingParentInfo = getEditorFor(existingParent).node; + existingChild = existingParentInfo.removeChild(nodeUuid, pathFactory); + ChildNode newChild = node.addChild(newNodeName, existingChild.getUuid(), pathFactory); + // Set the child's changed representation to point to this node as its parent ... - existingNodeInfo.setParent(node.getUuid()); + newChildInfo.setParent(node.getUuid()); // Set up the peer relationship between the two nodes that must be saved together node.addPeer(existingParent); existingParentInfo.addPeer(node.getUuid()); + // Set up the peer relationship between the two nodes that must be saved together + node.addPeer(existingParent); + existingParentInfo.addPeer(node.getUuid()); + // Now, record the operation to do this ... - operations.move(existingNodeEditor.currentLocation).into(currentLocation); + operations.move(newChildEditor.currentLocation).into(currentLocation); return newChild; } + public void addMixin( JcrNodeType mixinCandidateType ) throws javax.jcr.ValueFormatException, RepositoryException { + PropertyInfo existingMixinProperty = node.getProperty(JcrLexicon.MIXIN_TYPES); + + // getProperty(JcrLexicon.MIXIN_TYPES); + Value[] existingMixinValues; + if (existingMixinProperty != null) { + existingMixinValues = findJcrProperty(existingMixinProperty.getPropertyId()).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] = new JcrValue(factories(), SessionCache.this, PropertyType.NAME, + mixinCandidateType.getInternalName()); + + findJcrProperty(setProperty(JcrLexicon.MIXIN_TYPES, newMixinValues, PropertyType.NAME, false)); + + // ------------------------------------------------------------------------------ + // Create any auto-created properties/nodes from new type + // ------------------------------------------------------------------------------ + + for (JcrPropertyDefinition propertyDefinition : mixinCandidateType.propertyDefinitions()) { + if (propertyDefinition.isAutoCreated() && !propertyDefinition.isProtected()) { + if (null == findJcrProperty(new PropertyId(node.getUuid(), propertyDefinition.getInternalName()))) { + assert propertyDefinition.getDefaultValues() != null; + if (propertyDefinition.isMultiple()) { + setProperty(propertyDefinition.getInternalName(), + propertyDefinition.getDefaultValues(), + propertyDefinition.getRequiredType()); + } else { + assert propertyDefinition.getDefaultValues().length == 1; + setProperty(propertyDefinition.getInternalName(), (JcrValue)propertyDefinition.getDefaultValues()[0]); + } + } + } + } + + for (JcrNodeDefinition nodeDefinition : mixinCandidateType.childNodeDefinitions()) { + if (nodeDefinition.isAutoCreated() && !nodeDefinition.isProtected()) { + Name nodeName = nodeDefinition.getInternalName(); + if (!node.getChildren().getChildren(nodeName).hasNext()) { + assert nodeDefinition.getDefaultPrimaryType() != null; + createChild(nodeName, (UUID)null, ((JcrNodeType)nodeDefinition.getDefaultPrimaryType()).getInternalName()); + } + } + } + + } + /** * Create a new node as a child of this node, using the supplied name and (optionally) the supplied UUID. * Index: dna-jcr/src/test/java/org/jboss/dna/jcr/JcrWorkspaceTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/JcrWorkspaceTest.java (revision 844) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/JcrWorkspaceTest.java (working copy) @@ -27,10 +27,12 @@ import static org.hamcrest.core.IsNull.notNullValue; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.stub; +import java.io.ByteArrayInputStream; import java.util.HashMap; import java.util.Map; import javax.jcr.NamespaceRegistry; import javax.jcr.Node; +import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.query.Query; import javax.jcr.query.QueryManager; import org.jboss.dna.graph.ExecutionContext; @@ -166,7 +168,7 @@ assertThat(workspace.getNodeTypeManager(), is(notNullValue())); } - @Test( expected = UnsupportedOperationException.class ) + @Test( expected = UnsupportedRepositoryOperationException.class ) public void shouldNotAllowGetObservationManager() throws Exception { workspace.getObservationManager(); } @@ -224,9 +225,14 @@ assertThat(workspace.getSession(), is(notNullValue())); } - @Test( expected = UnsupportedOperationException.class ) - public void shouldNotAllowImportXml() throws Exception { - workspace.importXML(null, null, 0); + @Test + public void shouldAllowImportXml() throws Exception { + String inputData = "" + + "" + + "" + + "nt:unstructured"; + workspace.importXML("/", new ByteArrayInputStream(inputData.getBytes()), 0); } @Test( expected = IllegalArgumentException.class )