You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2006/07/12 15:33:27 UTC

svn commit: r421270 [5/23] - in /jackrabbit/trunk/contrib/spi: ./ commons/ commons/src/ commons/src/main/ commons/src/main/java/ commons/src/main/java/org/ commons/src/main/java/org/apache/ commons/src/main/java/org/apache/jackrabbit/ commons/src/main/...

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,1611 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi;
+
+import org.apache.jackrabbit.util.ChildrenCollectorFilter;
+import org.apache.jackrabbit.util.IteratorHelper;
+import org.apache.jackrabbit.value.ValueHelper;
+import org.apache.jackrabbit.value.ValueFormat;
+import org.apache.jackrabbit.value.QValue;
+import org.apache.jackrabbit.name.MalformedPathException;
+import org.apache.jackrabbit.name.IllegalNameException;
+import org.apache.jackrabbit.name.UnknownPrefixException;
+import org.apache.jackrabbit.name.NoPrefixDeclaredException;
+import org.apache.jackrabbit.name.NameException;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.jcr2spi.state.NodeState;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateException;
+import org.apache.jackrabbit.jcr2spi.state.NodeReferences;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateValidator;
+import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeManagerImpl;
+import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeType;
+import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeConflictException;
+import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeImpl;
+import org.apache.jackrabbit.jcr2spi.operation.SetMixin;
+import org.apache.jackrabbit.jcr2spi.operation.AddProperty;
+import org.apache.jackrabbit.jcr2spi.operation.AddNode;
+import org.apache.jackrabbit.jcr2spi.operation.ReorderNodes;
+import org.apache.jackrabbit.jcr2spi.operation.Operation;
+import org.apache.jackrabbit.jcr2spi.operation.Update;
+import org.apache.jackrabbit.jcr2spi.lock.LockManager;
+import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.spi.QNodeDefinition;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.PropertyId;
+import org.apache.jackrabbit.spi.ItemId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.AccessDeniedException;
+import javax.jcr.ItemVisitor;
+import javax.jcr.ItemExistsException;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.PropertyType;
+import javax.jcr.NodeIterator;
+import javax.jcr.PropertyIterator;
+import javax.jcr.Repository;
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.nodetype.NodeDefinition;
+import javax.jcr.lock.LockException;
+import javax.jcr.lock.Lock;
+import javax.jcr.version.VersionException;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionHistory;
+
+import java.io.InputStream;
+import java.util.Calendar;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * <code>NodeImpl</code>...
+ */
+public class NodeImpl extends ItemImpl implements Node {
+
+    private static Logger log = LoggerFactory.getLogger(NodeImpl.class);
+
+    private QName primaryTypeName;
+    private NodeDefinition definition;
+
+    protected NodeImpl(ItemManager itemMgr, SessionImpl session,
+                       NodeState state, NodeDefinition definition,
+                       ItemLifeCycleListener[] listeners) {
+        super(itemMgr, session, state, listeners);
+        this.definition = definition;
+        QName nodeTypeName = state.getNodeTypeName();
+        // paranoid sanity check
+        if (session.getNodeTypeManager().hasNodeType(nodeTypeName)) {
+            primaryTypeName = nodeTypeName;
+        } else {
+            /**
+             * todo need proper way of handling inconsistent/corrupt node type references
+             * e.g. 'flag' nodes that refer to non-registered node types
+             */
+            log.warn("Fallback to nt:unstructured due to unknown node type '" + nodeTypeName + "' of node " + safeGetJCRPath());
+            primaryTypeName = QName.NT_UNSTRUCTURED;
+        }
+    }
+
+    //-----------------------------------------------------< Item interface >---
+    /**
+     * @see Item#getName()
+     */
+    public String getName() throws RepositoryException {
+        checkStatus();
+        QName name = session.getHierarchyManager().getQName(getId());
+        try {
+            return session.getNamespaceResolver().getJCRName(name);
+        } catch (NoPrefixDeclaredException npde) {
+            // should never get here...
+            String msg = "internal error: encountered unregistered namespace " + name.getNamespaceURI();
+            log.debug(msg);
+            throw new RepositoryException(msg, npde);
+        }
+    }
+
+    /**
+     * @see Item#getParent()
+     */
+    public Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException {
+        checkStatus();
+        // check if root node
+        NodeId parentId = getItemState().getParentId();
+        if (parentId == null) {
+            String msg = "root node doesn't have a parent";
+            log.debug(msg);
+            throw new ItemNotFoundException(msg);
+        }
+
+        return (Node) itemMgr.getItem(parentId);
+    }
+
+    /**
+     * Implementation of {@link Item#accept(javax.jcr.ItemVisitor)} for nodes.
+     *
+     * @param visitor
+     * @throws RepositoryException
+     * @see Item#accept(javax.jcr.ItemVisitor)
+     */
+    public void accept(ItemVisitor visitor) throws RepositoryException {
+        checkStatus();
+        visitor.visit(this);
+    }
+
+    /**
+     * Returns true
+     *
+     * @return true
+     * @see Item#isNode()
+     */
+    public boolean isNode() {
+        return true;
+    }
+
+    //-----------------------------------------------------< Node interface >---
+    /**
+     * @see Node#addNode(String)
+     */
+    public Node addNode(String relPath) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, RepositoryException {
+        // validation performed in subsequent method
+        return addNode(relPath, null);
+    }
+
+    /**
+     * @see Node#addNode(String, String)
+     */
+    public Node addNode(String relPath, String primaryNodeTypeName) throws ItemExistsException, PathNotFoundException, NoSuchNodeTypeException, LockException, VersionException, ConstraintViolationException, RepositoryException {
+        checkIsWritable();
+        // 1. build qualified path and retrieve parent node
+        Path nodePath = getQPath(relPath);
+        if (nodePath.getNameElement().getIndex() != org.apache.jackrabbit.name.Path.INDEX_UNDEFINED) {
+            String msg = "Illegal subscript specified: " + relPath;
+            log.debug(msg);
+            throw new RepositoryException(msg);
+        }
+        Path parentPath = nodePath.getAncestor(1);
+        NodeImpl parentNode;
+        try {
+            Item parent = itemMgr.getItem(parentPath);
+            if (!parent.isNode()) {
+                String msg = "Cannot add a node to property " + parentPath;
+                log.debug(msg);
+                throw new ConstraintViolationException(msg);
+            }
+            parentNode = (NodeImpl) parent;
+        } catch (AccessDeniedException ade) {
+            throw new PathNotFoundException(relPath);
+        }
+
+        // 2. get qualified names for node and nt
+        QName nodeName = nodePath.getNameElement().getName();
+        QName ntName = (primaryNodeTypeName == null) ? null : getQName(primaryNodeTypeName);
+
+        // 3. create new node (including validation checks)
+        return parentNode.createNode(nodeName, ntName);
+    }
+
+    /**
+     * @see Node#orderBefore(String, String)
+     */
+    public void orderBefore(String srcChildRelPath, String destChildRelPath) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException {
+        checkIsWritable();
+
+        if (!getPrimaryNodeType().hasOrderableChildNodes()) {
+            throw new UnsupportedRepositoryOperationException("Child node ordering not supported on node " + safeGetJCRPath());
+        }
+        // check arguments
+        if (srcChildRelPath.equals(destChildRelPath)) {
+            // there's nothing to do
+            return;
+        }
+        // check existence
+        if (!hasNode(srcChildRelPath)) {
+            throw new ItemNotFoundException(safeGetJCRPath() + " has no child node with name " + srcChildRelPath);
+        }
+        if (destChildRelPath != null && !hasNode(destChildRelPath)) {
+            throw new ItemNotFoundException(safeGetJCRPath() + " has no child node with name " + destChildRelPath);
+        }
+
+        Path.PathElement srcName = getReorderPath(srcChildRelPath).getNameElement();
+        Path.PathElement beforeName = (destChildRelPath == null) ? null : getReorderPath(destChildRelPath).getNameElement();
+
+        NodeState nState = getNodeState();
+        Operation op = ReorderNodes.create(nState, srcName, beforeName);
+        itemStateMgr.execute(op);
+    }
+
+    /**
+     * @see Node#setProperty(String, Value)
+     */
+    public Property setProperty(String name, Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        // validation performed in subsequent method
+        int type = PropertyType.UNDEFINED;
+        if (value != null) {
+            type = value.getType();
+        }
+        return setProperty(name, value, type);
+    }
+
+    /**
+     * @see Node#setProperty(String, javax.jcr.Value, int)
+     */
+    public Property setProperty(String name, Value value, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        checkIsWritable();
+        Property prop;
+        if (hasProperty(name)) {
+            // property already exists: pass call to property
+            prop = getProperty(name);
+            Value v = (type == PropertyType.UNDEFINED) ? value : ValueHelper.convert(value, type, session.getValueFactory());
+            prop.setValue(v);
+        } else {
+            if (value == null) {
+                // create and remove property is a nop.
+                // TODO: check if is correct to avoid any validation exception that way
+                prop = null;
+            } else {
+                // new property to be added
+                prop = createProperty(getQName(name), value, type);
+            }
+        }
+        return prop;
+    }
+
+    /**
+     * @see Node#setProperty(String, Value[])
+     */
+    public Property setProperty(String name, Value[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        // validation performed in subsequent method
+        int type;
+        if (values == null || values.length == 0 || values[0] == null) {
+            type = PropertyType.UNDEFINED;
+        } else {
+            type = values[0].getType();
+        }
+        return setProperty(name, values, type);
+    }
+
+    /**
+     * @see Node#setProperty(String, Value[], int)
+     */
+    public Property setProperty(String name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        checkIsWritable();
+        Property prop;
+        if (hasProperty(name)) {
+            // property already exists: pass call to property
+            prop = getProperty(name);
+            Value[] vs = (type == PropertyType.UNDEFINED) ? values : ValueHelper.convert(values, type, session.getValueFactory());
+            prop.setValue(vs);
+        } else {
+            if (values == null) {
+                // create and remove property is a nop.
+                // TODO: check if is correct to avoid any validation exception that way
+                prop = null;
+            } else {
+                // new property to be added
+                prop = createProperty(getQName(name), values, type);
+            }
+        }
+        return prop;
+    }
+
+    /**
+     * @see Node#setProperty(String, String[])
+     */
+    public Property setProperty(String name, String[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        // validation performed in subsequent method
+        return setProperty(name, values, PropertyType.UNDEFINED);
+    }
+
+    /**
+     * @see Node#setProperty(String, String[], int)
+     */
+    public Property setProperty(String name, String[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        // validation performed in subsequent method
+        Value[] vs;
+        if (type == PropertyType.UNDEFINED) {
+            vs = ValueHelper.convert(values, PropertyType.STRING, session.getValueFactory());
+        } else {
+            vs = ValueHelper.convert(values, type, session.getValueFactory());
+        }
+        return setProperty(name, vs, type);
+    }
+
+    /**
+     * @see Node#setProperty(String, String)
+     */
+    public Property setProperty(String name, String value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        // validation performed in subsequent method
+        // best-effort conversion if the target property is not of type STRING
+        return setProperty(name, value, PropertyType.STRING);
+    }
+
+    /**
+     * @see Node#setProperty(String, String, int)
+     */
+    public Property setProperty(String name, String value, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        // validation performed in subsequent method
+        Value v = (value == null) ? null : session.getValueFactory().createValue(value, type);
+        return setProperty(name, v);
+    }
+
+    /**
+     * @see Node#setProperty(String, InputStream)
+     */
+    public Property setProperty(String name, InputStream value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        // validation performed in subsequent method
+        Value v = (value == null ? null : session.getValueFactory().createValue(value));
+        return setProperty(name, v);
+    }
+
+    /**
+     * @see Node#setProperty(String, boolean)
+     */
+    public Property setProperty(String name, boolean value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        // validation performed in subsequent method
+        return setProperty(name, session.getValueFactory().createValue(value));
+    }
+
+    /**
+     * @see Node#setProperty(String, double)
+     */
+    public Property setProperty(String name, double value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        // validation performed in subsequent method
+        return setProperty(name, session.getValueFactory().createValue(value));
+    }
+
+    /**
+     * @see Node#setProperty(String, long)
+     */
+    public Property setProperty(String name, long value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        // validation performed in subsequent method
+        return setProperty(name, session.getValueFactory().createValue(value));
+    }
+
+    /**
+     * @see Node#setProperty(String, Calendar)
+     */
+    public Property setProperty(String name, Calendar value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        // validation performed in subsequent method
+        Value v = (value == null ? null : session.getValueFactory().createValue(value));
+        return setProperty(name, v);
+    }
+
+    /**
+     * @see Node#setProperty(String, Node)
+     */
+    public Property setProperty(String name, Node value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        // validation performed in subsequent method
+        Value v = (value == null ? null : session.getValueFactory().createValue(value));
+        return setProperty(name, v);
+    }
+
+    /**
+     * @see Node#getNode(String)
+     */
+    public Node getNode(String relPath) throws PathNotFoundException, RepositoryException {
+        checkStatus();
+        NodeId id = resolveRelativeNodePath(relPath);
+        if (id == null) {
+            throw new PathNotFoundException(relPath);
+        }
+        return (Node) itemMgr.getItem(id);
+    }
+
+    /**
+     * @see Node#getNodes()
+     */
+    public NodeIterator getNodes() throws RepositoryException {
+        checkStatus();
+        /**
+         * IMPORTANT:
+         * an implementation of Node.getNodes()
+         * must not use a class derived from TraversingElementVisitor
+         * to traverse the hierarchy because this would lead to an infinite
+         * recursion!
+         */
+        try {
+            return itemMgr.getChildNodes(getNodeId());
+        } catch (ItemNotFoundException infe) {
+            String msg = "failed to list the child nodes of " + safeGetJCRPath();
+            log.debug(msg);
+            throw new RepositoryException(msg, infe);
+        } catch (AccessDeniedException ade) {
+            String msg = "failed to list the child nodes of " + safeGetJCRPath();
+            log.debug(msg);
+            throw new RepositoryException(msg, ade);
+        }
+    }
+
+    /**
+     * @see Node#getNodes(String)
+     */
+    public NodeIterator getNodes(String namePattern) throws RepositoryException {
+        checkStatus();
+        ArrayList nodes = new ArrayList();
+        // traverse children using a special filtering 'collector'
+        accept(new ChildrenCollectorFilter(namePattern, nodes, true, false, 1));
+        return new IteratorHelper(Collections.unmodifiableList(nodes));
+    }
+
+    /**
+     * @see Node#getProperty(String)
+     */
+    public Property getProperty(String relPath) throws PathNotFoundException, RepositoryException {
+        checkStatus();
+        PropertyId id = resolveRelativePropertyPath(relPath);
+        if (id == null) {
+            throw new PathNotFoundException(relPath);
+        }
+        return (Property) itemMgr.getItem(id);
+    }
+
+    /**
+     * @see Node#getProperties()
+     */
+    public PropertyIterator getProperties() throws RepositoryException {
+        checkStatus();
+        try {
+            return itemMgr.getChildProperties(getNodeId());
+        } catch (ItemNotFoundException infe) {
+            String msg = "Failed to list the child properties of " + getPath();
+            log.debug(msg);
+            throw new RepositoryException(msg, infe);
+        } catch (AccessDeniedException ade) {
+            String msg = "Failed to list the child properties of " + getPath();
+            log.debug(msg);
+            throw new RepositoryException(msg, ade);
+        }
+    }
+
+    /**
+     * @see Node#getProperties(String)
+     */
+    public PropertyIterator getProperties(String namePattern) throws RepositoryException {
+        checkStatus();
+        ArrayList properties = new ArrayList();
+        // traverse children using a special filtering 'collector'
+        accept(new ChildrenCollectorFilter(namePattern, properties, false, true, 1));
+        return new IteratorHelper(Collections.unmodifiableList(properties));
+    }
+
+    /**
+     * @see Node#getPrimaryItem()
+     */
+    public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException {
+        checkStatus();
+        String name = getPrimaryNodeType().getPrimaryItemName();
+        if (name == null) {
+            throw new ItemNotFoundException("No primary item present on Node " + getPath());
+        }
+        if (hasProperty(name)) {
+            return getProperty(name);
+        } else if (hasNode(name)) {
+            return getNode(name);
+        } else {
+            throw new ItemNotFoundException("Primary item " + name + " does not exist on Node " + getPath());
+        }
+    }
+
+    /**
+     * @see Node#getUUID()
+     */
+    public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException {
+        checkStatus();
+        if (!isNodeType(QName.MIX_REFERENCEABLE)) {
+            throw new UnsupportedRepositoryOperationException();
+        }
+        return getNodeState().getNodeId().getUUID();
+    }
+
+    /**
+     * @see Node#getIndex()
+     */
+    public int getIndex() throws RepositoryException {
+        checkStatus();
+        NodeId parentId = getItemState().getParentId();
+        if (parentId == null) {
+            // the root node cannot have same-name siblings; always return the
+            // default index
+            return org.apache.jackrabbit.name.Path.INDEX_DEFAULT;
+        }
+        try {
+            NodeState parent = (NodeState) itemStateMgr.getItemState(parentId);
+            NodeState.ChildNodeEntry parentEntry = parent.getChildNodeEntry(getNodeId());
+            return parentEntry.getIndex();
+        } catch (ItemStateException ise) {
+            // should never get here...
+            String msg = "internal error: failed to determine index";
+            log.error(msg, ise);
+            throw new RepositoryException(msg, ise);
+        }
+    }
+
+    /**
+     * @see Node#getReferences()
+     */
+    public PropertyIterator getReferences() throws RepositoryException {
+        checkStatus();
+        try {
+            if (itemStateMgr.hasNodeReferences(getNodeId())) {
+                NodeReferences refs = itemStateMgr.getNodeReferences(getNodeId());
+                // refs.getReferences() returns a list of PropertyId's
+                List idList = refs.getReferences();
+                return new LazyItemIterator(itemMgr, idList);
+            } else {
+                // there are no references, return empty iterator
+                return IteratorHelper.EMPTY;
+            }
+        } catch (ItemStateException e) {
+            String msg = "Unable to retrieve REFERENCE properties that refer to " + getId();
+            log.debug(msg);
+            throw new RepositoryException(msg, e);
+        }
+    }
+
+    /**
+     * @see Node#hasNode(String)
+     */
+    public boolean hasNode(String relPath) throws RepositoryException {
+        checkStatus();
+        NodeId id = resolveRelativeNodePath(relPath);
+        return (id != null) ? itemMgr.itemExists(id) : false;
+    }
+
+    /**
+     * @see Node#hasProperty(String)
+     */
+    public boolean hasProperty(String relPath) throws RepositoryException {
+        checkStatus();
+        PropertyId pId = resolveRelativePropertyPath(relPath);
+        return (pId != null) ? itemMgr.itemExists(pId) : false;
+    }
+
+    /**
+     * Returns true, if this <code>Node</code> has a property with the given
+     * qualified name.
+     *
+     * @param propertyName
+     * @return
+     */
+    private boolean hasProperty(QName propertyName) {
+        return getNodeState().hasPropertyName(propertyName);
+    }
+
+    /**
+     * @see Node#hasNodes()
+     */
+    public boolean hasNodes() throws RepositoryException {
+        checkStatus();
+        return itemMgr.hasChildNodes(getNodeId());
+    }
+
+    /**
+     * @see Node#hasProperties()
+     */
+    public boolean hasProperties() throws RepositoryException {
+        checkStatus();
+        return itemMgr.hasChildProperties(getNodeId());
+    }
+
+    /**
+     * @see Node#getPrimaryNodeType()
+     */
+    public NodeType getPrimaryNodeType() throws RepositoryException {
+        checkStatus();
+        return session.getNodeTypeManager().getNodeType(primaryTypeName);
+    }
+
+    /**
+     * @see Node#getMixinNodeTypes()
+     */
+    public NodeType[] getMixinNodeTypes() throws RepositoryException {
+        checkStatus();
+        QName[] mixinNames = getNodeState().getMixinTypeNames();
+        NodeType[] nta = new NodeType[mixinNames.length];
+        for (int i = 0; i < mixinNames.length; i++) {
+            nta[i++] = session.getNodeTypeManager().getNodeType(mixinNames[i]);
+        }
+        return nta;
+    }
+
+    /**
+     * @see Node#isNodeType(String)
+     */
+    public boolean isNodeType(String nodeTypeName) throws RepositoryException {
+        // check is performed by isNodeType(QName)
+        return isNodeType(getQName(nodeTypeName));
+    }
+
+    /**
+     * @see Node#addMixin(String)
+     */
+    public void addMixin(String mixinName) throws NoSuchNodeTypeException,
+        VersionException, ConstraintViolationException, LockException, RepositoryException {
+        checkIsWritable();
+        QName[] mixinQNames = new QName[] {getQName(mixinName)};
+        try {
+            isValidMixin(mixinQNames);
+        } catch (NodeTypeConflictException e) {
+            throw new ConstraintViolationException(e.getMessage());
+        }
+
+        // merge existing mixins and new mixins to one Array without modifying
+        // the node state.
+        QName[] currentMixins = getNodeState().getMixinTypeNames();
+        QName[] allMixins = new QName[currentMixins.length + mixinQNames.length];
+        System.arraycopy(currentMixins, 0, allMixins, 0, currentMixins.length);
+        for (int i = 0; i < mixinQNames.length; i++) {
+            allMixins[currentMixins.length + i] = mixinQNames[i];
+        }
+        // perform the operation
+        PropertyId mixinPId = session.getIdFactory().createPropertyId(getNodeId(), QName.JCR_MIXINTYPES);
+        Operation op = SetMixin.create(mixinPId, allMixins);
+        itemStateMgr.execute(op);
+    }
+
+    /**
+     * @see Node#removeMixin(String)
+     */
+    public void removeMixin(String mixinName) throws NoSuchNodeTypeException,
+        VersionException, ConstraintViolationException, LockException, RepositoryException {
+        checkIsWritable();
+        QName ntName = getQName(mixinName);
+        List remainingMixins = new ArrayList(Arrays.asList(getNodeState().getMixinTypeNames()));
+        // remove name of target mixin
+        if (!remainingMixins.remove(ntName)) {
+            throw new NoSuchNodeTypeException("Cannot remove mixin '" + mixinName + "': Nodetype is not present on this node.");
+        }
+
+        // build effective node type of remaining mixin's & primary type
+        NodeTypeManagerImpl ntMgr = session.getNodeTypeManager();
+        EffectiveNodeType entRemaining;
+
+        // build effective node type representing primary type including remaining mixin's
+        QName[] allRemaining = (QName[]) remainingMixins.toArray(new QName[remainingMixins.size() + 1]);
+        allRemaining[remainingMixins.size()] = primaryTypeName;
+        try {
+            entRemaining = session.getValidator().getEffectiveNodeType(allRemaining);
+        } catch (NodeTypeConflictException e) {
+            throw new ConstraintViolationException(e);
+        }
+
+        // mix:referenceable needs additional assertion: the mixin cannot be
+        // removed, if any references are left to this node.
+        NodeTypeImpl mixin = ntMgr.getNodeType(ntName);
+        if (mixin.isNodeType(QName.MIX_REFERENCEABLE) && !entRemaining.includesNodeType(QName.MIX_REFERENCEABLE)) {
+            PropertyIterator iter = getReferences();
+            if (iter.hasNext()) {
+                throw new ConstraintViolationException(mixinName + " can not be removed: the node is being referenced through at least one property of type REFERENCE");
+            }
+        }
+
+        // delegate to operation
+        QName[] mixins = (QName[]) remainingMixins.toArray(new QName[remainingMixins.size()]);
+        PropertyId mixinPId = session.getIdFactory().createPropertyId(getNodeId(), QName.JCR_MIXINTYPES);
+        Operation op = SetMixin.create(mixinPId, mixins);
+        itemStateMgr.execute(op);
+    }
+
+    /**
+     * @see Node#canAddMixin(String)
+     */
+    public boolean canAddMixin(String mixinName) throws RepositoryException {
+        checkStatus();
+        if (!isSupportedOption(Repository.LEVEL_2_SUPPORTED)) {
+            // shortcut: repository does not support writing anyway.
+            return false;
+        }
+        try {
+            // first check if node is writable regarding protection status,
+            // locks, versioning, acces restriction.
+            session.getValidator().checkIsWritable(getNodeState(), ItemStateValidator.CHECK_ALL);
+            // then make sure the new mixin would not conflict.
+            return isValidMixin(new QName[] {getQName(mixinName)});
+        } catch (RepositoryException e) {
+            log.debug("Cannot add mixin '" + mixinName + "' for the following reason: " + e.getMessage());
+            return false;
+        } catch (NodeTypeConflictException e) {
+            log.debug("Cannot add mixin '" + mixinName + "' for the following reason: " + e.getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * @see Node#getDefinition()
+     */
+    public NodeDefinition getDefinition() throws RepositoryException {
+        checkStatus();
+        return definition;
+    }
+
+    /**
+     * @see Node#checkin()
+     */
+    public Version checkin() throws VersionException, UnsupportedRepositoryOperationException, InvalidItemStateException, LockException, RepositoryException {
+        checkIsVersionable();
+        checkHasPendingChanges();
+        checkIsLocked();
+        // DIFF JR
+        if (isCheckedOut()) {
+            session.getVersionManager().checkin(getNodeId());
+        } else {
+            log.debug("Node " + getPath() + " is already checked in.");
+        }
+        return getBaseVersion();
+    }
+
+    /**
+     * @see Node#checkout()
+     */
+    public void checkout() throws UnsupportedRepositoryOperationException, LockException, RepositoryException {
+        checkIsVersionable();
+        checkIsLocked();
+        // DIFF JR
+        if (!isCheckedOut()) {
+            session.getVersionManager().checkout(getNodeId());
+        } else {
+            log.debug("Node " + getPath() + " is already checked out.");
+        }
+    }
+
+    /**
+     * @see Node#doneMerge(Version)
+     */
+    public void doneMerge(Version version) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException {
+        resolveMergeConflict(version, true);
+    }
+
+    /**
+     * @see Node#cancelMerge(Version)
+     */
+    public void cancelMerge(Version version) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException {
+        resolveMergeConflict(version, false);
+    }
+
+    /**
+     * Internal method covering both {@link #doneMerge(Version)} and {@link #cancelMerge(Version)}.
+     *
+     * @param version
+     * @param done
+     * @throws VersionException
+     * @throws InvalidItemStateException
+     * @throws UnsupportedRepositoryOperationException
+     * @throws RepositoryException
+     */
+    private void resolveMergeConflict(Version version, boolean done) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException {
+        checkIsVersionable();
+        checkHasPendingChanges();
+        checkIsLocked();
+
+        // check if checked out
+        if (!isCheckedOut()) {
+            String msg = "Unable to finish merge. Node is checked-in: " + safeGetJCRPath();
+            log.error(msg);
+            throw new VersionException(msg);
+        }
+
+        // check if version is in mergeFailed list
+        boolean isConflicting = false;
+        if (hasProperty(QName.JCR_MERGEFAILED)) {
+            Value[] vals = getProperty(QName.JCR_MERGEFAILED).getValues();
+            for (int i = 0; i < vals.length && !isConflicting; i++) {
+                isConflicting = vals[i].getString().equals(version.getUUID());
+            }
+        }
+        if (!isConflicting) {
+            String msg = "Unable to finish merge. Specified version is not in jcr:mergeFailed property: " + safeGetJCRPath();
+            log.error(msg);
+            throw new VersionException(msg);
+        }
+
+        ItemId versionId = session.getHierarchyManager().getItemId(version);
+        if (versionId.denotesNode()) {
+            session.getVersionManager().resolveMergeConflict(getNodeId(), (NodeId) versionId, done);
+        } else {
+            throw new RepositoryException("Unexpected error: Failed to retrieve a valid ID for version " + version.getPath());
+        }
+    }
+
+    /**
+     * @see Node#update(String)
+     */
+    public void update(String srcWorkspaceName) throws NoSuchWorkspaceException, AccessDeniedException, LockException, InvalidItemStateException, RepositoryException {
+        checkIsWritable();
+        checkSessionHasPendingChanges();
+
+        // if same workspace, ignore
+        if (session.getWorkspace().getName().equals(srcWorkspaceName)) {
+            return;
+        }
+        // make sure the specified workspace is visible for the current session.
+        session.checkAccessibleWorkspace(srcWorkspaceName);
+
+        Operation op = Update.create(getNodeId(), srcWorkspaceName);
+        session.getSessionItemStateManager().execute(op);
+    }
+
+    /**
+     * @see Node#merge(String, boolean)
+     */
+    public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws NoSuchWorkspaceException, AccessDeniedException, VersionException, LockException, InvalidItemStateException, RepositoryException {
+        checkSupportedOption(Repository.OPTION_VERSIONING_SUPPORTED);
+        checkIsWritable();
+        checkSessionHasPendingChanges();
+
+        // if same workspace, ignore
+        if (session.getWorkspace().getName().equals(srcWorkspace)) {
+            return IteratorHelper.EMPTY;
+        }
+        // make sure the workspace exists and is accessible for this session.
+        session.checkAccessibleWorkspace(srcWorkspace);
+
+        Collection failedIds = session.getVersionManager().merge(getNodeId(), srcWorkspace, bestEffort);
+        if (failedIds.isEmpty()) {
+            return IteratorHelper.EMPTY;
+        } else {
+            return new LazyItemIterator(itemMgr, failedIds);
+        }
+    }
+
+    /**
+     * @see Node#getCorrespondingNodePath(String)
+     */
+    public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException {
+        checkStatus();
+        SessionImpl srcSession = null;
+        try {
+            // create session on other workspace for current subject
+            // (may throw NoSuchWorkspaceException and AccessDeniedException)
+            // DIFF JR: srcSession = rep.createSession(session.getSubject(), workspaceName);
+            srcSession = session.switchWorkspace(workspaceName);
+
+            // search nearest ancestor that is referenceable
+            NodeImpl referenceableNode = this;
+            while (referenceableNode.getDepth() != org.apache.jackrabbit.name.Path.ROOT_DEPTH && !referenceableNode.isNodeType(QName.MIX_REFERENCEABLE)) {
+                referenceableNode = (NodeImpl) referenceableNode.getParent();
+            }
+
+            // if root is common ancestor, corresponding path is same as ours
+            // otherwise access referenceable ancestor and calcuate correspond. path.
+            String correspondingPath;
+            if (referenceableNode.getDepth() == org.apache.jackrabbit.name.Path.ROOT_DEPTH) {
+                if (!srcSession.getItemManager().itemExists(getQPath())) {
+                    throw new ItemNotFoundException("No corresponding path found in workspace " + workspaceName + "(" + safeGetJCRPath() + ")");
+                } else {
+                    correspondingPath = getPath();
+                }
+            } else {
+                // get corresponding ancestor
+                Node correspNode = srcSession.getNodeByUUID(referenceableNode.getUUID());
+                // path of m2 found, if m1 == n1
+                if (referenceableNode == this) {
+                    correspondingPath = correspNode.getPath();
+                } else {
+                    Path p = referenceableNode.getQPath().computeRelativePath(getQPath());
+                    // use prefix mappings of srcSession
+                    String relPath = srcSession.getNamespaceResolver().getJCRPath(p);
+                    if (!correspNode.hasNode(relPath)) {
+                        throw new ItemNotFoundException("No corresponding path found in workspace " + workspaceName + "(" + safeGetJCRPath() + ")");
+                    } else {
+                        correspondingPath = correspNode.getNode(relPath).getPath();
+                    }
+                }
+            }
+            return correspondingPath;
+        } catch (NameException be) {
+            // should never get here...
+            String msg = "internal error: failed to determine relative path";
+            log.error(msg, be);
+            throw new RepositoryException(msg, be);
+        } finally {
+            if (srcSession != null) {
+                // we don't need the other session anymore, logout
+                srcSession.logout();
+            }
+        }
+    }
+
+    /**
+     * @see Node#isCheckedOut()
+     */
+    public boolean isCheckedOut() throws RepositoryException {
+        checkStatus();
+        if (!isSupportedOption(Repository.OPTION_VERSIONING_SUPPORTED)) {
+            return true;
+        }
+        // shortcut: if state is new, its ancestor must be checkout
+        if (isNew()) {
+            return true;
+        }
+        return session.getVersionManager().isCheckedOut(getNodeId());
+    }
+
+    /**
+     * @see Node#restore(String, boolean)
+     */
+    public void restore(String versionName, boolean removeExisting) throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
+        checkSupportedOption(Repository.OPTION_VERSIONING_SUPPORTED);
+        checkSessionHasPendingChanges();
+        // check for version-enabled and lock are performed with subsequent calls.
+        Version v = getVersionHistory().getVersion(versionName);
+        restore(v, removeExisting);
+    }
+
+    /**
+     * @see Node#restore(Version, boolean)
+     */
+    public void restore(Version version, boolean removeExisting) throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, RepositoryException {
+        checkSupportedOption(Repository.OPTION_VERSIONING_SUPPORTED);
+        checkSessionHasPendingChanges();
+        checkIsWritable();
+        checkIsVersionable();
+        checkIsLocked();
+
+        restore(getNodeId(), version, removeExisting);
+    }
+
+    /**
+     * @see Node#restore(Version, String, boolean)
+     */
+    public void restore(Version version, String relPath, boolean removeExisting) throws PathNotFoundException, ItemExistsException, VersionException, ConstraintViolationException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
+        checkSupportedOption(Repository.OPTION_VERSIONING_SUPPORTED);
+        checkSessionHasPendingChanges();
+        // additional checks are performed with subsequest calls.
+        if (hasNode(relPath)) {
+            // node at 'relPath' exists -> call restore on that node
+            getNode(relPath).restore(version, removeExisting);
+        } else {
+            // node at 'relPath' does not yet exist -> build the NodeId
+            Path nPath = getQPath(relPath);
+            Path parentPath = nPath.getAncestor(1);
+            NodeId nId;
+            if (session.getItemManager().itemExists(parentPath)) {
+                // If the would-be parent of the location relPath is actually a
+                // property, or if a node type restriction would be violated,
+                // then a ConstraintViolationException is thrown.
+                Item parent = session.getItemManager().getItem(parentPath);
+                if (parent.isNode()) {
+                    try {
+                        Path relQPath = parentPath.computeRelativePath(nPath);
+                        nId = session.getIdFactory().createNodeId(((NodeImpl)parent).getNodeId(), relQPath);
+                    } catch (MalformedPathException e) {
+                        // should not occur
+                        throw new RepositoryException(e);
+                    }
+                } else {
+                    throw new ConstraintViolationException("Cannot restore to a parent presenting a property (relative path = '" + relPath + "'");
+                }
+            } else {
+                // although the node itself must not exist, is direct ancestor must.
+                throw new PathNotFoundException("Cannot restore to relative path '" + relPath + ": Ancestor does not exist.");
+            }
+            restore(nId, version, removeExisting);
+        }
+    }
+
+    /**
+     * Common internal restore method for the various Node#restore calls.
+     *
+     * @param nodeId
+     * @param version
+     * @param removeExisting
+     * @throws PathNotFoundException
+     * @throws ItemExistsException
+     * @throws VersionException
+     * @throws ConstraintViolationException
+     * @throws UnsupportedRepositoryOperationException
+     * @throws LockException
+     * @throws InvalidItemStateException
+     * @throws RepositoryException
+     */
+    private void restore(NodeId nodeId, Version version, boolean removeExisting) throws PathNotFoundException, ItemExistsException, VersionException, ConstraintViolationException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
+        ItemId versionId = session.getHierarchyManager().getItemId(version);
+        if (versionId.denotesNode()) {
+            session.getVersionManager().restore(nodeId, (NodeId) versionId, removeExisting);
+        } else {
+            throw new RepositoryException("Unexpected error: Failed to retrieve a valid ID for the given version " + version.getPath());
+        }
+    }
+
+    /**
+     * @see Node#restoreByLabel(String, boolean)
+     */
+    public void restoreByLabel(String versionLabel, boolean removeExisting) throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
+        checkSupportedOption(Repository.OPTION_VERSIONING_SUPPORTED);
+        checkSessionHasPendingChanges();
+        // check for version-enabled and lock are performed with subsequent calls.
+        Version v = getVersionHistory().getVersionByLabel(versionLabel);
+        if (v == null) {
+            throw new VersionException("No version for label " + versionLabel + " found.");
+        }
+        restore(getNodeId(), v, removeExisting);
+    }
+
+    /**
+     * @see Node#getVersionHistory()
+     */
+    public VersionHistory getVersionHistory() throws UnsupportedRepositoryOperationException, RepositoryException {
+        checkIsVersionable();
+        return (VersionHistory) getProperty(QName.JCR_VERSIONHISTORY).getNode();
+    }
+
+    /**
+     * @see Node#getBaseVersion()
+     */
+    public Version getBaseVersion() throws UnsupportedRepositoryOperationException, RepositoryException {
+        checkIsVersionable();
+        return (Version) getProperty(QName.JCR_BASEVERSION).getNode();
+    }
+
+    /**
+     * @see Node#lock(boolean, boolean)
+     */
+    public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException {
+        checkIsLockable();
+        checkHasPendingChanges();
+
+        return session.getLockManager().lock(getNodeId(), isDeep, isSessionScoped);
+    }
+
+    /**
+     * @see Node#getLock()
+     */
+    public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException {
+        // lock can be inherited from a parent > do not check for node being lockable.
+        checkSupportedOption(Repository.OPTION_LOCKING_SUPPORTED);
+        checkStatus();
+
+        return session.getLockManager().getLock(getNodeId());
+    }
+
+    /**
+     * @see javax.jcr.Node#unlock()
+     */
+    public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException {
+        checkIsLockable();
+        checkHasPendingChanges();
+
+        session.getLockManager().unlock(getNodeId());
+    }
+
+    /**
+     * @see javax.jcr.Node#holdsLock()
+     */
+    public boolean holdsLock() throws RepositoryException {
+        // lock can be inherited from a parent > do not check for node being lockable.
+        checkSupportedOption(Repository.OPTION_LOCKING_SUPPORTED);
+        checkStatus();
+
+        // DIFF JR: swich check
+        if (isNew() || !isNodeType(QName.MIX_LOCKABLE)) {
+            // a node that is new or not lockable never holds a lock
+            return false;
+        } else {
+            // DIFF JR: no separate LockManager.holdsLock
+            LockManager lMgr = session.getLockManager();
+            return (lMgr.isLocked(getNodeId()) && lMgr.getLock(getNodeId()).getNode().isSame(this));
+        }
+    }
+
+    /**
+     * @see javax.jcr.Node#isLocked()
+     */
+    public boolean isLocked() throws RepositoryException {
+        // lock can be inherited from a parent > do not check for node being lockable.
+        checkSupportedOption(Repository.OPTION_LOCKING_SUPPORTED);
+        checkStatus();
+
+        return session.getLockManager().isLocked(getNodeId());
+    }
+
+    //--------------------------------------------------------< public impl >---
+    /**
+     *
+     * @param qName
+     * @return
+     * @throws RepositoryException
+     */
+    boolean isNodeType(QName qName) throws RepositoryException {
+        checkStatus();
+        // first do trivial checks without using type hierarchy
+        if (qName.equals(primaryTypeName)) {
+            return true;
+        }
+        // check if contained in mixin types
+        QName[] mixins = getNodeState().getMixinTypeNames();
+        for (int i = 0; i < mixins.length; i++) {
+            if (mixins[i].equals(qName)) {
+                return true;
+            }
+        }
+        // check effective node type
+        return getEffectiveNodeType().includesNodeType(qName);
+    }
+
+    //-----------------------------------------------------------< ItemImpl >---
+    /**
+     * @see ItemImpl#getQName()
+     */
+    QName getQName() throws RepositoryException {
+        return session.getHierarchyManager().getQName(getId());
+    }
+
+
+    //------------------------------------------------------< check methods >---
+    /**
+     * Checks if this nodes session has pending changes.
+     *
+     * @throws InvalidItemStateException if this nodes session has pending changes
+     * @throws RepositoryException
+     */
+    private void checkSessionHasPendingChanges() throws RepositoryException {
+        session.checkHasPendingChanges();
+    }
+
+    /**
+     *
+     * @throws InvalidItemStateException
+     * @throws RepositoryException
+     */
+    private void checkHasPendingChanges() throws InvalidItemStateException, RepositoryException {
+        if (hasPendingChanges()) {
+            String msg = "Unable to lock node. Node has pending changes: " + getPath();
+            log.debug(msg);
+            throw new InvalidItemStateException(msg);
+        }
+    }
+
+    /**
+     *
+     * @return
+     */
+    private boolean hasPendingChanges() {
+        return isModified() || isNew();
+    }
+
+    /**
+     * Checks if this node is lockable, i.e. has 'mix:lockable'.
+     *
+     * @throws UnsupportedRepositoryOperationException
+     *                             if this node is not lockable
+     * @throws RepositoryException if another error occurs
+     */
+    private void checkIsLockable() throws UnsupportedRepositoryOperationException, RepositoryException {
+        checkSupportedOption(Repository.OPTION_LOCKING_SUPPORTED);
+        checkStatus();
+        if (!isNodeType(QName.MIX_LOCKABLE)) {
+            String msg = "Unable to perform locking operation on non-lockable node: " + getPath();
+            log.debug(msg);
+            throw new UnsupportedRepositoryOperationException(msg);
+        }
+    }
+
+    /**
+     * Check whether this node is locked by somebody else.
+     *
+     * @throws LockException       if this node is locked by somebody else
+     * @throws RepositoryException if some other error occurs
+     */
+    void checkIsLocked() throws LockException, RepositoryException {
+        if (!isSupportedOption(Repository.OPTION_LOCKING_SUPPORTED) || isNew()) {
+            // if locking is not support at all or if this node is new, no
+            // checks must be performed.
+            return;
+        }
+        // perform check
+        session.getLockManager().checkLock(getNodeId());
+    }
+
+    /**
+     *
+     * @throws UnsupportedRepositoryOperationException
+     * @throws RepositoryException
+     */
+    private void checkIsVersionable() throws UnsupportedRepositoryOperationException, RepositoryException {
+        checkSupportedOption(Repository.OPTION_VERSIONING_SUPPORTED);
+        checkStatus();
+        if (!isNodeType(QName.MIX_VERSIONABLE)) {
+            String msg = "Unable to perform versioning operation on non versionable node: " + getPath();
+            log.debug(msg);
+            throw new UnsupportedRepositoryOperationException(msg);
+        }
+    }
+
+    //---------------------------------------------< private implementation >---
+    /**
+     * Create a new <code>NodeState</code> and subsequently retrieves the
+     * corresponding <code>Node</code> object.
+     *
+     * @param nodeName     name of the new node
+     * @param nodeTypeName name of the new node's node type or <code>null</code>
+     *                     if it should be determined automatically
+     * @return the newly added node
+     * @throws ItemExistsException
+     * @throws NoSuchNodeTypeException
+     * @throws VersionException
+     * @throws ConstraintViolationException
+     * @throws LockException
+     * @throws RepositoryException
+     */
+    private synchronized Node createNode(QName nodeName, QName nodeTypeName)
+        throws ItemExistsException, NoSuchNodeTypeException, VersionException,
+        ConstraintViolationException, LockException, RepositoryException {
+
+        // DIFF JR: remove check that assert existing nt. this should be done within following statement
+        QNodeDefinition definition = session.getValidator().getApplicableNodeDefinition(nodeName, nodeTypeName, getNodeState());
+        if (nodeTypeName == null) {
+            // use default node type
+            nodeTypeName = definition.getDefaultPrimaryType();
+        }
+        // validation check are performed by item state manager
+        // NOTE: uuid is generated while creating new state.
+        Operation an = AddNode.create(getNodeState(), nodeName, nodeTypeName, null);
+        itemStateMgr.execute(an);
+
+        // TODO: find better solution...
+        NodeId childId = AddNode.getLastCreated(getNodeState(), nodeName);
+        // finally retrieve the new node
+        return (Node) itemMgr.getItem(childId);
+    }
+
+    /**
+     *
+     * @param qName
+     * @return
+     * @throws PathNotFoundException
+     * @throws RepositoryException
+     */
+    // TODO: protected due to usage within VersionImpl, VersionHistoryImpl (check for alternatives)
+    protected Property getProperty(QName qName) throws PathNotFoundException, RepositoryException {
+        checkStatus();
+        PropertyId propId = getNodeState().getPropertyId(qName);
+        try {
+            return (Property) itemMgr.getItem(propId);
+        } catch (AccessDeniedException ade) {
+            throw new ItemNotFoundException(qName.toString());
+        }
+    }
+
+    // DIFF JR: instead of 'getORCreate' only create...
+    /**
+     * Create a new single valued property
+     *
+     * @param qName
+     * @param type
+     * @param value
+     * @return
+     * @throws ConstraintViolationException if no applicable property definition
+     * could be found.
+     * @throws RepositoryException if another error occurs.
+     */
+    private Property createProperty(QName qName, Value value, int type)
+            throws ConstraintViolationException, RepositoryException {
+        QPropertyDefinition def = getApplicablePropertyDefinition(qName, type, false);
+        int targetType = def.getRequiredType();
+        if (targetType == PropertyType.UNDEFINED) {
+            targetType = type;
+        }
+        QValue qvs;
+        if (targetType == PropertyType.UNDEFINED) {
+            qvs = ValueFormat.getQValue(value, session.getNamespaceResolver());
+            targetType = qvs.getType();
+        } else {
+            Value targetValue = ValueHelper.convert(value, targetType, session.getValueFactory());
+            qvs = ValueFormat.getQValue(targetValue, session.getNamespaceResolver());
+        }
+        return createProperty(qName, targetType, def, new QValue[] {qvs});
+    }
+
+    // DIFF JR: instead of 'getORCreate' only create...
+    /**
+     * Create a new multivalue property
+     *
+     * @param qName
+     * @param type
+     * @param values
+     * @return
+     * @throws ConstraintViolationException
+     * @throws RepositoryException
+     */
+    private Property createProperty(QName qName, Value[] values, int type)
+        throws ConstraintViolationException, RepositoryException {
+        QPropertyDefinition def = getApplicablePropertyDefinition(qName, type, true);
+        int targetType = def.getRequiredType();
+        if (targetType == PropertyType.UNDEFINED) {
+            targetType = type;
+        }
+        Value[] targetValues = ValueHelper.convert(values, targetType, session.getValueFactory());
+        QValue[] qvs = ValueFormat.getQValues(targetValues, session.getNamespaceResolver());
+        // make sure, the final type is not set to undefined
+        if (targetType == PropertyType.UNDEFINED) {
+            targetType = (qvs.length > 0) ? qvs[0].getType() : PropertyType.STRING;
+        }
+        return createProperty(qName, targetType, def, qvs);
+    }
+
+    /**
+     *
+     * @param qName
+     * @param type
+     * @param def
+     * @param ivs
+     * @return
+     * @throws PathNotFoundException
+     * @throws ConstraintViolationException
+     * @throws RepositoryException
+     */
+    private Property createProperty(QName qName, int type, QPropertyDefinition def,
+                                    QValue[] qvs)
+        throws PathNotFoundException, ConstraintViolationException, RepositoryException {
+        PropertyId newPId = session.getIdFactory().createPropertyId(getNodeId(), qName);
+        Operation op = AddProperty.create(newPId, type, def, qvs);
+        itemStateMgr.execute(op);
+        return getProperty(qName);
+    }
+
+    /**
+     *
+     * @param jcrName
+     * @return
+     * @throws RepositoryException
+     */
+    private QName getQName(String jcrName) throws RepositoryException {
+        QName qName;
+        try {
+            qName = session.getNamespaceResolver().getQName(jcrName);
+        } catch (IllegalNameException ine) {
+            throw new RepositoryException("invalid name: " + jcrName, ine);
+        } catch (UnknownPrefixException upe) {
+            throw new RepositoryException("invalid name: "+ jcrName, upe);
+        }
+        return qName;
+    }
+
+    private boolean isValidMixin(QName[] mixinNames) throws RepositoryException, NodeTypeConflictException {
+        NodeTypeManagerImpl ntMgr = session.getNodeTypeManager();
+
+        // get list of existing nodetypes
+        QName[] existingNts = getNodeState().getNodeTypeNames();
+        // build effective node type representing primary type including existing mixin's
+        EffectiveNodeType entExisting = session.getValidator().getEffectiveNodeType(existingNts);
+
+        // first check characteristics of each mixin
+        for (int i = 0; i < mixinNames.length; i++) {
+            QName mixinName = mixinNames[i];
+            NodeType mixin = ntMgr.getNodeType(mixinName);
+            if (!mixin.isMixin()) {
+                log.error(mixinName + ": not a mixin node type");
+                return false;
+            }
+            NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName);
+            // DIFF JR: replaced 'isDerivedFrom' by 'isNodeType'
+            if (primaryType.isNodeType(mixinName)) {
+                log.error(mixinName + ": already contained in primary node type");
+                return false;
+            }
+            // check if adding new mixin conflicts with existing nodetypes
+            if (entExisting.includesNodeType(mixinName)) {
+                log.error(mixinName + ": already contained in mixin types");
+                return false;
+            }
+        }
+
+        // second, build new effective node type for nts including the new mixin
+        // types, detecting eventual incompatibilities
+        QName[] resultingNts = new QName[existingNts.length + mixinNames.length];
+        System.arraycopy(existingNts, 0, resultingNts, 0, existingNts.length);
+        System.arraycopy(mixinNames, 0, resultingNts, existingNts.length, mixinNames.length);
+        session.getValidator().getEffectiveNodeType(resultingNts);
+
+        // all validations succeeded: return true
+        return true;
+    }
+
+    /**
+     *
+     * @return
+     */
+    private NodeState getNodeState() {
+        return (NodeState) getItemState();
+    }
+
+    /**
+     * Return the id of this <code>Node</code>.
+     *
+     * @return the id of this <code>Node</code>
+     */
+    private NodeId getNodeId() {
+        return getNodeState().getNodeId();
+    }
+
+    /**
+     * 
+     * @param relativePath
+     * @return
+     * @throws RepositoryException
+     */
+    private Path getReorderPath(String relativePath) throws RepositoryException {
+        try {
+            Path p = session.getNamespaceResolver().getQPath(relativePath);
+            if (p.isAbsolute() || p.getLength() != 1 || p.getDepth() != 1) {
+                throw new RepositoryException("Invalid relative path: " + relativePath);
+            }
+            return p;
+        } catch (MalformedPathException e) {
+            String msg = "Invalid relative path: " + relativePath;
+            log.debug(msg);
+            throw new RepositoryException(msg, e);
+        }
+    }
+
+    /**
+     *
+     * @param relativePath
+     * @return
+     * @throws RepositoryException
+     */
+    private Path getQPath(String relativePath) throws RepositoryException {
+        try {
+            Path p = session.getNamespaceResolver().getQPath(relativePath);
+            return Path.create(getQPath(), p, true);
+        } catch (MalformedPathException e) {
+            String msg = "Invalid relative path: " + relativePath;
+            log.debug(msg);
+            throw new RepositoryException(msg, e);
+        }
+    }
+
+    /**
+     * Returns the id of the node at <code>relPath</code> or <code>null</code>
+     * if no node exists at <code>relPath</code>.
+     * <p/>
+     * Note that access rights are not checked.
+     *
+     * @param relPath relative path of a (possible) node.
+     * @return the id of the node at <code>relPath</code> or <code>null</code>
+     * if no node exists at <code>relPath</code>.
+     * @throws RepositoryException if <code>relPath</code> is not a valid
+     * relative path.
+     */
+    private NodeId resolveRelativeNodePath(String relPath) throws RepositoryException {
+        NodeId targetId = null;
+        try {
+            Path p = getQPath(relPath);
+            // if relative path is just the last path element -> simply retrieve
+            // the corresponding child-node.
+            if (p.getLength() == 1) {
+                Path.PathElement pe = p.getNameElement();
+                if (pe.denotesName()) {
+                    // check if node entry exists
+                    int index = pe.getNormalizedIndex();
+                    NodeState.ChildNodeEntry cne = getNodeState().getChildNodeEntry(pe.getName(), index);
+                    if (cne != null) {
+                        targetId = cne.getId();
+                    } // else: there's no child node with that name
+                }
+            } else {
+                ItemId id = session.getHierarchyManager().getItemId(p.getCanonicalPath());
+                if (id.denotesNode()) {
+                    targetId = (NodeId) id;
+                } // else:  not a node
+            }
+        } catch (PathNotFoundException e) {
+            // item does not exist -> ignore and return null
+        } catch (MalformedPathException e) {
+            e.printStackTrace();
+        }
+        return targetId;
+    }
+
+    /**
+     * Returns the id of the property at <code>relPath</code> or <code>null</code>
+     * if no property exists at <code>relPath</code>.
+     * <p/>
+     * Note that access rights are not checked.
+     *
+     * @param relPath relative path of a (possible) property
+     * @return the id of the property at <code>relPath</code> or
+     *         <code>null</code> if no property exists at <code>relPath</code>
+     * @throws RepositoryException if <code>relPath</code> is not a valid
+     *                             relative path
+     */
+    private PropertyId resolveRelativePropertyPath(String relPath) throws RepositoryException {
+        try {
+            /**
+             * first check if relPath is just a name (in which case we don't
+             * have to build & resolve absolute path)
+             */
+            if (relPath.indexOf('/') == -1) {
+                QName propName = session.getNamespaceResolver().getQName(relPath);
+                // check if property entry exists
+                if (getNodeState().hasPropertyName(propName)) {
+                    return getNodeState().getPropertyId(propName);
+                } else {
+                    // there's no property with that name
+                    return null;
+                }
+            }
+            /**
+             * build and resolve absolute path
+             */
+            Path p = getQPath(relPath).getCanonicalPath();
+            try {
+                ItemId id = session.getHierarchyManager().getItemId(p);
+                if (!id.denotesNode()) {
+                    return (PropertyId) id;
+                } else {
+                    // not a property
+                    return null;
+                }
+            } catch (PathNotFoundException pnfe) {
+                return null;
+            }
+        } catch (NameException e) {
+            String msg = "failed to resolve path " + relPath + " relative to " + safeGetJCRPath();
+            log.debug(msg);
+            throw new RepositoryException(msg, e);
+        }
+    }
+
+    /**
+     * Returns the effective (i.e. merged and resolved) node type representation
+     * of this node's primary and mixin node types.
+     *
+     * @return the effective node type
+     * @throws RepositoryException
+     */
+    private EffectiveNodeType getEffectiveNodeType() throws RepositoryException {
+        // build effective node type of mixins & primary type
+        ItemStateValidator validator = session.getValidator();
+        return validator.getEffectiveNodeType(getNodeState());
+    }
+
+    /**
+     * Returns the applicable property definition for a property with the
+     * specified name and type.
+     *
+     * @param propertyName
+     * @param type
+     * @param multiValued
+     * @return
+     * @throws ConstraintViolationException if no applicable property definition
+     *                                      could be found
+     * @throws RepositoryException          if another error occurs
+     */
+    private QPropertyDefinition getApplicablePropertyDefinition(QName propertyName,
+                                                                int type,
+                                                                boolean multiValued)
+            throws ConstraintViolationException, RepositoryException {
+        return session.getValidator().getApplicablePropertyDefinition(propertyName, type, multiValued, getNodeState());
+    }
+}

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/PropertyImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/PropertyImpl.java?rev=421270&view=auto
==============================================================================
--- jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/PropertyImpl.java (added)
+++ jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/PropertyImpl.java Wed Jul 12 06:33:19 2006
@@ -0,0 +1,568 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi;
+
+import org.apache.jackrabbit.jcr2spi.state.PropertyState;
+import org.apache.jackrabbit.jcr2spi.operation.SetPropertyValue;
+import org.apache.jackrabbit.jcr2spi.operation.Operation;
+import org.apache.jackrabbit.name.NoPrefixDeclaredException;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.spi.PropertyId;
+import org.apache.jackrabbit.value.QValue;
+import org.apache.jackrabbit.value.ValueFormat;
+import org.apache.jackrabbit.value.ValueHelper;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.PropertyDefinition;
+import javax.jcr.lock.LockException;
+import javax.jcr.version.VersionException;
+import javax.jcr.Property;
+import javax.jcr.Item;
+import javax.jcr.RepositoryException;
+import javax.jcr.Node;
+import javax.jcr.AccessDeniedException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.ItemVisitor;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.PropertyType;
+import java.io.InputStream;
+import java.util.Calendar;
+
+/**
+ * <code>PropertyImpl</code>...
+ */
+public class PropertyImpl extends ItemImpl implements Property {
+
+    private static Logger log = LoggerFactory.getLogger(PropertyImpl.class);
+
+    public static final int UNDEFINED_PROPERTY_LENGTH = -1;
+
+    private final PropertyDefinition definition;
+
+    public PropertyImpl(ItemManagerImpl itemManager, SessionImpl session,
+                        PropertyState state, PropertyDefinition definition,
+                        ItemLifeCycleListener[] listeners) {
+        super(itemManager, session, state, listeners);
+        this.definition = definition;
+        // value will be read on demand
+    }
+
+    //-----------------------------------------------------< Item interface >---
+    /**
+     * @see Item#getName()
+     */
+    public String getName() throws RepositoryException {
+        checkStatus();
+        PropertyId propId = getPropertyId();
+        QName name = propId.getQName();
+        try {
+            return session.getNamespaceResolver().getJCRName(name);
+        } catch (NoPrefixDeclaredException npde) {
+            // should never get here...
+            String msg = "Internal error: encountered unregistered namespace " + name.getNamespaceURI();
+            log.debug(msg);
+            throw new RepositoryException(msg, npde);
+        }
+    }
+
+    /**
+     * @see Item#getParent()
+     */
+    public Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException {
+        checkStatus();
+        return (Node) itemMgr.getItem(getItemState().getParentId());
+    }
+
+    /**
+     * Implementation of {@link Item#accept(javax.jcr.ItemVisitor)} for property.
+     *
+     * @param visitor
+     * @see Item#accept(javax.jcr.ItemVisitor)
+     */
+    public void accept(ItemVisitor visitor) throws RepositoryException {
+        checkStatus();
+        visitor.visit(this);
+    }
+
+    /**
+     * Returns false
+     *
+     * @return false
+     * @see javax.jcr.Item#isNode()
+     */
+    public boolean isNode() {
+	return false;
+    }
+
+    //-------------------------------------------------< Property interface >---
+    /**
+     * @see Property#setValue(javax.jcr.Value)
+     */
+    public void setValue(Value value) throws ValueFormatException, VersionException, LockException, RepositoryException {
+        checkIsWritable(false);
+        int reqType = getRequiredType(value.getType());
+        setValue(value, reqType);
+    }
+
+    /**
+     * @see Property#setValue(javax.jcr.Value[])
+     */
+    public void setValue(Value[] values) throws ValueFormatException, VersionException, LockException, RepositoryException {
+        checkIsWritable(true);
+        // assert equal types for all values entries
+        int valueType = PropertyType.UNDEFINED;
+        if (values != null) {
+            for (int i = 0; i < values.length; i++) {
+                if (values[i] == null) {
+                    // skip null values as those will be purged later
+                    continue;
+                }
+                if (valueType == PropertyType.UNDEFINED) {
+                    valueType = values[i].getType();
+                } else if (valueType != values[i].getType()) {
+                    String msg = "Inhomogeneous type of values (" + safeGetJCRPath() + ")";
+                    log.debug(msg);
+                    throw new ValueFormatException(msg);
+                }
+            }
+        }
+
+        int targetType = definition.getRequiredType();
+        if (targetType == PropertyType.UNDEFINED) {
+            targetType = (valueType == PropertyType.UNDEFINED) ?  PropertyType.STRING : valueType;
+        }
+        // convert to internal values of correct type
+        QValue[] qValues = null;
+        if (values != null) {
+            Value[] vs = ValueHelper.convert(values, targetType, session.getValueFactory());
+            qValues = ValueFormat.getQValues(vs, session.getNamespaceResolver());
+        }
+        setInternalValues(qValues, targetType);
+    }
+
+    /**
+     * @see Property#setValue(String)
+     */
+    public void setValue(String value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        checkIsWritable(false);
+        int reqType = getRequiredType(PropertyType.STRING);
+        if (value == null) {
+            setInternalValues(null, reqType);
+        } else {
+            setValue(session.getValueFactory().createValue(value), reqType);
+        }
+    }
+
+    /**
+     * @see Property#setValue(String[])
+     */
+    public void setValue(String[] values) throws ValueFormatException, VersionException, LockException, RepositoryException {
+        checkIsWritable(true);
+        int reqType = getRequiredType(PropertyType.STRING);
+
+        QValue[] qValues = null;
+        // convert to internal values of correct type
+        if (values != null) {
+            qValues = new QValue[values.length];
+            for (int i = 0; i < values.length; i++) {
+                String string = values[i];
+                QValue qValue = null;
+                if (string != null) {
+                    if (reqType != PropertyType.STRING) {
+                        // type conversion required
+                        Value v = ValueHelper.convert(string, reqType, session.getValueFactory());
+                        qValue = ValueFormat.getQValue(v, session.getNamespaceResolver());
+                    } else {
+                        // no type conversion required
+                        qValue = QValue.create(string);
+                    }
+                }
+                qValues[i] = qValue;
+            }
+        }
+        setInternalValues(qValues, reqType);
+    }
+
+    /**
+     * @see Property#setValue(InputStream)
+     */
+    public void setValue(InputStream value) throws ValueFormatException, VersionException, LockException, RepositoryException {
+        checkIsWritable(false);
+        int reqType = getRequiredType(PropertyType.BINARY);
+        setValue(session.getValueFactory().createValue(value), reqType);
+    }
+
+    /**
+     * @see Property#setValue(long)
+     */
+    public void setValue(long value) throws ValueFormatException, VersionException, LockException, RepositoryException {
+	checkIsWritable(false);
+        int reqType = getRequiredType(PropertyType.LONG);
+        setValue(session.getValueFactory().createValue(value), reqType);
+    }
+
+    /**
+     * @see Property#setValue(double)
+     */
+    public void setValue(double value) throws ValueFormatException, VersionException, LockException, RepositoryException {
+    	checkIsWritable(false);
+        int reqType = getRequiredType(PropertyType.DOUBLE);
+        setValue(session.getValueFactory().createValue(value), reqType);
+    }
+
+    /**
+     * @see Property#setValue(Calendar)
+     */
+    public void setValue(Calendar value) throws ValueFormatException, VersionException, LockException, RepositoryException {
+	checkIsWritable(false);
+        int reqType = getRequiredType(PropertyType.DATE);
+        if (value == null) {
+            setInternalValues(null, reqType);
+        } else {
+            setValue(session.getValueFactory().createValue(value), reqType);
+        }
+    }
+
+    /**
+     * @see Property#setValue(boolean)
+     */
+    public void setValue(boolean value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+    	checkIsWritable(false);
+        int reqType = getRequiredType(PropertyType.BOOLEAN);
+        setValue(session.getValueFactory().createValue(value), reqType);
+    }
+
+    /**
+     * @see Property#setValue(Node)
+     */
+    public void setValue(Node value) throws ValueFormatException, VersionException, LockException, RepositoryException {
+	checkIsWritable(false);
+        int reqType = getRequiredType(PropertyType.REFERENCE);
+        if (value == null) {
+            setInternalValues(null, reqType);
+        } else {
+            if (reqType == PropertyType.REFERENCE) {
+                if (value instanceof NodeImpl) {
+                    NodeImpl targetNode = (NodeImpl)value;
+                    if (targetNode.isNodeType(QName.MIX_REFERENCEABLE)) {
+                        QValue qValue = QValue.create(targetNode.getUUID(), PropertyType.REFERENCE);
+                        setInternalValues(new QValue[]{qValue}, reqType);
+                    } else {
+                        throw new ValueFormatException("Target node must be of node type mix:referenceable");
+                    }
+                } else {
+                    String msg = "Incompatible Node object: " + value + "(" + safeGetJCRPath() + ")";
+                    log.debug(msg);
+                    throw new RepositoryException(msg);
+                }
+            } else {
+                throw new ValueFormatException("Property must be of type REFERENCE (" + safeGetJCRPath() + ")");
+            }
+        }
+    }
+
+    /**
+     * @see Property#getValue()
+     */
+    public Value getValue() throws ValueFormatException, RepositoryException {
+        QValue value = getQValue();
+        return ValueFormat.getJCRValue(value, session.getNamespaceResolver(), session.getValueFactory());
+    }
+
+    /**
+     * @see Property#getValues()
+     */
+    public Value[] getValues() throws ValueFormatException, RepositoryException {
+        QValue[] qValues = getQValues();
+        Value[] values = new Value[qValues.length];
+        for (int i = 0; i < qValues.length; i++) {
+            values[i] = ValueFormat.getJCRValue(qValues[i], session.getNamespaceResolver(), session.getValueFactory());
+        }
+        return values;
+    }
+
+    /**
+     * @see Property#getString()
+     */
+    public String getString() throws ValueFormatException, RepositoryException {
+        return getValue().getString();
+    }
+
+    /**
+     * @see Property#getStream()
+     */
+    public InputStream getStream() throws ValueFormatException, RepositoryException {
+        return getValue().getStream();
+    }
+
+    /**
+     * @see Property#getLong()
+     */
+    public long getLong() throws ValueFormatException, RepositoryException {
+        return getValue().getLong();
+    }
+
+    /**
+     * @see Property#getDouble()
+     */
+    public double getDouble() throws ValueFormatException, RepositoryException {
+        return getValue().getDouble();
+    }
+
+    /**
+     * @see Property#getDate()
+     */
+    public Calendar getDate() throws ValueFormatException, RepositoryException {
+        return getValue().getDate();
+    }
+
+    /**
+     * @see Property#getBoolean()
+     */
+    public boolean getBoolean() throws ValueFormatException, RepositoryException {
+        return getValue().getBoolean();
+    }
+
+    /**
+     * @see Property#getNode()
+     */
+    public Node getNode() throws ValueFormatException, RepositoryException {
+        QValue value = getQValue();
+        if (value.getType() == PropertyType.REFERENCE) {
+            return session.getNodeByUUID(value.getString());
+        } else {
+            throw new ValueFormatException("Property must be of type REFERENCE (" + safeGetJCRPath() + ")");
+        }
+    }
+
+    /**
+     * @see Property#getLength
+     */
+    public long getLength() throws ValueFormatException, RepositoryException {
+        return getLength(getQValue());
+    }
+
+    /**
+     * @see Property#getLengths
+     */
+    public long[] getLengths() throws ValueFormatException, RepositoryException {
+        QValue[] values = getQValues();
+        long[] lengths = new long[values.length];
+        for (int i = 0; i < values.length; i++) {
+            lengths[i] = getLength(values[i]);
+        }
+        return lengths;
+    }
+
+    /**
+     *
+     * @param value
+     * @return
+     * @throws RepositoryException
+     */
+    private long getLength(QValue value) throws RepositoryException {
+        long length = UNDEFINED_PROPERTY_LENGTH;
+        switch (value.getType()) {
+            case PropertyType.STRING:
+            case PropertyType.BINARY:
+            case PropertyType.LONG:
+            case PropertyType.DOUBLE:
+                length = value.getLength();
+                break;
+            case PropertyType.NAME:
+                Value jcrValue = ValueFormat.getJCRValue(value, session.getNamespaceResolver(), session.getValueFactory());
+                length = jcrValue.getString().length();
+                break;
+        }
+        return length;
+    }
+
+    /**
+     * @see javax.jcr.Property#getDefinition()
+     */
+    public PropertyDefinition getDefinition() throws RepositoryException {
+	checkStatus();
+        return definition;
+    }
+
+    /**
+     * @see javax.jcr.Property#getType()
+     */
+    public int getType() throws RepositoryException {
+	checkStatus();
+	return getPropertyState().getType();
+    }
+
+    //-----------------------------------------------------------< ItemImpl >---
+    /**
+     * Returns the QName defined with this <code>PropertyId</code>
+     *
+     * @return
+     * @see PropertyId#getQName()
+     * @see ItemImpl#getQName()
+     */
+    QName getQName() {
+        PropertyId propId = (PropertyId)getId();
+        return propId.getQName();
+    }
+
+    //------------------------------------------------------< check methods >---
+    /**
+     *
+     * @param multiValues
+     * @throws RepositoryException
+     */
+    private void checkIsWritable(boolean multiValues) throws RepositoryException {
+        // check common to properties and nodes
+        checkIsWritable();
+
+        // property specific check
+        if (definition.isMultiple() != multiValues) {
+            throw new ValueFormatException(getPath() + "Multivalue definition of " + safeGetJCRPath() + " does not match to given value(s).");
+        }
+    }
+
+    //---------------------------------------------< private implementation >---
+    /**
+     *
+     * @return
+     */
+    private boolean isMultiple() {
+	return definition.isMultiple();
+    }
+
+    /**
+     *
+     * @param defaultType
+     * @return
+     */
+    private int getRequiredType(int defaultType) {
+        // check type according to definition of this property
+        int reqType = definition.getRequiredType();
+        if (reqType == PropertyType.UNDEFINED) {
+            if (defaultType == PropertyType.UNDEFINED) {
+                reqType = PropertyType.STRING;
+            } else {
+                reqType = defaultType;
+            }
+        }
+        return reqType;
+    }
+
+    /**
+     *
+     * @return
+     * @throws ValueFormatException
+     * @throws RepositoryException
+     */
+    private QValue getQValue() throws ValueFormatException, RepositoryException {
+        checkStatus();
+        if (isMultiple()) {
+            throw new ValueFormatException(safeGetJCRPath() + " is multi-valued and can therefore only be retrieved as an array of values");
+        }
+        // avoid unnecessary object creation if possible
+        return getPropertyState().getValue();
+    }
+
+    /**
+     *
+     * @return
+     * @throws ValueFormatException
+     * @throws RepositoryException
+     */
+    private QValue[] getQValues() throws ValueFormatException, RepositoryException {
+        checkStatus();
+        if (!isMultiple()) {
+            throw new ValueFormatException(safeGetJCRPath() + " is not multi-valued and can therefore only be retrieved as single value");
+        }
+        // avoid unnecessary object creation if possible
+        return getPropertyState().getValues();
+    }
+
+    // DIFF JR: extracted from JR setValue-methods
+    /**
+     *
+     * @param value
+     * @param requiredType
+     * @throws RepositoryException
+     */
+    private void setValue(Value value, int requiredType) throws RepositoryException {
+        if (requiredType == PropertyType.UNDEFINED) {
+            // should never get here since calling methods assert valid type
+            throw new IllegalArgumentException("Property type of a value cannot be undefined (" + safeGetJCRPath() + ").");
+        }
+        if (value == null) {
+            setInternalValues(null, requiredType);
+            return;
+        }
+
+        QValue qValue;
+        if (requiredType != value.getType()) {
+            // type conversion required
+            Value v = ValueHelper.convert(value, requiredType, session.getValueFactory());
+            qValue = ValueFormat.getQValue(v, session.getNamespaceResolver());
+        } else {
+            // no type conversion required
+            qValue = ValueFormat.getQValue(value, session.getNamespaceResolver());
+        }
+        setInternalValues(new QValue[]{qValue}, requiredType);
+    }
+
+    // DIFF JACKRABBIT: protected void internalSetValue(QValue[] values, int type)
+    /**
+     *
+     * @param internalValues
+     * @param valueType
+     * @throws ConstraintViolationException
+     * @throws RepositoryException
+     */
+    private void setInternalValues(QValue[] qValues, int valueType) throws ConstraintViolationException, RepositoryException {
+        // check for null value
+        if (qValues == null) {
+            // setting a property to null removes it automatically
+            // DIFF JR: ((NodeImpl)getParent()).removeChildProperty
+            remove();
+            return;
+        }
+
+        // DIFF JR: compaction of internalV-array has been performed before.
+
+        // modify the state of this property
+        Operation op = SetPropertyValue.create(getPropertyState(), qValues, valueType);
+        itemStateMgr.execute(op);
+    }
+
+    /**
+     * DIFF JACKRABBIT (not present in JR)
+     *
+     * @return
+     */
+    private PropertyState getPropertyState() {
+        return (PropertyState) getItemState();
+    }
+
+    /**
+     *
+     * @return
+     */
+    private PropertyId getPropertyId() {
+        return (PropertyId) getId();
+    }
+}

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/PropertyImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/contrib/spi/jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/PropertyImpl.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url