You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by pa...@apache.org on 2019/05/09 14:55:30 UTC

[sling-org-apache-sling-jcr-base] branch SLING-8411 created (now 63e7955)

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

pauls pushed a change to branch SLING-8411
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-base.git.


      at 63e7955  SLING-8411: Provide a way to bifurcat a repository path to a provider mount.

This branch includes the following new commits:

     new 63e7955  SLING-8411: Provide a way to bifurcat a repository path to a provider mount.

The 1 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.



[sling-org-apache-sling-jcr-base] 01/01: SLING-8411: Provide a way to bifurcat a repository path to a provider mount.

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

pauls pushed a commit to branch SLING-8411
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-base.git

commit 63e79550819e48d436b53903862d68253a501e40
Author: Karl Pauls <ka...@gmail.com>
AuthorDate: Thu May 9 16:55:12 2019 +0200

    SLING-8411: Provide a way to bifurcat a repository path to a provider mount.
---
 pom.xml                                            |   8 +-
 .../sling/jcr/base/AbstractSlingRepository2.java   |  27 +-
 .../jcr/base/AbstractSlingRepositoryManager.java   |  44 +-
 .../jcr/base/internal/mount/ChainedIterator.java   |  75 +++
 .../internal/mount/ProxyAccessControlManager.java  | 107 ++++
 .../sling/jcr/base/internal/mount/ProxyItem.java   | 135 +++++
 .../mount/ProxyJackrabbitAccessControlManager.java |  77 +++
 .../internal/mount/ProxyJackrabbitRepository.java  |  59 ++
 .../internal/mount/ProxyJackrabbitSession.java     |  85 +++
 .../internal/mount/ProxyJackrabbitWorkspace.java   |  42 ++
 .../sling/jcr/base/internal/mount/ProxyLock.java   |  75 +++
 .../internal/mount/ProxyNamespaceRegistry.java     |  67 +++
 .../sling/jcr/base/internal/mount/ProxyNode.java   | 413 ++++++++++++++
 .../base/internal/mount/ProxyNodeTypeManager.java  | 111 ++++
 .../base/internal/mount/ProxyPrivilegeManager.java |  62 +++
 .../jcr/base/internal/mount/ProxyProperty.java     | 157 ++++++
 .../sling/jcr/base/internal/mount/ProxyQuery.java  | 233 ++++++++
 .../jcr/base/internal/mount/ProxyQueryManager.java |  55 ++
 .../base/internal/mount/ProxyQueryObjectModel.java |  47 ++
 .../mount/ProxyQueryObjectModelFactory.java        | 183 ++++++
 .../jcr/base/internal/mount/ProxyQueryResult.java  |  46 ++
 .../jcr/base/internal/mount/ProxyRepository.java   | 133 +++++
 .../jcr/base/internal/mount/ProxySession.java      | 615 +++++++++++++++++++++
 .../jcr/base/internal/mount/ProxyUserManager.java  | 123 +++++
 .../jcr/base/internal/mount/ProxyWorkspace.java    | 168 ++++++
 .../jcr/base/internal/mount/ProxyWrapper.java      |  40 ++
 .../apache/sling/jcr/base/spi/RepositoryMount.java |  27 +
 .../apache/sling/jcr/base/spi/package-info.java    |  27 +
 28 files changed, 3231 insertions(+), 10 deletions(-)

diff --git a/pom.xml b/pom.xml
index 453b538..bab9c73 100644
--- a/pom.xml
+++ b/pom.xml
@@ -116,7 +116,7 @@
         <dependency>
             <groupId>org.apache.jackrabbit</groupId>
             <artifactId>jackrabbit-api</artifactId>
-            <version>2.0.0</version>
+            <version>2.13.4</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -131,6 +131,12 @@
             <version>1.3.6</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>oak-commons</artifactId>
+            <version>1.8.2</version>
+            <scope>provided</scope>
+        </dependency>
         
         <!-- OSGi Libraries -->
         <dependency>
diff --git a/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepository2.java b/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepository2.java
index d6d7307..56cbf81 100644
--- a/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepository2.java
+++ b/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepository2.java
@@ -36,6 +36,7 @@ import javax.jcr.Value;
 import javax.security.auth.Subject;
 
 import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.jcr.base.internal.mount.ProxyRepository;
 import org.apache.sling.serviceusermapping.ServiceUserMapper;
 import org.osgi.annotation.versioning.ProviderType;
 import org.osgi.framework.Bundle;
@@ -159,14 +160,22 @@ public abstract class AbstractSlingRepository2 implements SlingRepository {
     private Session createServiceSession(Bundle usingBundle, String subServiceName, String workspaceName) throws RepositoryException {
         final ServiceUserMapper serviceUserMapper = this.getSlingRepositoryManager().getServiceUserMapper();
         if (serviceUserMapper != null) {
+            Session session = null;
             final Iterable<String> principalNames = serviceUserMapper.getServicePrincipalNames(usingBundle, subServiceName);
             if (principalNames != null) {
-                return createServiceSession(principalNames, workspaceName);
+                session = createServiceSession(principalNames, workspaceName);
             }
-
-            final String userName = serviceUserMapper.getServiceUserID(usingBundle, subServiceName);
-            if (userName != null) {
-                return createServiceSession(userName, workspaceName);
+            else
+            {
+                final String userName = serviceUserMapper.getServiceUserID(usingBundle, subServiceName);
+                if (userName != null)
+                {
+                    session = createServiceSession(userName, workspaceName);
+                }
+            }
+            if (session != null) {
+                Repository repository = getRepository();
+                return repository instanceof ProxyRepository ? ((ProxyRepository) repository).wrap(session) : session;
             }
         }
         return null;
@@ -196,7 +205,9 @@ public abstract class AbstractSlingRepository2 implements SlingRepository {
         Session admin = null;
         try {
             admin = this.createAdministrativeSession(workspace);
-            return admin.impersonate(new SimpleCredentials(serviceUserName, new char[0]));
+            Session result = admin.impersonate(new SimpleCredentials(serviceUserName, new char[0]));
+            Repository repository = getRepository();
+            return repository instanceof ProxyRepository ? ((ProxyRepository) repository).wrap(result) : result;
         } finally {
             if (admin != null) {
                 admin.logout();
@@ -445,7 +456,9 @@ public abstract class AbstractSlingRepository2 implements SlingRepository {
         }
 
         logger.debug("SlingRepository.loginAdministrative is deprecated. Please use SlingRepository.loginService.");
-        return createAdministrativeSession(workspace);
+        Session result = createAdministrativeSession(workspace);
+        Repository repository = getRepository();
+        return repository instanceof ProxyRepository ? ((ProxyRepository) repository).wrap(result) : result;
     }
 
     // Remaining Repository service methods all backed by the actual
diff --git a/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepositoryManager.java b/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepositoryManager.java
index 761c956..8e54c49 100644
--- a/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepositoryManager.java
+++ b/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepositoryManager.java
@@ -21,20 +21,27 @@ package org.apache.sling.jcr.base;
 import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.jcr.Repository;
 
+import org.apache.jackrabbit.api.JackrabbitRepository;
 import org.apache.sling.jcr.api.SlingRepository;
 import org.apache.sling.jcr.api.SlingRepositoryInitializer;
 import org.apache.sling.jcr.base.internal.loader.Loader;
 import org.apache.sling.jcr.base.internal.LoginAdminWhitelist;
+import org.apache.sling.jcr.base.internal.mount.ProxyJackrabbitRepository;
+import org.apache.sling.jcr.base.internal.mount.ProxyRepository;
+import org.apache.sling.jcr.base.spi.RepositoryMount;
 import org.apache.sling.serviceusermapping.ServiceUserMapper;
 import org.osgi.annotation.versioning.ProviderType;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceFactory;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
@@ -116,6 +123,8 @@ public abstract class AbstractSlingRepositoryManager {
 
     private volatile boolean stopRequested;
 
+    volatile ServiceTracker<RepositoryMount, RepositoryMount> mountTracker;
+
     /**
      * Returns the default workspace, which may be <code>null</code> meaning to
      * use the repository provided default workspace.
@@ -283,7 +292,30 @@ public abstract class AbstractSlingRepositoryManager {
      * @return The repository
      */
     protected final Repository getRepository() {
-        return repository;
+        ServiceReference<RepositoryMount> ref = mountTracker != null ? mountTracker.getServiceReference() : null;
+
+        Repository mountRepo = (ref != null ? mountTracker.getService(ref) : null);
+        Object mounts = ref != null ? ref.getProperty(RepositoryMount.MOUNT_POINTS_KEY) : null;
+        Set<String> mountPoints = new HashSet<>();
+
+        if (mounts != null) {
+            if (mounts instanceof String[]) {
+                for (String mount : ((String[]) mounts)) {
+                    mountPoints.add(mount);
+                }
+            }
+            else {
+                mountPoints.add(mounts.toString());
+            }
+        }
+        else {
+            mountPoints.add("/content/jcrmount");
+        }
+        return mountRepo != null ?
+            repository instanceof JackrabbitRepository ?
+                new ProxyJackrabbitRepository((JackrabbitRepository) repository, (JackrabbitRepository) mountRepo, mountPoints) :
+                new ProxyRepository(repository, mountRepo, mountPoints) :
+            repository;
     }
 
     /**
@@ -393,6 +425,9 @@ public abstract class AbstractSlingRepositoryManager {
         this.defaultWorkspace = config.defaultWorkspace;
         this.disableLoginAdministrative = config.disableLoginAdministrative;
 
+        this.mountTracker = new ServiceTracker<>(this.bundleContext, RepositoryMount.class, null);
+        this.mountTracker.open();
+
         this.repoInitializerTracker = new ServiceTracker<SlingRepositoryInitializer, SlingRepositoryInitializerInfo>(bundleContext, SlingRepositoryInitializer.class,
                 new ServiceTrackerCustomizer<SlingRepositoryInitializer, SlingRepositoryInitializerInfo>() {
 
@@ -574,6 +609,11 @@ public abstract class AbstractSlingRepositoryManager {
             startupThread = null;
         }
 
+        if (this.mountTracker != null) {
+            this.mountTracker.close();
+            this.mountTracker = null;
+        }
+
         // ensure the repository is really disposed off
         if (repository != null || isRepositoryServiceRegistered()) {
             log.info("stop: Repository still running, forcing shutdown");
@@ -604,7 +644,7 @@ public abstract class AbstractSlingRepositoryManager {
                         this.destroy(this.masterSlingRepository);
 
                         try {
-                            disposeRepository(oldRepo);
+                            disposeRepository(oldRepo instanceof  ProxyRepository ? ((ProxyRepository) oldRepo).jcr : oldRepo);
                         } catch (Throwable t) {
                             log.info("stop: Uncaught problem disposing the repository", t);
                         }
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ChainedIterator.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ChainedIterator.java
new file mode 100644
index 0000000..015a920
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ChainedIterator.java
@@ -0,0 +1,75 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public class ChainedIterator<T> implements Iterator<T> {
+
+    private T nextElement;
+
+    private final Iterator<Iterator<T>> iterators;
+
+    private Iterator<T> currentIterator;
+
+    public ChainedIterator(final Iterator<Iterator<T>> iterators) {
+        this.iterators = iterators;
+    }
+
+    protected T seek() {
+        while (true) {
+            if (currentIterator == null) {
+                if (!iterators.hasNext()) {
+                    return null;
+                }
+                currentIterator = iterators.next();
+                continue;
+            }
+            if (currentIterator.hasNext()) {
+                return currentIterator.next();
+            } else {
+                currentIterator = null;
+            }
+        }
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (nextElement == null) {
+            nextElement = seek();
+        }
+        return nextElement != null;
+    }
+
+    @Override
+    public T next() {
+        if (nextElement == null && !hasNext()) {
+            throw new NoSuchElementException();
+        }
+        final T result = nextElement;
+        nextElement = null;
+        return result;
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyAccessControlManager.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyAccessControlManager.java
new file mode 100644
index 0000000..e82d362
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyAccessControlManager.java
@@ -0,0 +1,107 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.lock.LockException;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.AccessControlPolicyIterator;
+import javax.jcr.security.Privilege;
+import javax.jcr.version.VersionException;
+
+public class ProxyAccessControlManager<T extends AccessControlManager> extends ProxyWrapper<T> implements AccessControlManager {
+    final T mount;
+
+    public ProxyAccessControlManager(ProxySession<?> mountSession, T delegate, T mount) {
+        super(mountSession, delegate);
+        this.mount = mount;
+    }
+
+    public Privilege[] getSupportedPrivileges(String absPath) throws PathNotFoundException, RepositoryException {
+        if (mountSession.isMount(absPath)) {
+            return mount.getSupportedPrivileges(absPath);
+        }
+        return delegate.getSupportedPrivileges(absPath);
+    }
+
+    public Privilege privilegeFromName(String privilegeName) throws AccessControlException, RepositoryException {
+        try {
+            return delegate.privilegeFromName(privilegeName);
+        } catch (AccessControlException ex) {
+            return mount.privilegeFromName(privilegeName);
+        }
+    }
+
+    public boolean hasPrivileges(String absPath, Privilege[] privileges) throws PathNotFoundException, RepositoryException {
+        if (mountSession.isMount(absPath)) {
+            return mount.hasPrivileges(absPath, privileges);
+        }
+        return delegate.hasPrivileges(absPath, privileges);
+    }
+
+    public Privilege[] getPrivileges(String absPath) throws PathNotFoundException, RepositoryException {
+        if (mountSession.isMount(absPath)) {
+            return mount.getPrivileges(absPath);
+        }
+        return delegate.getPrivileges(absPath);
+    }
+
+    public AccessControlPolicy[] getPolicies(String absPath) throws PathNotFoundException, AccessDeniedException, RepositoryException {
+        if (mountSession.isMount(absPath)) {
+            return mount.getPolicies(absPath);
+        }
+        return delegate.getPolicies(absPath);
+    }
+
+    public AccessControlPolicy[] getEffectivePolicies(String absPath) throws PathNotFoundException, AccessDeniedException, RepositoryException {
+        if (mountSession.isMount(absPath)) {
+            return mount.getEffectivePolicies(absPath);
+        }
+        return delegate.getEffectivePolicies(absPath);
+    }
+
+    public AccessControlPolicyIterator getApplicablePolicies(String absPath) throws PathNotFoundException, AccessDeniedException, RepositoryException {
+        if (mountSession.isMount(absPath)) {
+            return mount.getApplicablePolicies(absPath);
+        }
+        return delegate.getApplicablePolicies(absPath);
+    }
+
+    public void setPolicy(String absPath, AccessControlPolicy policy) throws PathNotFoundException, AccessControlException, AccessDeniedException, LockException, VersionException, RepositoryException {
+        if (mountSession.isMountParent(absPath) || mountSession.isMount(absPath)) {
+            mount.setPolicy(absPath, policy);
+        }
+        if (!mountSession.isMount(absPath)) {
+            delegate.setPolicy(absPath, policy);
+        }
+    }
+
+    public void removePolicy(String absPath, AccessControlPolicy policy) throws PathNotFoundException, AccessControlException, AccessDeniedException, LockException, VersionException, RepositoryException {
+        if (mountSession.isMountParent(absPath) || mountSession.isMount(absPath)) {
+            mount.removePolicy(absPath, policy);
+        }
+        if (!mountSession.isMount(absPath)) {
+            delegate.removePolicy(absPath, policy);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyItem.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyItem.java
new file mode 100644
index 0000000..1207100
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyItem.java
@@ -0,0 +1,135 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.Item;
+import javax.jcr.ItemExistsException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.ItemVisitor;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.ReferentialIntegrityException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.version.VersionException;
+
+import org.apache.jackrabbit.oak.commons.PathUtils;
+
+public class ProxyItem<T extends Item> extends ProxyWrapper<T> implements Item {
+    public ProxyItem(ProxySession mountSession, T delegate) {
+        super(mountSession, delegate);
+    }
+
+    @Override
+    public String getPath() throws RepositoryException {
+        return delegate.getPath();
+    }
+
+    @Override
+    public String getName() throws RepositoryException {
+        return delegate.getName();
+    }
+
+    @Override
+    public Item getAncestor(int depth) throws ItemNotFoundException, AccessDeniedException, RepositoryException {
+        return this.mountSession.getItem(this.delegate.getAncestor(depth).getPath());
+    }
+
+    @Override
+    public Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException {
+
+        String parentPath;
+
+        try {
+            parentPath = this.delegate.getParent().getPath();
+        } catch (Exception ex) {
+            if (ex instanceof AccessDeniedException) {
+                throw (AccessDeniedException) ex;
+            }
+            parentPath = PathUtils.getParentPath(this.delegate.getPath());
+        }
+
+        return this.mountSession.getNode(parentPath);
+    }
+
+    @Override
+    public int getDepth() throws RepositoryException {
+        return delegate.getDepth();
+    }
+
+    @Override
+    public Session getSession() throws RepositoryException {
+        return mountSession;
+    }
+
+    @Override
+    public boolean isNode() {
+        return delegate.isNode();
+    }
+
+    @Override
+    public boolean isNew() {
+        return delegate.isNew();
+    }
+
+    @Override
+    public boolean isModified() {
+        return delegate.isModified();
+    }
+
+    @Override
+    public boolean isSame(Item otherItem) throws RepositoryException {
+        return delegate.isSame(this.mountSession.unwrap(otherItem));
+    }
+
+    @Override
+    public void accept(final ItemVisitor visitor) throws RepositoryException {
+        delegate.accept(new ItemVisitor() {
+            @Override
+            public void visit(Property property) throws RepositoryException {
+                visitor.visit(mountSession.wrap(property));
+            }
+
+            @Override
+            public void visit(Node node) throws RepositoryException {
+                visitor.visit(mountSession.wrap(node));
+            }
+        });
+    }
+
+    @Override
+    public void save() throws AccessDeniedException, ItemExistsException, ConstraintViolationException, InvalidItemStateException, ReferentialIntegrityException, VersionException, LockException, NoSuchNodeTypeException, RepositoryException {
+        this.mountSession.save();
+    }
+
+    @Override
+    public void refresh(boolean keepChanges) throws InvalidItemStateException, RepositoryException {
+        this.mountSession.refresh(getPath(), delegate, keepChanges);
+    }
+
+    @Override
+    public void remove() throws VersionException, LockException, ConstraintViolationException, AccessDeniedException, RepositoryException {
+        this.mountSession.removeItem(this.getPath());
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyJackrabbitAccessControlManager.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyJackrabbitAccessControlManager.java
new file mode 100644
index 0000000..f810cd8
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyJackrabbitAccessControlManager.java
@@ -0,0 +1,77 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlPolicy;
+
+public class ProxyJackrabbitAccessControlManager extends ProxyAccessControlManager<JackrabbitAccessControlManager> implements JackrabbitAccessControlManager {
+    public ProxyJackrabbitAccessControlManager(ProxySession<?> mountSession, JackrabbitAccessControlManager delegate, JackrabbitAccessControlManager mount) {
+        super(mountSession, delegate, mount);
+    }
+
+    public JackrabbitAccessControlPolicy[] getApplicablePolicies(Principal principal) throws AccessDeniedException, AccessControlException, UnsupportedRepositoryOperationException, RepositoryException {
+        List<JackrabbitAccessControlPolicy> result = new ArrayList<>();
+        result.addAll(Arrays.asList(delegate.getApplicablePolicies(principal)));
+        result.addAll(Arrays.asList(mount.getApplicablePolicies(principal)));
+        return result.toArray(new JackrabbitAccessControlPolicy[0]);
+    }
+
+    public JackrabbitAccessControlPolicy[] getPolicies(Principal principal) throws AccessDeniedException, AccessControlException, UnsupportedRepositoryOperationException, RepositoryException {
+        List<JackrabbitAccessControlPolicy> result = new ArrayList<>();
+        result.addAll(Arrays.asList(delegate.getPolicies(principal)));
+        result.addAll(Arrays.asList(mount.getPolicies(principal)));
+        return result.toArray(new JackrabbitAccessControlPolicy[0]);
+    }
+
+    public AccessControlPolicy[] getEffectivePolicies(Set<Principal> principals) throws AccessDeniedException, AccessControlException, UnsupportedRepositoryOperationException, RepositoryException {
+        List<AccessControlPolicy> result = new ArrayList<>();
+        result.addAll(Arrays.asList(delegate.getEffectivePolicies(principals)));
+        result.addAll(Arrays.asList(mount.getEffectivePolicies(principals)));
+        return result.toArray(new AccessControlPolicy[0]);
+    }
+
+    public boolean hasPrivileges(String absPath, Set<Principal> principals, Privilege[] privileges) throws PathNotFoundException, AccessDeniedException, RepositoryException {
+        if (mountSession.isMount(absPath)) {
+            return mount.hasPrivileges(absPath, principals, privileges);
+        }
+        return delegate.hasPrivileges(absPath, principals, privileges);
+    }
+
+    public Privilege[] getPrivileges(String absPath, Set<Principal> principals) throws PathNotFoundException, AccessDeniedException, RepositoryException {
+        if (mountSession.isMount(absPath)) {
+            return mount.getPrivileges(absPath, principals);
+        }
+        return delegate.getPrivileges(absPath, principals);
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyJackrabbitRepository.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyJackrabbitRepository.java
new file mode 100644
index 0000000..42e2c2a
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyJackrabbitRepository.java
@@ -0,0 +1,59 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Credentials;
+import javax.jcr.LoginException;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.api.JackrabbitRepository;
+import org.apache.jackrabbit.api.JackrabbitSession;
+
+public class ProxyJackrabbitRepository extends ProxyRepository<JackrabbitRepository> implements JackrabbitRepository {
+    public ProxyJackrabbitRepository(JackrabbitRepository jcr, JackrabbitRepository mount, Set<String> mountPoint) {
+        super(jcr, mount, mountPoint);
+    }
+
+    @Override
+    public Session login(Credentials credentials, String workspaceName, Map<String, Object> attributes) throws LoginException, NoSuchWorkspaceException, RepositoryException {
+        Session jcrSession = jcr.login(credentials, workspaceName, attributes);
+
+        if (attributes == null) {
+            attributes = new HashMap<>();
+        }
+        attributes.put(ProxyRepository.class.getPackage().getName() + ".PARENT_SESSION", jcrSession);
+
+        Session mountSession = mount.login(credentials, workspaceName, attributes);
+
+        return jcrSession instanceof JackrabbitSession ?
+                new ProxyJackrabbitSession(this, (JackrabbitSession) jcrSession, mountSession, this.mountPoints) :
+                new ProxySession<>(this, jcrSession, mountSession, this.mountPoints);
+    }
+
+    @Override
+    public void shutdown() {
+
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyJackrabbitSession.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyJackrabbitSession.java
new file mode 100644
index 0000000..97ee586
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyJackrabbitSession.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.jcr.base.internal.mount;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Workspace;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.JackrabbitWorkspace;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+import org.apache.jackrabbit.api.security.user.UserManager;
+
+public class ProxyJackrabbitSession extends ProxySession<JackrabbitSession> implements JackrabbitSession {
+    public ProxyJackrabbitSession(ProxyRepository repository, JackrabbitSession jcr, Session mount, Set<String> mountPoints) {
+        super(repository, jcr, mount, mountPoints);
+    }
+
+    @Override
+    public Workspace getWorkspace() {
+        return new ProxyJackrabbitWorkspace(this, (JackrabbitWorkspace) this.jcr.getWorkspace(), (JackrabbitWorkspace) this.mount.getWorkspace());
+    }
+
+    public boolean hasPermission(String absPath, String... actions) throws RepositoryException {
+        if (isMount(absPath)) {
+            return ((JackrabbitSession) mount).hasPermission(absPath, actions);
+        }
+        return jcr.hasPermission(absPath, actions);
+    }
+
+    public PrincipalManager getPrincipalManager() throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+        return jcr.getPrincipalManager();
+    }
+
+    public UserManager getUserManager() throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+        return new ProxyUserManager(this, jcr.getUserManager(), ((JackrabbitSession) mount).getUserManager());
+    }
+
+    public Item getItemOrNull(String absPath) throws RepositoryException {
+        if (super.itemExists(absPath)) {
+            return super.getItem(absPath);
+        } else {
+            return null;
+        }
+    }
+
+    public Property getPropertyOrNull(String absPath) throws RepositoryException {
+        if (super.propertyExists(absPath)) {
+            return super.getProperty(absPath);
+        } else {
+            return null;
+        }
+    }
+
+    public Node getNodeOrNull(String absPath) throws RepositoryException {
+        if (super.nodeExists(absPath)) {
+            return super.getNode(absPath);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyJackrabbitWorkspace.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyJackrabbitWorkspace.java
new file mode 100644
index 0000000..03f5206
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyJackrabbitWorkspace.java
@@ -0,0 +1,42 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.api.JackrabbitWorkspace;
+import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
+import org.xml.sax.InputSource;
+
+public class ProxyJackrabbitWorkspace extends ProxyWorkspace<JackrabbitWorkspace> implements JackrabbitWorkspace {
+    public ProxyJackrabbitWorkspace(ProxySession mountSession, JackrabbitWorkspace delegate, JackrabbitWorkspace delegate2) {
+        super(mountSession, delegate, delegate2);
+    }
+
+    @Override
+    public void createWorkspace(String workspaceName, InputSource workspaceTemplate) throws AccessDeniedException, RepositoryException {
+        this.delegate.createWorkspace(workspaceName, workspaceTemplate);
+    }
+
+    @Override
+    public PrivilegeManager getPrivilegeManager() throws RepositoryException {
+        return new ProxyPrivilegeManager(mountSession, this.delegate.getPrivilegeManager(), this.delegate2.getPrivilegeManager());
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyLock.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyLock.java
new file mode 100644
index 0000000..20f4316
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyLock.java
@@ -0,0 +1,75 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+
+public class ProxyLock extends ProxyWrapper<Lock> implements Lock {
+    public ProxyLock(ProxySession<?> mountSession, Lock source) {
+        super(mountSession, source);
+    }
+
+    @Override
+    public String getLockOwner() {
+        return delegate.getLockOwner();
+    }
+
+    @Override
+    public boolean isDeep() {
+        return delegate.isDeep();
+    }
+
+    @Override
+    public Node getNode() {
+        return this.mountSession.wrap(this.delegate.getNode());
+    }
+
+    @Override
+    public String getLockToken() {
+        return delegate.getLockToken();
+    }
+
+    @Override
+    public long getSecondsRemaining() throws RepositoryException {
+        return delegate.getSecondsRemaining();
+    }
+
+    @Override
+    public boolean isLive() throws RepositoryException {
+        return delegate.isLive();
+    }
+
+    @Override
+    public boolean isSessionScoped() {
+        return delegate.isSessionScoped();
+    }
+
+    @Override
+    public boolean isLockOwningSession() {
+        return delegate.isLockOwningSession();
+    }
+
+    @Override
+    public void refresh() throws LockException, RepositoryException {
+        delegate.refresh();
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyNamespaceRegistry.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyNamespaceRegistry.java
new file mode 100644
index 0000000..7f70e2f
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyNamespaceRegistry.java
@@ -0,0 +1,67 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.NamespaceException;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+
+public class ProxyNamespaceRegistry implements NamespaceRegistry {
+    private final NamespaceRegistry jcr;
+    private final NamespaceRegistry mount;
+
+    public ProxyNamespaceRegistry(NamespaceRegistry jcr, NamespaceRegistry mount) {
+        this.jcr = jcr;
+        this.mount = mount;
+    }
+
+    @Override
+    public void registerNamespace(String prefix, String uri) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException {
+        jcr.registerNamespace(prefix, uri);
+        mount.registerNamespace(prefix, uri);
+    }
+
+    @Override
+    public void unregisterNamespace(String prefix) throws NamespaceException, UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException {
+        jcr.unregisterNamespace(prefix);
+        mount.unregisterNamespace(prefix);
+    }
+
+    @Override
+    public String[] getPrefixes() throws RepositoryException {
+        return jcr.getPrefixes();
+    }
+
+    @Override
+    public String[] getURIs() throws RepositoryException {
+        return jcr.getURIs();
+    }
+
+    @Override
+    public String getURI(String prefix) throws NamespaceException, RepositoryException {
+        return jcr.getURI(prefix);
+    }
+
+    @Override
+    public String getPrefix(String uri) throws NamespaceException, RepositoryException {
+        return jcr.getPrefix(uri);
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyNode.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyNode.java
new file mode 100644
index 0000000..80a3b2c
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyNode.java
@@ -0,0 +1,413 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.Calendar;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Binary;
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.InvalidLifecycleTransitionException;
+import javax.jcr.Item;
+import javax.jcr.ItemExistsException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.MergeException;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.nodetype.NodeDefinition;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.version.ActivityViolationException;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionException;
+import javax.jcr.version.VersionHistory;
+
+public class ProxyNode extends ProxyItem<Node> implements Node {
+    public ProxyNode(ProxySession mountSession, Node node) {
+        super(mountSession, node);
+    }
+
+    @Override
+    public Node addNode(String relPath) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, RepositoryException {
+        return mountSession.addNode(getPath(), concat(getPath(), relPath), relPath);
+    }
+
+    @Override
+    public Node addNode(String relPath, String primaryNodeTypeName) throws ItemExistsException, PathNotFoundException, NoSuchNodeTypeException, LockException, VersionException, ConstraintViolationException, RepositoryException {
+        return mountSession.addNode(getPath(), concat(getPath(), relPath), relPath, primaryNodeTypeName);
+    }
+
+    @Override
+    public void orderBefore(String srcChildRelPath, String destChildRelPath) throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException, ItemNotFoundException, LockException, RepositoryException {
+        this.delegate.orderBefore(srcChildRelPath, destChildRelPath);
+    }
+
+    @Override
+    public Property setProperty(String name, Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.setProperty(name, value));
+    }
+
+    @Override
+    public Property setProperty(String name, Value value, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.setProperty(name, value, type));
+    }
+
+    @Override
+    public Property setProperty(String name, Value[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.setProperty(name, values));
+    }
+
+    @Override
+    public Property setProperty(String name, Value[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.setProperty(name, values, type));
+    }
+
+    @Override
+    public Property setProperty(String name, String[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.setProperty(name, values));
+    }
+
+    @Override
+    public Property setProperty(String name, String[] values, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.setProperty(name, values, type));
+    }
+
+    @Override
+    public Property setProperty(String name, String value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.setProperty(name, value));
+    }
+
+    @Override
+    public Property setProperty(String name, String value, int type) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.setProperty(name, value, type));
+    }
+
+    @Override
+    public Property setProperty(String name, InputStream value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.setProperty(name, value));
+    }
+
+    @Override
+    public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.setProperty(name, value));
+    }
+
+    @Override
+    public Property setProperty(String name, boolean value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.setProperty(name, value));
+    }
+
+    @Override
+    public Property setProperty(String name, double value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.setProperty(name, value));
+    }
+
+    @Override
+    public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.setProperty(name, value));
+    }
+
+    @Override
+    public Property setProperty(String name, long value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.setProperty(name, value));
+    }
+
+    @Override
+    public Property setProperty(String name, Calendar value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.setProperty(name, value));
+    }
+
+    @Override
+    public Property setProperty(String name, Node value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.setProperty(name, value));
+    }
+
+    @Override
+    public Node getNode(String relPath) throws PathNotFoundException, RepositoryException {
+        return this.mountSession.getNode(concat(getPath(), relPath));
+    }
+
+    @Override
+    public NodeIterator getNodes() throws RepositoryException {
+        return this.mountSession.getNodes(getPath(), this.delegate.getNodes());
+    }
+
+    @Override
+    public NodeIterator getNodes(String namePattern) throws RepositoryException {
+        return this.mountSession.getNodes(getPath(), this.delegate.getNodes(namePattern));
+    }
+
+    @Override
+    public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException {
+        return this.mountSession.getNodes(getPath(), this.delegate.getNodes(nameGlobs));
+    }
+
+    @Override
+    public Property getProperty(String relPath) throws PathNotFoundException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.getProperty(relPath));
+    }
+
+    @Override
+    public PropertyIterator getProperties() throws RepositoryException {
+        return this.mountSession.wrap(this.delegate.getProperties());
+    }
+
+    @Override
+    public PropertyIterator getProperties(String namePattern) throws RepositoryException {
+        return this.mountSession.wrap(this.delegate.getProperties(namePattern));
+    }
+
+    @Override
+    public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException {
+        return this.mountSession.wrap(this.delegate.getProperties(nameGlobs));
+    }
+
+    @Override
+    public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.getPrimaryItem());
+    }
+
+    @Override
+    public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException {
+        return this.delegate.getUUID();
+    }
+
+    @Override
+    public String getIdentifier() throws RepositoryException {
+        return this.delegate.getIdentifier();
+    }
+
+    @Override
+    public int getIndex() throws RepositoryException {
+        return this.delegate.getIndex();
+    }
+
+    @Override
+    public PropertyIterator getReferences() throws RepositoryException {
+        return this.mountSession.wrap(this.delegate.getReferences());
+    }
+
+    @Override
+    public PropertyIterator getReferences(String name) throws RepositoryException {
+        return this.mountSession.wrap(this.delegate.getReferences(name));
+    }
+
+    @Override
+    public PropertyIterator getWeakReferences() throws RepositoryException {
+        return this.mountSession.wrap(this.delegate.getWeakReferences());
+    }
+
+    @Override
+    public PropertyIterator getWeakReferences(String name) throws RepositoryException {
+        return this.mountSession.wrap(this.delegate.getWeakReferences(name));
+    }
+
+    @Override
+    public boolean hasNode(String relPath) throws RepositoryException {
+        return this.mountSession.nodeExists(concat(getPath(), relPath));
+    }
+
+    @Override
+    public boolean hasProperty(String relPath) throws RepositoryException {
+        return this.mountSession.propertyExists(concat(getPath(), relPath));
+    }
+
+    @Override
+    public boolean hasNodes() throws RepositoryException {
+        return this.mountSession.hasNodes(this.delegate);
+    }
+
+    @Override
+    public boolean hasProperties() throws RepositoryException {
+        return this.delegate.hasProperties();
+    }
+
+    @Override
+    public NodeType getPrimaryNodeType() throws RepositoryException {
+        return this.delegate.getPrimaryNodeType();
+    }
+
+    @Override
+    public NodeType[] getMixinNodeTypes() throws RepositoryException {
+        return this.delegate.getMixinNodeTypes();
+    }
+
+    @Override
+    public boolean isNodeType(String nodeTypeName) throws RepositoryException {
+        return this.delegate.isNodeType(nodeTypeName);
+    }
+
+    @Override
+    public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException {
+        this.delegate.setPrimaryType(nodeTypeName);
+    }
+
+    @Override
+    public void addMixin(String mixinName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException {
+        this.delegate.addMixin(mixinName);
+    }
+
+    @Override
+    public void removeMixin(String mixinName) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, RepositoryException {
+        this.delegate.removeMixin(mixinName);
+    }
+
+    @Override
+    public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException {
+        return this.delegate.canAddMixin(mixinName);
+    }
+
+    @Override
+    public NodeDefinition getDefinition() throws RepositoryException {
+        return this.delegate.getDefinition();
+    }
+
+    @Override
+    public Version checkin() throws VersionException, UnsupportedRepositoryOperationException, InvalidItemStateException, LockException, RepositoryException {
+        return this.delegate.checkin();
+    }
+
+    @Override
+    public void checkout() throws UnsupportedRepositoryOperationException, LockException, ActivityViolationException, RepositoryException {
+        this.delegate.checkout();
+    }
+
+    @Override
+    public void doneMerge(Version version) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException {
+        this.delegate.doneMerge(version);
+    }
+
+    @Override
+    public void cancelMerge(Version version) throws VersionException, InvalidItemStateException, UnsupportedRepositoryOperationException, RepositoryException {
+        this.delegate.cancelMerge(version);
+    }
+
+    @Override
+    public void update(String srcWorkspace) throws NoSuchWorkspaceException, AccessDeniedException, LockException, InvalidItemStateException, RepositoryException {
+        this.delegate.update(srcWorkspace);
+    }
+
+    @Override
+    public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws NoSuchWorkspaceException, AccessDeniedException, MergeException, LockException, InvalidItemStateException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.merge(srcWorkspace, bestEffort));
+    }
+
+    @Override
+    public String getCorrespondingNodePath(String workspaceName) throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException {
+        return this.delegate.getCorrespondingNodePath(workspaceName);
+    }
+
+    @Override
+    public NodeIterator getSharedSet() throws RepositoryException {
+        return this.mountSession.wrap(this.delegate.getSharedSet());
+    }
+
+    @Override
+    public void removeSharedSet() throws VersionException, LockException, ConstraintViolationException, RepositoryException {
+        this.delegate.removeSharedSet();
+    }
+
+    @Override
+    public void removeShare() throws VersionException, LockException, ConstraintViolationException, RepositoryException {
+        this.delegate.removeShare();
+    }
+
+    @Override
+    public boolean isCheckedOut() throws RepositoryException {
+        return this.delegate.isCheckedOut();
+    }
+
+    @Override
+    public void restore(String versionName, boolean removeExisting) throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
+        this.delegate.restore(versionName, removeExisting);
+    }
+
+    @Override
+    public void restore(Version version, boolean removeExisting) throws VersionException, ItemExistsException, InvalidItemStateException, UnsupportedRepositoryOperationException, LockException, RepositoryException {
+        this.delegate.restore(version, removeExisting);
+    }
+
+    @Override
+    public void restore(Version version, String relPath, boolean removeExisting) throws PathNotFoundException, ItemExistsException, VersionException, ConstraintViolationException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
+        this.delegate.restore(version, relPath, removeExisting);
+    }
+
+    @Override
+    public void restoreByLabel(String versionLabel, boolean removeExisting) throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
+        this.delegate.restoreByLabel(versionLabel, removeExisting);
+    }
+
+    @Override
+    public VersionHistory getVersionHistory() throws UnsupportedRepositoryOperationException, RepositoryException {
+        return this.delegate.getVersionHistory();
+    }
+
+    @Override
+    public Version getBaseVersion() throws UnsupportedRepositoryOperationException, RepositoryException {
+        return this.delegate.getBaseVersion();
+    }
+
+    @Override
+    public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.lock(isDeep, isSessionScoped));
+    }
+
+    @Override
+    public Lock getLock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException {
+        return this.mountSession.wrap(this.delegate.getLock());
+    }
+
+    @Override
+    public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException {
+        this.delegate.unlock();
+    }
+
+    @Override
+    public boolean holdsLock() throws RepositoryException {
+        return this.delegate.holdsLock();
+    }
+
+    @Override
+    public boolean isLocked() throws RepositoryException {
+        return this.delegate.isLocked();
+    }
+
+    @Override
+    public void followLifecycleTransition(String transition) throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException {
+        this.delegate.followLifecycleTransition(transition);
+    }
+
+    @Override
+    public String[] getAllowedLifecycleTransistions() throws UnsupportedRepositoryOperationException, RepositoryException {
+        return this.delegate.getAllowedLifecycleTransistions();
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyNodeTypeManager.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyNodeTypeManager.java
new file mode 100644
index 0000000..29d378e
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyNodeTypeManager.java
@@ -0,0 +1,111 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.nodetype.InvalidNodeTypeDefinitionException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.nodetype.NodeDefinitionTemplate;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.NodeTypeDefinition;
+import javax.jcr.nodetype.NodeTypeExistsException;
+import javax.jcr.nodetype.NodeTypeIterator;
+import javax.jcr.nodetype.NodeTypeManager;
+import javax.jcr.nodetype.NodeTypeTemplate;
+import javax.jcr.nodetype.PropertyDefinitionTemplate;
+
+public class ProxyNodeTypeManager implements NodeTypeManager {
+    private final NodeTypeManager nodeTypeManager;
+    private final NodeTypeManager nodeTypeManager1;
+
+    public ProxyNodeTypeManager(NodeTypeManager nodeTypeManager, NodeTypeManager nodeTypeManager1) {
+        this.nodeTypeManager = nodeTypeManager;
+        this.nodeTypeManager1 = nodeTypeManager1;
+    }
+
+    @Override
+    public NodeType getNodeType(String nodeTypeName) throws NoSuchNodeTypeException, RepositoryException {
+        return nodeTypeManager.getNodeType(nodeTypeName);
+    }
+
+    @Override
+    public boolean hasNodeType(String name) throws RepositoryException {
+        return nodeTypeManager.hasNodeType(name);
+    }
+
+    @Override
+    public NodeTypeIterator getAllNodeTypes() throws RepositoryException {
+        return nodeTypeManager.getAllNodeTypes();
+    }
+
+    @Override
+    public NodeTypeIterator getPrimaryNodeTypes() throws RepositoryException {
+        return nodeTypeManager.getPrimaryNodeTypes();
+    }
+
+    @Override
+    public NodeTypeIterator getMixinNodeTypes() throws RepositoryException {
+        return nodeTypeManager.getMixinNodeTypes();
+    }
+
+    @Override
+    public NodeTypeTemplate createNodeTypeTemplate() throws UnsupportedRepositoryOperationException, RepositoryException {
+        return nodeTypeManager.createNodeTypeTemplate();
+    }
+
+    @Override
+    public NodeTypeTemplate createNodeTypeTemplate(NodeTypeDefinition ntd) throws UnsupportedRepositoryOperationException, RepositoryException {
+        return nodeTypeManager.createNodeTypeTemplate(ntd);
+    }
+
+    @Override
+    public NodeDefinitionTemplate createNodeDefinitionTemplate() throws UnsupportedRepositoryOperationException, RepositoryException {
+        return nodeTypeManager.createNodeDefinitionTemplate();
+    }
+
+    @Override
+    public PropertyDefinitionTemplate createPropertyDefinitionTemplate() throws UnsupportedRepositoryOperationException, RepositoryException {
+        return nodeTypeManager.createPropertyDefinitionTemplate();
+    }
+
+    @Override
+    public NodeType registerNodeType(NodeTypeDefinition ntd, boolean allowUpdate) throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, UnsupportedRepositoryOperationException, RepositoryException {
+        nodeTypeManager1.registerNodeType(ntd, allowUpdate);
+        return nodeTypeManager.registerNodeType(ntd, allowUpdate);
+    }
+
+    @Override
+    public NodeTypeIterator registerNodeTypes(NodeTypeDefinition[] ntds, boolean allowUpdate) throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, UnsupportedRepositoryOperationException, RepositoryException {
+        nodeTypeManager1.registerNodeTypes(ntds, allowUpdate);
+        return nodeTypeManager.registerNodeTypes(ntds, allowUpdate);
+    }
+
+    @Override
+    public void unregisterNodeType(String name) throws UnsupportedRepositoryOperationException, NoSuchNodeTypeException, RepositoryException {
+        nodeTypeManager.unregisterNodeType(name);
+        nodeTypeManager1.unregisterNodeType(name);
+    }
+
+    @Override
+    public void unregisterNodeTypes(String[] names) throws UnsupportedRepositoryOperationException, NoSuchNodeTypeException, RepositoryException {
+        nodeTypeManager.unregisterNodeTypes(names);
+        nodeTypeManager1.unregisterNodeTypes(names);
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyPrivilegeManager.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyPrivilegeManager.java
new file mode 100644
index 0000000..ae4eda6
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyPrivilegeManager.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.jcr.base.internal.mount;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.NamespaceException;
+import javax.jcr.RepositoryException;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
+
+public class ProxyPrivilegeManager extends ProxyWrapper<PrivilegeManager> implements PrivilegeManager {
+    private final PrivilegeManager mount;
+
+    public ProxyPrivilegeManager(ProxySession<?> mountSession, PrivilegeManager delegate, PrivilegeManager mount) {
+        super(mountSession, delegate);
+        this.mount = mount;
+    }
+
+    public Privilege[] getRegisteredPrivileges() throws RepositoryException {
+        List<Privilege> result = new ArrayList<>();
+        result.addAll(Arrays.asList(delegate.getRegisteredPrivileges()));
+
+        result.addAll(Arrays.asList(mount.getRegisteredPrivileges()));
+
+        return result.toArray(new Privilege[0]);
+    }
+
+    public Privilege getPrivilege(String privilegeName) throws AccessControlException, RepositoryException {
+        try {
+            return mount.getPrivilege(privilegeName);
+        } catch (AccessControlException ex) {
+            return delegate.getPrivilege(privilegeName);
+        }
+    }
+
+    public Privilege registerPrivilege(String privilegeName, boolean isAbstract, String[] declaredAggregateNames) throws AccessDeniedException, NamespaceException, RepositoryException {
+        mount.registerPrivilege(privilegeName, isAbstract, declaredAggregateNames);
+        return delegate.registerPrivilege(privilegeName, isAbstract, declaredAggregateNames);
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyProperty.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyProperty.java
new file mode 100644
index 0000000..50cd8f9
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyProperty.java
@@ -0,0 +1,157 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.Calendar;
+
+import javax.jcr.Binary;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.PropertyDefinition;
+import javax.jcr.version.VersionException;
+
+public class ProxyProperty extends ProxyItem<Property> implements Property {
+    public ProxyProperty(ProxySession mountSession, Property delegate) {
+        super(mountSession, delegate);
+    }
+
+    public void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        delegate.setValue(value);
+    }
+
+    public void setValue(Value[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        delegate.setValue(values);
+    }
+
+    public void setValue(String value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        delegate.setValue(value);
+    }
+
+    public void setValue(String[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        delegate.setValue(values);
+    }
+
+    public void setValue(InputStream value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        delegate.setValue(value);
+    }
+
+    public void setValue(Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        delegate.setValue(value);
+    }
+
+    public void setValue(long value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        delegate.setValue(value);
+    }
+
+    public void setValue(double value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        delegate.setValue(value);
+    }
+
+    public void setValue(BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        delegate.setValue(value);
+    }
+
+    public void setValue(Calendar value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        delegate.setValue(value);
+    }
+
+    public void setValue(boolean value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        delegate.setValue(value);
+    }
+
+    public void setValue(Node value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        delegate.setValue(value);
+    }
+
+    public Value getValue() throws ValueFormatException, RepositoryException {
+        return delegate.getValue();
+    }
+
+    public Value[] getValues() throws ValueFormatException, RepositoryException {
+        return delegate.getValues();
+    }
+
+    public String getString() throws ValueFormatException, RepositoryException {
+        return delegate.getString();
+    }
+
+    public InputStream getStream() throws ValueFormatException, RepositoryException {
+        return delegate.getStream();
+    }
+
+    public Binary getBinary() throws ValueFormatException, RepositoryException {
+        return delegate.getBinary();
+    }
+
+    public long getLong() throws ValueFormatException, RepositoryException {
+        return delegate.getLong();
+    }
+
+    public double getDouble() throws ValueFormatException, RepositoryException {
+        return delegate.getDouble();
+    }
+
+    public BigDecimal getDecimal() throws ValueFormatException, RepositoryException {
+        return delegate.getDecimal();
+    }
+
+    public Calendar getDate() throws ValueFormatException, RepositoryException {
+        return delegate.getDate();
+    }
+
+    public boolean getBoolean() throws ValueFormatException, RepositoryException {
+        return delegate.getBoolean();
+    }
+
+    public Node getNode() throws ItemNotFoundException, ValueFormatException, RepositoryException {
+        return this.mountSession.getNode(delegate.getNode().getPath());
+    }
+
+    public Property getProperty() throws ItemNotFoundException, ValueFormatException, RepositoryException {
+        return this.mountSession.getProperty(delegate.getProperty().getPath());
+    }
+
+    public long getLength() throws ValueFormatException, RepositoryException {
+        return delegate.getLength();
+    }
+
+    public long[] getLengths() throws ValueFormatException, RepositoryException {
+        return delegate.getLengths();
+    }
+
+    public PropertyDefinition getDefinition() throws RepositoryException {
+        return delegate.getDefinition();
+    }
+
+    public int getType() throws RepositoryException {
+        return delegate.getType();
+    }
+
+    public boolean isMultiple() throws RepositoryException {
+        return delegate.isMultiple();
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyQuery.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyQuery.java
new file mode 100644
index 0000000..770f7a5
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyQuery.java
@@ -0,0 +1,233 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.jcr.ItemExistsException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.query.InvalidQueryException;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.Row;
+import javax.jcr.query.RowIterator;
+import javax.jcr.version.VersionException;
+
+public class ProxyQuery extends ProxyWrapper<Query> implements Query {
+    private final Query delegate2;
+
+    public ProxyQuery(ProxySession<?> mountSession, Query delegate, Query delegate2) {
+        super(mountSession, delegate);
+        this.delegate2 = delegate2;
+    }
+
+    public QueryResult execute() throws InvalidQueryException, RepositoryException {
+        final QueryResult result1 = delegate.execute();
+        QueryResult result2temp = null;
+        if (delegate2 != null) {
+            result2temp = delegate2.execute();
+        }
+        final QueryResult result2 = result2temp;
+
+        return this.mountSession.wrap(new QueryResult() {
+            @Override
+            public String[] getColumnNames() throws RepositoryException {
+            	return result1.getColumnNames();
+            }
+
+            @Override
+            public RowIterator getRows() throws RepositoryException {
+            	final RowIterator i1 = result1.getRows();
+            	RowIterator i2 = null;
+            	if (result2 != null) {
+                    i2 = result2.getRows();
+                }
+            	if ( i2 == null || !i2.hasNext() ) {
+            		return i1;
+            	}
+            	if ( !i1.hasNext() ) {
+            		return i2;
+            	}
+                final List<RowIterator> list = new ArrayList<>();
+                list.add(i1);
+                list.add(i2);
+                @SuppressWarnings({ "unchecked", "rawtypes" })
+				final Iterator<Row> iter = new ChainedIterator(list.iterator());
+                return new RowIterator() {
+					
+					@Override
+					public Object next() {
+						return iter.next();
+					}
+					
+					@Override
+					public boolean hasNext() {
+						return iter.hasNext();
+					}
+					
+					@Override
+					public void skip(long skipNum) {
+						// TODO Auto-generated method stub
+						
+					}
+					
+					@Override
+					public long getSize() {
+						// TODO Auto-generated method stub
+						return 0;
+					}
+					
+					@Override
+					public long getPosition() {
+						// TODO Auto-generated method stub
+						return 0;
+					}
+					
+					@Override
+					public Row nextRow() {
+						return iter.next();
+					}
+				};
+            }
+
+            @Override
+            public NodeIterator getNodes() throws RepositoryException {
+            	final NodeIterator i1 = result1.getNodes();
+                NodeIterator i2 = null;
+                if (result2 != null) {
+                    i2 = result2.getNodes();
+                }
+            	if ( i2 == null || !i2.hasNext() ) {
+            		return i1;
+            	}
+            	if ( !i1.hasNext() ) {
+            		return i2;
+            	}
+                final List<NodeIterator> list = new ArrayList<>();
+                list.add(i1);
+                list.add(i2);
+                @SuppressWarnings({ "unchecked", "rawtypes" })
+				final Iterator<Node> iter = new ChainedIterator(list.iterator());
+                return new NodeIterator() {
+					
+					@Override
+					public Object next() {
+						return iter.next();
+					}
+					
+					@Override
+					public boolean hasNext() {
+						return iter.hasNext();
+					}
+					
+					@Override
+					public void skip(long skipNum) {
+						// TODO Auto-generated method stub
+						
+					}
+					
+					@Override
+					public long getSize() {
+						// TODO Auto-generated method stub
+						return 0;
+					}
+					
+					@Override
+					public long getPosition() {
+						// TODO Auto-generated method stub
+						return 0;
+					}
+					
+					@Override
+					public Node nextNode() {
+						return iter.next();
+					}
+				};
+            }
+
+            @Override
+            public String[] getSelectorNames() throws RepositoryException {
+                return result1.getSelectorNames();
+            }
+        });
+    }
+
+    public void setLimit(long limit) {
+        delegate.setLimit(limit);
+        if (delegate2 != null) {
+            delegate2.setLimit(limit);
+        }
+    }
+
+    public void setOffset(long offset) {
+        delegate.setOffset(offset);
+        if (delegate2 != null) {
+            delegate2.setOffset(2);
+        }
+    }
+
+    public String getStatement() {
+        return delegate.getStatement();
+    }
+
+    public String getLanguage() {
+        return delegate.getLanguage();
+    }
+
+    public String getStoredQueryPath() throws ItemNotFoundException, RepositoryException {
+        try {
+            return delegate.getStoredQueryPath();
+        } catch (ItemNotFoundException ex) {
+           try {
+                if (delegate2 != null) {
+                    return delegate2.getStoredQueryPath();
+                } else {
+                    return "";
+                }
+            } catch (ItemNotFoundException ignore) {
+                throw ex;
+            }
+        }
+    }
+
+    public Node storeAsNode(String absPath) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, UnsupportedRepositoryOperationException, RepositoryException {
+        return this.mountSession.wrap(this.mountSession.isMount(absPath) ? delegate2.storeAsNode(absPath) : delegate.storeAsNode(absPath));
+    }
+
+    public void bindValue(String varName, Value value) throws IllegalArgumentException, RepositoryException {
+        delegate.bindValue(varName, value);
+        if (delegate2 != null) {
+            delegate2.bindValue(varName, value);
+        }
+    }
+
+    public String[] getBindVariableNames() throws RepositoryException {
+        return delegate.getBindVariableNames();
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyQueryManager.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyQueryManager.java
new file mode 100644
index 0000000..ed897ae
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyQueryManager.java
@@ -0,0 +1,55 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.query.InvalidQueryException;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.query.qom.QueryObjectModelFactory;
+
+public class ProxyQueryManager extends ProxyWrapper<QueryManager> implements QueryManager {
+    private final QueryManager delegate2;
+
+    public ProxyQueryManager(ProxySession<?> mountSession, QueryManager delegate, QueryManager delegate2) {
+        super(mountSession, delegate);
+        this.delegate2 = delegate2;
+    }
+
+    @Override
+    public Query createQuery(String statement, String language) throws InvalidQueryException, RepositoryException {
+        return new ProxyQuery(this.mountSession, delegate.createQuery(statement, language), delegate2.createQuery(statement, language));
+    }
+
+    @Override
+    public QueryObjectModelFactory getQOMFactory() {
+        return new ProxyQueryObjectModelFactory(this.mountSession, delegate.getQOMFactory(), delegate2.getQOMFactory());
+    }
+
+    @Override
+    public Query getQuery(Node node) throws InvalidQueryException, RepositoryException {
+        return this.mountSession.wrap(delegate.getQuery(this.mountSession.unwrap(node)));
+    }
+
+    @Override
+    public String[] getSupportedQueryLanguages() throws RepositoryException {
+        return delegate.getSupportedQueryLanguages();
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyQueryObjectModel.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyQueryObjectModel.java
new file mode 100644
index 0000000..4cc6ecc
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyQueryObjectModel.java
@@ -0,0 +1,47 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import javax.jcr.query.qom.Column;
+import javax.jcr.query.qom.Constraint;
+import javax.jcr.query.qom.Ordering;
+import javax.jcr.query.qom.QueryObjectModel;
+import javax.jcr.query.qom.Source;
+
+public class ProxyQueryObjectModel extends ProxyQuery implements QueryObjectModel {
+    public ProxyQueryObjectModel(ProxySession<?> mountSession, QueryObjectModel delegate, QueryObjectModel delegate2) {
+        super(mountSession, delegate, delegate2);
+    }
+
+    public Source getSource() {
+        return ((QueryObjectModel) delegate).getSource();
+    }
+
+    public Constraint getConstraint() {
+        return ((QueryObjectModel) delegate).getConstraint();
+    }
+
+    public Ordering[] getOrderings() {
+        return ((QueryObjectModel) delegate).getOrderings();
+    }
+
+    public Column[] getColumns() {
+        return ((QueryObjectModel) delegate).getColumns();
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyQueryObjectModelFactory.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyQueryObjectModelFactory.java
new file mode 100644
index 0000000..35d2aa0
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyQueryObjectModelFactory.java
@@ -0,0 +1,183 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.query.InvalidQueryException;
+import javax.jcr.query.qom.And;
+import javax.jcr.query.qom.BindVariableValue;
+import javax.jcr.query.qom.ChildNode;
+import javax.jcr.query.qom.ChildNodeJoinCondition;
+import javax.jcr.query.qom.Column;
+import javax.jcr.query.qom.Comparison;
+import javax.jcr.query.qom.Constraint;
+import javax.jcr.query.qom.DescendantNode;
+import javax.jcr.query.qom.DescendantNodeJoinCondition;
+import javax.jcr.query.qom.DynamicOperand;
+import javax.jcr.query.qom.EquiJoinCondition;
+import javax.jcr.query.qom.FullTextSearch;
+import javax.jcr.query.qom.FullTextSearchScore;
+import javax.jcr.query.qom.Join;
+import javax.jcr.query.qom.JoinCondition;
+import javax.jcr.query.qom.Length;
+import javax.jcr.query.qom.Literal;
+import javax.jcr.query.qom.LowerCase;
+import javax.jcr.query.qom.NodeLocalName;
+import javax.jcr.query.qom.NodeName;
+import javax.jcr.query.qom.Not;
+import javax.jcr.query.qom.Or;
+import javax.jcr.query.qom.Ordering;
+import javax.jcr.query.qom.PropertyExistence;
+import javax.jcr.query.qom.PropertyValue;
+import javax.jcr.query.qom.QueryObjectModel;
+import javax.jcr.query.qom.QueryObjectModelFactory;
+import javax.jcr.query.qom.SameNode;
+import javax.jcr.query.qom.SameNodeJoinCondition;
+import javax.jcr.query.qom.Selector;
+import javax.jcr.query.qom.Source;
+import javax.jcr.query.qom.StaticOperand;
+import javax.jcr.query.qom.UpperCase;
+
+public class ProxyQueryObjectModelFactory extends ProxyWrapper<QueryObjectModelFactory> implements QueryObjectModelFactory {
+    private final QueryObjectModelFactory delegate2;
+
+    public ProxyQueryObjectModelFactory(ProxySession<?> mountSession, QueryObjectModelFactory delegate, QueryObjectModelFactory delegate2) {
+        super(mountSession, delegate);
+        this.delegate2 = delegate2;
+    }
+
+    public QueryObjectModel createQuery(Source source, Constraint constraint, Ordering[] orderings, Column[] columns) throws InvalidQueryException, RepositoryException {
+        if (delegate2 != null) {
+            return new ProxyQueryObjectModel(this.mountSession, delegate.createQuery(source, constraint, orderings, columns),
+                    delegate2.createQuery(source, constraint, orderings, columns));
+        } else {
+            return new ProxyQueryObjectModel(this.mountSession, delegate.createQuery(source, constraint, orderings, columns),
+                    null);
+        }
+    }
+
+    public Selector selector(String nodeTypeName, String selectorName) throws InvalidQueryException, RepositoryException {
+        return delegate.selector(nodeTypeName, selectorName);
+    }
+
+    public Join join(Source left, Source right, String joinType, JoinCondition joinCondition) throws InvalidQueryException, RepositoryException {
+        return delegate.join(left, right, joinType, joinCondition);
+    }
+
+    public EquiJoinCondition equiJoinCondition(String selector1Name, String property1Name, String selector2Name, String property2Name) throws InvalidQueryException, RepositoryException {
+        return delegate.equiJoinCondition(selector1Name, property1Name, selector2Name, property2Name);
+    }
+
+    public SameNodeJoinCondition sameNodeJoinCondition(String selector1Name, String selector2Name, String selector2Path) throws InvalidQueryException, RepositoryException {
+        return delegate.sameNodeJoinCondition(selector1Name, selector2Name, selector2Path);
+    }
+
+    public ChildNodeJoinCondition childNodeJoinCondition(String childSelectorName, String parentSelectorName) throws InvalidQueryException, RepositoryException {
+        return delegate.childNodeJoinCondition(childSelectorName, parentSelectorName);
+    }
+
+    public DescendantNodeJoinCondition descendantNodeJoinCondition(String descendantSelectorName, String ancestorSelectorName) throws InvalidQueryException, RepositoryException {
+        return delegate.descendantNodeJoinCondition(descendantSelectorName, ancestorSelectorName);
+    }
+
+    public And and(Constraint constraint1, Constraint constraint2) throws InvalidQueryException, RepositoryException {
+        return delegate.and(constraint1, constraint2);
+    }
+
+    public Or or(Constraint constraint1, Constraint constraint2) throws InvalidQueryException, RepositoryException {
+        return delegate.or(constraint1, constraint2);
+    }
+
+    public Not not(Constraint constraint) throws InvalidQueryException, RepositoryException {
+        return delegate.not(constraint);
+    }
+
+    public Comparison comparison(DynamicOperand operand1, String operator, StaticOperand operand2) throws InvalidQueryException, RepositoryException {
+        return delegate.comparison(operand1, operator, operand2);
+    }
+
+    public PropertyExistence propertyExistence(String selectorName, String propertyName) throws InvalidQueryException, RepositoryException {
+        return delegate.propertyExistence(selectorName, propertyName);
+    }
+
+    public FullTextSearch fullTextSearch(String selectorName, String propertyName, StaticOperand fullTextSearchExpression) throws InvalidQueryException, RepositoryException {
+        return delegate.fullTextSearch(selectorName, propertyName, fullTextSearchExpression);
+    }
+
+    public SameNode sameNode(String selectorName, String path) throws InvalidQueryException, RepositoryException {
+        return delegate.sameNode(selectorName, path);
+    }
+
+    public ChildNode childNode(String selectorName, String path) throws InvalidQueryException, RepositoryException {
+        return delegate.childNode(selectorName, path);
+    }
+
+    public DescendantNode descendantNode(String selectorName, String path) throws InvalidQueryException, RepositoryException {
+        return delegate.descendantNode(selectorName, path);
+    }
+
+    public PropertyValue propertyValue(String selectorName, String propertyName) throws InvalidQueryException, RepositoryException {
+        return delegate.propertyValue(selectorName, propertyName);
+    }
+
+    public Length length(PropertyValue propertyValue) throws InvalidQueryException, RepositoryException {
+        return delegate.length(propertyValue);
+    }
+
+    public NodeName nodeName(String selectorName) throws InvalidQueryException, RepositoryException {
+        return delegate.nodeName(selectorName);
+    }
+
+    public NodeLocalName nodeLocalName(String selectorName) throws InvalidQueryException, RepositoryException {
+        return delegate.nodeLocalName(selectorName);
+    }
+
+    public FullTextSearchScore fullTextSearchScore(String selectorName) throws InvalidQueryException, RepositoryException {
+        return delegate.fullTextSearchScore(selectorName);
+    }
+
+    public LowerCase lowerCase(DynamicOperand operand) throws InvalidQueryException, RepositoryException {
+        return delegate.lowerCase(operand);
+    }
+
+    public UpperCase upperCase(DynamicOperand operand) throws InvalidQueryException, RepositoryException {
+        return delegate.upperCase(operand);
+    }
+
+    public BindVariableValue bindVariable(String bindVariableName) throws InvalidQueryException, RepositoryException {
+        return delegate.bindVariable(bindVariableName);
+    }
+
+    public Literal literal(Value literalValue) throws InvalidQueryException, RepositoryException {
+        return delegate.literal(literalValue);
+    }
+
+    public Ordering ascending(DynamicOperand operand) throws InvalidQueryException, RepositoryException {
+        return delegate.ascending(operand);
+    }
+
+    public Ordering descending(DynamicOperand operand) throws InvalidQueryException, RepositoryException {
+        return delegate.descending(operand);
+    }
+
+    public Column column(String selectorName, String propertyName, String columnName) throws InvalidQueryException, RepositoryException {
+        return delegate.column(selectorName, propertyName, columnName);
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyQueryResult.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyQueryResult.java
new file mode 100644
index 0000000..ee9e1fd
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyQueryResult.java
@@ -0,0 +1,46 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.RowIterator;
+
+public class ProxyQueryResult extends ProxyWrapper<QueryResult> implements QueryResult {
+    public ProxyQueryResult(ProxySession<?> mountSession, QueryResult delegate) {
+        super(mountSession, delegate);
+    }
+
+    public String[] getColumnNames() throws RepositoryException {
+        return delegate.getColumnNames();
+    }
+
+    public RowIterator getRows() throws RepositoryException {
+        return this.mountSession.wrap(delegate.getRows());
+    }
+
+    public NodeIterator getNodes() throws RepositoryException {
+        return this.mountSession.wrap(delegate.getNodes());
+    }
+
+    public String[] getSelectorNames() throws RepositoryException {
+        return delegate.getSelectorNames();
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyRepository.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyRepository.java
new file mode 100644
index 0000000..40b685c
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyRepository.java
@@ -0,0 +1,133 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Credentials;
+import javax.jcr.LoginException;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.api.JackrabbitRepository;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.sling.jcr.base.spi.RepositoryMount;
+
+public class ProxyRepository<T extends Repository> implements Repository {
+    public final T jcr;
+    final T mount;
+    final Set<String> mountPoints;
+
+    public ProxyRepository(T jcr, T mount, Set<String> mountPoint) {
+        this.jcr = jcr;
+        this.mount = mount;
+        this.mountPoints = new HashSet<>(mountPoint);
+    }
+
+
+    @Override
+    public String[] getDescriptorKeys() {
+        return jcr.getDescriptorKeys();
+    }
+
+    @Override
+    public boolean isStandardDescriptor(String key) {
+        return jcr.isStandardDescriptor(key);
+    }
+
+    @Override
+    public boolean isSingleValueDescriptor(String key) {
+        return jcr.isSingleValueDescriptor(key);
+    }
+
+    @Override
+    public Value getDescriptorValue(String key) {
+        return jcr.getDescriptorValue(key);
+    }
+
+    @Override
+    public Value[] getDescriptorValues(String key) {
+        return jcr.getDescriptorValues(key);
+    }
+
+    @Override
+    public String getDescriptor(String key) {
+        return jcr.getDescriptor(key);
+    }
+
+    @Override
+    public Session login(Credentials credentials, String workspaceName) throws LoginException, NoSuchWorkspaceException, RepositoryException {
+        Session jcrSession = jcr.login(credentials, workspaceName);
+
+        Session mountSession;
+        if (mount instanceof JackrabbitRepository) {
+            Map<String, Object> attributes = new HashMap<>();
+            attributes.put(RepositoryMount.PARENT_SESSION_KEY, jcrSession);
+            mountSession = ((JackrabbitRepository) mount).login(credentials, workspaceName, attributes);
+        }
+        else {
+            mountSession = mount.login(credentials, workspaceName);
+        }
+        return jcrSession instanceof JackrabbitSession ?
+                new ProxyJackrabbitSession(this, (JackrabbitSession) jcrSession, mountSession, this.mountPoints) :
+                new ProxySession<>(this, jcrSession, mountSession, this.mountPoints);
+    }
+
+    @Override
+    public Session login(Credentials credentials) throws LoginException, RepositoryException {
+        return login(credentials, null);
+    }
+
+    @Override
+    public Session login(String workspaceName) throws LoginException, NoSuchWorkspaceException, RepositoryException {
+        return login(null, workspaceName);
+    }
+
+    @Override
+    public Session login() throws LoginException, RepositoryException {
+        return login(null, null);
+    }
+
+    public Session wrap(Session session) throws RepositoryException {
+        if (session instanceof ProxySession) {
+            return session;
+        }
+
+        Map<String, Object> attributes = new HashMap<>();
+        attributes.put(RepositoryMount.PARENT_SESSION_KEY, session);
+        Session mountSession = ((JackrabbitRepository) mount).login(new SimpleCredentials(session.getUserID(), new char[0]),session.getWorkspace().getName(), attributes );
+
+        return session instanceof JackrabbitSession ?
+                new ProxyJackrabbitSession(this, (JackrabbitSession) session, mountSession, this.mountPoints) :
+                new ProxySession<>(this, session, mountSession, this.mountPoints);
+    }
+
+    Session impersonate(Credentials credentials, Session jcr, Session mount) throws RepositoryException {
+        return jcr instanceof JackrabbitSession ?
+                new ProxyJackrabbitSession(this, (JackrabbitSession) jcr.impersonate(credentials), mount.impersonate(credentials), this.mountPoints) :
+                new ProxySession<>(this, jcr.impersonate(credentials), mount.impersonate(credentials), this.mountPoints);
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxySession.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxySession.java
new file mode 100644
index 0000000..efbc475
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxySession.java
@@ -0,0 +1,615 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.AccessControlException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Credentials;
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.InvalidSerializedDataException;
+import javax.jcr.Item;
+import javax.jcr.ItemExistsException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.LoginException;
+import javax.jcr.NamespaceException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.ReferentialIntegrityException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.Workspace;
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.Row;
+import javax.jcr.query.RowIterator;
+import javax.jcr.retention.RetentionManager;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.version.VersionException;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+public class ProxySession<T extends Session> implements Session {
+    private final ProxyRepository repository;
+    public final T jcr;
+    protected final Session mount;
+    private final Set<String> mountPoints;
+
+    public ProxySession(ProxyRepository repository, T jcr, Session mount, Set<String> mountPoints) {
+        this.repository = repository;
+        this.jcr = jcr;
+        this.mount = mount;
+        this.mountPoints = mountPoints;
+    }
+
+    boolean isMount(String path) {
+        return path != null && (mountPoints.contains(path) || mountPoints.stream().anyMatch(mountPoint -> path.startsWith(mountPoint + "/")));
+    }
+
+    boolean isMountParent(String path) {
+        return mountPoints.stream().anyMatch(mountPoint -> mountPoint.startsWith((path + "/").replace("//", "/")));
+    }
+
+    boolean isMountDirectParent(String path) {
+        return mountPoints.stream().anyMatch(mountPoint -> PathUtils.getParentPath(mountPoint).equals(path));
+    }
+
+    public <F> F wrap(F source) {
+        if (source instanceof ProxyWrapper) {
+            return source;
+        }
+        return (F) (source instanceof Node ? new ProxyNode(this, (Node) source) :
+                source instanceof Property ? new ProxyProperty(this, (Property) source) :
+                        source instanceof Item ? new ProxyItem<>(this, (Item) source) :
+                                source instanceof Lock ? new ProxyLock(this, (Lock) source) :
+                                        source instanceof QueryResult ? new ProxyQueryResult(this, (QueryResult) source) :
+                                                source);
+    }
+
+    public <F> F unwrap(F source) {
+        return (F) (source instanceof ProxyWrapper ? ((ProxyWrapper) source).delegate : source);
+    }
+
+    public NodeIterator wrap(final NodeIterator iter) {
+        return new NodeIteratorAdapter(new Iterator<Node>() {
+            @Override
+            public boolean hasNext() {
+                return iter.hasNext();
+            }
+
+            @Override
+            public Node next() {
+                return wrap(iter.nextNode());
+            }
+
+            @Override
+            public void remove() {
+                iter.remove();
+            }
+        });
+    }
+
+    public PropertyIterator wrap(final PropertyIterator iter) {
+        return new PropertyIterator() {
+            @Override
+            public Property nextProperty() {
+                return wrap(iter.nextProperty());
+            }
+
+            @Override
+            public void skip(long skipNum) {
+                iter.skip(skipNum);
+            }
+
+            @Override
+            public long getSize() {
+                return iter.getSize();
+            }
+
+            @Override
+            public long getPosition() {
+                return iter.getPosition();
+            }
+
+            @Override
+            public boolean hasNext() {
+                return iter.hasNext();
+            }
+
+            @Override
+            public void remove() {
+                iter.remove();
+            }
+
+            @Override
+            public Object next() {
+                return wrap(iter.next());
+            }
+        };
+    }
+
+    public RowIterator wrap(final RowIterator iter) {
+        return new RowIterator() {
+
+            @Override
+            public Row nextRow() {
+                final Row row = iter.nextRow();
+
+                return new Row() {
+                    @Override
+                    public Value[] getValues() throws RepositoryException {
+                        return row.getValues();
+                    }
+
+                    @Override
+                    public Value getValue(String s) throws ItemNotFoundException, RepositoryException {
+                        return row.getValue(s);
+                    }
+
+                    @Override
+                    public Node getNode() throws RepositoryException {
+                        return wrap(row.getNode());
+                    }
+
+                    @Override
+                    public Node getNode(String s) throws RepositoryException {
+                        return wrap(row.getNode(s));
+                    }
+
+                    @Override
+                    public String getPath() throws RepositoryException {
+                        return row.getPath();
+                    }
+
+                    @Override
+                    public String getPath(String s) throws RepositoryException {
+                        return row.getPath(s);
+                    }
+
+                    @Override
+                    public double getScore() throws RepositoryException {
+                        return row.getScore();
+                    }
+
+                    @Override
+                    public double getScore(String s) throws RepositoryException {
+                        return row.getScore(s);
+                    }
+                };
+            }
+
+            @Override
+            public void skip(long l) {
+                iter.skip(l);
+            }
+
+            @Override
+            public long getSize() {
+                return iter.getSize();
+            }
+
+            @Override
+            public long getPosition() {
+                return iter.getPosition();
+            }
+
+            @Override
+            public boolean hasNext() {
+                return iter.hasNext();
+            }
+
+            @Override
+            public Object next() {
+                return nextRow();
+            }
+
+            @Override
+            public void remove() {
+                iter.remove();
+            }
+        };
+    }
+
+    @Override
+    public Repository getRepository() {
+        return this.repository;
+    }
+
+    @Override
+    public String getUserID() {
+        return this.jcr.getUserID();
+    }
+
+    @Override
+    public String[] getAttributeNames() {
+        return this.jcr.getAttributeNames();
+    }
+
+    @Override
+    public Object getAttribute(String name) {
+        return this.jcr.getAttribute(name);
+    }
+
+    @Override
+    public Node getRootNode() throws RepositoryException {
+        return wrap(this.jcr.getRootNode());
+    }
+
+    public NodeIterator getNodes(String path, NodeIterator childs) throws RepositoryException {
+        if (isMountDirectParent(path)) {
+            List<Node> buffer = new ArrayList<>();
+            while (childs.hasNext()) {
+                Node child = childs.nextNode();
+                if (!isMount(child.getPath())) {
+                    buffer.add(child);
+                }
+            }
+            for (String mountPoint : this.mountPoints) {
+                if (PathUtils.getParentPath(mountPoint).equals(path)) {
+                    buffer.add(this.mount.getNode(mountPoint));
+                }
+            }
+            childs = new NodeIteratorAdapter(buffer);
+        }
+        return wrap(childs);
+    }
+
+    public boolean hasNodes(Node node) throws RepositoryException {
+        return isMountDirectParent(node.getPath()) || node.hasNodes();
+    }
+
+    @Override
+    public Session impersonate(Credentials credentials) throws LoginException, RepositoryException {
+        return this.repository.impersonate(credentials, this.jcr, this.mount);
+    }
+
+    @Override
+    public Node getNodeByUUID(String uuid) throws ItemNotFoundException, RepositoryException {
+        try {
+            return wrap(this.jcr.getNodeByUUID(uuid));
+        } catch (RepositoryException ex) {
+            try {
+                return wrap(this.mount.getNodeByUUID(uuid));
+            } catch (RepositoryException ignore) {
+                throw ex;
+            }
+        }
+    }
+
+    @Override
+    public Node getNodeByIdentifier(String id) throws ItemNotFoundException, RepositoryException {
+        try {
+            return wrap(this.jcr.getNodeByIdentifier(id));
+        } catch (RepositoryException ex) {
+            try {
+                return wrap(this.mount.getNodeByIdentifier(id));
+            } catch (RepositoryException ignore) {
+                throw ex;
+            }
+        }
+    }
+
+    @Override
+    public Item getItem(String absPath) throws PathNotFoundException, RepositoryException {
+        return wrap(isMount(absPath) ? this.mount.getItem(absPath) : this.jcr.getItem(absPath));
+    }
+
+    @Override
+    public Node getNode(String absPath) throws PathNotFoundException, RepositoryException {
+        return wrap(isMount(absPath) ? this.mount.getNode(absPath) : this.jcr.getNode(absPath));
+    }
+
+    @Override
+    public Property getProperty(String absPath) throws PathNotFoundException, RepositoryException {
+        return wrap(isMount(absPath) ? this.mount.getProperty(absPath) : this.jcr.getProperty(absPath));
+    }
+
+    @Override
+    public boolean itemExists(String absPath) throws RepositoryException {
+        return isMount(absPath) ? this.mount.itemExists(absPath) : this.jcr.itemExists(absPath);
+    }
+
+    @Override
+    public boolean nodeExists(String absPath) throws RepositoryException {
+        return isMount(absPath) ? this.mount.nodeExists(absPath) : this.jcr.nodeExists(absPath);
+    }
+
+    @Override
+    public boolean propertyExists(String absPath) throws RepositoryException {
+        return isMount(absPath) ? this.mount.propertyExists(absPath) : this.jcr.propertyExists(absPath);
+    }
+
+    @Override
+    public void removeItem(String absPath) throws VersionException, LockException, ConstraintViolationException, AccessDeniedException, RepositoryException {
+        if (sync != null) {
+            sync.remove(absPath);
+        }
+        if (isMount(absPath)) {
+            this.mount.removeItem(absPath);
+        } else {
+            this.jcr.removeItem(absPath);
+            if (isMountParent(absPath)) {
+                for (String mountPoint : this.mountPoints) {
+                    if (mountPoint.startsWith((absPath + "/").replace("//", "/"))) {
+                        for (NodeIterator iter = this.mount.getNode(mountPoint).getNodes(); iter.hasNext(); ) {
+                            iter.nextNode().remove();
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private volatile Set<String> sync;
+
+    private final static List<String> ignore = Arrays.asList("jcr:primaryType", "jcr:created", "jcr:createdBy");
+
+    @Override
+    public void save() throws AccessDeniedException, ItemExistsException, ReferentialIntegrityException, ConstraintViolationException, InvalidItemStateException, VersionException, LockException, NoSuchNodeTypeException, RepositoryException {
+        if (sync != null) {
+            for (String path : sync) {
+                if (this.jcr.nodeExists(path)) {
+                    Node jcrNode = jcr.getNode(path);
+                    Node mountNode = mount.nodeExists(path) ?
+                            mount.getNode(path) :
+                            mount.getNode(PathUtils.getParentPath(path)).addNode(PathUtils.getName(path), jcrNode.getPrimaryNodeType().getName());
+                    for (PropertyIterator iter = jcrNode.getProperties(); iter.hasNext(); ) {
+                        Property property = iter.nextProperty();
+                        try {
+                            if (property.isMultiple()) {
+                                mountNode.setProperty(property.getName(), property.getValues());
+                            } else {
+                                mountNode.setProperty(property.getName(), property.getValue());
+                            }
+                        } catch (ConstraintViolationException ex) {
+                        }
+                    }
+                }
+            }
+            sync = null;
+        }
+
+        this.jcr.save();
+
+        this.mount.save();
+    }
+
+    @Override
+    public void refresh(boolean keepChanges) throws RepositoryException {
+        sync = null;
+        this.jcr.refresh(keepChanges);
+        this.mount.refresh(keepChanges);
+    }
+
+    public void refresh(String path, Item item, boolean keepChanges) throws RepositoryException {
+        sync = null;
+        item.refresh(keepChanges);
+        if (!isMount(path) && isMountParent(path)) {
+            this.mount.getRootNode().refresh(keepChanges);
+        }
+    }
+
+    @Override
+    public boolean hasPendingChanges() throws RepositoryException {
+        return this.jcr.hasPendingChanges() || this.mount.hasPendingChanges();
+    }
+
+    @Override
+    public ValueFactory getValueFactory() throws UnsupportedRepositoryOperationException, RepositoryException {
+        return this.jcr.getValueFactory();
+    }
+
+    @Override
+    public boolean hasPermission(String absPath, String actions) throws RepositoryException {
+        return isMount(absPath) ? true : this.jcr.hasPermission(absPath, actions);
+    }
+
+    @Override
+    public void checkPermission(String absPath, String actions) throws AccessControlException, RepositoryException {
+        if (!isMount(absPath)) {
+            this.jcr.checkPermission(absPath, actions);
+        }
+    }
+
+    @Override
+    public boolean hasCapability(String methodName, Object target, Object[] arguments) throws RepositoryException {
+        return this.jcr.hasCapability(methodName, target, arguments);
+    }
+
+    @Override
+    public ContentHandler getImportContentHandler(String parentAbsPath, int uuidBehavior) throws PathNotFoundException, ConstraintViolationException, VersionException, LockException, RepositoryException {
+        if (isMount(parentAbsPath)) {
+            return this.mount.getImportContentHandler(parentAbsPath, uuidBehavior);
+        } else {
+            return this.jcr.getImportContentHandler(parentAbsPath, uuidBehavior);
+        }
+    }
+
+    @Override
+    public void importXML(String parentAbsPath, InputStream in, int uuidBehavior) throws IOException, PathNotFoundException, ItemExistsException, ConstraintViolationException, VersionException, InvalidSerializedDataException, LockException, RepositoryException {
+        if (isMount(parentAbsPath)) {
+            this.mount.importXML(parentAbsPath, in, uuidBehavior);
+        } else {
+            this.jcr.importXML(parentAbsPath, in, uuidBehavior);
+        }
+    }
+
+    @Override
+    public void exportSystemView(String absPath, ContentHandler contentHandler, boolean skipBinary, boolean noRecurse) throws PathNotFoundException, SAXException, RepositoryException {
+        if (isMount(absPath)) {
+            this.mount.exportSystemView(absPath, contentHandler, skipBinary, noRecurse);
+        } else {
+            this.jcr.exportSystemView(absPath, contentHandler, skipBinary, noRecurse);
+        }
+    }
+
+    @Override
+    public void exportSystemView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse) throws IOException, PathNotFoundException, RepositoryException {
+        if (isMount(absPath)) {
+            this.mount.exportSystemView(absPath, out, skipBinary, noRecurse);
+        } else {
+            this.jcr.exportSystemView(absPath, out, skipBinary, noRecurse);
+        }
+    }
+
+    @Override
+    public void exportDocumentView(String absPath, ContentHandler contentHandler, boolean skipBinary, boolean noRecurse) throws PathNotFoundException, SAXException, RepositoryException {
+        if (isMount(absPath)) {
+            this.mount.exportDocumentView(absPath, contentHandler, skipBinary, noRecurse);
+        } else {
+            this.jcr.exportDocumentView(absPath, contentHandler, skipBinary, noRecurse);
+        }
+    }
+
+    @Override
+    public void exportDocumentView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse) throws IOException, PathNotFoundException, RepositoryException {
+        if (isMount(absPath)) {
+            this.mount.exportDocumentView(absPath, out, skipBinary, noRecurse);
+        } else {
+            this.jcr.exportDocumentView(absPath, out, skipBinary, noRecurse);
+        }
+    }
+
+    @Override
+    public void setNamespacePrefix(String prefix, String uri) throws NamespaceException, RepositoryException {
+        this.jcr.setNamespacePrefix(prefix, uri);
+        this.mount.setNamespacePrefix(prefix, uri);
+    }
+
+    @Override
+    public String[] getNamespacePrefixes() throws RepositoryException {
+        return this.jcr.getNamespacePrefixes();
+    }
+
+    @Override
+    public String getNamespaceURI(String prefix) throws NamespaceException, RepositoryException {
+        return this.jcr.getNamespaceURI(prefix);
+    }
+
+    @Override
+    public String getNamespacePrefix(String uri) throws NamespaceException, RepositoryException {
+        return this.jcr.getNamespacePrefix(uri);
+    }
+
+    @Override
+    public void logout() {
+        this.jcr.logout();
+        this.mount.logout();
+    }
+
+    @Override
+    public boolean isLive() {
+        return this.jcr.isLive();
+    }
+
+    @Override
+    public void addLockToken(String lt) {
+        this.jcr.addLockToken(lt);
+    }
+
+    @Override
+    public String[] getLockTokens() {
+        return this.jcr.getLockTokens();
+    }
+
+    @Override
+    public void removeLockToken(String lt) {
+        this.jcr.removeLockToken(lt);
+    }
+
+    @Override
+    public AccessControlManager getAccessControlManager() throws UnsupportedRepositoryOperationException, RepositoryException {
+        AccessControlManager manager = this.jcr.getAccessControlManager();
+        return manager instanceof JackrabbitAccessControlManager ?
+                new ProxyJackrabbitAccessControlManager(this, (JackrabbitAccessControlManager) manager, (JackrabbitAccessControlManager) this.mount.getAccessControlManager()) :
+                new ProxyAccessControlManager<>(this, manager, this.mount.getAccessControlManager());
+    }
+
+    @Override
+    public RetentionManager getRetentionManager() throws UnsupportedRepositoryOperationException, RepositoryException {
+        return this.jcr.getRetentionManager();
+    }
+
+    @Override
+    public void move(String srcAbsPath, String destAbsPath) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, RepositoryException {
+        if (isMount(srcAbsPath) && isMount(destAbsPath)) {
+            this.mount.move(srcAbsPath, destAbsPath);
+        } else if (!isMount(srcAbsPath) && !isMount(destAbsPath)) {
+            this.jcr.move(srcAbsPath, destAbsPath);
+        } else {
+            throw new IllegalStateException("Move between jcr and mount not supported");
+        }
+    }
+
+    @Override
+    public Workspace getWorkspace() {
+        return new ProxyWorkspace(this, this.jcr.getWorkspace(), this.mount.getWorkspace());
+    }
+
+    public Node addNode(String parent, String path, String name) throws RepositoryException {
+        if (isMount(path)) {
+            return wrap(this.mount.getNode(parent).addNode(name));
+        }
+        if (isMountParent(path)) {
+            this.mount.getNode(parent).addNode(name);
+            if (sync == null) {
+                sync = new HashSet<>();
+            }
+            sync.add(path);
+        }
+        return wrap(this.jcr.getNode(parent).addNode(name));
+    }
+
+    public Node addNode(String parent, String path, String name, String type) throws RepositoryException {
+        if (isMount(path)) {
+            return wrap(this.mount.getNode(parent).addNode(name, type));
+        }
+        if (isMountParent(path)) {
+            this.mount.getNode(parent).addNode(name, type);
+            if (sync == null) {
+                sync = new HashSet<>();
+            }
+            sync.add(path);
+        }
+        return wrap(this.jcr.getNode(parent).addNode(name, type));
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyUserManager.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyUserManager.java
new file mode 100644
index 0000000..7755789
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyUserManager.java
@@ -0,0 +1,123 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import java.security.Principal;
+import java.util.Iterator;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.AuthorizableExistsException;
+import org.apache.jackrabbit.api.security.user.AuthorizableTypeException;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.Query;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+
+public class ProxyUserManager extends ProxyWrapper<UserManager> implements UserManager {
+    private final UserManager mount;
+
+    public ProxyUserManager(ProxySession<JackrabbitSession> mountSession, UserManager delegate, UserManager mount) {
+        super(mountSession, delegate);
+        this.mount = mount;
+    }
+
+
+    public Authorizable getAuthorizable(String id) throws RepositoryException {
+        return delegate.getAuthorizable(id);
+    }
+
+    public <T extends Authorizable> T getAuthorizable(String id, Class<T> authorizableClass) throws AuthorizableTypeException, RepositoryException {
+        return delegate.getAuthorizable(id, authorizableClass);
+    }
+
+    public Authorizable getAuthorizable(Principal principal) throws RepositoryException {
+        return delegate.getAuthorizable(principal);
+    }
+
+    public Authorizable getAuthorizableByPath(String path) throws UnsupportedRepositoryOperationException, RepositoryException {
+        return delegate.getAuthorizableByPath(path);
+    }
+
+    public Iterator<Authorizable> findAuthorizables(String relPath, String value) throws RepositoryException {
+        return delegate.findAuthorizables(relPath, value);
+    }
+
+    public Iterator<Authorizable> findAuthorizables(String relPath, String value, int searchType) throws RepositoryException {
+        return delegate.findAuthorizables(relPath, value, searchType);
+    }
+
+    public Iterator<Authorizable> findAuthorizables(Query query) throws RepositoryException {
+        return delegate.findAuthorizables(query);
+    }
+
+    public User createUser(String userID, String password) throws AuthorizableExistsException, RepositoryException {
+        User user = delegate.createUser(userID, password);
+        mount.createUser(userID, password, user.getPrincipal(), user.getPath());
+        return user;
+    }
+
+    public User createUser(String userID, String password, Principal principal, String intermediatePath) throws AuthorizableExistsException, RepositoryException {
+        User user = delegate.createUser(userID, password, principal, intermediatePath);
+        mount.createUser(userID, password, principal, user.getPath());
+        return user;
+    }
+
+    public User createSystemUser(String userID, String intermediatePath) throws AuthorizableExistsException, RepositoryException {
+        User user = delegate.createSystemUser(userID, intermediatePath);
+        mount.createSystemUser(userID, user.getPath());
+        return user;
+    }
+
+    public Group createGroup(String groupID) throws AuthorizableExistsException, RepositoryException {
+        Group group = delegate.createGroup(groupID);
+        mount.createGroup(groupID, group.getPrincipal(), group.getPath());
+        return group;
+    }
+
+    public Group createGroup(Principal principal) throws AuthorizableExistsException, RepositoryException {
+        Group group = delegate.createGroup(principal);
+        mount.createGroup(group.getID(), principal, group.getPath());
+        return group;
+    }
+
+    public Group createGroup(Principal principal, String intermediatePath) throws AuthorizableExistsException, RepositoryException {
+        Group group = delegate.createGroup(principal, intermediatePath);
+        mount.createGroup(principal, group.getPath());
+        return group;
+    }
+
+    public Group createGroup(String groupID, Principal principal, String intermediatePath) throws AuthorizableExistsException, RepositoryException {
+        Group group = delegate.createGroup(groupID, principal, intermediatePath);
+        mount.createGroup(groupID, principal, group.getPath());
+        return group;
+    }
+
+    public boolean isAutoSave() {
+        return delegate.isAutoSave();
+    }
+
+    public void autoSave(boolean enable) throws UnsupportedRepositoryOperationException, RepositoryException {
+        delegate.autoSave(enable);
+        mount.autoSave(enable);
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyWorkspace.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyWorkspace.java
new file mode 100644
index 0000000..75c1fb4
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyWorkspace.java
@@ -0,0 +1,168 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.InvalidSerializedDataException;
+import javax.jcr.ItemExistsException;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Workspace;
+import javax.jcr.lock.LockException;
+import javax.jcr.lock.LockManager;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NodeTypeManager;
+import javax.jcr.observation.ObservationManager;
+import javax.jcr.query.QueryManager;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionException;
+import javax.jcr.version.VersionManager;
+
+import org.xml.sax.ContentHandler;
+
+public class ProxyWorkspace<T extends Workspace> extends ProxyWrapper<T> implements Workspace {
+    final T delegate2;
+
+    public ProxyWorkspace(ProxySession mountSession, T delegate, T delegate2) {
+        super(mountSession, delegate);
+        this.delegate2 = delegate2;
+    }
+
+    @Override
+    public Session getSession() {
+        return this.mountSession;
+    }
+
+    @Override
+    public String getName() {
+        return delegate.getName();
+    }
+
+    @Override
+    public QueryManager getQueryManager() throws RepositoryException {
+        return new ProxyQueryManager(this.mountSession, delegate.getQueryManager(), this.delegate2.getQueryManager());
+    }
+
+    // TODO: revisit the below
+
+    @Override
+    public ContentHandler getImportContentHandler(String parentAbsPath, int uuidBehavior) throws PathNotFoundException, ConstraintViolationException, VersionException, LockException, AccessDeniedException, RepositoryException {
+        return this.mountSession.getImportContentHandler(parentAbsPath, uuidBehavior);
+    }
+
+    @Override
+    public void importXML(String parentAbsPath, InputStream in, int uuidBehavior) throws IOException, VersionException, PathNotFoundException, ItemExistsException, ConstraintViolationException, InvalidSerializedDataException, LockException, AccessDeniedException, RepositoryException {
+        this.mountSession.importXML(parentAbsPath, in, uuidBehavior);
+    }
+
+    @Override
+    public void copy(String srcAbsPath, String destAbsPath) throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
+        if (mountSession.isMount(srcAbsPath) && mountSession.isMount(destAbsPath)) {
+            delegate2.copy(srcAbsPath, destAbsPath);
+        } else {
+            delegate.copy(srcAbsPath, destAbsPath);
+        }
+    }
+
+    @Override
+    public void copy(String srcWorkspace, String srcAbsPath, String destAbsPath) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
+        if (mountSession.isMount(srcAbsPath) && mountSession.isMount(destAbsPath)) {
+            delegate2.copy(srcWorkspace, srcAbsPath, destAbsPath);
+        } else {
+            delegate.copy(srcWorkspace, srcAbsPath, destAbsPath);
+        }
+    }
+
+    @Override
+    public void clone(String srcWorkspace, String srcAbsPath, String destAbsPath, boolean removeExisting) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
+        if (mountSession.isMount(srcAbsPath) && mountSession.isMount(destAbsPath)) {
+            delegate2.clone(srcWorkspace, srcAbsPath, destAbsPath, removeExisting);
+        } else {
+            delegate.clone(srcWorkspace, srcAbsPath, destAbsPath, removeExisting);
+        }
+    }
+
+    @Override
+    public void move(String srcAbsPath, String destAbsPath) throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
+        if (mountSession.isMount(srcAbsPath) && mountSession.isMount(destAbsPath)) {
+            delegate2.move(srcAbsPath, destAbsPath);
+        } else {
+            delegate.move(srcAbsPath, destAbsPath);
+        }
+    }
+
+    @Override
+    public void restore(Version[] versions, boolean removeExisting) throws ItemExistsException, UnsupportedRepositoryOperationException, VersionException, LockException, InvalidItemStateException, RepositoryException {
+        delegate.restore(versions, removeExisting);
+    }
+
+    @Override
+    public void createWorkspace(String name) throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
+        delegate.createWorkspace(name);
+    }
+
+    @Override
+    public void createWorkspace(String name, String srcWorkspace) throws AccessDeniedException, UnsupportedRepositoryOperationException, NoSuchWorkspaceException, RepositoryException {
+        delegate.createWorkspace(name, srcWorkspace);
+    }
+
+    @Override
+    public void deleteWorkspace(String name) throws AccessDeniedException, UnsupportedRepositoryOperationException, NoSuchWorkspaceException, RepositoryException {
+        delegate.deleteWorkspace(name);
+    }
+
+
+    @Override
+    public LockManager getLockManager() throws UnsupportedRepositoryOperationException, RepositoryException {
+        return delegate.getLockManager();
+    }
+
+    @Override
+    public NamespaceRegistry getNamespaceRegistry() throws RepositoryException {
+        return new ProxyNamespaceRegistry(delegate.getNamespaceRegistry(), this.delegate2.getNamespaceRegistry());
+    }
+
+    @Override
+    public NodeTypeManager getNodeTypeManager() throws RepositoryException {
+        return new ProxyNodeTypeManager(delegate.getNodeTypeManager(), this.delegate2.getNodeTypeManager());
+    }
+
+    @Override
+    public ObservationManager getObservationManager() throws UnsupportedRepositoryOperationException, RepositoryException {
+        return delegate.getObservationManager();
+    }
+
+    @Override
+    public VersionManager getVersionManager() throws UnsupportedRepositoryOperationException, RepositoryException {
+        return delegate.getVersionManager();
+    }
+
+    @Override
+    public String[] getAccessibleWorkspaceNames() throws RepositoryException {
+        return delegate.getAccessibleWorkspaceNames();
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyWrapper.java b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyWrapper.java
new file mode 100644
index 0000000..2de05ee
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyWrapper.java
@@ -0,0 +1,40 @@
+/*
+ * 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.jcr.base.internal.mount;
+
+import org.apache.jackrabbit.oak.commons.PathUtils;
+
+public class ProxyWrapper<T> {
+    final ProxySession<?> mountSession;
+    final T delegate;
+
+    public ProxyWrapper(ProxySession<?> mountSession, T delegate) {
+        this.mountSession = mountSession;
+        this.delegate = delegate;
+    }
+
+    String concat(String parent, String relPath) {
+        if (relPath != null) {
+            while (relPath.startsWith("/")) {
+                relPath = relPath.substring(1);
+            }
+        }
+        return PathUtils.concat(parent, relPath);
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/spi/RepositoryMount.java b/src/main/java/org/apache/sling/jcr/base/spi/RepositoryMount.java
new file mode 100644
index 0000000..f009877
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/spi/RepositoryMount.java
@@ -0,0 +1,27 @@
+/*
+ * 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.jcr.base.spi;
+
+import org.apache.jackrabbit.api.JackrabbitRepository;
+
+public interface RepositoryMount extends JackrabbitRepository
+{
+    String PARENT_SESSION_KEY = "org.apache.sling.jcr.base.RepositoryMount.PARENT_SESSION";
+    String MOUNT_POINTS_KEY = "org.apache.sling.jcr.base.RepositoryMount.MOUNT_POINTS";
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/spi/package-info.java b/src/main/java/org/apache/sling/jcr/base/spi/package-info.java
new file mode 100644
index 0000000..a34c77c
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/spi/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/**
+ * The {@code org.apache.sling.jcr.base.spi} package provides a 
+ * way to bifurcate requests to subpaths to a mount provider.
+ */
+@org.osgi.annotation.versioning.Version("0.1.0")
+package org.apache.sling.jcr.base.spi;
+
+