Merge pull request #1 from apache/SLING-8411

SLING-8411: Provide a way to bifurcat a repository path to a provider…
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..9082158 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.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,19 @@
     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);
+            } else {
+                final String userName = serviceUserMapper.getServiceUserID(usingBundle, subServiceName);
+                if (userName != null) {
+                    session = createServiceSession(userName, workspaceName);
+                }
             }
-
-            final String userName = serviceUserMapper.getServiceUserID(usingBundle, subServiceName);
-            if (userName != null) {
-                return createServiceSession(userName, workspaceName);
+            if (session != null) {
+                Repository repository = getRepository();
+                return repository instanceof ProxyRepository ? ((ProxyRepository) repository).wrap(session) : session;
             }
         }
         return null;
@@ -196,7 +202,9 @@
         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 +453,9 @@
         }
 
         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..dbda40d 100644
--- a/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepositoryManager.java
+++ b/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepositoryManager.java
@@ -21,16 +21,22 @@
 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;
@@ -116,6 +122,8 @@
 
     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 +291,30 @@
      * @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 +424,9 @@
         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 +608,11 @@
             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 +643,7 @@
                         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..437d6c5
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/internal/mount/ProxyQuery.java
@@ -0,0 +1,235 @@
+/*
+ * 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() {
+					private volatile long position = 0;
+					@Override
+					public Object next() {
+					    position++;
+					    return iter.next();
+					}
+
+					@Override
+					public boolean hasNext() {
+						return iter.hasNext();
+					}
+
+					@Override
+					public void skip(long skipNum) {
+						while (skipNum-- > 0) {
+						    next();
+						}
+					}
+
+					@Override
+					public long getSize() {
+						return -1;
+					}
+
+					@Override
+					public long getPosition() {
+						return position;
+					}
+
+					@Override
+					public Row nextRow() {
+						position++;
+						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() {
+					private volatile long position = 0;
+					@Override
+					public Object next() {
+					    position++;
+					    return iter.next();
+					}
+
+					@Override
+					public boolean hasNext() {
+						return iter.hasNext();
+					}
+
+					@Override
+					public void skip(long skipNum) {
+						while (skipNum-- > 0) {
+						    next();
+						}
+					}
+
+					@Override
+					public long getSize() {
+						return -1;
+					}
+
+					@Override
+					public long getPosition() {
+						return position;
+					}
+
+					@Override
+					public Node nextNode() {
+					    position++;
+					    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;
+
+
diff --git a/src/test/java/org/apache/sling/jcr/base/RepositoryMountTest.java b/src/test/java/org/apache/sling/jcr/base/RepositoryMountTest.java
new file mode 100644
index 0000000..43815d7
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/base/RepositoryMountTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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;
+
+import java.util.Dictionary;
+import java.util.Properties;
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+import org.apache.sling.jcr.base.spi.RepositoryMount;
+import org.apache.sling.testing.mock.jcr.MockJcr;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.osgi.framework.ServiceRegistration;
+
+public class RepositoryMountTest
+{
+    @Rule
+    public final SlingContext context = new SlingContext(ResourceResolverType.JCR_MOCK);
+
+    private Repository rootRepository;
+    private Repository mountRepository;
+    private MockSlingRepositoryManager manager;
+
+    @Before
+    public void setup() throws RepositoryException
+    {
+        rootRepository = MockJcr.newRepository();
+        mountRepository = MockJcr.newRepository();
+
+        manager = new MockSlingRepositoryManager(rootRepository);
+    }
+
+    @Test
+    public void testRepositoryMount() throws RepositoryException {
+        Session rootSession = rootRepository.login();
+        rootSession.getRootNode()
+            .addNode("/root", NodeType.NT_UNSTRUCTURED)
+            .addNode("test", NodeType.NT_UNSTRUCTURED)
+            .setProperty("test", "root");
+
+        Session mountSession = mountRepository.login();
+        mountSession.getRootNode().addNode("/mount")
+            .addNode("test", NodeType.NT_UNSTRUCTURED)
+            .setProperty("test", "test");
+
+        Assert.assertFalse(rootSession.nodeExists("/mount"));
+        Assert.assertFalse(mountSession.nodeExists("/root"));
+
+        rootSession.logout();
+        mountSession.logout();
+
+        Assert.assertTrue(manager.start(context.bundleContext(), "ws", false));
+
+        Repository repository = context.getService(Repository.class);
+        Assert.assertNotNull(repository);
+
+        Session session = repository.login();
+        Assert.assertFalse(session.nodeExists("/mount"));
+        testExists(session, "/root", "root");
+        session.logout();
+
+        Properties props = new Properties();
+        props.put(RepositoryMount.MOUNT_POINTS_KEY, "/mount");
+
+        ServiceRegistration reg = context.bundleContext().registerService(RepositoryMount.class.getName(), mountRepository, (Dictionary) props);
+
+        session = repository.login();
+
+        testExists(session, "/root", "root");
+
+        testExists(session, "/mount", "test");
+
+        testTraversal(session, "/root", "root");
+        testTraversal(session, "/mount", "test");
+
+        session.logout();
+
+        testCreate(repository, "/mount", "test2", "test3");
+
+        testCreate(repository, "/root", "test2", "test3");
+
+        reg.unregister();
+
+        session = repository.login();
+
+        Assert.assertFalse(session.nodeExists("/mount"));
+
+        testExists(session, "/root", "root");
+
+        session.logout();
+
+        reg = context.bundleContext().registerService(RepositoryMount.class.getName(), mountRepository, (Dictionary) props);
+
+        session = repository.login();
+
+        testExists(session, "/root", "root");
+
+        testExists(session, "/mount", "test");
+
+        session.logout();
+    }
+
+    private void testExists(Session session, String path, String value) throws RepositoryException {
+        Assert.assertTrue(session.nodeExists(path));
+        Assert.assertTrue(session.nodeExists(path + "/test"));
+        Assert.assertTrue(session.itemExists(path + "/test/test"));
+        Assert.assertEquals(value, session.getProperty(path + "/test/test").getString());
+    }
+
+    private void testTraversal(Session session, String path, String value) throws RepositoryException {
+        Assert.assertEquals(value, session.getRootNode().getNode(path).getNode("test").getProperty("test").getString());
+    }
+
+    private void testCreate(Repository repo, String start, String... path) throws RepositoryException {
+        Session session = repo.login();
+        Node current = session.getNode(start);
+        String target = start;
+        for (String next : path) {
+            current = current.addNode(next, NodeType.NT_UNSTRUCTURED);
+            target += "/" + next;
+        }
+        session.logout();
+        session = repo.login();
+        Assert.assertTrue(session.nodeExists(target));
+        session.logout();
+    }
+}