You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by ju...@apache.org on 2013/07/01 14:07:55 UTC

svn commit: r1498401 - in /jackrabbit/oak/trunk: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/

Author: jukka
Date: Mon Jul  1 12:07:54 2013
New Revision: 1498401

URL: http://svn.apache.org/r1498401
Log:
OAK-702: Optimize access to node type information

Optimize property definition access in setProperty()

Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/PropertyStates.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ItemImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/NodeImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/PropertyImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/PropertyStates.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/PropertyStates.java?rev=1498401&r1=1498400&r2=1498401&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/PropertyStates.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/PropertyStates.java Mon Jul  1 12:07:54 2013
@@ -289,4 +289,14 @@ public final class PropertyStates {
         }
     }
 
+    public static PropertyState convert(PropertyState state, Type<?> type) {
+        if (type == state.getType()
+                || (type == Type.UNDEFINED && !state.isArray())
+                || (type == Type.UNDEFINEDS && state.isArray())) {
+            return state;
+        } else {
+            return createProperty(state.getName(), state.getValue(type), type);
+        }
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ItemImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ItemImpl.java?rev=1498401&r1=1498400&r2=1498401&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ItemImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/ItemImpl.java Mon Jul  1 12:07:54 2013
@@ -16,7 +16,17 @@
  */
 package org.apache.jackrabbit.oak.jcr;
 
-import java.util.Arrays;
+import static com.google.common.collect.Lists.newArrayListWithCapacity;
+import static org.apache.jackrabbit.oak.api.Type.NAME;
+import static org.apache.jackrabbit.oak.api.Type.NAMES;
+import static org.apache.jackrabbit.oak.api.Type.PATH;
+import static org.apache.jackrabbit.oak.api.Type.PATHS;
+import static org.apache.jackrabbit.oak.api.Type.STRING;
+import static org.apache.jackrabbit.oak.api.Type.UNDEFINED;
+import static org.apache.jackrabbit.oak.api.Type.UNDEFINEDS;
+import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty;
+
+import java.util.List;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
@@ -25,16 +35,13 @@ import javax.jcr.Item;
 import javax.jcr.ItemNotFoundException;
 import javax.jcr.Node;
 import javax.jcr.PathNotFoundException;
-import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Value;
 import javax.jcr.ValueFactory;
-import javax.jcr.ValueFormatException;
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.nodetype.ItemDefinition;
 import javax.jcr.nodetype.NodeTypeManager;
-import javax.jcr.nodetype.PropertyDefinition;
 import javax.jcr.version.VersionManager;
 
 import org.apache.jackrabbit.oak.api.PropertyState;
@@ -45,10 +52,8 @@ import org.apache.jackrabbit.oak.jcr.del
 import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate;
 import org.apache.jackrabbit.oak.jcr.delegate.SessionOperation;
 import org.apache.jackrabbit.oak.plugins.memory.MemoryPropertyBuilder;
-import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
 import org.apache.jackrabbit.oak.plugins.nodetype.DefinitionProvider;
 import org.apache.jackrabbit.oak.plugins.nodetype.EffectiveNodeTypeProvider;
-import org.apache.jackrabbit.value.ValueHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -336,110 +341,57 @@ abstract class ItemImpl<T extends ItemDe
         return sessionContext.getVersionManager();
     }
 
-    /**
-     * Determine property type from property definition and default to
-     * {@code defaultType} in the case of {@link javax.jcr.PropertyType#UNDEFINED}.
-     *
-     * @param definition
-     * @param defaultType
-     * @return the type determined from the property definition
-     */
-    private static int getType(PropertyDefinition definition, int defaultType) {
-        if (definition.getRequiredType() == PropertyType.UNDEFINED) {
-            return defaultType == PropertyType.UNDEFINED ? PropertyType.STRING : defaultType;
-        } else {
-            return definition.getRequiredType();
-        }
-    }
-
-    /**
-     * Removes all {@code null} values from the given array.
-     *
-     * @param values value array
-     * @return value array without {@code null} entries
-     */
-    private static Value[] compact(Value[] values) {
-        int n = 0;
-        for (Value value : values) {
-            if (value != null) {
-                n++;
-            }
+    protected PropertyState createSingleState(
+            String oakName, Value value, Type<?> type)
+            throws RepositoryException {
+        if (type == UNDEFINED) {
+            type = Type.fromTag(value.getType(), false);
         }
-        if (n == values.length) {
-            return values;
+        if (type == NAME || type == PATH) {
+            return createProperty(oakName, getOakValue(value, type), type);
         } else {
-            Value[] nonNullValues = new Value[n];
-            for (int i = 0, j = 0; i < values.length; i++) {
-                if (values[i] != null) {
-                    nonNullValues[j++] = values[i];
-                }
-            }
-            return nonNullValues;
+            return createProperty(oakName, value);
         }
     }
 
-    /**
-     * Create a single value property state given a {@code name}, a {@code value} and a
-     * property {@code definition}. If the value does not match the property definition
-     * it is converted accordingly
-     *
-     * @param name
-     * @param value
-     * @param definition
-     * @return  single valued property state
-     * @throws ValueFormatException  if {@code definition} defines a multi valued property
-     * @throws RepositoryException  if value conversion fails
-     */
-    PropertyState createSingleState(String name, Value value, PropertyDefinition definition)
+    protected PropertyState createMultiState(
+            String oakName, List<Value> values, Type<?> type)
             throws RepositoryException {
-        if (definition.isMultiple()) {
-            throw new ValueFormatException("Cannot set single value to multivalued property");
+        if (values.isEmpty()) {
+            Type<?> base = type.getBaseType();
+            if (base == UNDEFINED) {
+                base = STRING;
+            }
+            return MemoryPropertyBuilder.array(base)
+                    .setName(oakName).getPropertyState();
         }
-
-        int targetType = getType(definition, value.getType());
-        if (targetType == value.getType()) {
-            return PropertyStates.createProperty(name, value);
+        if (type == UNDEFINEDS) {
+            type = Type.fromTag(values.get(0).getType(), true);
         }
-        else {
-            return PropertyStates.createProperty(name, ValueHelper.convert(
-                    value, targetType, getValueFactory()));
+        if (type == NAMES || type == PATHS) {
+            Type<?> base = type.getBaseType();
+            List<String> strings = newArrayListWithCapacity(values.size());
+            for (Value value : values) {
+                strings.add(getOakValue(value, base));
+            }
+            return createProperty(oakName, strings, type);
+        } else {
+            return createProperty(oakName, values, type.tag());
         }
     }
 
-    /**
-     * Create a single value property state given a {@code name}, a {@code type}, a
-     * {@code value} and a property {@code definition}. If the type does not match the
-     * property definition it is converted accordingly
-     *
-     * @param name
-     * @param type
-     * @param values
-     * @param definition
-     * @return  array valued property state
-     * @throws ValueFormatException  if {@code definition} does not define a multi valued property
-     * @throws RepositoryException  if value conversion fails
-     */
-    PropertyState createMultiState(String name, int type, Value[] values, PropertyDefinition definition)
+    private String getOakValue(Value value, Type<?> type)
             throws RepositoryException {
-        if (!definition.isMultiple()) {
-            throw new ValueFormatException("Cannot set value array to single value property");
-        }
-
-        Value[] nonNullValues = compact(values);
-        int targetType = getType(definition, type);
-        if (nonNullValues.length == 0) {
-            if (targetType == PropertyType.UNDEFINED) {
-                // default to string when no other type hints are available
-                targetType = PropertyType.STRING;
+        if (type == NAME) {
+            return getOakName(value.getString());
+        } else if (type == PATH) {
+            String path = value.getString();
+            if (!path.startsWith("[")) { // leave identifiers unmodified
+                path = getOakPathOrThrow(path);
             }
-            return MemoryPropertyBuilder
-                    .array(Type.fromTag(targetType, false), name)
-                    .getPropertyState();
-        } else if (targetType == type) {
-            return PropertyStates.createProperty(name, Arrays.asList(nonNullValues));
+            return path;
         } else {
-            return PropertyStates.createProperty(name, Arrays.asList(ValueHelper.convert(
-                    values, targetType, getValueFactory())));
+            throw new IllegalArgumentException();
         }
     }
 

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/NodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/NodeImpl.java?rev=1498401&r1=1498400&r2=1498401&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/NodeImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/NodeImpl.java Mon Jul  1 12:07:54 2013
@@ -20,6 +20,7 @@ import java.io.InputStream;
 import java.math.BigDecimal;
 import java.util.Calendar;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Set;
 import javax.annotation.Nonnull;
 import javax.jcr.AccessDeniedException;
@@ -46,7 +47,6 @@ import javax.jcr.nodetype.NoSuchNodeType
 import javax.jcr.nodetype.NodeDefinition;
 import javax.jcr.nodetype.NodeType;
 import javax.jcr.nodetype.NodeTypeManager;
-import javax.jcr.nodetype.PropertyDefinition;
 import javax.jcr.version.Version;
 import javax.jcr.version.VersionException;
 import javax.jcr.version.VersionHistory;
@@ -56,6 +56,8 @@ import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
+import com.google.common.collect.Lists;
+
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.commons.ItemNameMatcher;
 import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter;
@@ -83,7 +85,6 @@ import static com.google.common.base.Pre
 import static java.util.Collections.singleton;
 import static javax.jcr.Property.JCR_LOCK_IS_DEEP;
 import static javax.jcr.Property.JCR_LOCK_OWNER;
-import static javax.jcr.PropertyType.UNDEFINED;
 import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
 import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
 import static org.apache.jackrabbit.oak.api.Type.BOOLEAN;
@@ -313,10 +314,11 @@ public class NodeImpl<T extends NodeDele
         if (value != null) {
             boolean exactTypeMatch = true;
             if (type == PropertyType.UNDEFINED) {
-                type = PropertyType.STRING;
+                type = value.getType();
                 exactTypeMatch = false;
+            } else {
+                value = ValueHelper.convert(value, type, getValueFactory());
             }
-            value = ValueHelper.convert(value, type, getValueFactory());
             return internalSetProperty(name, value, exactTypeMatch);
         } else {
             return internalRemoveProperty(name);
@@ -327,6 +329,7 @@ public class NodeImpl<T extends NodeDele
     public Property setProperty(String name, Value[] values)
             throws RepositoryException {
         if (values != null) {
+            // TODO: type
             return internalSetProperty(name, values, ValueHelper.getType(values), false);
         } else {
             return internalRemoveProperty(name);
@@ -336,11 +339,14 @@ public class NodeImpl<T extends NodeDele
     @Override @Nonnull
     public Property setProperty(String jcrName, Value[] values, int type)
             throws RepositoryException {
-        if (type == UNDEFINED) {
-            return setProperty(jcrName, values);
-        } else if (values != null) {
+        if (values != null) {
+            boolean exactTypeMatch = true;
+            if (type == PropertyType.UNDEFINED) {
+                type = PropertyType.STRING;
+                exactTypeMatch = false;
+            }
             values = ValueHelper.convert(values, type, getValueFactory());
-            return internalSetProperty(jcrName, values, type, true);
+            return internalSetProperty(jcrName, values, type, exactTypeMatch);
         } else {
             return internalRemoveProperty(jcrName);
         }
@@ -350,9 +356,9 @@ public class NodeImpl<T extends NodeDele
     public Property setProperty(String name, String[] values)
             throws RepositoryException {
         if (values != null) {
-            Value[] vs = ValueHelper.convert(
-                    values, PropertyType.STRING, getValueFactory());
-            return internalSetProperty(name, vs, UNDEFINED, false);
+            int type = PropertyType.STRING;
+            Value[] vs = ValueHelper.convert(values, type, getValueFactory());
+            return internalSetProperty(name, vs, type, false);
         } else {
             return internalRemoveProperty(name);
         }
@@ -361,11 +367,14 @@ public class NodeImpl<T extends NodeDele
     @Override @Nonnull
     public Property setProperty(String name, String[] values, int type)
             throws RepositoryException {
-        if (type == UNDEFINED) {
-            return setProperty(name, values);
-        } else if (values != null) {
+        if (values != null) {
+            boolean exactTypeMatch = true;
+            if (type == PropertyType.UNDEFINED) {
+                type = PropertyType.STRING;
+                exactTypeMatch = false;
+            }
             Value[] vs = ValueHelper.convert(values, type, getValueFactory());
-            return internalSetProperty(name, vs, type, true);
+            return internalSetProperty(name, vs, type, exactTypeMatch);
         } else {
             return internalRemoveProperty(name);
         }
@@ -1332,10 +1341,9 @@ public class NodeImpl<T extends NodeDele
         }
         // TODO: END
 
-        String jcrPrimaryType = getOakPathOrThrow(Property.JCR_PRIMARY_TYPE);
-        Value value = getValueFactory().createValue(nodeTypeName, PropertyType.NAME);
-
-        dlg.setProperty(PropertyStates.createProperty(jcrPrimaryType, value));
+        PropertyState state = PropertyStates.createProperty(
+                JCR_PRIMARYTYPE, getOakName(nodeTypeName), NAME);
+        dlg.setProperty(state, true, true);
         dlg.setOrderableChildren(nt.hasOrderableChildNodes());
     }
 
@@ -1343,25 +1351,22 @@ public class NodeImpl<T extends NodeDele
             String jcrName, final Value value, final boolean exactTypeMatch)
             throws RepositoryException {
         final String oakName = getOakPathOrThrow(checkNotNull(jcrName));
-        checkNotNull(value);
+        final PropertyState state = createSingleState(
+                oakName, value, Type.fromTag(value.getType(), false));
         return perform(new ItemWriteOperation<Property>() {
             @Override
             protected void checkPreconditions() throws RepositoryException {
                 super.checkPreconditions();
                 if (!isCheckedOut()) {
-                    throw new VersionException("Cannot set property. Node is checked in.");
+                    throw new VersionException(
+                            "Cannot set property. Node is checked in.");
                 }
             }
-
             @Override
             public Property perform() throws RepositoryException {
-                // TODO: Avoid extra JCR method calls (OAK-672)
-                PropertyDefinition definition = getEffectiveNodeType()
-                        .getPropertyDefinition(oakName, false, value.getType(), exactTypeMatch);
-
-                checkProtected(definition);
-                PropertyState state = createSingleState(oakName, value, definition);
-                return new PropertyImpl(dlg.setProperty(state), sessionContext);
+                return new PropertyImpl(
+                        dlg.setProperty(state, exactTypeMatch, false),
+                        sessionContext);
             }
         });
     }
@@ -1371,21 +1376,43 @@ public class NodeImpl<T extends NodeDele
             final int type, final boolean exactTypeMatch)
             throws RepositoryException {
         final String oakName = getOakPathOrThrow(checkNotNull(jcrName));
-        checkNotNull(values);
+        final PropertyState state = createMultiState(
+                oakName, compact(values), Type.fromTag(type, true));
         return perform(new ItemWriteOperation<Property>() {
             @Override
+            protected void checkPreconditions() throws RepositoryException {
+                super.checkPreconditions();
+                if (!isCheckedOut()) {
+                    throw new VersionException(
+                            "Cannot set property. Node is checked in.");
+                }
+            }
+            @Override
             public Property perform() throws RepositoryException {
-                // TODO: Avoid extra JCR method calls (OAK-672)
-                PropertyDefinition definition = getEffectiveNodeType()
-                        .getPropertyDefinition(oakName, true, type, exactTypeMatch);
-
-                checkProtected(definition);
-                PropertyState state = createMultiState(oakName, type, values, definition);
-                return new PropertyImpl(dlg.setProperty(state), sessionContext);
+                return new PropertyImpl(
+                        dlg.setProperty(state, exactTypeMatch, false),
+                        sessionContext);
             }
         });
     }
 
+    /**
+     * Removes all {@code null} values from the given array.
+     *
+     * @param values value array
+     * @return value list without {@code null} entries
+     */
+    private static List<Value> compact(Value[] values) {
+        List<Value> list = Lists.newArrayListWithCapacity(values.length);
+        for (Value value : values) {
+            if (value != null) {
+                list.add(value);
+            }
+        }
+        return list;
+    }
+
+
     private Property internalRemoveProperty(final String jcrName)
             throws RepositoryException {
         final String oakName = getOakName(checkNotNull(jcrName));

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/PropertyImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/PropertyImpl.java?rev=1498401&r1=1498400&r2=1498401&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/PropertyImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/PropertyImpl.java Mon Jul  1 12:07:54 2013
@@ -17,11 +17,6 @@
 package org.apache.jackrabbit.oak.jcr;
 
 import static com.google.common.collect.Lists.newArrayListWithCapacity;
-import static org.apache.jackrabbit.oak.api.Type.NAME;
-import static org.apache.jackrabbit.oak.api.Type.NAMES;
-import static org.apache.jackrabbit.oak.api.Type.PATH;
-import static org.apache.jackrabbit.oak.api.Type.PATHS;
-import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty;
 
 import java.io.InputStream;
 import java.math.BigDecimal;
@@ -43,7 +38,6 @@ import javax.jcr.ValueFactory;
 import javax.jcr.ValueFormatException;
 import javax.jcr.nodetype.PropertyDefinition;
 
-import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Tree.Status;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.jcr.delegate.NodeDelegate;
@@ -444,19 +438,7 @@ public class PropertyImpl extends ItemIm
 
                 Value converted = ValueHelper.convert(
                         value, type.tag(), getValueFactory());
-
-                PropertyState state;
-                if (type == NAME) {
-                    String name = getOakName(converted.getString());
-                    state = createProperty(dlg.getName(), name, NAME);
-                } else if (type == PATH) {
-                    String path = getOakPathOrThrow(converted.getString());
-                    state = createProperty(dlg.getName(), path, PATH);
-                } else {
-                    state = createProperty(dlg.getName(), converted);
-                }
-
-                dlg.setState(state);
+                dlg.setState(createSingleState(dlg.getName(), converted, type));
                 return null;
             }
         });
@@ -481,27 +463,7 @@ public class PropertyImpl extends ItemIm
                                 values[i], type.tag(), factory));
                     }
                 }
-
-                PropertyState state;
-                if (type == NAMES) {
-                    List<String> names =
-                            newArrayListWithCapacity(converted.size());
-                    for (Value name : converted) {
-                        names.add(getOakName(name.getString()));
-                    }
-                    state = createProperty(dlg.getName(), names, NAMES);
-                } else if (type == PATHS) {
-                    List<String> paths =
-                            newArrayListWithCapacity(converted.size());
-                    for (Value path : converted) {
-                        paths.add(getOakPathOrThrow(path.getString()));
-                    }
-                    state = createProperty(dlg.getName(), paths, PATHS);
-                } else {
-                    state = createProperty(dlg.getName(), converted, type.tag());
-                }
-
-                dlg.setState(state);
+                dlg.setState(createMultiState(dlg.getName(), converted, type));
                 return null;
             }
         });

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java?rev=1498401&r1=1498400&r2=1498401&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java Mon Jul  1 12:07:54 2013
@@ -36,13 +36,17 @@ import static org.apache.jackrabbit.JcrC
 import static org.apache.jackrabbit.JcrConstants.JCR_NODETYPENAME;
 import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
 import static org.apache.jackrabbit.JcrConstants.JCR_PROTECTED;
+import static org.apache.jackrabbit.JcrConstants.JCR_REQUIREDTYPE;
 import static org.apache.jackrabbit.JcrConstants.JCR_SAMENAMESIBLINGS;
 import static org.apache.jackrabbit.JcrConstants.JCR_UUID;
+import static org.apache.jackrabbit.JcrConstants.NT_BASE;
 import static org.apache.jackrabbit.oak.api.Type.BOOLEAN;
 import static org.apache.jackrabbit.oak.api.Type.DATE;
 import static org.apache.jackrabbit.oak.api.Type.NAME;
 import static org.apache.jackrabbit.oak.api.Type.NAMES;
 import static org.apache.jackrabbit.oak.api.Type.STRING;
+import static org.apache.jackrabbit.oak.api.Type.UNDEFINED;
+import static org.apache.jackrabbit.oak.api.Type.UNDEFINEDS;
 import static org.apache.jackrabbit.oak.commons.PathUtils.dropIndexFromName;
 import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.JCR_CREATEDBY;
 import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.JCR_IS_ABSTRACT;
@@ -419,18 +423,120 @@ public class NodeDelegate extends ItemDe
      * @return the set property
      */
     @Nonnull
-    public PropertyDelegate setProperty(PropertyState propertyState) throws RepositoryException {
+    public PropertyDelegate setProperty(
+            PropertyState propertyState, boolean exactTypeMatch,
+            boolean setProtected) throws RepositoryException {
         Tree tree = getTree();
-        String propName = propertyState.getName();
-        PropertyState old = tree.getProperty(propName);
+        String name = propertyState.getName();
+        Type<?> type = propertyState.getType();
+
+        PropertyState old = tree.getProperty(name);
         if (old != null && old.isArray() && !propertyState.isArray()) {
-            throw new ValueFormatException("Attempt to assign a single value to multi-valued property.");
+            throw new ValueFormatException(
+                    "Can not assign a single value to multi-valued property: "
+                    + propertyState);
         }
         if (old != null && !old.isArray() && propertyState.isArray()) {
-            throw new ValueFormatException("Attempt to assign multiple values to single valued property.");
+            throw new ValueFormatException(
+                    "Can not assign multiple values to single valued property: "
+                    + propertyState);
+        }
+
+        Tree definition = findMatchingPropertyDefinition(
+                tree, name, type, exactTypeMatch);
+        if (definition == null) {
+            throw new ConstraintViolationException(
+                    "No matching property definition: " + propertyState);
+        } else if (!setProtected && getBoolean(definition, JCR_PROTECTED)) {
+            throw new ConstraintViolationException(
+                    "Property is protected: " + propertyState);
         }
+        Type<?> requiredType =
+                Type.fromString(getString(definition, JCR_REQUIREDTYPE));
+        if (requiredType != Type.UNDEFINED) {
+            if (getBoolean(definition, JCR_MULTIPLE)) {
+                requiredType = requiredType.getArrayType();
+            }
+            propertyState = PropertyStates.convert(propertyState, requiredType);
+        }
+
         tree.setProperty(propertyState);
-        return new PropertyDelegate(sessionDelegate, tree, propName);
+        return new PropertyDelegate(sessionDelegate, tree, name);
+    }
+
+    private Tree findMatchingPropertyDefinition(
+            Tree tree, String propertyName, Type<?> propertyType,
+            boolean exactTypeMatch) {
+        Tree typeRoot = sessionDelegate.getRoot().getTree(NODE_TYPES_PATH);
+
+        // Find applicable node types
+        List<Tree> types = newArrayList();
+        String primaryName = getName(tree, JCR_PRIMARYTYPE);
+        if (primaryName == null) {
+            primaryName = NT_BASE;
+        }
+        types.add(typeRoot.getChild(primaryName));
+        for (String mixinName : getNames(tree, JCR_MIXINTYPES)) {
+            types.add(typeRoot.getChild(mixinName));
+        }
+
+        // Escape the property name for looking up a matching definition
+        String escapedName;
+        if (JCR_PRIMARYTYPE.equals(propertyName)) {
+            escapedName = "oak:primaryType";
+        } else if (JCR_MIXINTYPES.equals(propertyName)) {
+            escapedName = "oak:mixinTypes";
+        } else if (JCR_UUID.equals(propertyName)) {
+            escapedName = "oak:uuid";
+        } else {
+            escapedName = propertyName;
+        }
+
+        String definedType = propertyType.toString();
+        String undefinedType = UNDEFINED.toString();
+        if (propertyType.isArray()) {
+            undefinedType = UNDEFINEDS.toString();
+        }
+
+        // First look for a matching named property definition
+        for (Tree type : types) {
+            Tree definitions = type
+                    .getChild(OAK_NAMED_PROPERTY_DEFINITIONS)
+                    .getChild(escapedName);
+            Tree definition = definitions.getChild(definedType);
+            if (definition.exists()) {
+                return definition;
+            }
+            definition = definitions.getChild(undefinedType);
+            if (definition.exists()) {
+                return definition;
+            }
+            if (!exactTypeMatch) {
+                for (Tree def : definitions.getChildren()) {
+                    return def;
+                }
+            }
+        }
+
+        // Then look through any residual property definitions
+        for (Tree type : types) {
+            Tree definitions = type.getChild(OAK_RESIDUAL_PROPERTY_DEFINITIONS);
+            Tree definition = definitions.getChild(definedType);
+            if (definition.exists()) {
+                return definition;
+            }
+            definition = definitions.getChild(undefinedType);
+            if (definition.exists()) {
+                return definition;
+            }
+            if (!exactTypeMatch) {
+                for (Tree def : definitions.getChildren()) {
+                    return def;
+                }
+            }
+        }
+
+        return null;
     }
 
     /**
@@ -707,6 +813,16 @@ public class NodeDelegate extends ItemDe
     }
 
     @CheckForNull
+    private static String getString(Tree tree, String name) {
+        PropertyState property = tree.getProperty(name);
+        if (property != null && property.getType() == STRING) {
+            return property.getValue(STRING);
+        } else {
+            return null;
+        }
+    }
+
+    @CheckForNull
     private static String getName(Tree tree, String name) {
         PropertyState property = tree.getProperty(name);
         if (property != null && property.getType() == NAME) {