Index: dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java (revision 839) +++ 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 +740,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 +859,7 @@ } } - cache.findJcrProperty(editor().setProperty(JcrLexicon.MIXIN_TYPES, newMixinValues, false)); + cache.findJcrProperty(editor().setProperty(JcrLexicon.MIXIN_TYPES, newMixinValues, PropertyType.NAME, false)); } @@ -1015,31 +983,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())); } /** @@ -1050,10 +1003,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))); } /** @@ -1064,15 +1014,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))); } /** @@ -1083,10 +1030,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))); } /** @@ -1098,12 +1042,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))); } /** @@ -1114,9 +1056,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))); } /** @@ -1128,13 +1068,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))); } /** @@ -1146,12 +1083,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))); } /** @@ -1164,12 +1099,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))); } /** @@ -1181,12 +1114,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)); } /** @@ -1199,12 +1130,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)); } /** @@ -1216,24 +1145,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)); } /** @@ -1246,12 +1161,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))); } /** @@ -1266,44 +1179,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); } /** @@ -1320,48 +1197,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 839) +++ 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.currentNode = (AbstractJcrNode)session.getNode(parentPath); + 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); } } @@ -384,7 +430,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(); @@ -393,7 +439,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(); @@ -402,7 +449,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))) { @@ -410,7 +459,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; } @@ -423,7 +473,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); @@ -450,12 +501,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/JcrMultiValueProperty.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrMultiValueProperty.java (revision 839) +++ 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 839) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java (working copy) @@ -62,6 +62,7 @@ import org.jboss.dna.graph.property.Name; import org.jboss.dna.graph.property.NamespaceRegistry; import org.jboss.dna.graph.property.Path; +import org.jboss.dna.graph.property.PathFactory; import org.jboss.dna.graph.property.ValueFactories; import org.jboss.dna.graph.property.basic.LocalNamespaceRegistry; import org.jboss.dna.jcr.JcrContentHandler.EnclosingSAXException; @@ -179,6 +180,10 @@ JcrWorkspace workspace() { return this.workspace; } + + Graph.Batch createBatch() { + return graph.batch(); + } /** * {@inheritDoc} @@ -420,7 +425,7 @@ * @throws PathNotFoundException if the path could not be found * @throws RepositoryException if there is a problem */ - Node getNode( Path path ) throws RepositoryException, PathNotFoundException { + AbstractJcrNode getNode( Path path ) throws RepositoryException, PathNotFoundException { if (path.isRoot()) return cache.findJcrRootNode(); return cache.findJcrNode(null, path.relativeTo(rootPath)); } Index: dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java (revision 839) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java (working copy) @@ -640,7 +654,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 @@ -649,6 +665,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; @@ -666,7 +702,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); } /** @@ -675,14 +711,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. * @@ -691,20 +762,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) { @@ -776,13 +850,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); } /** @@ -794,16 +873,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; @@ -823,14 +952,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; } } } @@ -843,21 +972,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 ... @@ -865,7 +994,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); @@ -959,17 +1090,71 @@ // Remove the node from the current parent and add it to this ... child = existingParentInfo.removeChild(nodeUuid, pathFactory); - ChildNode newChild = node.addChild(child.getName(), child.getUuid(), pathFactory); + ChildNode newChild = node.addChild(newNodeName, child.getUuid(), pathFactory); // Set the child's changed representation to point to this node as its parent ... existingNodeInfo.setParent(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); 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. * @@ -1108,10 +1293,7 @@ // --------------------------------------- // Now record the changes to the store ... // --------------------------------------- - Graph.Create create = operations.createUnder(currentLocation) - .nodeNamed(name) - .with(desiredUuid) - .with(primaryTypeProp); + Graph.Create create = operations.createUnder(currentLocation).nodeNamed(name).with(desiredUuid).with(primaryTypeProp); if (nodeDefnDefn != null) { create = create.with(nodeDefinitionProp); } Index: dna-jcr/src/test/java/org/jboss/dna/jcr/JcrWorkspaceTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/JcrWorkspaceTest.java (revision 839) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/JcrWorkspaceTest.java (working copy) @@ -27,6 +27,7 @@ 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; @@ -176,7 +177,6 @@ assertThat(workspace.getQueryManager(), notNullValue()); } - @Test public void shouldCreateQuery() throws Exception { String statement = "Some query syntax"; @@ -224,9 +224,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 )