You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 10:17:27 UTC

[sling-org-apache-sling-testing-jcr-mock] annotated tag org.apache.sling.testing.jcr-mock-1.0.0 created (now 198474d)

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a change to annotated tag org.apache.sling.testing.jcr-mock-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-jcr-mock.git.


      at 198474d  (tag)
 tagging eea313ef9229d7416f3228173ead7bdc41eb47f0 (commit)
      by sseifert
      on Fri Oct 17 08:15:16 2014 +0000

- Log -----------------------------------------------------------------
org.apache.sling.testing.jcr-mock-1.0.0
-----------------------------------------------------------------------

This annotated tag includes the following new commits:

     new 3957224  SLING-4042 Donate sling-mock, jcr-mock, osgi-mock implementation
     new 6ca6ae1  svn:ignore
     new a518e9d  SLING-4042 mock documentation
     new 7144d67  SLING-4042 move all mock projects to mocks/ subdirectory
     new 3c1c4dd  SLING-4042 make sure JCR mock supports accessing data using multiple sessions
     new 38bb1c1  SLING-4042 minor optimization
     new 3e8c979  SLING-4042 add README files
     new 4e3739f  add/update SCM urls
     new e3c1ed4  [maven-release-plugin] prepare release org.apache.sling.testing.jcr-mock-1.0.0
     new eea313e  [maven-release-plugin]  copy for tag org.apache.sling.testing.jcr-mock-1.0.0

The 10 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


-- 
To stop receiving notification emails like this one, please contact
['"commits@sling.apache.org" <co...@sling.apache.org>'].

[sling-org-apache-sling-testing-jcr-mock] 03/10: SLING-4042 mock documentation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.testing.jcr-mock-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-jcr-mock.git

commit a518e9d8a45ce86aa41d25919bb76bf0986edb15
Author: sseifert <ss...@unknown>
AuthorDate: Mon Oct 13 13:54:56 2014 +0000

    SLING-4042 mock documentation
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/testing/jcr-mock@1631393 13f79535-47bb-0310-9956-ffa450edef68
---
 src/site/markdown/index.md | 38 --------------------------------------
 src/site/markdown/usage.md | 17 -----------------
 2 files changed, 55 deletions(-)

diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
deleted file mode 100644
index 544541e..0000000
--- a/src/site/markdown/index.md
+++ /dev/null
@@ -1,38 +0,0 @@
-## About JCR Mocks
-
-Mock implementation of selected JCR APIs.
-
-### Maven Dependency
-
-```xml
-<dependency>
-  <groupId>org.apache.sling</groupId>
-  <artifactId>org.apache.sling.testing.jcr-mock</artifactId>
-  <version>1.0.0-SNAPHOT</version>
-</dependency>
-```
-
-### Documentation
-
-* [Usage](usage.html)
-* [API Documentation](apidocs/)
-* [Changelog](changes-report.html)
-
-### Implemented mock features
-
-The mock implementation supports:
-
-* Reading and writing all data (primitive values, arrays, binary data) via the JCR API
-* Creating any number of nodes and properties (stored in-memory in a hash map)
-* Register namespaces
-
-The following features are *not supported*:
-
-* Node types are supported in the API, but their definitions and constraints are not applied
-* Versioning not supported
-* Search not supported
-* Transactions not supported
-* Observation events can be registered but are ignored
-* Access control always grants access
-* Exporting/Importing data via document and system views not supported 
-* Workspace management methods not supported
diff --git a/src/site/markdown/usage.md b/src/site/markdown/usage.md
deleted file mode 100644
index 9ae3ce7..0000000
--- a/src/site/markdown/usage.md
+++ /dev/null
@@ -1,17 +0,0 @@
-## Usage
-
-### Getting JCR mock objects
-
-The factory class `MockJcr` allows to instantiate the different mock implementations.
-
-Example:
-
-```java
-// get session
-Session session = MockJcr.newSession();
-
-// get repository
-Repository repository = MockJcr.newRepository();
-```
-
-The repository is empty and contains only the root node. You can use the JCR API to fill it with content.

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-jcr-mock] 01/10: SLING-4042 Donate sling-mock, jcr-mock, osgi-mock implementation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.testing.jcr-mock-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-jcr-mock.git

commit 39572249976327b490288b8f876908d1e49cbd1f
Author: sseifert <ss...@unknown>
AuthorDate: Mon Oct 13 11:54:39 2014 +0000

    SLING-4042 Donate sling-mock, jcr-mock, osgi-mock implementation
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/testing/jcr-mock@1631356 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  91 ++++
 .../sling/testing/mock/jcr/AbstractItem.java       | 129 ++++++
 .../apache/sling/testing/mock/jcr/ItemFilter.java  |  31 ++
 .../org/apache/sling/testing/mock/jcr/MockJcr.java |  62 +++
 .../testing/mock/jcr/MockNamespaceRegistry.java    |  72 +++
 .../apache/sling/testing/mock/jcr/MockNode.java    | 499 +++++++++++++++++++++
 .../sling/testing/mock/jcr/MockNodeType.java       | 155 +++++++
 .../testing/mock/jcr/MockNodeTypeManager.java      | 103 +++++
 .../sling/testing/mock/jcr/MockNodeTypes.java      |  49 ++
 .../testing/mock/jcr/MockObservationManager.java   |  65 +++
 .../sling/testing/mock/jcr/MockProperty.java       | 306 +++++++++++++
 .../sling/testing/mock/jcr/MockRepository.java     |  85 ++++
 .../apache/sling/testing/mock/jcr/MockSession.java | 354 +++++++++++++++
 .../sling/testing/mock/jcr/MockWorkspace.java      | 149 ++++++
 .../sling/testing/mock/jcr/ResourceUtil.java       | 219 +++++++++
 .../sling/testing/mock/jcr/package-info.java       |  23 +
 src/site/markdown/index.md                         |  38 ++
 src/site/markdown/usage.md                         |  17 +
 .../sling/testing/mock/jcr/AbstractItemTest.java   |  95 ++++
 .../sling/testing/mock/jcr/MockNodeTest.java       | 129 ++++++
 .../sling/testing/mock/jcr/MockPropertyTest.java   | 240 ++++++++++
 .../sling/testing/mock/jcr/MockRepositoryTest.java |  63 +++
 .../sling/testing/mock/jcr/MockSessionTest.java    | 228 ++++++++++
 .../sling/testing/mock/jcr/MockWorkspaceTest.java  |  64 +++
 .../sling/testing/mock/jcr/ResourceUtilTest.java   | 221 +++++++++
 25 files changed, 3487 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..6bd600b
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>22</version>
+        <relativePath>../../parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.sling.testing.jcr-mock</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <name>Apache Sling Testing JCR Mock</name>
+    <description>Mock implementation of selected JCR APIs.</description>
+
+    <properties>
+        <sling.java.version>6</sling.java.version>
+    </properties>
+
+    <dependencies>
+    
+        <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+            <version>2.0</version>
+            <scope>compile</scope>
+        </dependency>
+    
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-jcr-commons</artifactId>
+            <version>2.8.0</version>
+            <scope>compile</scope>
+        </dependency>
+    
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>15.0</version>
+            <scope>compile</scope>
+        </dependency>
+    
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.0.1</version>
+            <scope>compile</scope>
+        </dependency>
+    
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.4</version>
+            <scope>test</scope>
+        </dependency>  
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+  
+    </dependencies>
+  
+</project>
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/AbstractItem.java b/src/main/java/org/apache/sling/testing/mock/jcr/AbstractItem.java
new file mode 100644
index 0000000..0eb92f3
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/AbstractItem.java
@@ -0,0 +1,129 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import javax.jcr.Item;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.ItemVisitor;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Mock {@link Item} implementation.
+ */
+abstract class AbstractItem implements Item {
+
+    private final String path;
+    private final Session session;
+
+    public AbstractItem(final String path, final Session session) {
+        this.path = path;
+        this.session = session;
+    }
+
+    @Override
+    public String getName() {
+        return ResourceUtil.getName(this.path);
+    }
+
+    @Override
+    public String getPath() {
+        return this.path;
+    }
+
+    @Override
+    public Node getParent() throws RepositoryException {
+        return (Node) getSession().getItem(ResourceUtil.getParent(this.path));
+    }
+
+    @Override
+    public Session getSession() {
+        return this.session;
+    }
+
+    @Override
+    public boolean isModified() {
+        return false;
+    }
+
+    @Override
+    public boolean isNew() {
+        return false;
+    }
+
+    @Override
+    public Item getAncestor(final int depth) throws RepositoryException {
+        if (depth < 0 || depth > getDepth()) {
+            throw new ItemNotFoundException();
+        }
+        return this.session.getItem(ResourceUtil.getParent(this.path, depth));
+    }
+
+    protected String makeAbsolutePath(final String relativePath) {
+        String absolutePath = relativePath;
+        // ensure the path is absolute and normalized
+        if (!StringUtils.startsWith(absolutePath, "/")) {
+            absolutePath = this.path + "/" + absolutePath; // NOPMD
+        }
+        return ResourceUtil.normalize(absolutePath);
+    }
+
+    protected MockSession getMockedSession() {
+        return (MockSession) this.session;
+    }
+
+    @Override
+    public void remove() throws RepositoryException {
+        getSession().removeItem(getPath());
+    }
+
+    @Override
+    public int getDepth() throws RepositoryException {
+        if (StringUtils.equals("/", this.path)) {
+            return 0;
+        } else {
+            return StringUtils.countMatches(this.path, "/");
+        }
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public void accept(final ItemVisitor visitor) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isSame(final Item otherItem) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void refresh(final boolean keepChanges) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void save() {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/ItemFilter.java b/src/main/java/org/apache/sling/testing/mock/jcr/ItemFilter.java
new file mode 100644
index 0000000..2e144a1
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/ItemFilter.java
@@ -0,0 +1,31 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import javax.jcr.Item;
+import javax.jcr.RepositoryException;
+
+/**
+ * Used internally for filtering items in JCR data map.
+ */
+interface ItemFilter {
+
+    boolean accept(Item item) throws RepositoryException;
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockJcr.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockJcr.java
new file mode 100644
index 0000000..11c3c1d
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockJcr.java
@@ -0,0 +1,62 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+/**
+ * Factory for mock JCR objects.
+ */
+public final class MockJcr {
+
+    /**
+     * Default workspace name
+     */
+    public static final String DEFAULT_WORKSPACE = "mockedWorkspace";
+
+    private MockJcr() {
+        // static methods only
+    }
+
+    /**
+     * Create a new mocked in-memory JCR repository. Beware: each session has
+     * its own data store.
+     * @return JCR repository
+     */
+    public static Repository newRepository() {
+        return new MockRepository();
+    }
+
+    /**
+     * Create a new mocked in-memory JCR session. It contains only the root
+     * node. All data of the session is thrown away if it gets garbage
+     * collected.
+     * @return JCR session
+     */
+    public static Session newSession() {
+        try {
+            return newRepository().login();
+        } catch (RepositoryException ex) {
+            throw new RuntimeException("Creating mocked JCR session failed.", ex);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockNamespaceRegistry.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockNamespaceRegistry.java
new file mode 100644
index 0000000..91b194f
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockNamespaceRegistry.java
@@ -0,0 +1,72 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import java.util.Set;
+
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.RepositoryException;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+
+/**
+ * Mock {@link NamespaceRegistry} implementation.
+ */
+class MockNamespaceRegistry implements NamespaceRegistry {
+
+    private final BiMap<String, String> namespacePrefixMapping = HashBiMap.create();
+
+    public MockNamespaceRegistry() {
+        this.namespacePrefixMapping.put("jcr", "http://www.jcp.org/jcr/1.0");
+    }
+
+    @Override
+    public String getURI(final String prefix) {
+        return this.namespacePrefixMapping.get(prefix);
+    }
+
+    @Override
+    public String getPrefix(final String uri) {
+        return this.namespacePrefixMapping.inverse().get(uri);
+    }
+
+    @Override
+    public void registerNamespace(final String prefix, final String uri) {
+        this.namespacePrefixMapping.put(prefix, uri);
+    }
+
+    @Override
+    public void unregisterNamespace(final String prefix) {
+        this.namespacePrefixMapping.remove(prefix);
+    }
+
+    @Override
+    public String[] getPrefixes() throws RepositoryException {
+        Set<String> keys = this.namespacePrefixMapping.keySet();
+        return keys.toArray(new String[keys.size()]);
+    }
+
+    @Override
+    public String[] getURIs() throws RepositoryException {
+        Set<String> values = this.namespacePrefixMapping.values();
+        return values.toArray(new String[values.size()]);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockNode.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockNode.java
new file mode 100644
index 0000000..41350d6
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockNode.java
@@ -0,0 +1,499 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.Calendar;
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+import javax.jcr.Binary;
+import javax.jcr.Item;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RangeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.lock.Lock;
+import javax.jcr.nodetype.NodeDefinition;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionHistory;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter;
+import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter;
+
+/**
+ * Mock {@link Node} implementation
+ */
+class MockNode extends AbstractItem implements Node {
+
+    private final UUID uuid = UUID.randomUUID();
+    private final NodeType nodeType;
+
+    public MockNode(final String path, final Session session, final NodeType nodeType) {
+        super(path, session);
+        this.nodeType = nodeType;
+    }
+
+    @Override
+    public Node addNode(final String relPath) throws RepositoryException {
+        return addNode(relPath, JcrConstants.NT_UNSTRUCTURED);
+    }
+
+    @Override
+    public Node addNode(final String relPath, final String primaryNodeTypeName) throws RepositoryException {
+        String path = makeAbsolutePath(relPath);
+        Node node = new MockNode(path, getSession(), new MockNodeType(primaryNodeTypeName));
+        getMockedSession().addItem(node);
+        return node;
+    }
+
+    @Override
+    public Node getNode(final String relPath) throws RepositoryException {
+        String path = makeAbsolutePath(relPath);
+        return getSession().getNode(path);
+    }
+
+    @Override
+    public NodeIterator getNodes() throws RepositoryException {
+        RangeIterator items = getMockedSession().listChildren(getPath(), new ItemFilter() {
+            @Override
+            public boolean accept(final Item item) {
+                return item instanceof Node;
+            }
+        });
+        return new NodeIteratorAdapter(items, items.getSize());
+    }
+
+    @Override
+    public NodeIterator getNodes(final String namePattern) throws RepositoryException {
+        final Pattern pattern = Pattern.compile(namePattern);
+        RangeIterator items = getMockedSession().listChildren(getPath(), new ItemFilter() {
+            @Override
+            public boolean accept(final Item item) throws RepositoryException {
+                return (item instanceof Node) && pattern.matcher(item.getName()).matches();
+            }
+        });
+        return new NodeIteratorAdapter(items, items.getSize());
+    }
+
+    @Override
+    public PropertyIterator getProperties() throws RepositoryException {
+        RangeIterator items = getMockedSession().listChildren(getPath(), new ItemFilter() {
+            @Override
+            public boolean accept(final Item item) {
+                return item instanceof Property;
+            }
+        });
+        return new PropertyIteratorAdapter(items, items.getSize());
+    }
+
+    @Override
+    public PropertyIterator getProperties(final String namePattern) throws RepositoryException {
+        final Pattern pattern = Pattern.compile(namePattern);
+        RangeIterator items = getMockedSession().listChildren(getPath(), new ItemFilter() {
+            @Override
+            public boolean accept(final Item item) throws RepositoryException {
+                return (item instanceof Property) && pattern.matcher(item.getName()).matches();
+            }
+        });
+        return new PropertyIteratorAdapter(items, items.getSize());
+    }
+
+    @Override
+    public Property getProperty(final String relPath) throws RepositoryException {
+        String path = makeAbsolutePath(relPath);
+        return getSession().getProperty(path);
+    }
+
+    @Override
+    public String getIdentifier() {
+        return this.uuid.toString();
+    }
+
+    @Override
+    public String getUUID() {
+        return getIdentifier();
+    }
+
+    @Override
+    public boolean hasNode(final String relPath) throws RepositoryException {
+        String path = makeAbsolutePath(relPath);
+        return getSession().nodeExists(path);
+    }
+
+    @Override
+    public boolean hasNodes() throws RepositoryException {
+        return getNodes().hasNext();
+    }
+
+    @Override
+    public boolean hasProperties() throws RepositoryException {
+        return getProperties().hasNext();
+    }
+
+    @Override
+    public boolean hasProperty(final String relPath) throws RepositoryException {
+        String path = makeAbsolutePath(relPath);
+        return getSession().propertyExists(path);
+    }
+
+    @Override
+    public Property setProperty(final String name, final Value value) throws RepositoryException {
+        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        property.setValue(value);
+        getMockedSession().addItem(property);
+        return property;
+    }
+
+    @Override
+    public Property setProperty(final String name, final Value[] values) throws RepositoryException {
+        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        property.setValue(values);
+        getMockedSession().addItem(property);
+        return property;
+    }
+
+    @Override
+    public Property setProperty(final String name, final String[] values) throws RepositoryException {
+        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        property.setValue(values);
+        getMockedSession().addItem(property);
+        return property;
+    }
+
+    @Override
+    public Property setProperty(final String name, final String value) throws RepositoryException {
+        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        property.setValue(value);
+        getMockedSession().addItem(property);
+        return property;
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public Property setProperty(final String name, final InputStream value) throws RepositoryException {
+        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        property.setValue(value);
+        getMockedSession().addItem(property);
+        return property;
+    }
+
+    @Override
+    public Property setProperty(final String name, final boolean value) throws RepositoryException {
+        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        property.setValue(value);
+        getMockedSession().addItem(property);
+        return property;
+    }
+
+    @Override
+    public Property setProperty(final String name, final double value) throws RepositoryException {
+        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        property.setValue(value);
+        getMockedSession().addItem(property);
+        return property;
+    }
+
+    @Override
+    public Property setProperty(final String name, final long value) throws RepositoryException {
+        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        property.setValue(value);
+        getMockedSession().addItem(property);
+        return property;
+    }
+
+    @Override
+    public Property setProperty(final String name, final Calendar value) throws RepositoryException {
+        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        property.setValue(value);
+        getMockedSession().addItem(property);
+        return property;
+    }
+
+    @Override
+    public Property setProperty(final String name, final Node value) throws RepositoryException {
+        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        property.setValue(value);
+        getMockedSession().addItem(property);
+        return property;
+    }
+
+    @Override
+    public Property setProperty(final String name, final Binary value) throws RepositoryException {
+        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        property.setValue(value);
+        getMockedSession().addItem(property);
+        return property;
+    }
+
+    @Override
+    public Property setProperty(final String name, final BigDecimal value) throws RepositoryException {
+        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        property.setValue(value);
+        getMockedSession().addItem(property);
+        return property;
+    }
+
+    @Override
+    public boolean isNode() {
+        return true;
+    }
+
+    @Override
+    public boolean isNodeType(final String nodeTypeName) throws RepositoryException {
+        return this.nodeType.isNodeType(nodeTypeName);
+    }
+
+    @Override
+    public NodeType getPrimaryNodeType() {
+        return this.nodeType;
+    }
+
+    @Override
+    public Item getPrimaryItem() throws RepositoryException {
+        // support "jcr:content" node and "jcr:data" property as primary items
+        if (hasProperty(JcrConstants.JCR_DATA)) {
+            return getProperty(JcrConstants.JCR_DATA);
+        } else if (hasNode(JcrConstants.JCR_CONTENT)) {
+            return getNode(JcrConstants.JCR_CONTENT);
+        } else {
+            throw new ItemNotFoundException();
+        }
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public Property setProperty(final String name, final Value value, final int type) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(final String name, final Value[] values, final int type) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(final String name, final String[] values, final int type) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(final String name, final String value, final int type) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void addMixin(final String pMixinName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean canAddMixin(final String pMixinName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void cancelMerge(final Version pVersion) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Version checkin() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void checkout() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void doneMerge(final Version pVersion) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Version getBaseVersion() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getCorrespondingNodePath(final String workspaceName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeDefinition getDefinition() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getIndex() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Lock getLock() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeType[] getMixinNodeTypes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PropertyIterator getReferences() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public VersionHistory getVersionHistory() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean holdsLock() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isCheckedOut() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isLocked() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Lock lock(final boolean isDeep, final boolean isSessionScoped) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeIterator merge(final String srcWorkspace, final boolean bestEffort) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void orderBefore(final String srcChildRelPath, final String destChildRelPath) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void removeMixin(final String mixinName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void restore(final String versionName, final boolean removeExisting) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void restore(final Version version, final boolean removeExisting) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void restore(final Version version, final String relPath, final boolean removeExisting) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void restoreByLabel(final String versionLabel, final boolean removeExisting) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void unlock() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void update(final String srcWorkspaceName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void followLifecycleTransition(final String transition) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String[] getAllowedLifecycleTransistions() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeIterator getNodes(final String[] nameGlobs) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PropertyIterator getProperties(final String[] nameGlobs) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PropertyIterator getReferences(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeIterator getSharedSet() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PropertyIterator getWeakReferences() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PropertyIterator getWeakReferences(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void removeShare() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void removeSharedSet() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setPrimaryType(final String pNodeTypeName) {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockNodeType.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockNodeType.java
new file mode 100644
index 0000000..7033df5
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockNodeType.java
@@ -0,0 +1,155 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import javax.jcr.Value;
+import javax.jcr.nodetype.NodeDefinition;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.NodeTypeIterator;
+import javax.jcr.nodetype.PropertyDefinition;
+
+/**
+ * Mock {@link NodeType} implementation.
+ */
+class MockNodeType implements NodeType {
+
+    private final String name;
+
+    public MockNodeType(final String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String getName() {
+        return this.name;
+    }
+
+    @Override
+    public boolean isNodeType(final String nodeTypeName) {
+        // node type inheritance not supported
+        return this.name.equals(nodeTypeName);
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public boolean canAddChildNode(final String childNodeName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean canAddChildNode(final String childNodeName, final String nodeTypeName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean canRemoveItem(final String itemName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean canSetProperty(final String propertyName, final Value value) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean canSetProperty(final String propertyName, final Value[] values) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeDefinition[] getChildNodeDefinitions() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeDefinition[] getDeclaredChildNodeDefinitions() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PropertyDefinition[] getDeclaredPropertyDefinitions() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeType[] getDeclaredSupertypes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getPrimaryItemName() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PropertyDefinition[] getPropertyDefinitions() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeType[] getSupertypes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean hasOrderableChildNodes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isMixin() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean canRemoveNode(final String nodeName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean canRemoveProperty(final String propertyName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeTypeIterator getDeclaredSubtypes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeTypeIterator getSubtypes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String[] getDeclaredSupertypeNames() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isAbstract() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isQueryable() {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockNodeTypeManager.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockNodeTypeManager.java
new file mode 100644
index 0000000..edec788
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockNodeTypeManager.java
@@ -0,0 +1,103 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import javax.jcr.nodetype.NodeDefinitionTemplate;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.NodeTypeDefinition;
+import javax.jcr.nodetype.NodeTypeIterator;
+import javax.jcr.nodetype.NodeTypeManager;
+import javax.jcr.nodetype.NodeTypeTemplate;
+import javax.jcr.nodetype.PropertyDefinitionTemplate;
+
+/**
+ * Mock {@link NodeTypeManager} implementation.
+ */
+class MockNodeTypeManager implements NodeTypeManager {
+
+    @Override
+    public NodeType getNodeType(String nodeTypeName) {
+        // accept all node types and return a mock
+        return new MockNodeType(nodeTypeName);
+    }
+
+    @Override
+    public boolean hasNodeType(String name) {
+        // accept all node types
+        return true;
+    }
+
+    // --- unsupported operations ---
+
+    @Override
+    public NodeTypeIterator getAllNodeTypes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeTypeIterator getPrimaryNodeTypes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeTypeIterator getMixinNodeTypes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeTypeTemplate createNodeTypeTemplate() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeTypeTemplate createNodeTypeTemplate(NodeTypeDefinition ntd) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeDefinitionTemplate createNodeDefinitionTemplate() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PropertyDefinitionTemplate createPropertyDefinitionTemplate() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeType registerNodeType(NodeTypeDefinition ntd, boolean allowUpdate) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeTypeIterator registerNodeTypes(NodeTypeDefinition[] ntds, boolean allowUpdate) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void unregisterNodeType(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void unregisterNodeTypes(String[] names) {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockNodeTypes.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockNodeTypes.java
new file mode 100644
index 0000000..1b081b6
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockNodeTypes.java
@@ -0,0 +1,49 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import javax.jcr.nodetype.NodeType;
+
+import org.apache.jackrabbit.JcrConstants;
+
+/**
+ * Collection of mocked node type instances.
+ */
+public final class MockNodeTypes {
+
+    private MockNodeTypes() {
+        // constants only
+    }
+
+    /**
+     * Node type NT_UNSTRUCTURED
+     */
+    public static final NodeType NT_UNSTRUCTURED = new MockNodeType(JcrConstants.NT_UNSTRUCTURED);
+
+    /**
+     * Node type NT_FOLDER
+     */
+    public static final NodeType NT_FOLDER = new MockNodeType(JcrConstants.NT_FOLDER);
+
+    /**
+     * Node type NT_FILE
+     */
+    public static final NodeType NT_FILE = new MockNodeType(JcrConstants.NT_FILE);
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockObservationManager.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockObservationManager.java
new file mode 100644
index 0000000..4988fa0
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockObservationManager.java
@@ -0,0 +1,65 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.observation.EventJournal;
+import javax.jcr.observation.EventListener;
+import javax.jcr.observation.EventListenerIterator;
+import javax.jcr.observation.ObservationManager;
+
+/**
+ * Mock {@link ObservationManager} implementation.
+ */
+class MockObservationManager implements ObservationManager {
+
+    @Override
+    public void addEventListener(final EventListener listener, final int eventTypes, final String absPath,
+            final boolean isDeep, final String[] uuid, final String[] nodeTypeName, final boolean noLocal) {
+        // do nothing
+    }
+
+    @Override
+    public void removeEventListener(final EventListener listener) {
+        // do nothing
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public EventListenerIterator getRegisteredEventListeners() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setUserData(final String userData) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public EventJournal getEventJournal() throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public EventJournal getEventJournal(final int eventTypes, final String absPath, final boolean isDeep,
+            final String[] uuid, final String[] nodeTypeName) {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockProperty.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockProperty.java
new file mode 100644
index 0000000..468a840
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockProperty.java
@@ -0,0 +1,306 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.Calendar;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.PropertyDefinition;
+
+import org.apache.jackrabbit.value.BinaryValue;
+
+/**
+ * Mock {@link Property} implementation
+ */
+class MockProperty extends AbstractItem implements Property {
+
+    private Value[] values;
+    private boolean isMultiple;
+
+    public MockProperty(final String path, final Session session) throws RepositoryException {
+        super(path, session);
+        this.values = new Value[] { getSession().getValueFactory().createValue("") };
+    }
+
+    private Value internalGetValue() throws ValueFormatException {
+        if (this.values.length > 1) {
+            throw new ValueFormatException(this
+                    + " is a multi-valued property, so it's values can only be retrieved as an array");
+        } else {
+            return this.values[0];
+        }
+    }
+
+    @Override
+    public Value getValue() throws ValueFormatException {
+        return internalGetValue();
+    }
+
+    @Override
+    public Value[] getValues() {
+        Value[] valuesCopy = new Value[this.values.length];
+        for (int i = 0; i < this.values.length; i++) {
+            valuesCopy[i] = this.values[i];
+        }
+        return valuesCopy;
+    }
+
+    @Override
+    public void setValue(final Value newValue) {
+        this.values = new Value[] { newValue };
+        this.isMultiple = false;
+    }
+
+    @Override
+    public void setValue(final Value[] newValues) {
+        this.values = new Value[newValues.length];
+        for (int i = 0; i < newValues.length; i++) {
+            this.values[i] = newValues[i];
+        }
+        this.isMultiple = true;
+    }
+
+    @Override
+    public void setValue(final String newValue) throws RepositoryException {
+        this.values = new Value[] { getSession().getValueFactory().createValue(newValue) };
+        this.isMultiple = false;
+    }
+
+    @Override
+    public void setValue(final String[] newValues) throws RepositoryException {
+        this.values = new Value[newValues.length];
+        for (int i = 0; i < newValues.length; i++) {
+            this.values[i] = getSession().getValueFactory().createValue(newValues[i]);
+        }
+        this.isMultiple = true;
+    }
+
+    @Override
+    public void setValue(final InputStream newValue) throws RepositoryException {
+        this.values = new Value[] { new BinaryValue(newValue) };
+        this.isMultiple = false;
+    }
+
+    @Override
+    public void setValue(final long newValue) throws RepositoryException {
+        this.values = new Value[] { getSession().getValueFactory().createValue(newValue) };
+        this.isMultiple = false;
+    }
+
+    @Override
+    public void setValue(final double newValue) throws RepositoryException {
+        this.values = new Value[] { getSession().getValueFactory().createValue(newValue) };
+        this.isMultiple = false;
+    }
+
+    @Override
+    public void setValue(final Calendar newValue) throws RepositoryException {
+        this.values = new Value[] { getSession().getValueFactory().createValue(newValue) };
+        this.isMultiple = false;
+    }
+
+    @Override
+    public void setValue(final boolean newValue) throws RepositoryException {
+        this.values = new Value[] { getSession().getValueFactory().createValue(newValue) };
+        this.isMultiple = false;
+    }
+
+    @Override
+    public void setValue(final Node newValue) throws RepositoryException {
+        this.values = new Value[] { getSession().getValueFactory().createValue(newValue) };
+        this.isMultiple = false;
+    }
+
+    @Override
+    public void setValue(final Binary newValue) throws RepositoryException {
+        this.values = new Value[] { new BinaryValue(newValue) };
+        this.isMultiple = false;
+    }
+
+    @Override
+    public void setValue(final BigDecimal newValue) throws RepositoryException {
+        this.values = new Value[] { getSession().getValueFactory().createValue(newValue) };
+        this.isMultiple = false;
+    }
+
+    @Override
+    public boolean getBoolean() throws RepositoryException {
+        return internalGetValue().getBoolean();
+    }
+
+    @Override
+    public Calendar getDate() throws RepositoryException {
+        return internalGetValue().getDate();
+    }
+
+    @Override
+    public double getDouble() throws RepositoryException {
+        return internalGetValue().getDouble();
+    }
+
+    @Override
+    public long getLong() throws RepositoryException {
+        return internalGetValue().getLong();
+    }
+
+    @Override
+    public String getString() throws RepositoryException {
+        return internalGetValue().getString();
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public InputStream getStream() throws RepositoryException {
+        return internalGetValue().getStream();
+    }
+
+    @Override
+    public Binary getBinary() throws RepositoryException {
+        return internalGetValue().getBinary();
+    }
+
+    @Override
+    public BigDecimal getDecimal() throws RepositoryException {
+        return internalGetValue().getDecimal();
+    }
+
+    @Override
+    public int getType() throws RepositoryException {
+        return this.values[0].getType();
+    }
+
+    @Override
+    public long getLength() throws RepositoryException {
+        return getValue().getString().length();
+    }
+
+    @Override
+    public long[] getLengths() throws RepositoryException {
+        long[] lengths = new long[this.values.length];
+        for (int i = 0; i < this.values.length; i++) {
+            lengths[i] = this.values[i].getString().length();
+        }
+        return lengths;
+    }
+
+    @Override
+    public boolean isNode() {
+        return false;
+    }
+
+    @Override
+    public boolean isMultiple() {
+        return this.isMultiple;
+    }
+
+    @Override
+    public PropertyDefinition getDefinition() {
+        return new MockPropertyDefinition();
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public Node getNode() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property getProperty() {
+        throw new UnsupportedOperationException();
+    }
+
+    private final class MockPropertyDefinition implements PropertyDefinition {
+
+        @Override
+        public boolean isMultiple() {
+            return MockProperty.this.isMultiple();
+        }
+
+        // --- unsupported operations ---
+        @Override
+        public Value[] getDefaultValues() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int getRequiredType() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String[] getValueConstraints() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public NodeType getDeclaringNodeType() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String getName() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int getOnParentVersion() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean isAutoCreated() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean isMandatory() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean isProtected() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String[] getAvailableQueryOperators() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean isFullTextSearchable() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean isQueryOrderable() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockRepository.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockRepository.java
new file mode 100644
index 0000000..324820e
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockRepository.java
@@ -0,0 +1,85 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import javax.jcr.Credentials;
+import javax.jcr.Repository;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+/**
+ * Mock {@link Repository} implementation. The data is stored inside the mocked
+ * session, not the repository - so it is not possible to open multiple session
+ * to access the same data in this mock implementation.
+ */
+class MockRepository implements Repository {
+
+    @Override
+    public Session login() {
+        return new MockSession(this);
+    }
+
+    @Override
+    public Session login(final String workspaceName) {
+        return login();
+    }
+
+    @Override
+    public Session login(final Credentials credentials) {
+        return login();
+    }
+
+    @Override
+    public Session login(final Credentials credentials, final String workspaceName) {
+        return login();
+    }
+
+    @Override
+    public String[] getDescriptorKeys() {
+        return ArrayUtils.EMPTY_STRING_ARRAY;
+    }
+
+    @Override
+    public boolean isStandardDescriptor(final String key) {
+        return false;
+    }
+
+    @Override
+    public boolean isSingleValueDescriptor(final String key) {
+        return false;
+    }
+
+    @Override
+    public Value getDescriptorValue(final String key) {
+        return null;
+    }
+
+    @Override
+    public Value[] getDescriptorValues(final String key) { // NOPMD
+        return null; // NOPMD
+    }
+
+    @Override
+    public String getDescriptor(final String key) {
+        return null;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockSession.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockSession.java
new file mode 100644
index 0000000..5567318
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockSession.java
@@ -0,0 +1,354 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import javax.jcr.Credentials;
+import javax.jcr.Item;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.RangeIterator;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.ValueFactory;
+import javax.jcr.Workspace;
+import javax.jcr.retention.RetentionManager;
+import javax.jcr.security.AccessControlManager;
+
+import org.apache.jackrabbit.commons.iterator.RangeIteratorAdapter;
+import org.apache.jackrabbit.value.ValueFactoryImpl;
+import org.xml.sax.ContentHandler;
+
+/**
+ * Mock {@link Session} implementation. This instance holds the JCR data in a
+ * simple ordered map.
+ */
+class MockSession implements Session {
+
+    private final Repository repository;
+    private final Workspace workspace;
+
+    // Use linked hashmap to ensure ordering when adding items is preserved.
+    private final Map<String, Item> items = new LinkedHashMap<String, Item>();
+
+    public MockSession(final Repository repository) {
+        this.repository = repository;
+        this.workspace = new MockWorkspace(this);
+        this.items.put("/", new MockNode("/", this, MockNodeTypes.NT_UNSTRUCTURED));
+    }
+
+    @Override
+    public ValueFactory getValueFactory() {
+        return ValueFactoryImpl.getInstance();
+    }
+
+    @Override
+    public Item getItem(final String absPath) throws RepositoryException {
+        Item item = this.items.get(absPath);
+        if (item != null) {
+            return item;
+        } else {
+            throw new PathNotFoundException(String.format("No item found at: %s.", absPath));
+        }
+    }
+
+    @Override
+    public Node getNode(final String absPath) throws RepositoryException {
+        Item item = getItem(absPath);
+        if (item instanceof Node) {
+            return (Node) item;
+        } else {
+            throw new PathNotFoundException(String.format("No node found at: %s.", absPath));
+        }
+    }
+
+    @Override
+    public Node getNodeByIdentifier(final String id) throws RepositoryException {
+        for (Item item : this.items.values()) {
+            if (item instanceof Node) {
+                Node node = (Node) item;
+                if (node.getIdentifier().equals(id)) {
+                    return node;
+                }
+            }
+        }
+        throw new ItemNotFoundException(String.format("No node found with id: %s.", id));
+    }
+
+    @Override
+    public Property getProperty(final String absPath) throws RepositoryException {
+        Item item = getItem(absPath);
+        if (item instanceof Property) {
+            return (Property) item;
+        } else {
+            throw new PathNotFoundException(String.format("No property found at: %s.", absPath));
+        }
+    }
+
+    @Override
+    public boolean nodeExists(final String absPath) throws RepositoryException {
+        try {
+            getNode(absPath);
+            return true;
+        } catch (PathNotFoundException ex) {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean propertyExists(final String absPath) throws RepositoryException {
+        try {
+            getProperty(absPath);
+            return true;
+        } catch (PathNotFoundException ex) {
+            return false;
+        }
+    }
+
+    @Override
+    public void removeItem(final String absPath) {
+        removeItemWithChildren(absPath);
+    }
+
+    @Override
+    public Node getRootNode() {
+        return (Node) this.items.get("/");
+    }
+
+    @Override
+    public Node getNodeByUUID(final String uuid) throws RepositoryException {
+        return getNodeByIdentifier(uuid);
+    }
+
+    /**
+     * Add item
+     * @param item item
+     * @throws RepositoryException
+     */
+    void addItem(final Item item) throws RepositoryException {
+        this.items.put(item.getPath(), item);
+    }
+
+    /**
+     * Remove item incl. children
+     * @param path Item path
+     */
+    void removeItemWithChildren(final String path) {
+        List<String> pathsToRemove = new ArrayList<String>();
+
+        // build regex pattern for node and all its children
+        Pattern pattern = Pattern.compile("^" + Pattern.quote(path) + "(/.+)?$");
+
+        for (String itemPath : this.items.keySet()) {
+            if (pattern.matcher(itemPath).matches()) {
+                pathsToRemove.add(itemPath);
+            }
+        }
+        for (String pathToRemove : pathsToRemove) {
+            this.items.remove(pathToRemove);
+        }
+    }
+
+    RangeIterator listChildren(final String parentPath, final ItemFilter filter) throws RepositoryException {
+        List<Item> children = new ArrayList<Item>();
+
+        // build regex pattern for all child paths of parent
+        Pattern pattern = Pattern.compile("^" + Pattern.quote(parentPath) + "/[^/]+$");
+
+        // collect child resources
+        for (Item item : this.items.values()) {
+            if (pattern.matcher(item.getPath()).matches() && (filter == null || filter.accept(item))) {
+                children.add(item);
+            }
+        }
+
+        return new RangeIteratorAdapter(children.iterator(), children.size());
+    }
+
+    @Override
+    public boolean hasPendingChanges() {
+        return false;
+    }
+
+    @Override
+    public boolean itemExists(final String absPath) {
+        return this.items.get(absPath) != null;
+    }
+
+    @Override
+    public Workspace getWorkspace() {
+        return this.workspace;
+    }
+
+    @Override
+    public String getUserID() {
+        return "mockedUserId";
+    }
+
+    @Override
+    public String getNamespacePrefix(final String uri) throws RepositoryException {
+        return getWorkspace().getNamespaceRegistry().getPrefix(uri);
+    }
+
+    @Override
+    public String[] getNamespacePrefixes() throws RepositoryException {
+        return getWorkspace().getNamespaceRegistry().getPrefixes();
+    }
+
+    @Override
+    public String getNamespaceURI(final String prefix) throws RepositoryException {
+        return getWorkspace().getNamespaceRegistry().getURI(prefix);
+    }
+
+    @Override
+    public void setNamespacePrefix(final String prefix, final String uri) throws RepositoryException {
+        getWorkspace().getNamespaceRegistry().registerNamespace(prefix, uri);
+    }
+
+    @Override
+    public Repository getRepository() {
+        return this.repository;
+    }
+
+    @Override
+    public void save() {
+        // do nothing
+    }
+
+    @Override
+    public void refresh(final boolean keepChanges) throws RepositoryException {
+        // do nothing
+    }
+
+    @Override
+    public void checkPermission(final String absPath, final String actions) {
+        // always grant permission
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public void addLockToken(final String lt) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void exportDocumentView(final String absPath, final ContentHandler contentHandler, final boolean skipBinary,
+            final boolean noRecurse) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void exportDocumentView(final String absPath, final OutputStream out, final boolean skipBinary,
+            final boolean noRecurse) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void exportSystemView(final String absPath, final ContentHandler contentHandler, final boolean skipBinary,
+            final boolean noRecurse) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void exportSystemView(final String absPath, final OutputStream out, final boolean skipBinary,
+            final boolean noRecurse) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Object getAttribute(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String[] getAttributeNames() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ContentHandler getImportContentHandler(final String parentAbsPath, final int uuidBehavior) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String[] getLockTokens() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Session impersonate(final Credentials credentials) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void importXML(final String parentAbsPath, final InputStream in, final int uuidBehavior) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isLive() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void logout() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void move(final String srcAbsPath, final String destAbsPath) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void removeLockToken(final String lt) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public AccessControlManager getAccessControlManager() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RetentionManager getRetentionManager() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean hasCapability(final String methodName, final Object target, final Object[] arguments) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean hasPermission(final String absPath, final String actions) {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockWorkspace.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockWorkspace.java
new file mode 100644
index 0000000..6996103
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockWorkspace.java
@@ -0,0 +1,149 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import java.io.InputStream;
+
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.Session;
+import javax.jcr.Workspace;
+import javax.jcr.lock.LockManager;
+import javax.jcr.nodetype.NodeTypeManager;
+import javax.jcr.observation.ObservationManager;
+import javax.jcr.query.QueryManager;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionManager;
+
+import org.xml.sax.ContentHandler;
+
+/**
+ * Mock {@link Workspace} implementation
+ */
+class MockWorkspace implements Workspace {
+
+    private final Session session;
+    private final NamespaceRegistry namespaceRegistry = new MockNamespaceRegistry();
+    private final ObservationManager observationManager = new MockObservationManager();
+    private final NodeTypeManager nodeTypeManager = new MockNodeTypeManager();
+
+    /**
+     * @param session JCR session
+     */
+    public MockWorkspace(final Session session) {
+        this.session = session;
+    }
+
+    @Override
+    public Session getSession() {
+        return this.session;
+    }
+
+    @Override
+    public String getName() {
+        return MockJcr.DEFAULT_WORKSPACE;
+    }
+
+    @Override
+    public NamespaceRegistry getNamespaceRegistry() {
+        return this.namespaceRegistry;
+    }
+
+    @Override
+    public ObservationManager getObservationManager() {
+        return this.observationManager;
+    }
+
+    @Override
+    public NodeTypeManager getNodeTypeManager() {
+        return this.nodeTypeManager;
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public void copy(final String srcAbsPath, final String destAbsPath) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void copy(final String srcWorkspace, final String srcAbsPath, final String destAbsPath) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clone(final String srcWorkspace, final String srcAbsPath, final String destAbsPath,
+            final boolean removeExisting) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void move(final String srcAbsPath, final String destAbsPath) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void restore(final Version[] versions, final boolean removeExisting) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public LockManager getLockManager() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public QueryManager getQueryManager() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public VersionManager getVersionManager() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String[] getAccessibleWorkspaceNames() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ContentHandler getImportContentHandler(final String parentAbsPath, final int uuidBehavior) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void importXML(final String parentAbsPath, final InputStream in, final int uuidBehavior) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createWorkspace(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createWorkspace(final String name, final String srcWorkspace) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void deleteWorkspace(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/ResourceUtil.java b/src/main/java/org/apache/sling/testing/mock/jcr/ResourceUtil.java
new file mode 100644
index 0000000..f1488bb
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/ResourceUtil.java
@@ -0,0 +1,219 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+/**
+ * This is a stripped-down copy of org.apache.sling.api.resource.ResourceUtil
+ * with some methods required by the jcr-mock implementation internally.
+ */
+class ResourceUtil {
+
+    /**
+     * Resolves relative path segments '.' and '..' in the absolute path.
+     * Returns null if not possible (.. points above root) or if path is not
+     * absolute.
+     */
+    public static String normalize(String path) {
+
+        // don't care for empty paths
+        if (path.length() == 0) {
+            return path;
+        }
+
+        // prepare the path buffer with trailing slash (simplifies impl)
+        int absOffset = (path.charAt(0) == '/') ? 0 : 1;
+        char[] buf = new char[path.length() + 1 + absOffset];
+        if (absOffset == 1) {
+            buf[0] = '/';
+        }
+        path.getChars(0, path.length(), buf, absOffset);
+        buf[buf.length - 1] = '/';
+
+        int lastSlash = 0; // last slash in path
+        int numDots = 0; // number of consecutive dots after last slash
+
+        int bufPos = 0;
+        for (int bufIdx = lastSlash; bufIdx < buf.length; bufIdx++) {
+            char c = buf[bufIdx];
+            if (c == '/') {
+                if (numDots == 2) {
+                    if (bufPos == 0) {
+                        return null;
+                    }
+
+                    do {
+                        bufPos--;
+                    } while (bufPos > 0 && buf[bufPos] != '/');
+                }
+
+                lastSlash = bufIdx;
+                numDots = 0;
+            } else if (c == '.' && numDots < 2) {
+                numDots++;
+            } else {
+                // find the next slash
+                int nextSlash = bufIdx + 1;
+                while (nextSlash < buf.length && buf[nextSlash] != '/') {
+                    nextSlash++;
+                }
+
+                // append up to the next slash (or end of path)
+                if (bufPos < lastSlash) {
+                    int segLen = nextSlash - bufIdx + 1;
+                    System.arraycopy(buf, lastSlash, buf, bufPos, segLen);
+                    bufPos += segLen;
+                } else {
+                    bufPos = nextSlash;
+                }
+
+                numDots = 0;
+                lastSlash = nextSlash;
+                bufIdx = nextSlash;
+            }
+        }
+
+        String resolved;
+        if (bufPos == 0 && numDots == 0) {
+            resolved = (absOffset == 0) ? "/" : "";
+        } else if ((bufPos - absOffset) == path.length()) {
+            resolved = path;
+        } else {
+            resolved = new String(buf, absOffset, bufPos - absOffset);
+        }
+
+        return resolved;
+    }
+
+    /**
+     * Utility method returns the parent path of the given <code>path</code>,
+     * which is normalized by {@link #normalize(String)} before resolving the
+     * parent.
+     *
+     * @param path The path whose parent is to be returned.
+     * @return <code>null</code> if <code>path</code> is the root path (
+     *         <code>/</code>) or if <code>path</code> is a single name
+     *         containing no slash (<code>/</code>) characters.
+     * @throws IllegalArgumentException If the path cannot be normalized by the
+     *             {@link #normalize(String)} method.
+     * @throws NullPointerException If <code>path</code> is <code>null</code>.
+     */
+    public static String getParent(String path) {
+        if ("/".equals(path)) {
+            return null;
+        }
+
+        // normalize path (remove . and ..)
+        path = normalize(path);
+
+        // if normalized to root, there is no parent
+        if (path == null || "/".equals(path)) {
+            return null;
+        }
+
+        String workspaceName = null;
+
+        final int wsSepPos = path.indexOf(":/");
+        if (wsSepPos != -1) {
+            workspaceName = path.substring(0, wsSepPos);
+            path = path.substring(wsSepPos + 1);
+        }
+
+        // find the last slash, after which to cut off
+        int lastSlash = path.lastIndexOf('/');
+        if (lastSlash < 0) {
+            // no slash in the path
+            return null;
+        } else if (lastSlash == 0) {
+            // parent is root
+            if (workspaceName != null) {
+                return workspaceName + ":/";
+            }
+            return "/";
+        }
+
+        String parentPath = path.substring(0, lastSlash);
+        if (workspaceName != null) {
+            return workspaceName + ":" + parentPath;
+        }
+        return parentPath;
+    }
+
+    /**
+     * Utility method returns the ancestor's path at the given <code>level</code>
+     * relative to <code>path</code>, which is normalized by {@link #normalize(String)}
+     * before resolving the ancestor.
+     *
+     * <ul>
+     * <li><code>level</code> = 0 returns the <code>path</code>.</li>
+     * <li><code>level</code> = 1 returns the parent of <code>path</code>, if it exists, <code>null</code> otherwise.</li>
+     * <li><code>level</code> = 2 returns the grandparent of <code>path</code>, if it exists, <code>null</code> otherwise.</li>
+     * </ul>
+     *
+     * @param path The path whose ancestor is to be returned.
+     * @param level The relative level of the ancestor, relative to <code>path</code>.
+     * @return <code>null</code> if <code>path</code> doesn't have an ancestor at the
+     *            specified <code>level</code>.
+     * @throws IllegalArgumentException If the path cannot be normalized by the
+     *             {@link #normalize(String)} method or if <code>level</code> < 0.
+     * @throws NullPointerException If <code>path</code> is <code>null</code>.
+     * @since 2.2
+     */
+    public static String getParent(final String path, final int level) {
+        if ( level < 0 ) {
+            throw new IllegalArgumentException("level must be non-negative");
+        }
+        String result = path;
+        for(int i=0; i<level; i++) {
+            result = getParent(result);
+            if ( result == null ) {
+                break;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Utility method returns the name of the given <code>path</code>, which is
+     * normalized by {@link #normalize(String)} before resolving the name.
+     *
+     * @param path The path whose name (the last path element) is to be
+     *            returned.
+     * @return The empty string if <code>path</code> is the root path (
+     *         <code>/</code>) or if <code>path</code> is a single name
+     *         containing no slash (<code>/</code>) characters.
+     * @throws IllegalArgumentException If the path cannot be normalized by the
+     *             {@link #normalize(String)} method.
+     * @throws NullPointerException If <code>path</code> is <code>null</code>.
+     */
+    public static String getName(String path) {
+        if ("/".equals(path)) {
+            return "";
+        }
+
+        // normalize path (remove . and ..)
+        path = normalize(path);
+        if ("/".equals(path)) {
+            return "";
+        }
+
+        // find the last slash
+        return path.substring(path.lastIndexOf('/') + 1);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/package-info.java b/src/main/java/org/apache/sling/testing/mock/jcr/package-info.java
new file mode 100644
index 0000000..cf6e2e7
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+/**
+ * Mock implementation of selected JCR APIs.
+ */
+package org.apache.sling.testing.mock.jcr;
+
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
new file mode 100644
index 0000000..544541e
--- /dev/null
+++ b/src/site/markdown/index.md
@@ -0,0 +1,38 @@
+## About JCR Mocks
+
+Mock implementation of selected JCR APIs.
+
+### Maven Dependency
+
+```xml
+<dependency>
+  <groupId>org.apache.sling</groupId>
+  <artifactId>org.apache.sling.testing.jcr-mock</artifactId>
+  <version>1.0.0-SNAPHOT</version>
+</dependency>
+```
+
+### Documentation
+
+* [Usage](usage.html)
+* [API Documentation](apidocs/)
+* [Changelog](changes-report.html)
+
+### Implemented mock features
+
+The mock implementation supports:
+
+* Reading and writing all data (primitive values, arrays, binary data) via the JCR API
+* Creating any number of nodes and properties (stored in-memory in a hash map)
+* Register namespaces
+
+The following features are *not supported*:
+
+* Node types are supported in the API, but their definitions and constraints are not applied
+* Versioning not supported
+* Search not supported
+* Transactions not supported
+* Observation events can be registered but are ignored
+* Access control always grants access
+* Exporting/Importing data via document and system views not supported 
+* Workspace management methods not supported
diff --git a/src/site/markdown/usage.md b/src/site/markdown/usage.md
new file mode 100644
index 0000000..9ae3ce7
--- /dev/null
+++ b/src/site/markdown/usage.md
@@ -0,0 +1,17 @@
+## Usage
+
+### Getting JCR mock objects
+
+The factory class `MockJcr` allows to instantiate the different mock implementations.
+
+Example:
+
+```java
+// get session
+Session session = MockJcr.newSession();
+
+// get repository
+Repository repository = MockJcr.newRepository();
+```
+
+The repository is empty and contains only the root node. You can use the JCR API to fill it with content.
diff --git a/src/test/java/org/apache/sling/testing/mock/jcr/AbstractItemTest.java b/src/test/java/org/apache/sling/testing/mock/jcr/AbstractItemTest.java
new file mode 100644
index 0000000..e7bd268
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/jcr/AbstractItemTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public abstract class AbstractItemTest {
+
+    private Session session;
+    private Node rootNode;
+    private Node node1;
+    private Property prop1;
+    private Node node11;
+
+    @Before
+    public void setUp() throws RepositoryException {
+        this.session = MockJcr.newSession();
+        this.rootNode = this.session.getRootNode();
+        this.node1 = this.rootNode.addNode("node1");
+        this.prop1 = this.node1.setProperty("prop1", "value1");
+        this.node11 = this.node1.addNode("node11");
+    }
+
+    @Test
+    public void testGetName() throws RepositoryException {
+        assertEquals("node1", this.node1.getName());
+        assertEquals("prop1", this.prop1.getName());
+    }
+
+    @Test
+    public void testGetParent() throws RepositoryException {
+        assertSame(this.rootNode, this.node1.getParent());
+        assertSame(this.node1, this.prop1.getParent());
+        assertSame(this.node1, this.node11.getParent());
+    }
+
+    @Test
+    public void testGetAncestor() throws RepositoryException {
+        assertSame(this.node11, this.node11.getAncestor(0));
+        assertSame(this.node1, this.node11.getAncestor(1));
+        assertSame(this.rootNode, this.node11.getAncestor(2));
+    }
+
+    @Test(expected = ItemNotFoundException.class)
+    public void testGetAncestorNegative() throws RepositoryException {
+        assertSame(this.node11, this.node11.getAncestor(-1));
+    }
+
+    @Test(expected = ItemNotFoundException.class)
+    public void testGetAncestorTooDeep() throws RepositoryException {
+        this.node11.getAncestor(3);
+    }
+
+    @Test
+    public void testGetDepth() throws RepositoryException {
+        assertEquals(2, this.node11.getDepth());
+        assertEquals(1, this.node1.getDepth());
+        assertEquals(0, this.rootNode.getDepth());
+    }
+
+    @Test
+    public void testModifiedNew() {
+        // methods return always false
+        assertFalse(this.node1.isModified());
+        assertFalse(this.node1.isNew());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/jcr/MockNodeTest.java b/src/test/java/org/apache/sling/testing/mock/jcr/MockNodeTest.java
new file mode 100644
index 0000000..f02df0e
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/jcr/MockNodeTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MockNodeTest {
+
+    private Session session;
+    private Node rootNode;
+    private Node node1;
+    private Property prop1;
+    private Node node11;
+
+    @Before
+    public void setUp() throws RepositoryException {
+        this.session = MockJcr.newSession();
+        this.rootNode = this.session.getRootNode();
+        this.node1 = this.rootNode.addNode("node1");
+        this.prop1 = this.node1.setProperty("prop1", "value1");
+        this.node11 = this.node1.addNode("node11");
+    }
+
+    @Test
+    public void testGetNodes() throws RepositoryException {
+        NodeIterator nodes = this.node1.getNodes();
+        assertEquals(1, nodes.getSize());
+        assertSame(this.node11, nodes.next());
+
+        assertTrue(this.node1.hasNodes());
+        assertFalse(this.node11.hasNodes());
+
+        nodes = this.node1.getNodes("^node.*$");
+        assertEquals(1, nodes.getSize());
+        assertSame(this.node11, nodes.next());
+
+        nodes = this.node1.getNodes("unknown?");
+        assertEquals(0, nodes.getSize());
+    }
+
+    @Test
+    public void testGetProperties() throws RepositoryException {
+        PropertyIterator properties = this.node1.getProperties();
+        assertEquals(1, properties.getSize());
+        assertSame(this.prop1, properties.next());
+
+        assertTrue(this.node1.hasProperties());
+        assertFalse(this.node11.hasProperties());
+
+        properties = this.node1.getProperties("^prop.*$");
+        assertEquals(1, properties.getSize());
+        assertSame(this.prop1, properties.next());
+
+        properties = this.node1.getProperties("unknown?");
+        assertEquals(0, properties.getSize());
+    }
+
+    @Test
+    public void testIsNode() {
+        assertTrue(this.node1.isNode());
+        assertFalse(this.prop1.isNode());
+    }
+
+    @Test
+    public void testHasNode() throws RepositoryException {
+        assertTrue(this.node1.hasNode("node11"));
+        assertFalse(this.node1.hasNode("node25"));
+    }
+
+    @Test
+    public void testHasProperty() throws RepositoryException {
+        assertTrue(this.node1.hasProperty("prop1"));
+        assertFalse(this.node1.hasProperty("prop25"));
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testGetUUID() throws RepositoryException {
+        assertEquals(this.node1.getIdentifier(), this.node1.getUUID());
+    }
+
+    @Test
+    public void testGetPrimaryItem() throws RepositoryException {
+        Node dataParent = this.node1.addNode("dataParent");
+        Property dataProperty = dataParent.setProperty(JcrConstants.JCR_DATA, "data");
+        assertSame(dataProperty, dataParent.getPrimaryItem());
+
+        Node contentParent = this.node1.addNode("contentParent");
+        Node contentNode = contentParent.addNode(JcrConstants.JCR_CONTENT);
+        assertSame(contentNode, contentParent.getPrimaryItem());
+    }
+
+    @Test(expected = ItemNotFoundException.class)
+    public void testGetPrimaryItemNotFound() throws RepositoryException {
+        this.node1.getPrimaryItem();
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/jcr/MockPropertyTest.java b/src/test/java/org/apache/sling/testing/mock/jcr/MockPropertyTest.java
new file mode 100644
index 0000000..a8f30a4
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/jcr/MockPropertyTest.java
@@ -0,0 +1,240 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.Calendar;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.value.BinaryValue;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MockPropertyTest {
+
+    private Session session;
+    private Node rootNode;
+    private Node node1;
+
+    @Before
+    public void setUp() throws RepositoryException {
+        this.session = MockJcr.newSession();
+        this.rootNode = this.session.getRootNode();
+        this.node1 = this.rootNode.addNode("node1");
+    }
+
+    @Test
+    public void testRemove() throws RepositoryException {
+        this.node1.setProperty("prop1", "value1");
+        Property prop1 = this.node1.getProperty("prop1");
+        assertEquals("value1", prop1.getString());
+
+        prop1.remove();
+        assertFalse(this.node1.hasProperty("prop1"));
+    }
+
+    @Test
+    public void testString() throws RepositoryException {
+        this.node1.setProperty("prop1", "value1");
+        Property prop1 = this.node1.getProperty("prop1");
+        assertEquals("value1", prop1.getString());
+        assertEquals("value1", prop1.getValue().getString());
+
+        prop1.setValue("value2");
+        assertEquals("value2", prop1.getString());
+        assertEquals("value2", prop1.getValue().getString());
+
+        assertFalse(prop1.isMultiple());
+        assertFalse(prop1.getDefinition().isMultiple());
+        assertEquals(6, prop1.getLength());
+    }
+
+    @Test
+    public void testStringArray() throws RepositoryException {
+        String[] value1 = new String[] { "aaa", "bbb" };
+        this.node1.setProperty("prop1", value1);
+        Property prop1 = this.node1.getProperty("prop1");
+
+        Value[] values = prop1.getValues();
+        for (int i = 0; i < values.length; i++) {
+            assertEquals("value #" + i, value1[i], values[i].getString());
+        }
+
+        String[] value2 = new String[] { "cc" };
+        prop1.setValue(value2);
+        values = prop1.getValues();
+        for (int i = 0; i < values.length; i++) {
+            assertEquals("value #" + i, value2[i], values[i].getString());
+        }
+
+        assertTrue(prop1.isMultiple());
+        assertTrue(prop1.getDefinition().isMultiple());
+        assertArrayEquals(new long[] { 2 }, prop1.getLengths());
+    }
+
+    @Test
+    public void testBoolean() throws RepositoryException {
+        this.node1.setProperty("prop1", true);
+        Property prop1 = this.node1.getProperty("prop1");
+        assertEquals(true, prop1.getBoolean());
+        assertEquals(true, prop1.getValue().getBoolean());
+
+        prop1.setValue(false);
+        assertEquals(false, prop1.getBoolean());
+        assertEquals(false, prop1.getValue().getBoolean());
+    }
+
+    @Test
+    public void testDouble() throws RepositoryException {
+        this.node1.setProperty("prop1", 1.5d);
+        Property prop1 = this.node1.getProperty("prop1");
+        assertEquals(1.5d, prop1.getDouble(), 0.001d);
+        assertEquals(1.5d, prop1.getValue().getDouble(), 0.001d);
+
+        prop1.setValue(Double.MAX_VALUE);
+        assertEquals(Double.MAX_VALUE, prop1.getDouble(), 0.001d);
+        assertEquals(Double.MAX_VALUE, prop1.getValue().getDouble(), 0.001d);
+    }
+
+    @Test
+    public void testLong() throws RepositoryException {
+        this.node1.setProperty("prop1", 5L);
+        Property prop1 = this.node1.getProperty("prop1");
+        assertEquals(5L, prop1.getLong());
+        assertEquals(5L, prop1.getValue().getLong());
+
+        prop1.setValue(Long.MAX_VALUE);
+        assertEquals(Long.MAX_VALUE, prop1.getLong());
+        assertEquals(Long.MAX_VALUE, prop1.getValue().getLong());
+    }
+
+    @Test
+    public void testBigDecimal() throws RepositoryException {
+        this.node1.setProperty("prop1", new BigDecimal("1.5"));
+        Property prop1 = this.node1.getProperty("prop1");
+        assertEquals(new BigDecimal("1.5"), prop1.getDecimal());
+        assertEquals(new BigDecimal("1.5"), prop1.getValue().getDecimal());
+
+        prop1.setValue(new BigDecimal("99999999.99999"));
+        assertEquals(new BigDecimal("99999999.99999"), prop1.getDecimal());
+        assertEquals(new BigDecimal("99999999.99999"), prop1.getValue().getDecimal());
+    }
+
+    @Test
+    public void testCalendar() throws RepositoryException {
+        Calendar value1 = Calendar.getInstance();
+
+        this.node1.setProperty("prop1", value1);
+        Property prop1 = this.node1.getProperty("prop1");
+        assertEquals(value1, prop1.getDate());
+        assertEquals(value1, prop1.getValue().getDate());
+
+        Calendar value2 = Calendar.getInstance();
+        value2.add(Calendar.MONTH, -1);
+
+        prop1.setValue(value2);
+        assertEquals(value2, prop1.getDate());
+        assertEquals(value2, prop1.getValue().getDate());
+    }
+
+    @Test
+    public void testBinary() throws RepositoryException, IOException {
+        byte[] value1 = new byte[] { 0x01, 0x01, 0x03 };
+
+        this.node1.setProperty("prop1", new BinaryValue(value1).getBinary());
+        Property prop1 = this.node1.getProperty("prop1");
+        assertArrayEquals(value1, IOUtils.toByteArray(prop1.getBinary().getStream()));
+        assertArrayEquals(value1, IOUtils.toByteArray(prop1.getValue().getBinary().getStream()));
+
+        byte[] value2 = new byte[] { 0x04, 0x05, 0x06 };
+
+        prop1.setValue(new BinaryValue(value2).getBinary());
+        assertArrayEquals(value2, IOUtils.toByteArray(prop1.getBinary().getStream()));
+        assertArrayEquals(value2, IOUtils.toByteArray(prop1.getValue().getBinary().getStream()));
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testInputStream() throws RepositoryException, IOException {
+        byte[] value1 = new byte[] { 0x01, 0x01, 0x03 };
+
+        this.node1.setProperty("prop1", new ByteArrayInputStream(value1));
+        Property prop1 = this.node1.getProperty("prop1");
+        assertArrayEquals(value1, IOUtils.toByteArray(prop1.getStream()));
+
+        byte[] value2 = new byte[] { 0x04, 0x05, 0x06 };
+
+        prop1.setValue(new ByteArrayInputStream(value2));
+        assertArrayEquals(value2, IOUtils.toByteArray(prop1.getValue().getStream()));
+    }
+
+    @Test
+    public void testValue() throws RepositoryException {
+        this.node1.setProperty("prop1", this.session.getValueFactory().createValue("value1"));
+        Property prop1 = this.node1.getProperty("prop1");
+        assertEquals("value1", prop1.getString());
+        assertEquals("value1", prop1.getValue().getString());
+
+        prop1.setValue(this.session.getValueFactory().createValue("value2"));
+        assertEquals("value2", prop1.getString());
+        assertEquals("value2", prop1.getValue().getString());
+
+        assertFalse(prop1.isMultiple());
+        assertFalse(prop1.getDefinition().isMultiple());
+        assertEquals(6, prop1.getLength());
+    }
+
+    @Test
+    public void testValueArray() throws RepositoryException {
+        Value[] value1 = new Value[] { this.session.getValueFactory().createValue("aaa"),
+                this.session.getValueFactory().createValue("bbb") };
+        this.node1.setProperty("prop1", value1);
+        Property prop1 = this.node1.getProperty("prop1");
+
+        Value[] values = prop1.getValues();
+        for (int i = 0; i < values.length; i++) {
+            assertEquals("value #" + i, value1[i].getString(), values[i].getString());
+        }
+
+        Value[] value2 = new Value[] { this.session.getValueFactory().createValue("cc") };
+        prop1.setValue(value2);
+        values = prop1.getValues();
+        for (int i = 0; i < values.length; i++) {
+            assertEquals("value #" + i, value2[i].getString(), values[i].getString());
+        }
+
+        assertTrue(prop1.isMultiple());
+        assertTrue(prop1.getDefinition().isMultiple());
+        assertArrayEquals(new long[] { 2 }, prop1.getLengths());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/jcr/MockRepositoryTest.java b/src/test/java/org/apache/sling/testing/mock/jcr/MockRepositoryTest.java
new file mode 100644
index 0000000..6143456
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/jcr/MockRepositoryTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.SimpleCredentials;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class MockRepositoryTest {
+
+    private static final String USER_NAME = "user";
+    private static final char[] PASSWORD = "pwd".toCharArray();
+
+    private Repository repository;
+
+    @Before
+    public void setUp() {
+        this.repository = MockJcr.newRepository();
+    }
+
+    @Test
+    public void testLogin() throws RepositoryException {
+        assertNotNull(this.repository.login());
+        assertNotNull(this.repository.login(new SimpleCredentials(USER_NAME, PASSWORD)));
+        assertNotNull(this.repository.login(MockJcr.DEFAULT_WORKSPACE));
+        assertNotNull(this.repository.login(new SimpleCredentials(USER_NAME, PASSWORD), MockJcr.DEFAULT_WORKSPACE));
+    }
+
+    @Test
+    public void testDescriptor() {
+        assertEquals(0, this.repository.getDescriptorKeys().length);
+        assertNull(this.repository.getDescriptor("test"));
+        assertNull(this.repository.getDescriptorValue("test"));
+        assertNull(this.repository.getDescriptorValues("test"));
+        assertFalse(this.repository.isStandardDescriptor("test"));
+        assertFalse(this.repository.isSingleValueDescriptor("test"));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/jcr/MockSessionTest.java b/src/test/java/org/apache/sling/testing/mock/jcr/MockSessionTest.java
new file mode 100644
index 0000000..4c297c1
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/jcr/MockSessionTest.java
@@ -0,0 +1,228 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+public class MockSessionTest {
+
+    private Session session;
+
+    @Before
+    public void setUp() {
+        this.session = MockJcr.newSession();
+    }
+
+    @Test
+    public void testEmptySession() throws RepositoryException {
+        Node rootNode = this.session.getRootNode();
+        assertNotNull(rootNode);
+        assertFalse(rootNode.getProperties().hasNext());
+        assertFalse(rootNode.getNodes().hasNext());
+    }
+
+    @Test
+    public void testNodePropertyCreateRead() throws RepositoryException {
+        Node rootNode = this.session.getNode("/");
+        assertSame(rootNode, this.session.getRootNode());
+
+        Node node1 = rootNode.addNode("node1");
+        node1.setProperty("prop1a", "value1a");
+        node1.setProperty("prop1b", "value1b");
+
+        Node node2 = rootNode.addNode("node2");
+        node2.setProperty("prop2", "value2");
+
+        assertSame(node1, rootNode.getNode("node1"));
+        assertSame(node1, this.session.getNode("/node1"));
+        assertSame(node1, this.session.getItem("/node1"));
+        assertSame(node1, this.session.getNodeByIdentifier(node1.getIdentifier()));
+        assertTrue(this.session.nodeExists("/node1"));
+        assertTrue(this.session.itemExists("/node1"));
+        assertSame(node2, rootNode.getNode("node2"));
+        assertSame(node2, this.session.getNode("/node2"));
+        assertSame(node2, this.session.getItem("/node2"));
+        assertSame(node2, this.session.getNodeByIdentifier(node2.getIdentifier()));
+        assertTrue(this.session.nodeExists("/node2"));
+        assertTrue(this.session.itemExists("/node2"));
+
+        Property prop1a = node1.getProperty("prop1a");
+        Property prop1b = node1.getProperty("prop1b");
+        Property prop2 = node2.getProperty("prop2");
+
+        assertSame(prop1a, this.session.getProperty("/node1/prop1a"));
+        assertSame(prop1a, this.session.getItem("/node1/prop1a"));
+        assertTrue(this.session.propertyExists("/node1/prop1a"));
+        assertTrue(this.session.itemExists("/node1/prop1a"));
+        assertSame(prop1b, this.session.getProperty("/node1/prop1b"));
+        assertSame(prop1b, this.session.getItem("/node1/prop1b"));
+        assertTrue(this.session.propertyExists("/node1/prop1b"));
+        assertTrue(this.session.itemExists("/node1/prop1b"));
+        assertSame(prop2, this.session.getProperty("/node2/prop2"));
+        assertSame(prop2, this.session.getItem("/node2/prop2"));
+        assertTrue(this.session.propertyExists("/node2/prop2"));
+        assertTrue(this.session.itemExists("/node2/prop2"));
+
+        assertEquals("value1a", prop1a.getString());
+        assertEquals("value1b", prop1b.getString());
+        assertEquals("value2", prop2.getString());
+
+        assertFalse(this.session.propertyExists("/node1"));
+        assertFalse(this.session.nodeExists("/node1/prop1a"));
+
+        assertEquals(JcrConstants.NT_UNSTRUCTURED, node1.getPrimaryNodeType().getName());
+        assertTrue(node1.isNodeType(JcrConstants.NT_UNSTRUCTURED));
+        assertTrue(node1.getPrimaryNodeType().isNodeType(JcrConstants.NT_UNSTRUCTURED));
+
+    }
+
+    @Test
+    public void testNodeRemove() throws RepositoryException {
+        Node rootNode = this.session.getRootNode();
+        Node node1 = rootNode.addNode("node1");
+        assertTrue(this.session.itemExists("/node1"));
+        node1.remove();
+        assertFalse(this.session.itemExists("/node1"));
+        assertFalse(rootNode.getNodes().hasNext());
+    }
+
+    @Test
+    public void testNodesWithSpecialNames() throws RepositoryException {
+        Node rootNode = this.session.getRootNode();
+
+        Node node1 = rootNode.addNode("node1.ext");
+        Node node11 = node1.addNode("Node Name With Spaces");
+        node11.setProperty("prop11", "value11");
+        Node node12 = node1.addNode("node12_ext");
+        node12.setProperty("prop12", "value12");
+
+        assertTrue(this.session.itemExists("/node1.ext"));
+        assertTrue(this.session.itemExists("/node1.ext/Node Name With Spaces"));
+        assertTrue(this.session.itemExists("/node1.ext/node12_ext"));
+
+        assertEquals("value11", node11.getProperty("prop11").getString());
+        assertEquals("value12", node12.getProperty("prop12").getString());
+
+        NodeIterator nodes = node1.getNodes();
+        assertEquals(2, nodes.getSize());
+    }
+
+    @Test
+    public void testItemsExists() throws RepositoryException {
+        assertFalse(this.session.nodeExists("/node1"));
+        assertFalse(this.session.itemExists("/node2"));
+        assertFalse(this.session.propertyExists("/node1/prop1"));
+    }
+
+    @Test(expected = PathNotFoundException.class)
+    public void testNodeNotFoundException() throws RepositoryException {
+        this.session.getNode("/node1");
+    }
+
+    @Test(expected = PathNotFoundException.class)
+    public void testPropertyNotFoundException() throws RepositoryException {
+        this.session.getProperty("/node1/prop1");
+    }
+
+    @Test(expected = PathNotFoundException.class)
+    public void testItemNotFoundException() throws RepositoryException {
+        this.session.getItem("/node2");
+    }
+
+    @Test(expected = ItemNotFoundException.class)
+    public void testIdentifierFoundException() throws RepositoryException {
+        this.session.getNodeByIdentifier("unknown");
+    }
+
+    @Test
+    public void testNamespaces() throws RepositoryException {
+        // test initial namespaces
+        assertArrayEquals(new String[] { "jcr" }, this.session.getNamespacePrefixes());
+        assertEquals("http://www.jcp.org/jcr/1.0", this.session.getNamespaceURI("jcr"));
+        assertEquals("jcr", this.session.getNamespacePrefix("http://www.jcp.org/jcr/1.0"));
+
+        // add dummy namespace
+        this.session.setNamespacePrefix("dummy", "http://mydummy");
+
+        assertEquals(ImmutableSet.of("jcr", "dummy"), ImmutableSet.copyOf(this.session.getNamespacePrefixes()));
+        assertEquals("http://mydummy", this.session.getNamespaceURI("dummy"));
+        assertEquals("dummy", this.session.getNamespacePrefix("http://mydummy"));
+
+        // test via namespace registry
+        NamespaceRegistry namespaceRegistry = this.session.getWorkspace().getNamespaceRegistry();
+
+        assertEquals(ImmutableSet.of("jcr", "dummy"), ImmutableSet.copyOf(namespaceRegistry.getPrefixes()));
+        assertEquals(ImmutableSet.of("http://www.jcp.org/jcr/1.0", "http://mydummy"),
+                ImmutableSet.copyOf(namespaceRegistry.getURIs()));
+        assertEquals("http://mydummy", namespaceRegistry.getURI("dummy"));
+        assertEquals("dummy", namespaceRegistry.getPrefix("http://mydummy"));
+
+        // remove dummy namespace
+        namespaceRegistry.unregisterNamespace("dummy");
+
+        assertEquals(ImmutableSet.of("jcr"), ImmutableSet.copyOf(this.session.getNamespacePrefixes()));
+        assertEquals("http://www.jcp.org/jcr/1.0", this.session.getNamespaceURI("jcr"));
+        assertEquals("jcr", this.session.getNamespacePrefix("http://www.jcp.org/jcr/1.0"));
+    }
+
+    @Test
+    public void testUserId() {
+        assertEquals("mockedUserId", this.session.getUserID());
+    }
+
+    @Test
+    public void testSaveRefresh() throws RepositoryException {
+        // methods can be called without any effect
+        assertFalse(this.session.hasPendingChanges());
+        this.session.save();
+        this.session.refresh(true);
+        this.session.refresh(false);
+    }
+
+    @Test
+    public void testGetRepository() {
+        assertNotNull(this.session.getRepository());
+    }
+
+    @Test
+    public void testCheckPermission() throws RepositoryException {
+        this.session.checkPermission("/any/path", "anyActions");
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/jcr/MockWorkspaceTest.java b/src/test/java/org/apache/sling/testing/mock/jcr/MockWorkspaceTest.java
new file mode 100644
index 0000000..662a623
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/jcr/MockWorkspaceTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Workspace;
+import javax.jcr.observation.ObservationManager;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class MockWorkspaceTest {
+
+    private Workspace underTest;
+
+    @Before
+    public void setUp() {
+        underTest = MockJcr.newSession().getWorkspace();
+    }
+
+    @Test
+    public void testName() {
+        assertEquals(MockJcr.DEFAULT_WORKSPACE, underTest.getName());
+    }
+
+    @Test
+    public void testNameSpaceRegistry() throws RepositoryException {
+        assertNotNull(underTest.getNamespaceRegistry());
+    }
+
+    @Test
+    public void testObservationManager() throws RepositoryException {
+        // just mage sure listener methods can be called, although they do
+        // nothing
+        ObservationManager observationManager = underTest.getObservationManager();
+        observationManager.addEventListener(null, 0, null, false, null, null, false);
+        observationManager.removeEventListener(null);
+    }
+
+    @Test
+    public void testNodeTypeManager() throws RepositoryException {
+        assertNotNull(underTest.getNodeTypeManager());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/jcr/ResourceUtilTest.java b/src/test/java/org/apache/sling/testing/mock/jcr/ResourceUtilTest.java
new file mode 100644
index 0000000..05256bc
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/jcr/ResourceUtilTest.java
@@ -0,0 +1,221 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+public class ResourceUtilTest {
+
+    @Test
+    public void testResolveRelativeSegments() {
+
+        assertEquals("/", ResourceUtil.normalize("/"));
+        assertEquals("/", ResourceUtil.normalize("///"));
+
+        assertEquals("/a/b/c", ResourceUtil.normalize("/a//b/c"));
+        assertEquals("/a/b/c", ResourceUtil.normalize("/a/b//c"));
+        assertEquals("/a/b/c", ResourceUtil.normalize("/a///b///c"));
+        assertEquals("/a/b/c", ResourceUtil.normalize("/a/b/c/"));
+        assertEquals("/a/b/c", ResourceUtil.normalize("/a/b/c//"));
+        assertEquals("/a/b/c", ResourceUtil.normalize("/a/b/c///"));
+
+        assertEquals("/az/bz/cz", ResourceUtil.normalize("/az//bz/cz"));
+        assertEquals("/az/bz/cz", ResourceUtil.normalize("/az/bz//cz"));
+        assertEquals("/az/bz/cz", ResourceUtil.normalize("/az///bz///cz"));
+        assertEquals("/az/bz/cz", ResourceUtil.normalize("/az/bz/cz/"));
+        assertEquals("/az/bz/cz", ResourceUtil.normalize("/az/bz/cz//"));
+        assertEquals("/az/bz/cz", ResourceUtil.normalize("/az/bz/cz///"));
+
+        assertEquals("/a", ResourceUtil.normalize("/a"));
+        assertEquals("/a", ResourceUtil.normalize("//a"));
+        assertEquals("/a", ResourceUtil.normalize("///a"));
+
+        assertEquals("/az", ResourceUtil.normalize("/az"));
+        assertEquals("/az", ResourceUtil.normalize("//az"));
+        assertEquals("/az", ResourceUtil.normalize("///az"));
+
+        assertEquals("/", ResourceUtil.normalize("/."));
+        assertEquals("/a", ResourceUtil.normalize("/a/."));
+        assertEquals("/a", ResourceUtil.normalize("/./a"));
+        assertEquals("/a/b", ResourceUtil.normalize("/a/./b"));
+        assertEquals("/a/b", ResourceUtil.normalize("/a/b/."));
+        assertEquals("/a/b", ResourceUtil.normalize("/a/./b/."));
+
+        assertEquals("/", ResourceUtil.normalize("/."));
+        assertEquals("/az", ResourceUtil.normalize("/az/."));
+        assertEquals("/az", ResourceUtil.normalize("/./az"));
+        assertEquals("/az/bz", ResourceUtil.normalize("/az/./bz"));
+        assertEquals("/az/bz", ResourceUtil.normalize("/az/bz/."));
+        assertEquals("/az/bz", ResourceUtil.normalize("/az/./bz/."));
+
+        assertNull(ResourceUtil.normalize("/.."));
+        assertNull(ResourceUtil.normalize("/.."));
+        assertEquals("/", ResourceUtil.normalize("/a/.."));
+        assertEquals("/a", ResourceUtil.normalize("/a/b/.."));
+        assertEquals("/", ResourceUtil.normalize("/a/b/../.."));
+        assertNull(ResourceUtil.normalize("/a/b/../../.."));
+
+        assertNull(ResourceUtil.normalize("/.."));
+        assertNull(ResourceUtil.normalize("/.."));
+        assertEquals("/", ResourceUtil.normalize("/az/.."));
+        assertEquals("/az", ResourceUtil.normalize("/az/bz/.."));
+        assertEquals("/", ResourceUtil.normalize("/az/bz/../.."));
+        assertNull(ResourceUtil.normalize("/az/bz/../../.."));
+
+        assertEquals("/b", ResourceUtil.normalize("/a/../b"));
+        assertEquals("/a/c", ResourceUtil.normalize("/a/b/../c"));
+        assertEquals("/c", ResourceUtil.normalize("/a/b/../../c"));
+        assertNull(ResourceUtil.normalize("/a/b/../../../c"));
+
+        assertEquals("/bz", ResourceUtil.normalize("/az/../bz"));
+        assertEquals("/az/cz", ResourceUtil.normalize("/az/bz/../cz"));
+        assertEquals("/cz", ResourceUtil.normalize("/az/bz/../../cz"));
+        assertNull(ResourceUtil.normalize("/az/bz/../../../cz"));
+
+        assertEquals("/...", ResourceUtil.normalize("/..."));
+        assertEquals("/a/...", ResourceUtil.normalize("/a/..."));
+        assertEquals("/a/b/...", ResourceUtil.normalize("/a/b/..."));
+
+        assertEquals("/az/...", ResourceUtil.normalize("/az/..."));
+        assertEquals("/az/bz/...", ResourceUtil.normalize("/az/bz/..."));
+
+        try {
+            ResourceUtil.normalize(null);
+            fail("Resolving null expects NullPointerException");
+        } catch (NullPointerException npe) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testResolveRelativeSegmentsRelative() {
+        assertEquals("a/b", ResourceUtil.normalize("a/b"));
+        assertEquals("a", ResourceUtil.normalize("a/b/.."));
+
+        assertEquals("b", ResourceUtil.normalize("a/../b"));
+        assertEquals("a/c", ResourceUtil.normalize("a/b/../c"));
+        assertEquals("c", ResourceUtil.normalize("a/b/../../c"));
+        assertEquals("", ResourceUtil.normalize("a/b/../.."));
+        assertEquals("a/c/d", ResourceUtil.normalize("a/b/../c/d"));
+        assertNull(ResourceUtil.normalize("a/b/../../../c"));
+
+        assertEquals("a/b/c", ResourceUtil.normalize("a/b/c"));
+        assertEquals("az/bz/cz", ResourceUtil.normalize("az/bz/cz"));
+        assertEquals("", ResourceUtil.normalize(""));
+    }
+
+    @Test
+    public void testGetParent() {
+        assertNull(ResourceUtil.getParent("/"));
+        assertNull(ResourceUtil.getParent("/.."));
+
+        assertEquals("/", ResourceUtil.getParent("/b"));
+        assertEquals("b/c", ResourceUtil.getParent("b/c/d"));
+        assertEquals("/b/c", ResourceUtil.getParent("/b/c/d"));
+
+        assertNull(ResourceUtil.getParent("b"));
+        assertNull(ResourceUtil.getParent("/b/.."));
+
+        assertEquals("security:/", ResourceUtil.getParent("security:/b"));
+        assertEquals("security:/b", ResourceUtil.getParent("security:/b/c"));
+        assertEquals("security:/b/c", ResourceUtil.getParent("security:/b/c/d"));
+    }
+
+    @Test
+    public void testGetName() {
+        assertEquals("", ResourceUtil.getName("/"));
+        assertEquals("", ResourceUtil.getName("/a/.."));
+
+        assertEquals("c", ResourceUtil.getName("c"));
+        assertEquals("c", ResourceUtil.getName("/c"));
+
+        assertEquals("c", ResourceUtil.getName("b/c"));
+        assertEquals("c", ResourceUtil.getName("/b/c"));
+
+        assertEquals("c", ResourceUtil.getName("b/c/"));
+        assertEquals("c", ResourceUtil.getName("/b/c/"));
+
+        assertEquals("b", ResourceUtil.getName("b/c/.."));
+        assertEquals("b", ResourceUtil.getName("/b/c/.."));
+        assertEquals("", ResourceUtil.getName("/b/c/../.."));
+    }
+
+    @Test
+    public void testGetParentLevel() throws Exception {
+        boolean caughtNullPointerException = false;
+        try {
+            ResourceUtil.getParent(null, 4);
+        } catch (NullPointerException e) {
+            // Expected exception
+            caughtNullPointerException = true;
+        } catch (Exception e) {
+            fail("Expected NullPointerException, but caught " + e.getClass().getName() + " instead.");
+        }
+        if (!caughtNullPointerException) {
+            fail("Expected NullPointerException, but no exception was thrown.");
+        }
+
+        boolean caughtIllegalArgumentException = false;
+        try {
+            ResourceUtil.getParent("/a/b", -2);
+        } catch (IllegalArgumentException e) {
+            // Expected exception
+            caughtIllegalArgumentException = true;
+        } catch (Exception e) {
+            fail("Expected IllegalArgumentException, but caught " + e.getClass().getName() + " instead.");
+        }
+        if (!caughtIllegalArgumentException) {
+            fail("Expected IllegalArgumentException, but no exception was thrown.");
+        }
+
+        assertNull(ResourceUtil.getParent("/a", 4));
+        assertNull(ResourceUtil.getParent("/", 1));
+        assertNull(ResourceUtil.getParent("b/c", 2));
+        assertNull(ResourceUtil.getParent("/b/..", 1));
+        assertNull(ResourceUtil.getParent("b", 1));
+        assertNull(ResourceUtil.getParent("", 3));
+        assertNull(ResourceUtil.getParent("/..", 1));
+        assertNull(ResourceUtil.getParent("security:/b", 2));
+        assertNull(ResourceUtil.getParent("/b///", 2));
+
+        assertEquals("", ResourceUtil.getParent("", 0));
+        assertEquals("b", ResourceUtil.getParent("b", 0));
+        assertEquals("/", ResourceUtil.getParent("/", 0));
+        assertEquals("/a/b", ResourceUtil.getParent("/a/b", 0));
+        assertEquals("security:/b", ResourceUtil.getParent("security:/b", 0));
+
+        assertEquals("/", ResourceUtil.getParent("/b", 1));
+        assertEquals("b", ResourceUtil.getParent("b/c", 1));
+        assertEquals("b/c", ResourceUtil.getParent("b/c/d", 1));
+        assertEquals("/b/c", ResourceUtil.getParent("/b/c/d", 1));
+        assertEquals("security:/", ResourceUtil.getParent("security:/b", 1));
+        assertEquals("security:/b", ResourceUtil.getParent("security:/b/c", 1));
+        assertEquals("security:/b/c", ResourceUtil.getParent("security:/b/c/d", 1));
+
+        assertEquals("b", ResourceUtil.getParent("b/c/d", 2));
+        assertEquals("b/c", ResourceUtil.getParent("b/c/d/e", 2));
+        assertEquals("/", ResourceUtil.getParent("/b/c/d", 3));
+        assertEquals("/", ResourceUtil.getParent("/b///", 1));
+    }
+
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-jcr-mock] 08/10: add/update SCM urls

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.testing.jcr-mock-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-jcr-mock.git

commit 4e3739f121abe6362c7661a13ed81416f93a2991
Author: sseifert <ss...@unknown>
AuthorDate: Fri Oct 17 08:10:43 2014 +0000

    add/update SCM urls
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/testing/mocks/jcr-mock@1632490 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/pom.xml b/pom.xml
index 9eadbc4..ad39b2d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,6 +38,12 @@
         <sling.java.version>6</sling.java.version>
     </properties>
 
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/testing/mocks/jcr-mock</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/testing/mocks/jcr-mock</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/testing/mocks/jcr-mock</url>
+    </scm>
+
     <dependencies>
     
         <dependency>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-jcr-mock] 02/10: svn:ignore

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.testing.jcr-mock-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-jcr-mock.git

commit 6ca6ae147f7edeb87952bccb8eaf3cbedd5df50b
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Mon Oct 13 13:23:53 2014 +0000

    svn:ignore
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/testing/jcr-mock@1631387 13f79535-47bb-0310-9956-ffa450edef68

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-jcr-mock] 10/10: [maven-release-plugin] copy for tag org.apache.sling.testing.jcr-mock-1.0.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.testing.jcr-mock-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-jcr-mock.git

commit eea313ef9229d7416f3228173ead7bdc41eb47f0
Author: sseifert <ss...@unknown>
AuthorDate: Fri Oct 17 08:15:16 2014 +0000

    [maven-release-plugin]  copy for tag org.apache.sling.testing.jcr-mock-1.0.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.jcr-mock-1.0.0@1632495 13f79535-47bb-0310-9956-ffa450edef68

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-jcr-mock] 07/10: SLING-4042 add README files

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.testing.jcr-mock-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-jcr-mock.git

commit 3e8c9797e879183b524e8075de22386cde526109
Author: sseifert <ss...@unknown>
AuthorDate: Thu Oct 16 19:09:39 2014 +0000

    SLING-4042 add README files
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/testing/mocks/jcr-mock@1632415 13f79535-47bb-0310-9956-ffa450edef68
---
 README.txt | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..b9e814f
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,6 @@
+Apache Sling Testing JCR Mock
+
+Mock implementation of selected JCR APIs.
+
+Documentation:
+http://sling.apache.org/documentation/development/jcr-mock.html

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-jcr-mock] 04/10: SLING-4042 move all mock projects to mocks/ subdirectory

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.testing.jcr-mock-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-jcr-mock.git

commit 7144d672a28445370b60972a3ce2b446b41444ed
Author: sseifert <ss...@unknown>
AuthorDate: Mon Oct 13 14:32:45 2014 +0000

    SLING-4042 move all mock projects to mocks/ subdirectory
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/testing/mocks/jcr-mock@1631416 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 6bd600b..9eadbc4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
         <version>22</version>
-        <relativePath>../../parent/pom.xml</relativePath>
+        <relativePath>../../../parent/pom.xml</relativePath>
     </parent>
 
     <artifactId>org.apache.sling.testing.jcr-mock</artifactId>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-jcr-mock] 05/10: SLING-4042 make sure JCR mock supports accessing data using multiple sessions

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.testing.jcr-mock-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-jcr-mock.git

commit 3c1c4ddba01d15e1ec7abcd9acac2c5e34ac7fd4
Author: sseifert <ss...@unknown>
AuthorDate: Thu Oct 16 17:53:47 2014 +0000

    SLING-4042 make sure JCR mock supports accessing data using multiple sessions
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/testing/mocks/jcr-mock@1632397 13f79535-47bb-0310-9956-ffa450edef68
---
 .../apache/sling/testing/mock/jcr/ItemData.java    | 136 +++++++++++++++++++++
 .../apache/sling/testing/mock/jcr/ItemFilter.java  |   3 +-
 .../apache/sling/testing/mock/jcr/MockNode.java    | 110 ++++++++++-------
 .../sling/testing/mock/jcr/MockProperty.java       | 106 +++++++++-------
 .../sling/testing/mock/jcr/MockRepository.java     |  12 +-
 .../apache/sling/testing/mock/jcr/MockSession.java |  40 +++---
 .../sling/testing/mock/jcr/MockNodeTest.java       |  13 +-
 .../sling/testing/mock/jcr/MockRepositoryTest.java |  53 ++++++--
 .../sling/testing/mock/jcr/MockSessionTest.java    |  31 +++--
 9 files changed, 362 insertions(+), 142 deletions(-)

diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/ItemData.java b/src/main/java/org/apache/sling/testing/mock/jcr/ItemData.java
new file mode 100644
index 0000000..21b2678
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/ItemData.java
@@ -0,0 +1,136 @@
+/*
+ * 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.sling.testing.mock.jcr;
+
+import java.util.UUID;
+
+import javax.jcr.Item;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.nodetype.NodeType;
+
+/**
+ * Holds node and property item data independently from session.
+ */
+class ItemData {
+    
+    private final String path;
+    private final boolean isNode;
+    private final String uuid;
+    private final NodeType nodeType;
+    private Value[] values;
+    private boolean isMultiple;
+    
+    private ItemData(String path, boolean isNode, String uuid, NodeType nodeType) {
+        super();
+        this.path = path;
+        this.uuid = uuid;
+        this.isNode = isNode;
+        this.nodeType = nodeType;
+    }
+    
+    public String getPath() {
+        return path;
+    }
+    
+    public String getName() {
+        return ResourceUtil.getName(path);
+    }
+
+    public boolean isNode() {
+        return isNode;
+    }
+    
+    public boolean isProperty() {
+        return !isNode;
+    }
+    
+    public String getUuid() {
+        if (!isNode()) {
+            throw new UnsupportedOperationException();
+        }
+        return uuid;
+    }
+
+    public NodeType getNodeType() {
+        if (!isNode()) {
+            throw new UnsupportedOperationException();
+        }
+        return nodeType;
+    }
+
+    public Value[] getValues() {
+        if (!isProperty()) {
+            throw new UnsupportedOperationException();
+        }
+        return values;
+    }
+
+    public void setValues(Value[] values) {
+        if (!isProperty()) {
+            throw new UnsupportedOperationException();
+        }
+        this.values = values;
+    }
+
+    public boolean isMultiple() {
+        if (!isProperty()) {
+            throw new UnsupportedOperationException();
+        }
+        return isMultiple;
+    }
+
+    public void setMultiple(boolean isMultiple) {
+        if (!isProperty()) {
+            throw new UnsupportedOperationException();
+        }
+        this.isMultiple = isMultiple;
+    }
+    
+    public Item getItem(Session session) {
+        if (isNode) {
+            return new MockNode(this, session);
+        }
+        else {
+            return new MockProperty(this, session);
+        }
+    }
+    
+    @Override
+    public int hashCode() {
+        return path.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof ItemData) {
+            return path.equals(((ItemData)obj).path);
+        }
+        return false;
+    }
+
+    public static ItemData newNode(String path, NodeType nodeType) {
+        return new ItemData(path, true, UUID.randomUUID().toString(), nodeType);
+    }
+    
+    public static ItemData newProperty(String path) {
+        return new ItemData(path, false, null, null);
+    }
+    
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/ItemFilter.java b/src/main/java/org/apache/sling/testing/mock/jcr/ItemFilter.java
index 2e144a1..cda6b42 100644
--- a/src/main/java/org/apache/sling/testing/mock/jcr/ItemFilter.java
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/ItemFilter.java
@@ -18,7 +18,6 @@
  */
 package org.apache.sling.testing.mock.jcr;
 
-import javax.jcr.Item;
 import javax.jcr.RepositoryException;
 
 /**
@@ -26,6 +25,6 @@ import javax.jcr.RepositoryException;
  */
 interface ItemFilter {
 
-    boolean accept(Item item) throws RepositoryException;
+    boolean accept(ItemData item) throws RepositoryException;
 
 }
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockNode.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockNode.java
index 41350d6..d53f43c 100644
--- a/src/main/java/org/apache/sling/testing/mock/jcr/MockNode.java
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockNode.java
@@ -21,7 +21,6 @@ package org.apache.sling.testing.mock.jcr;
 import java.io.InputStream;
 import java.math.BigDecimal;
 import java.util.Calendar;
-import java.util.UUID;
 import java.util.regex.Pattern;
 
 import javax.jcr.Binary;
@@ -50,12 +49,11 @@ import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter;
  */
 class MockNode extends AbstractItem implements Node {
 
-    private final UUID uuid = UUID.randomUUID();
-    private final NodeType nodeType;
+    private final ItemData itemData;
 
-    public MockNode(final String path, final Session session, final NodeType nodeType) {
-        super(path, session);
-        this.nodeType = nodeType;
+    public MockNode(final ItemData itemData, final Session session) {
+        super(itemData.getPath(), session);
+        this.itemData = itemData;
     }
 
     @Override
@@ -66,8 +64,9 @@ class MockNode extends AbstractItem implements Node {
     @Override
     public Node addNode(final String relPath, final String primaryNodeTypeName) throws RepositoryException {
         String path = makeAbsolutePath(relPath);
-        Node node = new MockNode(path, getSession(), new MockNodeType(primaryNodeTypeName));
-        getMockedSession().addItem(node);
+        ItemData itemData = ItemData.newNode(path, new MockNodeType(primaryNodeTypeName));
+        Node node = new MockNode(itemData, getSession());
+        getMockedSession().addItem(itemData);
         return node;
     }
 
@@ -81,8 +80,8 @@ class MockNode extends AbstractItem implements Node {
     public NodeIterator getNodes() throws RepositoryException {
         RangeIterator items = getMockedSession().listChildren(getPath(), new ItemFilter() {
             @Override
-            public boolean accept(final Item item) {
-                return item instanceof Node;
+            public boolean accept(final ItemData item) {
+                return item.isNode();
             }
         });
         return new NodeIteratorAdapter(items, items.getSize());
@@ -93,8 +92,8 @@ class MockNode extends AbstractItem implements Node {
         final Pattern pattern = Pattern.compile(namePattern);
         RangeIterator items = getMockedSession().listChildren(getPath(), new ItemFilter() {
             @Override
-            public boolean accept(final Item item) throws RepositoryException {
-                return (item instanceof Node) && pattern.matcher(item.getName()).matches();
+            public boolean accept(final ItemData item) throws RepositoryException {
+                return item.isNode() && pattern.matcher(item.getName()).matches();
             }
         });
         return new NodeIteratorAdapter(items, items.getSize());
@@ -104,8 +103,8 @@ class MockNode extends AbstractItem implements Node {
     public PropertyIterator getProperties() throws RepositoryException {
         RangeIterator items = getMockedSession().listChildren(getPath(), new ItemFilter() {
             @Override
-            public boolean accept(final Item item) {
-                return item instanceof Property;
+            public boolean accept(final ItemData item) {
+                return item.isProperty();
             }
         });
         return new PropertyIteratorAdapter(items, items.getSize());
@@ -116,8 +115,8 @@ class MockNode extends AbstractItem implements Node {
         final Pattern pattern = Pattern.compile(namePattern);
         RangeIterator items = getMockedSession().listChildren(getPath(), new ItemFilter() {
             @Override
-            public boolean accept(final Item item) throws RepositoryException {
-                return (item instanceof Property) && pattern.matcher(item.getName()).matches();
+            public boolean accept(final ItemData item) throws RepositoryException {
+                return item.isProperty() && pattern.matcher(item.getName()).matches();
             }
         });
         return new PropertyIteratorAdapter(items, items.getSize());
@@ -131,7 +130,7 @@ class MockNode extends AbstractItem implements Node {
 
     @Override
     public String getIdentifier() {
-        return this.uuid.toString();
+        return this.itemData.getUuid();
     }
 
     @Override
@@ -163,98 +162,110 @@ class MockNode extends AbstractItem implements Node {
 
     @Override
     public Property setProperty(final String name, final Value value) throws RepositoryException {
-        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        ItemData itemData = ItemData.newProperty(getPath() + "/" + name);
+        Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
-        getMockedSession().addItem(property);
+        getMockedSession().addItem(itemData);
         return property;
     }
 
     @Override
     public Property setProperty(final String name, final Value[] values) throws RepositoryException {
-        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        ItemData itemData = ItemData.newProperty(getPath() + "/" + name);
+        Property property = new MockProperty(itemData, getSession());
         property.setValue(values);
-        getMockedSession().addItem(property);
+        getMockedSession().addItem(itemData);
         return property;
     }
 
     @Override
     public Property setProperty(final String name, final String[] values) throws RepositoryException {
-        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        ItemData itemData = ItemData.newProperty(getPath() + "/" + name);
+        Property property = new MockProperty(itemData, getSession());
         property.setValue(values);
-        getMockedSession().addItem(property);
+        getMockedSession().addItem(itemData);
         return property;
     }
 
     @Override
     public Property setProperty(final String name, final String value) throws RepositoryException {
-        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        ItemData itemData = ItemData.newProperty(getPath() + "/" + name);
+        Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
-        getMockedSession().addItem(property);
+        getMockedSession().addItem(itemData);
         return property;
     }
 
     @Override
     @SuppressWarnings("deprecation")
     public Property setProperty(final String name, final InputStream value) throws RepositoryException {
-        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        ItemData itemData = ItemData.newProperty(getPath() + "/" + name);
+        Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
-        getMockedSession().addItem(property);
+        getMockedSession().addItem(itemData);
         return property;
     }
 
     @Override
     public Property setProperty(final String name, final boolean value) throws RepositoryException {
-        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        ItemData itemData = ItemData.newProperty(getPath() + "/" + name);
+        Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
-        getMockedSession().addItem(property);
+        getMockedSession().addItem(itemData);
         return property;
     }
 
     @Override
     public Property setProperty(final String name, final double value) throws RepositoryException {
-        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        ItemData itemData = ItemData.newProperty(getPath() + "/" + name);
+        Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
-        getMockedSession().addItem(property);
+        getMockedSession().addItem(itemData);
         return property;
     }
 
     @Override
     public Property setProperty(final String name, final long value) throws RepositoryException {
-        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        ItemData itemData = ItemData.newProperty(getPath() + "/" + name);
+        Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
-        getMockedSession().addItem(property);
+        getMockedSession().addItem(itemData);
         return property;
     }
 
     @Override
     public Property setProperty(final String name, final Calendar value) throws RepositoryException {
-        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        ItemData itemData = ItemData.newProperty(getPath() + "/" + name);
+        Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
-        getMockedSession().addItem(property);
+        getMockedSession().addItem(itemData);
         return property;
     }
 
     @Override
     public Property setProperty(final String name, final Node value) throws RepositoryException {
-        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        ItemData itemData = ItemData.newProperty(getPath() + "/" + name);
+        Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
-        getMockedSession().addItem(property);
+        getMockedSession().addItem(itemData);
         return property;
     }
 
     @Override
     public Property setProperty(final String name, final Binary value) throws RepositoryException {
-        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        ItemData itemData = ItemData.newProperty(getPath() + "/" + name);
+        Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
-        getMockedSession().addItem(property);
+        getMockedSession().addItem(itemData);
         return property;
     }
 
     @Override
     public Property setProperty(final String name, final BigDecimal value) throws RepositoryException {
-        Property property = new MockProperty(getPath() + "/" + name, getSession());
+        ItemData itemData = ItemData.newProperty(getPath() + "/" + name);
+        Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
-        getMockedSession().addItem(property);
+        getMockedSession().addItem(itemData);
         return property;
     }
 
@@ -265,12 +276,12 @@ class MockNode extends AbstractItem implements Node {
 
     @Override
     public boolean isNodeType(final String nodeTypeName) throws RepositoryException {
-        return this.nodeType.isNodeType(nodeTypeName);
+        return this.itemData.getNodeType().isNodeType(nodeTypeName);
     }
 
     @Override
     public NodeType getPrimaryNodeType() {
-        return this.nodeType;
+        return this.itemData.getNodeType();
     }
 
     @Override
@@ -284,6 +295,19 @@ class MockNode extends AbstractItem implements Node {
             throw new ItemNotFoundException();
         }
     }
+    
+    @Override
+    public int hashCode() {
+        return itemData.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof MockNode) {
+            return itemData.equals(((MockNode)obj).itemData);
+        }
+        return false;
+    }
 
     // --- unsupported operations ---
     @Override
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockProperty.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockProperty.java
index 468a840..40a3b40 100644
--- a/src/main/java/org/apache/sling/testing/mock/jcr/MockProperty.java
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockProperty.java
@@ -39,20 +39,27 @@ import org.apache.jackrabbit.value.BinaryValue;
  */
 class MockProperty extends AbstractItem implements Property {
 
-    private Value[] values;
-    private boolean isMultiple;
-
-    public MockProperty(final String path, final Session session) throws RepositoryException {
-        super(path, session);
-        this.values = new Value[] { getSession().getValueFactory().createValue("") };
+    private final ItemData itemData;
+
+    public MockProperty(final ItemData itemData, final Session session) {
+        super(itemData.getPath(), session);
+        this.itemData = itemData;
+        if (this.itemData.getValues() == null) {
+            try {
+                this.itemData.setValues(new Value[] { getSession().getValueFactory().createValue("") });
+            }
+            catch (RepositoryException ex) {
+                throw new RuntimeException("Initializing property failed.", ex);
+            }
+        }
     }
 
     private Value internalGetValue() throws ValueFormatException {
-        if (this.values.length > 1) {
+        if (this.itemData.getValues().length > 1) {
             throw new ValueFormatException(this
                     + " is a multi-valued property, so it's values can only be retrieved as an array");
         } else {
-            return this.values[0];
+            return this.itemData.getValues()[0];
         }
     }
 
@@ -63,89 +70,91 @@ class MockProperty extends AbstractItem implements Property {
 
     @Override
     public Value[] getValues() {
-        Value[] valuesCopy = new Value[this.values.length];
-        for (int i = 0; i < this.values.length; i++) {
-            valuesCopy[i] = this.values[i];
+        Value[] valuesCopy = new Value[this.itemData.getValues().length];
+        for (int i = 0; i < this.itemData.getValues().length; i++) {
+            valuesCopy[i] = this.itemData.getValues()[i];
         }
         return valuesCopy;
     }
 
     @Override
     public void setValue(final Value newValue) {
-        this.values = new Value[] { newValue };
-        this.isMultiple = false;
+        this.itemData.setValues(new Value[] { newValue });
+        this.itemData.setMultiple(false);
     }
 
     @Override
     public void setValue(final Value[] newValues) {
-        this.values = new Value[newValues.length];
+        Value[] values = new Value[newValues.length];
         for (int i = 0; i < newValues.length; i++) {
-            this.values[i] = newValues[i];
+            values[i] = newValues[i];
         }
-        this.isMultiple = true;
+        this.itemData.setValues(values);
+        this.itemData.setMultiple(true);
     }
 
     @Override
     public void setValue(final String newValue) throws RepositoryException {
-        this.values = new Value[] { getSession().getValueFactory().createValue(newValue) };
-        this.isMultiple = false;
+        this.itemData.setValues(new Value[] { getSession().getValueFactory().createValue(newValue) });
+        this.itemData.setMultiple(false);
     }
 
     @Override
     public void setValue(final String[] newValues) throws RepositoryException {
-        this.values = new Value[newValues.length];
+        Value[] values = new Value[newValues.length];
         for (int i = 0; i < newValues.length; i++) {
-            this.values[i] = getSession().getValueFactory().createValue(newValues[i]);
+            values[i] = getSession().getValueFactory().createValue(newValues[i]);
         }
-        this.isMultiple = true;
+        this.itemData.setValues(values);
+        this.itemData.setMultiple(true);
     }
 
     @Override
     public void setValue(final InputStream newValue) throws RepositoryException {
-        this.values = new Value[] { new BinaryValue(newValue) };
-        this.isMultiple = false;
+        this.itemData.setValues(new Value[] { new BinaryValue(newValue) });
+        this.itemData.setMultiple(false);
     }
 
     @Override
     public void setValue(final long newValue) throws RepositoryException {
-        this.values = new Value[] { getSession().getValueFactory().createValue(newValue) };
-        this.isMultiple = false;
+        this.itemData.setValues(new Value[] { getSession().getValueFactory().createValue(newValue) });
+        this.itemData.setMultiple(false);
     }
 
     @Override
     public void setValue(final double newValue) throws RepositoryException {
-        this.values = new Value[] { getSession().getValueFactory().createValue(newValue) };
-        this.isMultiple = false;
+        this.itemData.setValues(new Value[] { getSession().getValueFactory().createValue(newValue) });
+        this.itemData.setMultiple(false);
     }
 
     @Override
     public void setValue(final Calendar newValue) throws RepositoryException {
-        this.values = new Value[] { getSession().getValueFactory().createValue(newValue) };
-        this.isMultiple = false;
+        this.itemData.setValues(new Value[] { getSession().getValueFactory().createValue(newValue) });
+        this.itemData.setMultiple(false);
     }
 
     @Override
     public void setValue(final boolean newValue) throws RepositoryException {
-        this.values = new Value[] { getSession().getValueFactory().createValue(newValue) };
-        this.isMultiple = false;
+        this.itemData.setValues(new Value[] { getSession().getValueFactory().createValue(newValue) });
+        this.itemData.setMultiple(false);
     }
 
     @Override
     public void setValue(final Node newValue) throws RepositoryException {
-        this.values = new Value[] { getSession().getValueFactory().createValue(newValue) };
-        this.isMultiple = false;
+        this.itemData.setValues(new Value[] { getSession().getValueFactory().createValue(newValue) });
+        this.itemData.setMultiple(false);
     }
 
     @Override
     public void setValue(final Binary newValue) throws RepositoryException {
-        this.values = new Value[] { new BinaryValue(newValue) };
-        this.isMultiple = false;
+        this.itemData.setValues(new Value[] { new BinaryValue(newValue) });
+        this.itemData.setMultiple(false);
     }
 
     @Override
     public void setValue(final BigDecimal newValue) throws RepositoryException {
-        this.values = new Value[] { getSession().getValueFactory().createValue(newValue) };
-        this.isMultiple = false;
+        this.itemData.setValues(new Value[] { getSession().getValueFactory().createValue(newValue) });
+        this.itemData.setMultiple(false);
     }
 
     @Override
@@ -191,7 +200,7 @@ class MockProperty extends AbstractItem implements Property {
 
     @Override
     public int getType() throws RepositoryException {
-        return this.values[0].getType();
+        return this.itemData.getValues()[0].getType();
     }
 
     @Override
@@ -201,9 +210,9 @@ class MockProperty extends AbstractItem implements Property {
 
     @Override
     public long[] getLengths() throws RepositoryException {
-        long[] lengths = new long[this.values.length];
-        for (int i = 0; i < this.values.length; i++) {
-            lengths[i] = this.values[i].getString().length();
+        long[] lengths = new long[this.itemData.getValues().length];
+        for (int i = 0; i < this.itemData.getValues().length; i++) {
+            lengths[i] = this.itemData.getValues()[i].getString().length();
         }
         return lengths;
     }
@@ -215,7 +224,7 @@ class MockProperty extends AbstractItem implements Property {
 
     @Override
     public boolean isMultiple() {
-        return this.isMultiple;
+        return this.itemData.isMultiple();
     }
 
     @Override
@@ -223,6 +232,19 @@ class MockProperty extends AbstractItem implements Property {
         return new MockPropertyDefinition();
     }
 
+    @Override
+    public int hashCode() {
+        return itemData.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof MockProperty) {
+            return itemData.equals(((MockProperty)obj).itemData);
+        }
+        return false;
+    }
+    
     // --- unsupported operations ---
     @Override
     public Node getNode() {
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockRepository.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockRepository.java
index 324820e..84e342b 100644
--- a/src/main/java/org/apache/sling/testing/mock/jcr/MockRepository.java
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockRepository.java
@@ -18,6 +18,9 @@
  */
 package org.apache.sling.testing.mock.jcr;
 
+import java.util.LinkedHashMap;
+import java.util.Map;
+
 import javax.jcr.Credentials;
 import javax.jcr.Repository;
 import javax.jcr.Session;
@@ -32,9 +35,16 @@ import org.apache.commons.lang3.ArrayUtils;
  */
 class MockRepository implements Repository {
 
+    // Use linked hashmap to ensure ordering when adding items is preserved.
+    private final Map<String, ItemData> items = new LinkedHashMap<String, ItemData>();
+    
+    public MockRepository() {
+        this.items.put("/", ItemData.newNode("/", MockNodeTypes.NT_UNSTRUCTURED));
+    }
+    
     @Override
     public Session login() {
-        return new MockSession(this);
+        return new MockSession(this, items);
     }
 
     @Override
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockSession.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockSession.java
index 5567318..37f5876 100644
--- a/src/main/java/org/apache/sling/testing/mock/jcr/MockSession.java
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockSession.java
@@ -21,7 +21,6 @@ package org.apache.sling.testing.mock.jcr;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.ArrayList;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Pattern;
@@ -41,6 +40,7 @@ import javax.jcr.Workspace;
 import javax.jcr.retention.RetentionManager;
 import javax.jcr.security.AccessControlManager;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.jackrabbit.commons.iterator.RangeIteratorAdapter;
 import org.apache.jackrabbit.value.ValueFactoryImpl;
 import org.xml.sax.ContentHandler;
@@ -53,14 +53,12 @@ class MockSession implements Session {
 
     private final Repository repository;
     private final Workspace workspace;
+    private final Map<String, ItemData> items;
 
-    // Use linked hashmap to ensure ordering when adding items is preserved.
-    private final Map<String, Item> items = new LinkedHashMap<String, Item>();
-
-    public MockSession(final Repository repository) {
+    public MockSession(Repository repository, Map<String,ItemData> items) {
         this.repository = repository;
         this.workspace = new MockWorkspace(this);
-        this.items.put("/", new MockNode("/", this, MockNodeTypes.NT_UNSTRUCTURED));
+        this.items = items;
     }
 
     @Override
@@ -70,9 +68,14 @@ class MockSession implements Session {
 
     @Override
     public Item getItem(final String absPath) throws RepositoryException {
-        Item item = this.items.get(absPath);
-        if (item != null) {
-            return item;
+        ItemData itemData = this.items.get(absPath);
+        if (itemData != null) {
+            if (itemData.isNode()) {
+                return new MockNode(itemData, this);
+            }
+            else {
+                return new MockProperty(itemData, this);
+            }
         } else {
             throw new PathNotFoundException(String.format("No item found at: %s.", absPath));
         }
@@ -90,12 +93,9 @@ class MockSession implements Session {
 
     @Override
     public Node getNodeByIdentifier(final String id) throws RepositoryException {
-        for (Item item : this.items.values()) {
-            if (item instanceof Node) {
-                Node node = (Node) item;
-                if (node.getIdentifier().equals(id)) {
-                    return node;
-                }
+        for (ItemData item : this.items.values()) {
+            if (item.isNode() && StringUtils.equals(item.getUuid(), id)) {
+                return new MockNode(item, this);
             }
         }
         throw new ItemNotFoundException(String.format("No node found with id: %s.", id));
@@ -138,7 +138,7 @@ class MockSession implements Session {
 
     @Override
     public Node getRootNode() {
-        return (Node) this.items.get("/");
+        return (Node)this.items.get("/").getItem(this);
     }
 
     @Override
@@ -151,8 +151,8 @@ class MockSession implements Session {
      * @param item item
      * @throws RepositoryException
      */
-    void addItem(final Item item) throws RepositoryException {
-        this.items.put(item.getPath(), item);
+    void addItem(final ItemData itemData) throws RepositoryException {
+        this.items.put(itemData.getPath(), itemData);
     }
 
     /**
@@ -182,9 +182,9 @@ class MockSession implements Session {
         Pattern pattern = Pattern.compile("^" + Pattern.quote(parentPath) + "/[^/]+$");
 
         // collect child resources
-        for (Item item : this.items.values()) {
+        for (ItemData item : this.items.values()) {
             if (pattern.matcher(item.getPath()).matches() && (filter == null || filter.accept(item))) {
-                children.add(item);
+                children.add(item.getItem(this));
             }
         }
 
diff --git a/src/test/java/org/apache/sling/testing/mock/jcr/MockNodeTest.java b/src/test/java/org/apache/sling/testing/mock/jcr/MockNodeTest.java
index f02df0e..ef1bed3 100644
--- a/src/test/java/org/apache/sling/testing/mock/jcr/MockNodeTest.java
+++ b/src/test/java/org/apache/sling/testing/mock/jcr/MockNodeTest.java
@@ -20,7 +20,6 @@ package org.apache.sling.testing.mock.jcr;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
 import javax.jcr.ItemNotFoundException;
@@ -56,14 +55,14 @@ public class MockNodeTest {
     public void testGetNodes() throws RepositoryException {
         NodeIterator nodes = this.node1.getNodes();
         assertEquals(1, nodes.getSize());
-        assertSame(this.node11, nodes.next());
+        assertEquals(this.node11, nodes.nextNode());
 
         assertTrue(this.node1.hasNodes());
         assertFalse(this.node11.hasNodes());
 
         nodes = this.node1.getNodes("^node.*$");
         assertEquals(1, nodes.getSize());
-        assertSame(this.node11, nodes.next());
+        assertEquals(this.node11, nodes.nextNode());
 
         nodes = this.node1.getNodes("unknown?");
         assertEquals(0, nodes.getSize());
@@ -73,14 +72,14 @@ public class MockNodeTest {
     public void testGetProperties() throws RepositoryException {
         PropertyIterator properties = this.node1.getProperties();
         assertEquals(1, properties.getSize());
-        assertSame(this.prop1, properties.next());
+        assertEquals(this.prop1, properties.next());
 
         assertTrue(this.node1.hasProperties());
         assertFalse(this.node11.hasProperties());
 
         properties = this.node1.getProperties("^prop.*$");
         assertEquals(1, properties.getSize());
-        assertSame(this.prop1, properties.next());
+        assertEquals(this.prop1, properties.next());
 
         properties = this.node1.getProperties("unknown?");
         assertEquals(0, properties.getSize());
@@ -114,11 +113,11 @@ public class MockNodeTest {
     public void testGetPrimaryItem() throws RepositoryException {
         Node dataParent = this.node1.addNode("dataParent");
         Property dataProperty = dataParent.setProperty(JcrConstants.JCR_DATA, "data");
-        assertSame(dataProperty, dataParent.getPrimaryItem());
+        assertEquals(dataProperty, dataParent.getPrimaryItem());
 
         Node contentParent = this.node1.addNode("contentParent");
         Node contentNode = contentParent.addNode(JcrConstants.JCR_CONTENT);
-        assertSame(contentNode, contentParent.getPrimaryItem());
+        assertEquals(contentNode, contentParent.getPrimaryItem());
     }
 
     @Test(expected = ItemNotFoundException.class)
diff --git a/src/test/java/org/apache/sling/testing/mock/jcr/MockRepositoryTest.java b/src/test/java/org/apache/sling/testing/mock/jcr/MockRepositoryTest.java
index 6143456..b5f2add 100644
--- a/src/test/java/org/apache/sling/testing/mock/jcr/MockRepositoryTest.java
+++ b/src/test/java/org/apache/sling/testing/mock/jcr/MockRepositoryTest.java
@@ -22,9 +22,13 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
 
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
+import javax.jcr.Session;
 import javax.jcr.SimpleCredentials;
 
 import org.junit.Before;
@@ -39,25 +43,52 @@ public class MockRepositoryTest {
 
     @Before
     public void setUp() {
-        this.repository = MockJcr.newRepository();
+        repository = MockJcr.newRepository();
     }
 
     @Test
     public void testLogin() throws RepositoryException {
-        assertNotNull(this.repository.login());
-        assertNotNull(this.repository.login(new SimpleCredentials(USER_NAME, PASSWORD)));
-        assertNotNull(this.repository.login(MockJcr.DEFAULT_WORKSPACE));
-        assertNotNull(this.repository.login(new SimpleCredentials(USER_NAME, PASSWORD), MockJcr.DEFAULT_WORKSPACE));
+        assertNotNull(repository.login());
+        assertNotNull(repository.login(new SimpleCredentials(USER_NAME, PASSWORD)));
+        assertNotNull(repository.login(MockJcr.DEFAULT_WORKSPACE));
+        assertNotNull(repository.login(new SimpleCredentials(USER_NAME, PASSWORD), MockJcr.DEFAULT_WORKSPACE));
     }
 
     @Test
     public void testDescriptor() {
-        assertEquals(0, this.repository.getDescriptorKeys().length);
-        assertNull(this.repository.getDescriptor("test"));
-        assertNull(this.repository.getDescriptorValue("test"));
-        assertNull(this.repository.getDescriptorValues("test"));
-        assertFalse(this.repository.isStandardDescriptor("test"));
-        assertFalse(this.repository.isSingleValueDescriptor("test"));
+        assertEquals(0, repository.getDescriptorKeys().length);
+        assertNull(repository.getDescriptor("test"));
+        assertNull(repository.getDescriptorValue("test"));
+        assertNull(repository.getDescriptorValues("test"));
+        assertFalse(repository.isStandardDescriptor("test"));
+        assertFalse(repository.isSingleValueDescriptor("test"));
+    }
+    
+    @Test
+    public void testMultipleSessions() throws RepositoryException {
+        Session session1 = repository.login();
+        Session session2 = repository.login();
+
+        // add a node in session 1
+        Node root = session1.getRootNode();
+        root.addNode("test");
+        session1.save();
+        
+        // try to get node in session 2
+        Node testNode2 = session2.getNode("/test");
+        assertNotNull(testNode2);
+        
+        // delete node and make sure it is removed in session 1 as well
+        testNode2.remove();
+        session2.save();
+        
+        try {
+            session1.getNode("/test");
+            fail("Node was not removed");
+        }
+        catch (PathNotFoundException ex) {
+            // expected
+        }
     }
 
 }
diff --git a/src/test/java/org/apache/sling/testing/mock/jcr/MockSessionTest.java b/src/test/java/org/apache/sling/testing/mock/jcr/MockSessionTest.java
index 4c297c1..5f9d895 100644
--- a/src/test/java/org/apache/sling/testing/mock/jcr/MockSessionTest.java
+++ b/src/test/java/org/apache/sling/testing/mock/jcr/MockSessionTest.java
@@ -22,7 +22,6 @@ import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
 import javax.jcr.ItemNotFoundException;
@@ -60,7 +59,7 @@ public class MockSessionTest {
     @Test
     public void testNodePropertyCreateRead() throws RepositoryException {
         Node rootNode = this.session.getNode("/");
-        assertSame(rootNode, this.session.getRootNode());
+        assertEquals(rootNode, this.session.getRootNode());
 
         Node node1 = rootNode.addNode("node1");
         node1.setProperty("prop1a", "value1a");
@@ -69,16 +68,16 @@ public class MockSessionTest {
         Node node2 = rootNode.addNode("node2");
         node2.setProperty("prop2", "value2");
 
-        assertSame(node1, rootNode.getNode("node1"));
-        assertSame(node1, this.session.getNode("/node1"));
-        assertSame(node1, this.session.getItem("/node1"));
-        assertSame(node1, this.session.getNodeByIdentifier(node1.getIdentifier()));
+        assertEquals(node1, rootNode.getNode("node1"));
+        assertEquals(node1, this.session.getNode("/node1"));
+        assertEquals(node1, this.session.getItem("/node1"));
+        assertEquals(node1, this.session.getNodeByIdentifier(node1.getIdentifier()));
         assertTrue(this.session.nodeExists("/node1"));
         assertTrue(this.session.itemExists("/node1"));
-        assertSame(node2, rootNode.getNode("node2"));
-        assertSame(node2, this.session.getNode("/node2"));
-        assertSame(node2, this.session.getItem("/node2"));
-        assertSame(node2, this.session.getNodeByIdentifier(node2.getIdentifier()));
+        assertEquals(node2, rootNode.getNode("node2"));
+        assertEquals(node2, this.session.getNode("/node2"));
+        assertEquals(node2, this.session.getItem("/node2"));
+        assertEquals(node2, this.session.getNodeByIdentifier(node2.getIdentifier()));
         assertTrue(this.session.nodeExists("/node2"));
         assertTrue(this.session.itemExists("/node2"));
 
@@ -86,16 +85,16 @@ public class MockSessionTest {
         Property prop1b = node1.getProperty("prop1b");
         Property prop2 = node2.getProperty("prop2");
 
-        assertSame(prop1a, this.session.getProperty("/node1/prop1a"));
-        assertSame(prop1a, this.session.getItem("/node1/prop1a"));
+        assertEquals(prop1a, this.session.getProperty("/node1/prop1a"));
+        assertEquals(prop1a, this.session.getItem("/node1/prop1a"));
         assertTrue(this.session.propertyExists("/node1/prop1a"));
         assertTrue(this.session.itemExists("/node1/prop1a"));
-        assertSame(prop1b, this.session.getProperty("/node1/prop1b"));
-        assertSame(prop1b, this.session.getItem("/node1/prop1b"));
+        assertEquals(prop1b, this.session.getProperty("/node1/prop1b"));
+        assertEquals(prop1b, this.session.getItem("/node1/prop1b"));
         assertTrue(this.session.propertyExists("/node1/prop1b"));
         assertTrue(this.session.itemExists("/node1/prop1b"));
-        assertSame(prop2, this.session.getProperty("/node2/prop2"));
-        assertSame(prop2, this.session.getItem("/node2/prop2"));
+        assertEquals(prop2, this.session.getProperty("/node2/prop2"));
+        assertEquals(prop2, this.session.getItem("/node2/prop2"));
         assertTrue(this.session.propertyExists("/node2/prop2"));
         assertTrue(this.session.itemExists("/node2/prop2"));
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-jcr-mock] 09/10: [maven-release-plugin] prepare release org.apache.sling.testing.jcr-mock-1.0.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.testing.jcr-mock-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-jcr-mock.git

commit e3c1ed41fbc8b0b91d2f349f5ed10a7a2ceabf41
Author: sseifert <ss...@unknown>
AuthorDate: Fri Oct 17 08:15:13 2014 +0000

    [maven-release-plugin] prepare release org.apache.sling.testing.jcr-mock-1.0.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/testing/mocks/jcr-mock@1632494 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index ad39b2d..b85609b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.testing.jcr-mock</artifactId>
-    <version>1.0.0-SNAPSHOT</version>
+    <version>1.0.0</version>
     <packaging>jar</packaging>
 
     <name>Apache Sling Testing JCR Mock</name>
@@ -39,9 +39,9 @@
     </properties>
 
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/testing/mocks/jcr-mock</connection>
-        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/testing/mocks/jcr-mock</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/trunk/testing/mocks/jcr-mock</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.jcr-mock-1.0.0</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.testing.jcr-mock-1.0.0</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.testing.jcr-mock-1.0.0</url>
     </scm>
 
     <dependencies>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-testing-jcr-mock] 06/10: SLING-4042 minor optimization

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.testing.jcr-mock-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-jcr-mock.git

commit 38bb1c116fd315025bb97dd04a6f52a637b0535f
Author: sseifert <ss...@unknown>
AuthorDate: Thu Oct 16 19:02:45 2014 +0000

    SLING-4042 minor optimization
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/testing/mocks/jcr-mock@1632413 13f79535-47bb-0310-9956-ffa450edef68
---
 .../apache/sling/testing/mock/jcr/AbstractItem.java  | 20 ++++++++++----------
 .../org/apache/sling/testing/mock/jcr/ItemData.java  |  4 +++-
 .../org/apache/sling/testing/mock/jcr/MockNode.java  |  5 +----
 .../apache/sling/testing/mock/jcr/MockProperty.java  |  5 +----
 4 files changed, 15 insertions(+), 19 deletions(-)

diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/AbstractItem.java b/src/main/java/org/apache/sling/testing/mock/jcr/AbstractItem.java
index 0eb92f3..f174514 100644
--- a/src/main/java/org/apache/sling/testing/mock/jcr/AbstractItem.java
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/AbstractItem.java
@@ -32,27 +32,27 @@ import org.apache.commons.lang3.StringUtils;
  */
 abstract class AbstractItem implements Item {
 
-    private final String path;
+    protected final ItemData itemData;
     private final Session session;
 
-    public AbstractItem(final String path, final Session session) {
-        this.path = path;
+    public AbstractItem(final ItemData itemData, final Session session) {
+        this.itemData = itemData;
         this.session = session;
     }
 
     @Override
     public String getName() {
-        return ResourceUtil.getName(this.path);
+        return this.itemData.getName();
     }
 
     @Override
     public String getPath() {
-        return this.path;
+        return this.itemData.getPath();
     }
 
     @Override
     public Node getParent() throws RepositoryException {
-        return (Node) getSession().getItem(ResourceUtil.getParent(this.path));
+        return (Node) getSession().getItem(ResourceUtil.getParent(getPath()));
     }
 
     @Override
@@ -75,14 +75,14 @@ abstract class AbstractItem implements Item {
         if (depth < 0 || depth > getDepth()) {
             throw new ItemNotFoundException();
         }
-        return this.session.getItem(ResourceUtil.getParent(this.path, depth));
+        return this.session.getItem(ResourceUtil.getParent(getPath(), depth));
     }
 
     protected String makeAbsolutePath(final String relativePath) {
         String absolutePath = relativePath;
         // ensure the path is absolute and normalized
         if (!StringUtils.startsWith(absolutePath, "/")) {
-            absolutePath = this.path + "/" + absolutePath; // NOPMD
+            absolutePath = getPath() + "/" + absolutePath; // NOPMD
         }
         return ResourceUtil.normalize(absolutePath);
     }
@@ -98,10 +98,10 @@ abstract class AbstractItem implements Item {
 
     @Override
     public int getDepth() throws RepositoryException {
-        if (StringUtils.equals("/", this.path)) {
+        if (StringUtils.equals("/", getPath())) {
             return 0;
         } else {
-            return StringUtils.countMatches(this.path, "/");
+            return StringUtils.countMatches(getPath(), "/");
         }
     }
 
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/ItemData.java b/src/main/java/org/apache/sling/testing/mock/jcr/ItemData.java
index 21b2678..64ec7c1 100644
--- a/src/main/java/org/apache/sling/testing/mock/jcr/ItemData.java
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/ItemData.java
@@ -31,6 +31,7 @@ import javax.jcr.nodetype.NodeType;
 class ItemData {
     
     private final String path;
+    private final String name;
     private final boolean isNode;
     private final String uuid;
     private final NodeType nodeType;
@@ -40,6 +41,7 @@ class ItemData {
     private ItemData(String path, boolean isNode, String uuid, NodeType nodeType) {
         super();
         this.path = path;
+        this.name = ResourceUtil.getName(path);
         this.uuid = uuid;
         this.isNode = isNode;
         this.nodeType = nodeType;
@@ -50,7 +52,7 @@ class ItemData {
     }
     
     public String getName() {
-        return ResourceUtil.getName(path);
+        return name;
     }
 
     public boolean isNode() {
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockNode.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockNode.java
index d53f43c..45a8060 100644
--- a/src/main/java/org/apache/sling/testing/mock/jcr/MockNode.java
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockNode.java
@@ -49,11 +49,8 @@ import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter;
  */
 class MockNode extends AbstractItem implements Node {
 
-    private final ItemData itemData;
-
     public MockNode(final ItemData itemData, final Session session) {
-        super(itemData.getPath(), session);
-        this.itemData = itemData;
+        super(itemData, session);
     }
 
     @Override
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockProperty.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockProperty.java
index 40a3b40..2bc4182 100644
--- a/src/main/java/org/apache/sling/testing/mock/jcr/MockProperty.java
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockProperty.java
@@ -39,11 +39,8 @@ import org.apache.jackrabbit.value.BinaryValue;
  */
 class MockProperty extends AbstractItem implements Property {
 
-    private final ItemData itemData;
-
     public MockProperty(final ItemData itemData, final Session session) {
-        super(itemData.getPath(), session);
-        this.itemData = itemData;
+        super(itemData, session);
         if (this.itemData.getValues() == null) {
             try {
                 this.itemData.setValues(new Value[] { getSession().getValueFactory().createValue("") });

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.