Index: modeshape-jcr/src/main/java/org/modeshape/jcr/AbstractJcrNode.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/AbstractJcrNode.java (revision 1789) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/AbstractJcrNode.java (working copy) @@ -123,8 +123,12 @@ abstract class AbstractJcrNode extends AbstractJcrItem implements javax.jcr.Node } final Node nodeInfo() - throws ItemNotFoundException, AccessDeniedException, RepositoryException { - return cache.findNode(nodeId, location.getPath()); + throws InvalidItemStateException, AccessDeniedException, RepositoryException { + try { + return cache.findNode(nodeId, location.getPath()); + } catch (ItemNotFoundException infe) { + throw new InvalidItemStateException(infe.getMessage()); + } } final NodeEditor editorForParent() throws RepositoryException { Index: modeshape-jcr/src/main/java/org/modeshape/jcr/AbstractJcrProperty.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/AbstractJcrProperty.java (revision 1789) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/AbstractJcrProperty.java (working copy) @@ -251,6 +251,18 @@ abstract class AbstractJcrProperty extends AbstractJcrItem implements Property, * @see javax.jcr.Item#remove() */ public void remove() throws VersionException, LockException, ConstraintViolationException, RepositoryException { + Node parentNode = getParent(); + if (parentNode.isLocked()) { + Lock parentLock = parentNode.getLock(); + if (parentLock != null && parentLock.getLockToken() == null) { + throw new LockException(JcrI18n.lockTokenNotHeld.text(getPath())); + } + } + + if (!parentNode.isCheckedOut()) { + throw new VersionException(JcrI18n.nodeIsCheckedIn.text(getPath())); + } + editor().removeProperty(name); } Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrI18n.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrI18n.java (revision 1789) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrI18n.java (working copy) @@ -57,6 +57,7 @@ public final class JcrI18n { public static I18n failedToReadPropertyFromManifest; public static I18n rootNodeHasNoParent; + public static I18n rootNodeIsNotProperty; public static I18n childNodeAlreadyExists; public static I18n noNamespaceWithPrefix; Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrNode.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrNode.java (revision 1789) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrNode.java (working copy) @@ -29,6 +29,7 @@ import javax.jcr.RepositoryException; import javax.jcr.lock.Lock; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; +import javax.jcr.version.VersionException; import net.jcip.annotations.NotThreadSafe; import org.modeshape.graph.Location; import org.modeshape.graph.session.GraphSession.NodeId; @@ -108,6 +109,10 @@ class JcrNode extends AbstractJcrNode { } } + if (!parentNode.isCheckedOut()) { + throw new VersionException(JcrI18n.nodeIsCheckedIn.text(parentNode.getPath())); + } + JcrNodeDefinition nodeDefn = cache.nodeTypes().getNodeDefinition(nodeInfo().getPayload().getDefinitionId()); if (nodeDefn.isProtected()) { Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSession.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSession.java (revision 1789) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSession.java (working copy) @@ -68,6 +68,7 @@ import net.jcip.annotations.NotThreadSafe; import org.modeshape.common.util.CheckArg; import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.Graph; +import org.modeshape.graph.GraphI18n; import org.modeshape.graph.Location; import org.modeshape.graph.SecurityContext; import org.modeshape.graph.property.Binary; @@ -76,6 +77,7 @@ import org.modeshape.graph.property.NamespaceRegistry; import org.modeshape.graph.property.Path; import org.modeshape.graph.property.PathFactory; import org.modeshape.graph.property.ValueFactories; +import org.modeshape.graph.property.Path.Segment; import org.modeshape.graph.query.QueryBuilder; import org.modeshape.graph.query.model.QueryCommand; import org.modeshape.graph.query.model.TypeSystem; @@ -572,6 +574,107 @@ class JcrSession implements Session { } /** + * + * @throws IllegalArgumentException if absolutePath is empty or null. + * @see javax.jcr.Session#getItem(java.lang.String) + */ + public AbstractJcrNode getNode( String absolutePath ) throws PathNotFoundException, RepositoryException { + CheckArg.isNotEmpty(absolutePath, "absolutePath"); + // Return root node if path is "/" + Path path = executionContext.getValueFactories().getPathFactory().create(absolutePath); + if (path.isRoot()) { + return getRootNode(); + } + return getNode(path); + } + + /** + * Returns true if a node exists at the given path and is accessible to the current user. + * + * @param absolutePath the absolute path to the node + * @return true if a node exists at absolute path and is accessible to the current user. + * @throws IllegalArgumentException if absolutePath is empty or null. + */ + public boolean nodeExists( String absolutePath ) throws PathNotFoundException, RepositoryException { + CheckArg.isNotEmpty(absolutePath, "absolutePath"); + // Return root node if path is "/" + Path path = executionContext.getValueFactories().getPathFactory().create(absolutePath); + if (path.isRoot()) { + return true; + } + + try { + cache.findJcrNode(null, path); + return true; + } catch (ItemNotFoundException e) { + return false; + } + } + + /** + * @throws IllegalArgumentException if absolutePath is empty or null. + * @see javax.jcr.Session#getItem(java.lang.String) + */ + public AbstractJcrProperty getProperty( String absolutePath ) throws PathNotFoundException, RepositoryException { + CheckArg.isNotEmpty(absolutePath, "absolutePath"); + // Return root node if path is "/" + Path path = executionContext.getValueFactories().getPathFactory().create(absolutePath); + if (path.isRoot()) { + throw new PathNotFoundException(JcrI18n.rootNodeIsNotProperty.text()); + } + + Segment lastSegment = path.getLastSegment(); + if (lastSegment.hasIndex()) { + throw new RepositoryException(JcrI18n.pathCannotHaveSameNameSiblingIndex.text(absolutePath)); + } + + // This will throw a PNFE if the parent path does not exist + AbstractJcrNode parentNode = getNode(path.getParent()); + AbstractJcrProperty property = parentNode.getProperty(lastSegment.getName()); + + if (property == null) { + throw new PathNotFoundException(GraphI18n.pathNotFoundExceptionLowestExistingLocationFound.text(absolutePath, + parentNode.getPath())); + } + return property; + } + + /** + * Returns true if a property exists at the given path and is accessible to the current user. + * + * @param absolutePath the absolute path to the property + * @return true if a property exists at absolute path and is accessible to the current user. + * @throws IllegalArgumentException if absolutePath is empty or null. + */ + public boolean propertyExists( String absolutePath ) throws RepositoryException { + CheckArg.isNotEmpty(absolutePath, "absolutePath"); + // Return root node if path is "/" + Path path = executionContext.getValueFactories().getPathFactory().create(absolutePath); + if (path.isRoot()) { + return false; + } + + Segment lastSegment = path.getLastSegment(); + if (lastSegment.hasIndex()) { + throw new RepositoryException(JcrI18n.pathCannotHaveSameNameSiblingIndex.text(absolutePath)); + } + + try { + // This will throw a PNFE if the parent path does not exist + AbstractJcrNode parentNode = getNode(path.getParent()); + return parentNode.hasProperty(lastSegment.getName()); + } catch (PathNotFoundException pnfe) { + return false; + } + } + + public void removeItem( String absolutePath ) throws RepositoryException { + Item item = getItem(absolutePath); + + item.remove(); + } + + /** * {@inheritDoc} * * @see javax.jcr.Session#getLockTokens() @@ -613,7 +716,7 @@ class JcrSession implements Session { * * @see javax.jcr.Session#getRootNode() */ - public Node getRootNode() throws RepositoryException { + public AbstractJcrNode getRootNode() throws RepositoryException { return cache.findJcrRootNode(); } Index: modeshape-jcr/src/main/resources/org/modeshape/jcr/JcrI18n.properties =================================================================== --- modeshape-jcr/src/main/resources/org/modeshape/jcr/JcrI18n.properties (revision 1789) +++ modeshape-jcr/src/main/resources/org/modeshape/jcr/JcrI18n.properties (working copy) @@ -47,6 +47,7 @@ failedToReadPropertiesFromManifest = Error reading manifest properties: {0} failedToReadPropertyFromManifest = "{0}" property not found in manifest rootNodeHasNoParent = The root node has no parent node +rootNodeIsNotProperty = The root path "/" refers to the root node, not a property childNodeAlreadyExists = A child node named "{0}" already exists at node "{1}" noNamespaceWithPrefix = There is no namespace with prefix "{0}" Index: modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrNodeTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrNodeTest.java (revision 1789) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrNodeTest.java (working copy) @@ -816,5 +816,4 @@ public class AbstractJcrNodeTest extends AbstractJcrTest { Node autoProp = testNode.getNode("autoChild"); assertThat(autoProp.getPrimaryNodeType().getName(), is("nt:unstructured")); } - } Index: modeshape-jcr/src/test/java/org/modeshape/jcr/JcrSessionTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/JcrSessionTest.java (revision 1789) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/JcrSessionTest.java (working copy) @@ -237,6 +237,40 @@ public class JcrSessionTest extends AbstractSessionTest { public void shouldProvidePropertiesByPath() throws Exception { Item item = session.getItem("/a/b/booleanProperty"); assertThat(item, instanceOf(Property.class)); + + Property property = session.getProperty("/a/b/booleanProperty"); + assertThat(property, instanceOf(Property.class)); + } + + @Test + public void shouldProvideNodesByPath() throws Exception { + Node node = session.getNode("/a"); + assertThat(node, instanceOf(Node.class)); + node = session.getNode("/a/b"); + } + + @Test( expected = PathNotFoundException.class ) + public void shouldNotReturnPropertyAsNode() throws Exception { + assertThat(session.nodeExists("/a/b/booleanProperty"), is(false)); + session.getNode("/a/b/booleanProperty"); + } + + @Test( expected = PathNotFoundException.class ) + public void shouldNotReturnNonExistantNode() throws Exception { + assertThat(session.nodeExists("/a/b/argleBargle"), is(false)); + session.getNode("/a/b/argleBargle"); + } + + @Test( expected = PathNotFoundException.class ) + public void shouldNotReturnNodeAsProperty() throws Exception { + assertThat(session.propertyExists("/a/b"), is(false)); + session.getProperty("/a/b"); + } + + @Test( expected = PathNotFoundException.class ) + public void shouldNotReturnNonExistantProperty() throws Exception { + assertThat(session.propertyExists("/a/b/argleBargle"), is(false)); + session.getProperty("/a/b/argleBargle"); } @Test Index: modeshape-jcr/src/test/java/org/modeshape/jcr/ModeShapeTckTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/ModeShapeTckTest.java (revision 1789) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/ModeShapeTckTest.java (working copy) @@ -10,6 +10,7 @@ import java.util.Collections; import javax.jcr.AccessDeniedException; import javax.jcr.Credentials; import javax.jcr.ImportUUIDBehavior; +import javax.jcr.InvalidItemStateException; import javax.jcr.LoginException; import javax.jcr.Node; import javax.jcr.NodeIterator; @@ -19,6 +20,7 @@ import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.SimpleCredentials; +import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.version.Version; import javax.jcr.version.VersionException; @@ -896,4 +898,124 @@ public class ModeShapeTckTest extends AbstractJCRTest { sourceNode = targetNode.getNode(sourceName); sourceNode.checkout(); } + + public void testShouldNotAllowLockedNodeToBeRemoved() throws Exception { + session = helper.getReadWriteSession(); + + Node root = session.getRootNode(); + Node parentNode = root.addNode("lockedParent"); + parentNode.addMixin("mix:lockable"); + + Node targetNode = parentNode.addNode("lockedTarget"); + session.save(); + + parentNode.lock(true, true); + + Session session2 = helper.getReadWriteSession(); + Node targetNode2 = (Node)session2.getItem("/lockedParent/lockedTarget"); + + try { + targetNode2.remove(); + fail("Locked nodes should not be able to be removed"); + } catch (LockException le) { + // Success + } + + targetNode.remove(); + session.save(); + } + + public void testShouldNotAllowPropertyOfLockedNodeToBeRemoved() throws Exception { + session = helper.getReadWriteSession(); + + Node root = session.getRootNode(); + Node parentNode = root.addNode("lockedPropParent"); + parentNode.addMixin("mix:lockable"); + + Node targetNode = parentNode.addNode("lockedTarget"); + targetNode.setProperty("foo", "bar"); + session.save(); + + parentNode.lock(true, true); + + Session session2 = helper.getReadWriteSession(); + Property targetProp2 = (Property)session2.getItem("/lockedPropParent/lockedTarget/foo"); + + try { + targetProp2.remove(); + fail("Properties of locked nodes should not be able to be removed"); + } catch (LockException le) { + // Success + } + + targetNode.getProperty("foo").remove(); + session.save(); + } + + public void testShouldNotAllowCheckedInNodeToBeRemoved() throws Exception { + session = helper.getReadWriteSession(); + + Node root = session.getRootNode(); + Node parentNode = root.addNode("checkedInParent"); + parentNode.addMixin("mix:versionable"); + + Node targetNode = parentNode.addNode("checkedInTarget"); + session.save(); + + parentNode.checkin(); + + try { + targetNode.remove(); + fail("Checked in nodes should not be able to be removed"); + } catch (VersionException ve) { + // Success + } + + parentNode.checkout(); + targetNode.remove(); + session.save(); + } + + public void testShouldNotAllowPropertyOfCheckedInNodeToBeRemoved() throws Exception { + session = helper.getReadWriteSession(); + + Node root = session.getRootNode(); + Node parentNode = root.addNode("checkedInPropParent"); + parentNode.addMixin("mix:versionable"); + + Node targetNode = parentNode.addNode("checkedInTarget"); + Property targetProp = targetNode.setProperty("foo", "bar"); + session.save(); + + parentNode.checkin(); + + try { + targetProp.remove(); + fail("Properties of checked in nodes should not be able to be removed"); + } catch (VersionException ve) { + // Success + } + + parentNode.checkout(); + targetProp.remove(); + session.save(); + } + + public void testGetPathOnRemovedNodeShouldThrowException() throws Exception { + session = helper.getReadWriteSession(); + + Node root = session.getRootNode(); + Node parentNode = root.addNode("invalidItemStateTest"); + session.save(); + + parentNode.remove(); + + try { + parentNode.getPath(); + fail("getPath on removed node should throw InvalidItemStateException per section 7.1.3.3 of 1.0.1 spec"); + } catch (InvalidItemStateException iise) { + // Success + } + + } }