[Prototype] Multitenant SecurityManager
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/model/CDOModelUtil.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/model/CDOModelUtil.java
index 5d3a523..a8e27a2 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/model/CDOModelUtil.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/model/CDOModelUtil.java
@@ -14,6 +14,7 @@
 package org.eclipse.emf.cdo.common.model;
 
 import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.model.CDOPackageUnit.State;
 import org.eclipse.emf.cdo.common.revision.CDORevision;
 import org.eclipse.emf.cdo.common.util.CDOException;
 import org.eclipse.emf.cdo.internal.common.bundle.OM;
@@ -23,6 +24,7 @@
 import org.eclipse.emf.cdo.internal.common.model.CDOPackageRegistryImpl;
 import org.eclipse.emf.cdo.internal.common.model.CDOPackageUnitImpl;
 import org.eclipse.emf.cdo.internal.common.model.CDOTypeImpl;
+import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageInfo;
 import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageUnit;
 
 import org.eclipse.net4j.util.io.ExtendedDataInput;
@@ -589,6 +591,34 @@
   }
 
   /**
+   * @since 4.13
+   */
+  public static CDOPackageUnit copyPackageUnit(CDOPackageUnit packageUnit)
+  {
+    InternalCDOPackageUnit newPackageUnit = (InternalCDOPackageUnit)CDOModelUtil.createPackageUnit();
+    newPackageUnit.setOriginalType(packageUnit.getOriginalType());
+    newPackageUnit.setTimeStamp(packageUnit.getTimeStamp());
+    newPackageUnit.setState(State.LOADED);
+
+    InternalCDOPackageInfo[] packageInfos = (InternalCDOPackageInfo[])packageUnit.getPackageInfos();
+    InternalCDOPackageInfo[] newPackageInfos = new InternalCDOPackageInfo[packageInfos.length];
+
+    for (int i = 0; i < packageInfos.length; i++)
+    {
+      InternalCDOPackageInfo packageInfo = packageInfos[i];
+
+      newPackageInfos[i] = (InternalCDOPackageInfo)CDOModelUtil.createPackageInfo();
+      newPackageInfos[i].setPackageUnit(newPackageUnit);
+      newPackageInfos[i].setEPackage(packageInfo.getEPackage());
+      newPackageInfos[i].setPackageURI(packageInfo.getPackageURI());
+      newPackageInfos[i].setParentURI(packageInfo.getParentURI());
+    }
+
+    newPackageUnit.setPackageInfos(newPackageInfos);
+    return newPackageUnit;
+  }
+
+  /**
    * @since 2.0
    */
   public static CDOPackageUnit createPackageUnit()
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/model/CDOPackageRegistryImpl.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/model/CDOPackageRegistryImpl.java
index da71cc2..b3156c3 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/model/CDOPackageRegistryImpl.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/model/CDOPackageRegistryImpl.java
@@ -261,6 +261,7 @@
   {
     LifecycleUtil.checkActive(this);
     packageUnit.setPackageRegistry(this);
+
     for (InternalCDOPackageInfo packageInfo : packageUnit.getPackageInfos())
     {
       EPackage ePackage = packageInfo.getEPackage(false);
diff --git a/plugins/org.eclipse.emf.cdo.security/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.cdo.security/META-INF/MANIFEST.MF
index c11406d..2717426 100644
--- a/plugins/org.eclipse.emf.cdo.security/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.emf.cdo.security/META-INF/MANIFEST.MF
@@ -2,22 +2,22 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.emf.cdo.security;singleton:=true
-Bundle-Version: 4.4.1.qualifier
+Bundle-Version: 4.4.2.qualifier
 Bundle-ClassPath: .
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Export-Package: org.eclipse.emf.cdo.internal.security;version="4.4.1";
+Export-Package: org.eclipse.emf.cdo.internal.security;version="4.4.2";
   x-friends:="org.eclipse.emf.cdo.security.edit,
    org.eclipse.emf.cdo.security.editor,
    org.eclipse.emf.cdo.server.security,
    org.eclipse.emf.cdo.tests,
    org.eclipse.emf.cdo.tests.db,
    org.eclipse.emf.cdo.tests.mongodb",
- org.eclipse.emf.cdo.internal.security.bundle;version="4.4.1";x-internal:=true,
- org.eclipse.emf.cdo.security;version="4.4.1",
- org.eclipse.emf.cdo.security.impl;version="4.4.1",
- org.eclipse.emf.cdo.security.util;version="4.4.1"
+ org.eclipse.emf.cdo.internal.security.bundle;version="4.4.2";x-internal:=true,
+ org.eclipse.emf.cdo.security;version="4.4.2",
+ org.eclipse.emf.cdo.security.impl;version="4.4.2",
+ org.eclipse.emf.cdo.security.util;version="4.4.2"
 Require-Bundle: org.eclipse.emf.cdo;bundle-version="[4.1.0,5.0.0)";visibility:=reexport,
  org.eclipse.emf.cdo.expressions;bundle-version="[4.3.0,5.0.0)";visibility:=reexport
 Bundle-ActivationPolicy: lazy
diff --git a/plugins/org.eclipse.emf.cdo.security/pom.xml b/plugins/org.eclipse.emf.cdo.security/pom.xml
index 58f4a44..7f830c5 100644
--- a/plugins/org.eclipse.emf.cdo.security/pom.xml
+++ b/plugins/org.eclipse.emf.cdo.security/pom.xml
@@ -25,7 +25,7 @@
 
   <groupId>org.eclipse.emf.cdo</groupId>
   <artifactId>org.eclipse.emf.cdo.security</artifactId>
-  <version>4.4.1-SNAPSHOT</version>
+  <version>4.4.2-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 
 </project>
diff --git a/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/internal/security/ViewCreator.java b/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/internal/security/ViewCreator.java
index d5bffab..304c7ac 100644
--- a/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/internal/security/ViewCreator.java
+++ b/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/internal/security/ViewCreator.java
@@ -16,6 +16,7 @@
 /**
  * @author Eike Stepper
  */
+@FunctionalInterface
 public interface ViewCreator
 {
   public CDOView createView(CDORevisionProvider revisionProvider);
diff --git a/plugins/org.eclipse.emf.cdo.server.security/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.cdo.server.security/META-INF/MANIFEST.MF
index 1a4bfdf..8bc1d0c 100644
--- a/plugins/org.eclipse.emf.cdo.server.security/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.emf.cdo.server.security/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-SymbolicName: org.eclipse.emf.cdo.server.security;singleton:=true
 Bundle-Name: %pluginName
-Bundle-Version: 4.5.0.qualifier
+Bundle-Version: 4.6.0.qualifier
 Bundle-ClassPath: .
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin
@@ -12,13 +12,13 @@
  org.eclipse.emf.cdo.security;bundle-version="[4.1.0,5.0.0)",
  org.eclipse.emf.cdo.net4j;bundle-version="[4.1.0,5.0.0)",
  org.eclipse.net4j.jvm;bundle-version="[4.1.0,5.0.0)"
-Export-Package: org.eclipse.emf.cdo.server.internal.security;version="4.5.0";
+Export-Package: org.eclipse.emf.cdo.server.internal.security;version="4.6.0";
   x-friends:="org.eclipse.emf.cdo.tests,
    org.eclipse.emf.cdo.tests.db,
    org.eclipse.emf.cdo.tests.mongodb",
- org.eclipse.emf.cdo.server.internal.security.bundle;version="4.5.0";x-internal:=true,
- org.eclipse.emf.cdo.server.security;version="4.5.0",
- org.eclipse.emf.cdo.server.spi.security;version="4.5.0"
+ org.eclipse.emf.cdo.server.internal.security.bundle;version="4.6.0";x-internal:=true,
+ org.eclipse.emf.cdo.server.security;version="4.6.0",
+ org.eclipse.emf.cdo.server.spi.security;version="4.6.0"
 Bundle-ActivationPolicy: lazy
 Bundle-Activator: org.eclipse.emf.cdo.server.internal.security.bundle.OM$Activator
 Automatic-Module-Name: org.eclipse.emf.cdo.server.security
diff --git a/plugins/org.eclipse.emf.cdo.server.security/pom.xml b/plugins/org.eclipse.emf.cdo.server.security/pom.xml
index 5fd8edb..1951fa1 100644
--- a/plugins/org.eclipse.emf.cdo.server.security/pom.xml
+++ b/plugins/org.eclipse.emf.cdo.server.security/pom.xml
@@ -25,7 +25,7 @@
 
   <groupId>org.eclipse.emf.cdo</groupId>
   <artifactId>org.eclipse.emf.cdo.server.security</artifactId>
-  <version>4.5.0-SNAPSHOT</version>
+  <version>4.6.0-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 
 </project>
diff --git a/plugins/org.eclipse.emf.cdo.server.security/src/org/eclipse/emf/cdo/server/internal/security/SecurityManager.java b/plugins/org.eclipse.emf.cdo.server.security/src/org/eclipse/emf/cdo/server/internal/security/SecurityManager.java
index ebffb83..d071268 100644
--- a/plugins/org.eclipse.emf.cdo.server.security/src/org/eclipse/emf/cdo/server/internal/security/SecurityManager.java
+++ b/plugins/org.eclipse.emf.cdo.server.security/src/org/eclipse/emf/cdo/server/internal/security/SecurityManager.java
@@ -17,6 +17,8 @@
 import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
 import org.eclipse.emf.cdo.common.commit.CDOCommitInfo;
 import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.model.CDOModelUtil;
+import org.eclipse.emf.cdo.common.model.CDOPackageUnit;
 import org.eclipse.emf.cdo.common.protocol.CDOProtocol.CommitNotificationInfo;
 import org.eclipse.emf.cdo.common.revision.CDORevision;
 import org.eclipse.emf.cdo.common.revision.CDORevisionProvider;
@@ -47,10 +49,14 @@
 import org.eclipse.emf.cdo.server.IPermissionManager;
 import org.eclipse.emf.cdo.server.IRepository;
 import org.eclipse.emf.cdo.server.ISession;
+import org.eclipse.emf.cdo.server.IStoreAccessor;
 import org.eclipse.emf.cdo.server.IStoreAccessor.CommitContext;
 import org.eclipse.emf.cdo.server.ITransaction;
+import org.eclipse.emf.cdo.server.StoreThreadLocal;
 import org.eclipse.emf.cdo.server.internal.security.bundle.OM;
 import org.eclipse.emf.cdo.server.spi.security.InternalSecurityManager;
+import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageRegistry;
+import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageUnit;
 import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
 import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta;
 import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionManager;
@@ -68,6 +74,7 @@
 import org.eclipse.net4j.acceptor.IAcceptor;
 import org.eclipse.net4j.connector.IConnector;
 import org.eclipse.net4j.util.ArrayUtil;
+import org.eclipse.net4j.util.RunnableWithException;
 import org.eclipse.net4j.util.WrappedException;
 import org.eclipse.net4j.util.collection.HashBag;
 import org.eclipse.net4j.util.concurrent.TimeoutRuntimeException;
@@ -80,6 +87,7 @@
 import org.eclipse.net4j.util.lifecycle.Lifecycle;
 import org.eclipse.net4j.util.lifecycle.LifecycleEventAdapter;
 import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
+import org.eclipse.net4j.util.om.monitor.Monitor;
 import org.eclipse.net4j.util.om.monitor.OMMonitor;
 import org.eclipse.net4j.util.security.IAuthenticator;
 import org.eclipse.net4j.util.security.IAuthenticator2;
@@ -93,18 +101,24 @@
 import org.eclipse.emf.ecore.EValidator;
 import org.eclipse.emf.spi.cdo.InternalCDOSessionInvalidationEvent;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * @author Eike Stepper
  */
 public class SecurityManager extends Lifecycle implements InternalSecurityManager
 {
-  private static final Map<IRepository, InternalSecurityManager> SECURITY_MANAGERS = new HashMap<>();
+  private static final Map<IRepository, InternalSecurityManager> SECURITY_MANAGERS = Collections.synchronizedMap(new HashMap<>());
 
   private static final SecurityFactory SF = SecurityFactory.eINSTANCE;
 
@@ -127,13 +141,19 @@
   private final IListener sessionManagerListener = new ContainerEventAdapter<ISession>()
   {
     @Override
+    protected void onAdded(IContainer<ISession> container, ISession session)
+    {
+      sessionAdded(session);
+    }
+
+    @Override
     protected void onRemoved(IContainer<ISession> container, ISession session)
     {
-      removeUserInfo(session);
+      sessionRemoved(session);
     }
   };
 
-  private final IListener systemListener = new IListener()
+  private final IListener realmInvalidationListener = new IListener()
   {
     private boolean clearUserInfos;
 
@@ -152,7 +172,7 @@
       {
         if (clearUserInfos)
         {
-          clearUserInfos();
+          clearUserInfos(true);
           clearUserInfos = false;
         }
       }
@@ -169,7 +189,9 @@
 
   private final IManagedContainer container;
 
-  private final Map<ISession, UserInfo> userInfos = new HashMap<>();
+  private final ConcurrentMap<InternalRepository, SecondaryRepository> secondaryRepositories = new ConcurrentHashMap<>();
+
+  private final ConcurrentMap<String, UserInfo> userInfos = new ConcurrentHashMap<>();
 
   private final HashBag<PermissionImpl> permissionBag = new HashBag<>();
 
@@ -187,9 +209,9 @@
 
   private IConnector connector;
 
-  private CDONet4jSession systemSession;
+  private CDONet4jSession realmSession;
 
-  private CDOView systemView;
+  private CDOView realmView;
 
   private Realm realm;
 
@@ -216,7 +238,7 @@
   }
 
   @Override
-  public final IRepository getRepository()
+  public final InternalRepository getRepository()
   {
     return repository;
   }
@@ -232,6 +254,30 @@
   }
 
   @Override
+  public InternalRepository[] getSecondaryRepositories()
+  {
+    return secondaryRepositories.keySet().toArray(new InternalRepository[0]);
+  }
+
+  @Override
+  public void addSecondaryRepository(InternalRepository repository)
+  {
+    secondaryRepositories.computeIfAbsent(repository, k -> {
+      return new SecondaryRepository(k);
+    });
+  }
+
+  @Override
+  public void removeSecondaryRepository(InternalRepository repository)
+  {
+    SecondaryRepository secondaryRepository = secondaryRepositories.remove(repository);
+    if (secondaryRepository != null)
+    {
+      secondaryRepository.dispose();
+    }
+  }
+
+  @Override
   public Realm getRealm()
   {
     return realm;
@@ -274,68 +320,39 @@
   }
 
   @Override
-  public Role addRole(final String id)
+  public Role addRole(String id)
   {
-    final Role[] result = { null };
-    modify(new RealmOperation()
-    {
-      @Override
-      public void execute(Realm realm)
-      {
-        result[0] = realm.addRole(id);
-      }
-    });
-
+    Role[] result = { null };
+    modify(realm -> result[0] = realm.addRole(id));
     return result[0];
   }
 
   @Override
-  public Group addGroup(final String id)
+  public Group addGroup(String id)
   {
-    final Group[] result = { null };
-    modify(new RealmOperation()
-    {
-      @Override
-      public void execute(Realm realm)
-      {
-        result[0] = realm.addGroup(id);
-      }
-    });
-
+    Group[] result = { null };
+    modify(realm -> result[0] = realm.addGroup(id));
     return result[0];
   }
 
   @Override
-  public User addUser(final String id)
+  public User addUser(String id)
   {
-    final User[] result = { null };
-    modify(new RealmOperation()
-    {
-      @Override
-      public void execute(Realm realm)
-      {
-        result[0] = realm.addUser(id);
-      }
-    });
-
+    User[] result = { null };
+    modify(realm -> result[0] = realm.addUser(id));
     return result[0];
   }
 
   @Override
-  public User addUser(final String id, final String password)
+  public User addUser(String id, String password)
   {
-    final User[] result = { null };
-    modify(new RealmOperation()
-    {
-      @Override
-      public void execute(Realm realm)
-      {
-        UserPassword userPassword = SF.createUserPassword();
-        userPassword.setEncrypted(new String(password));
+    User[] result = { null };
+    modify(realm -> {
+      UserPassword userPassword = SF.createUserPassword();
+      userPassword.setEncrypted(new String(password));
 
-        result[0] = realm.addUser(id);
-        result[0].setPassword(userPassword);
-      }
+      result[0] = realm.addUser(id);
+      result[0].setPassword(userPassword);
     });
 
     return result[0];
@@ -348,66 +365,34 @@
   }
 
   @Override
-  public User setPassword(final String id, final String password)
+  public User setPassword(String id, String password)
   {
-    final User[] result = { null };
-    modify(new RealmOperation()
-    {
-      @Override
-      public void execute(Realm realm)
-      {
-        result[0] = realm.setPassword(id, password);
-      }
-    });
-
+    User[] result = { null };
+    modify(realm -> result[0] = realm.setPassword(id, password));
     return result[0];
   }
 
   @Override
-  public Role removeRole(final String id)
+  public Role removeRole(String id)
   {
-    final Role[] result = { null };
-    modify(new RealmOperation()
-    {
-      @Override
-      public void execute(Realm realm)
-      {
-        result[0] = realm.removeRole(id);
-      }
-    });
-
+    Role[] result = { null };
+    modify(realm -> result[0] = realm.removeRole(id));
     return result[0];
   }
 
   @Override
-  public Group removeGroup(final String id)
+  public Group removeGroup(String id)
   {
-    final Group[] result = { null };
-    modify(new RealmOperation()
-    {
-      @Override
-      public void execute(Realm realm)
-      {
-        result[0] = realm.removeGroup(id);
-      }
-    });
-
+    Group[] result = { null };
+    modify(realm -> result[0] = realm.removeGroup(id));
     return result[0];
   }
 
   @Override
-  public User removeUser(final String id)
+  public User removeUser(String id)
   {
-    final User[] result = { null };
-    modify(new RealmOperation()
-    {
-      @Override
-      public void execute(Realm realm)
-      {
-        result[0] = realm.removeUser(id);
-      }
-    });
-
+    User[] result = { null };
+    modify(realm -> result[0] = realm.removeUser(id));
     return result[0];
   }
 
@@ -434,7 +419,7 @@
   public CDOCommitInfo modifyWithInfo(RealmOperation operation, boolean waitUntilReadable)
   {
     checkReady();
-    CDOTransaction transaction = systemSession.openTransaction();
+    CDOTransaction transaction = realmSession.openTransaction();
 
     try
     {
@@ -444,7 +429,7 @@
 
       if (waitUntilReadable)
       {
-        if (!systemView.waitForUpdate(info.getTimeStamp(), 10000))
+        if (!realmView.waitForUpdate(info.getTimeStamp(), 10000))
         {
           throw new TimeoutRuntimeException();
         }
@@ -566,7 +551,7 @@
    */
   protected void checkReady()
   {
-    if (realm == null || systemSession == null)
+    if (realm == null || realmSession == null)
     {
       // If I have no realm or session, I am probably inactive, so this will throw
       checkActive();
@@ -605,11 +590,11 @@
     config.setRepositoryName(repositoryName);
     config.setUserID(SYSTEM_USER_ID);
 
-    systemSession = config.openNet4jSession();
-    systemSession.options().setGeneratedPackageEmulationEnabled(true);
-    systemSession.addListener(systemListener);
+    realmSession = config.openNet4jSession();
+    realmSession.options().setGeneratedPackageEmulationEnabled(true);
+    realmSession.addListener(realmInvalidationListener);
 
-    CDOTransaction initialTransaction = systemSession.openTransaction();
+    CDOTransaction initialTransaction = realmSession.openTransaction();
 
     boolean firstTime = !initialTransaction.hasResource(realmPath);
     if (firstTime)
@@ -641,10 +626,10 @@
       initialTransaction.close();
     }
 
-    systemView = systemSession.openView();
-    systemView.addListener(systemListener);
+    realmView = realmSession.openView();
+    realmView.addListener(realmInvalidationListener);
 
-    realm = systemView.getObject(realm);
+    realm = realmView.getObject(realm);
     realmID = realm.cdoID();
 
     InternalSessionManager sessionManager = repository.getSessionManager();
@@ -704,11 +689,11 @@
     return directory;
   }
 
-  protected CDOPermission convertPermission(Access permission)
+  protected CDOPermission convertPermission(Access access)
   {
-    if (permission != null)
+    if (access != null)
     {
-      switch (permission)
+      switch (access)
       {
       case READ:
         return CDOPermission.READ;
@@ -726,7 +711,7 @@
   {
     if (lastRealmModification != CDOBranchPoint.UNSPECIFIED_DATE)
     {
-      if (!systemView.waitForUpdate(lastRealmModification, 10000L))
+      if (!realmView.waitForUpdate(lastRealmModification, 10000L))
       {
         throw new TimeoutRuntimeException();
       }
@@ -786,80 +771,196 @@
     }
   }
 
-  protected UserInfo getUserInfo(ISession session)
+  protected void authorizeCommit(CommitContext commitContext, UserInfo userInfo)
   {
-    UserInfo userInfo;
-    synchronized (userInfos)
+    PermissionUtil.setUser(userInfo.getUserId());
+    PermissionUtil.initViewCreation(xxx -> CDOServerUtil.openView(commitContext));
+
+    try
     {
-      userInfo = userInfos.get(session);
-    }
+      CDOBranchPoint securityContext = commitContext.getBranchPoint();
 
-    if (userInfo == null)
-    {
-      userInfo = addUserInfo(session);
-    }
+      ITransaction transaction = commitContext.getTransaction();
+      ISession session = transaction.getSession();
 
-    return userInfo;
-  }
+      Access userDefaultAccess = userInfo.getDefaultAccess();
+      Permission[] userPermissions = userInfo.getPermissions();
 
-  protected UserInfo addUserInfo(ISession session)
-  {
-    String userID = session.getUserID();
-    User user = getUser(userID);
-    UserInfo userInfo = new UserInfo(user);
+      InternalCDORevision[] revisions = commitContext.getDirtyObjects();
+      InternalCDORevisionDelta[] revisionDeltas = commitContext.getDirtyObjectDeltas();
 
-    synchronized (userInfos)
-    {
-      userInfos.put(session, userInfo);
-
-      Permission[] permissions = userInfo.getPermissions();
-      for (int i = 0; i < permissions.length; i++)
+      // Check permissions on the commit changes and detect realm modifications
+      byte securityImpact = CommitNotificationInfo.IMPACT_NONE;
+      for (int i = 0; i < revisions.length; i++)
       {
-        Permission permission = permissions[i];
-        permissionBag.add((PermissionImpl)permission);
+        InternalCDORevision revision = revisions[i];
+        CDOPermission permission = authorize(revision, commitContext, securityContext, session, userDefaultAccess, userPermissions);
+
+        if (permission != CDOPermission.WRITE)
+        {
+          throw new SecurityException("User " + commitContext.getUserID() + " is not allowed to write to " + revision);
+        }
+
+        if (securityImpact != CommitNotificationInfo.IMPACT_REALM)
+        {
+          InternalCDORevisionDelta revisionDelta = revisionDeltas[i];
+          if (CDORevisionUtil.isContained(revisionDelta.getID(), realmID, transaction)) // Use "before commit" state
+          {
+            securityImpact = CommitNotificationInfo.IMPACT_REALM;
+          }
+        }
       }
 
-      // Atomic update
-      permissionArray = permissionBag.toArray(new PermissionImpl[permissionBag.size()]);
+      // Determine permissions that are impacted by the commit changes
+      Set<Permission> impactedRules = null;
+      if (securityImpact != CommitNotificationInfo.IMPACT_REALM)
+      {
+        PermissionImpl[] assignedPermissions = permissionArray; // Thread-safe
+        if (assignedPermissions.length != 0)
+        {
+          CommitImpactContext commitImpactContext = new PermissionImpl.CommitImpactContext()
+          {
+            @Override
+            public CDORevision[] getNewObjects()
+            {
+              return commitContext.getNewObjects();
+            }
+
+            @Override
+            public CDORevision[] getDirtyObjects()
+            {
+              return revisions;
+            }
+
+            @Override
+            public CDORevisionDelta[] getDirtyObjectDeltas()
+            {
+              return revisionDeltas;
+            }
+
+            @Override
+            public CDOID[] getDetachedObjects()
+            {
+              return commitContext.getDetachedObjects();
+            }
+          };
+
+          for (int i = 0; i < assignedPermissions.length; i++)
+          {
+            PermissionImpl permission = assignedPermissions[i];
+            if (permission.isImpacted(commitImpactContext))
+            {
+              if (impactedRules == null)
+              {
+                impactedRules = new HashSet<>();
+              }
+
+              impactedRules.add(permission);
+            }
+          }
+
+          if (impactedRules != null)
+          {
+            securityImpact = CommitNotificationInfo.IMPACT_PERMISSIONS;
+          }
+        }
+      }
+
+      ((InternalCommitContext)commitContext).setSecurityImpact(securityImpact, impactedRules);
+    }
+    finally
+    {
+      PermissionUtil.setUser(null);
+      PermissionUtil.doneViewCreation();
+    }
+  }
+
+  protected void sessionAdded(ISession session)
+  {
+    String userID = session.getUserID();
+
+    UserInfo result = userInfos.computeIfAbsent(userID, k -> {
+      User user = getUser(k);
+      UserInfo userInfo = new UserInfo(user);
+      updatePermissions(userInfo, true);
+      return userInfo;
+    });
+
+    result.addSessionRef();
+  }
+
+  protected void sessionRemoved(ISession session)
+  {
+    String userID = session.getUserID();
+
+    userInfos.computeIfPresent(userID, (k, v) -> {
+      if (v.removeSessionRef())
+      {
+        return null;
+      }
+
+      return v;
+    });
+  }
+
+  protected UserInfo getUserInfo(ISession session)
+  {
+    String userID = session.getUserID();
+
+    UserInfo userInfo = userInfos.get(userID);
+    if (userInfo == null)
+    {
+      throw new IllegalStateException("No user info for " + userID);
     }
 
     return userInfo;
   }
 
-  protected UserInfo removeUserInfo(ISession session)
+  protected void clearUserInfos(boolean rebuild)
   {
-    UserInfo userInfo;
-    synchronized (userInfos)
+    synchronized (permissionBag)
     {
-      userInfo = userInfos.remove(session);
+      permissionBag.clear();
+      permissionArray = null;
 
-      if (userInfo != null)
+      if (rebuild)
       {
-        Permission[] permissions = userInfo.getPermissions();
+        for (UserInfo userInfo : userInfos.values())
+        {
+          userInfo.rebuildPermissions();
+          updatePermissions(userInfo, false);
+        }
+
+        updatePermissions(null, true);
+      }
+      else
+      {
+        userInfos.clear();
+      }
+    }
+  }
+
+  protected void updatePermissions(UserInfo userInfo, boolean updateArray)
+  {
+    Permission[] permissions = userInfo == null ? null : userInfo.getPermissions();
+
+    synchronized (permissionBag)
+    {
+      if (permissions != null)
+      {
         for (int i = 0; i < permissions.length; i++)
         {
           Permission permission = permissions[i];
-          permissionBag.remove(permission);
+          permissionBag.add((PermissionImpl)permission);
         }
+      }
 
+      if (updateArray)
+      {
         // Atomic update
         permissionArray = permissionBag.toArray(new PermissionImpl[permissionBag.size()]);
       }
     }
-
-    return userInfo;
-  }
-
-  protected void clearUserInfos()
-  {
-    synchronized (userInfos)
-    {
-      // System.out.println("clearUserInfos()");
-
-      userInfos.clear();
-      permissionBag.clear();
-      permissionArray = null;
-    }
   }
 
   protected final boolean isAdministrator(User user)
@@ -895,14 +996,14 @@
   @Override
   protected void doDeactivate() throws Exception
   {
-    clearUserInfos();
+    clearUserInfos(false);
 
     realm = null;
     realmID = null;
 
-    systemSession.close();
-    systemSession = null;
-    systemView = null;
+    realmSession.close();
+    realmSession = null;
+    realmView = null;
 
     connector.close();
     connector = null;
@@ -921,17 +1022,18 @@
   /**
    * @author Eike Stepper
    */
-  private static final class UserInfo
+  private static final class UserInfo extends AtomicInteger
   {
+    private static final long serialVersionUID = 1L;
+
     private final User user;
 
-    private final Permission[] permissions;
+    private Permission[] permissions;
 
     public UserInfo(User user)
     {
       this.user = user;
-      EList<Permission> allPermissions = user.getAllPermissions();
-      permissions = allPermissions.toArray(new Permission[allPermissions.size()]);
+      rebuildPermissions();
     }
 
     public User getUser()
@@ -939,10 +1041,42 @@
       return user;
     }
 
+    public String getUserId()
+    {
+      return user.getId();
+    }
+
+    public Access getDefaultAccess()
+    {
+      return user.getDefaultAccess();
+    }
+
     public Permission[] getPermissions()
     {
       return permissions;
     }
+
+    public void rebuildPermissions()
+    {
+      EList<Permission> allPermissions = user.getAllPermissions();
+      permissions = allPermissions.toArray(new Permission[allPermissions.size()]);
+    }
+
+    public synchronized void addSessionRef()
+    {
+      incrementAndGet();
+    }
+
+    public synchronized boolean removeSessionRef()
+    {
+      return decrementAndGet() == 0;
+    }
+
+    @Override
+    public String toString()
+    {
+      return "UserInfo[user=" + getUserId() + ", refs=" + super.toString() + "]";
+    }
   }
 
   /**
@@ -950,6 +1084,10 @@
    */
   private final class Authenticator implements IAuthenticator2
   {
+    public Authenticator()
+    {
+    }
+
     @Override
     public void authenticate(String userID, char[] password) throws SecurityException
     {
@@ -1008,6 +1146,10 @@
    */
   private final class PermissionManager implements IPermissionManager
   {
+    public PermissionManager()
+    {
+    }
+
     @Override
     @Deprecated
     public CDOPermission getPermission(CDORevision revision, CDOBranchPoint securityContext, String userID)
@@ -1016,7 +1158,7 @@
     }
 
     @Override
-    public CDOPermission getPermission(CDORevision revision, final CDOBranchPoint securityContext, final ISession session)
+    public CDOPermission getPermission(CDORevision revision, CDOBranchPoint securityContext, ISession session)
     {
       String userID = session.getUserID();
       if (SYSTEM_USER_ID.equals(userID))
@@ -1086,21 +1228,14 @@
   {
     private final IRepository.WriteAccessHandler realmValidationHandler = new RealmValidationHandler();
 
-    @Override
-    public void handleTransactionBeforeCommitting(ITransaction transaction, final CommitContext commitContext, OMMonitor monitor) throws RuntimeException
+    public WriteAccessHandler()
     {
-      doHandleTransactionBeforeCommitting(transaction, commitContext, monitor);
-
-      if (commitContext.getSecurityImpact() == CommitNotificationInfo.IMPACT_REALM)
-      {
-        // Validate changes to the realm
-        realmValidationHandler.handleTransactionBeforeCommitting(transaction, commitContext, monitor);
-      }
     }
 
-    protected void doHandleTransactionBeforeCommitting(ITransaction transaction, final CommitContext commitContext, OMMonitor monitor) throws RuntimeException
+    @Override
+    public void handleTransactionBeforeCommitting(ITransaction transaction, CommitContext commitContext, OMMonitor monitor) throws RuntimeException
     {
-      if (transaction.getSessionID() == systemSession.getSessionID())
+      if (transaction.getSessionID() == realmSession.getSessionID())
       {
         // Access through ISecurityManager.modify(RealmOperation)
         handleCommit(commitContext, null);
@@ -1109,119 +1244,19 @@
       }
 
       UserInfo userInfo = getUserInfo(transaction.getSession());
-      User user = userInfo.getUser();
 
-      handleCommit(commitContext, user);
+      handleCommit(commitContext, userInfo.getUser());
+      authorizeCommit(commitContext, userInfo);
 
-      PermissionUtil.setUser(user.getId());
-      PermissionUtil.initViewCreation(new ViewCreator()
+      if (commitContext.getSecurityImpact() == CommitNotificationInfo.IMPACT_REALM)
       {
-        @Override
-        public CDOView createView(CDORevisionProvider revisionProvider)
-        {
-          return CDOServerUtil.openView(commitContext);
-        }
-      });
-
-      try
-      {
-        CDOBranchPoint securityContext = commitContext.getBranchPoint();
-        ISession session = transaction.getSession();
-
-        Access userDefaultAccess = user.getDefaultAccess();
-        Permission[] userPermissions = userInfo.getPermissions();
-
-        final InternalCDORevision[] revisions = commitContext.getDirtyObjects();
-        final InternalCDORevisionDelta[] revisionDeltas = commitContext.getDirtyObjectDeltas();
-
-        // Check permissions on the commit changes and detect realm modifications
-        byte securityImpact = CommitNotificationInfo.IMPACT_NONE;
-        for (int i = 0; i < revisions.length; i++)
-        {
-          InternalCDORevision revision = revisions[i];
-          CDOPermission permission = authorize(revision, commitContext, securityContext, session, userDefaultAccess, userPermissions);
-
-          if (permission != CDOPermission.WRITE)
-          {
-            throw new SecurityException("User " + commitContext.getUserID() + " is not allowed to write to " + revision);
-          }
-
-          if (securityImpact != CommitNotificationInfo.IMPACT_REALM)
-          {
-            InternalCDORevisionDelta revisionDelta = revisionDeltas[i];
-            if (CDORevisionUtil.isContained(revisionDelta.getID(), realmID, transaction)) // Use "before commit" state
-            {
-              securityImpact = CommitNotificationInfo.IMPACT_REALM;
-            }
-          }
-        }
-
-        // Determine permissions that are impacted by the commit changes
-        Set<Permission> impactedRules = null;
-        if (securityImpact != CommitNotificationInfo.IMPACT_REALM)
-        {
-          PermissionImpl[] assignedPermissions = permissionArray; // Thread-safe
-          if (assignedPermissions.length != 0)
-          {
-            CommitImpactContext commitImpactContext = new PermissionImpl.CommitImpactContext()
-            {
-              @Override
-              public CDORevision[] getNewObjects()
-              {
-                return commitContext.getNewObjects();
-              }
-
-              @Override
-              public CDORevision[] getDirtyObjects()
-              {
-                return revisions;
-              }
-
-              @Override
-              public CDORevisionDelta[] getDirtyObjectDeltas()
-              {
-                return revisionDeltas;
-              }
-
-              @Override
-              public CDOID[] getDetachedObjects()
-              {
-                return commitContext.getDetachedObjects();
-              }
-            };
-
-            for (int i = 0; i < assignedPermissions.length; i++)
-            {
-              PermissionImpl permission = assignedPermissions[i];
-              if (permission.isImpacted(commitImpactContext))
-              {
-                if (impactedRules == null)
-                {
-                  impactedRules = new HashSet<>();
-                }
-
-                impactedRules.add(permission);
-              }
-            }
-
-            if (impactedRules != null)
-            {
-              securityImpact = CommitNotificationInfo.IMPACT_PERMISSIONS;
-            }
-          }
-        }
-
-        ((InternalCommitContext)commitContext).setSecurityImpact(securityImpact, impactedRules);
-      }
-      finally
-      {
-        PermissionUtil.setUser(null);
-        PermissionUtil.doneViewCreation();
+        // Validate changes to the realm
+        realmValidationHandler.handleTransactionBeforeCommitting(transaction, commitContext, monitor);
       }
     }
 
     @Override
-    public void handleTransactionAfterCommitted(ITransaction transaction, final CommitContext commitContext, OMMonitor monitor)
+    public void handleTransactionAfterCommitted(ITransaction transaction, CommitContext commitContext, OMMonitor monitor)
     {
       if (commitContext.getSecurityImpact() == CommitNotificationInfo.IMPACT_REALM)
       {
@@ -1243,11 +1278,15 @@
   {
     private final EValidator realmValidator = EValidator.Registry.INSTANCE.getEValidator(SecurityPackage.eINSTANCE);
 
+    public RealmValidationHandler()
+    {
+    }
+
     @Override
     protected void handleTransactionBeforeCommitting(OMMonitor monitor) throws RuntimeException
     {
-      final BasicDiagnostic diagnostic = new BasicDiagnostic();
-      final Map<Object, Object> context = createValidationContext();
+      BasicDiagnostic diagnostic = new BasicDiagnostic();
+      Map<Object, Object> context = createValidationContext();
 
       boolean realmChecked = false;
       for (EObject object : getDirtyObjects())
@@ -1278,7 +1317,7 @@
     protected Map<Object, Object> createValidationContext()
     {
       Map<Object, Object> result = new java.util.HashMap<>();
-      final CommitContext commitContext = getCommitContext();
+      CommitContext commitContext = getCommitContext();
 
       // Supply the revision-provider and branch point required by realm validation
       result.put(CDORevisionProvider.class, commitContext);
@@ -1315,4 +1354,109 @@
       return null;
     }
   }
+
+  /**
+   * @author Eike Stepper
+   */
+  private final class SecondaryRepository extends LifecycleEventAdapter implements IRepository.WriteAccessHandler
+  {
+    private final InternalRepository delegate;
+
+    public SecondaryRepository(InternalRepository delegate)
+    {
+      this.delegate = delegate;
+
+      InternalSessionManager sessionManager = delegate.getSessionManager();
+      sessionManager.setAuthenticator(authenticator);
+      sessionManager.setPermissionManager(permissionManager);
+      sessionManager.addListener(sessionManagerListener);
+
+      delegate.addListener(this);
+      delegate.addHandler(this);
+
+      SECURITY_MANAGERS.put(delegate, SecurityManager.this);
+    }
+
+    @Override
+    public void handleTransactionBeforeCommitting(ITransaction transaction, CommitContext commitContext, OMMonitor monitor) throws RuntimeException
+    {
+      handleNewPackageUnits(commitContext);
+
+      UserInfo userInfo = getUserInfo(transaction.getSession());
+      authorizeCommit(commitContext, userInfo);
+    }
+
+    @Override
+    public void handleTransactionAfterCommitted(ITransaction transaction, CommitContext commitContext, OMMonitor monitor)
+    {
+      // Do nothing.
+    }
+
+    public void dispose()
+    {
+      SECURITY_MANAGERS.remove(delegate);
+    }
+
+    @Override
+    protected void onDeactivated(ILifecycle lifecycle)
+    {
+      removeSecondaryRepository(delegate);
+    }
+
+    private void handleNewPackageUnits(CommitContext commitContext)
+    {
+      InternalCDOPackageUnit[] newPackageUnits = commitContext.getNewPackageUnits();
+      if (newPackageUnits != null && newPackageUnits.length != 0)
+      {
+        InternalCDOPackageRegistry realmPackageRegistry = getRepository().getPackageRegistry();
+        List<InternalCDOPackageUnit> unknownPackageUnits = new ArrayList<>();
+    
+        for (InternalCDOPackageUnit packageUnit : newPackageUnits)
+        {
+          String nsURI = packageUnit.getID();
+          if (!realmPackageRegistry.containsKey(nsURI))
+          {
+            unknownPackageUnits.add((InternalCDOPackageUnit)CDOModelUtil.copyPackageUnit(packageUnit));
+          }
+        }
+    
+        if (!unknownPackageUnits.isEmpty())
+        {
+          InternalCDOPackageUnit[] unitArray = unknownPackageUnits.toArray(new InternalCDOPackageUnit[unknownPackageUnits.size()]);
+    
+          try
+          {
+            RunnableWithException.forkAndWait(() -> {
+              synchronized (realmPackageRegistry)
+              {
+                realmPackageRegistry.putPackageUnits(unitArray, CDOPackageUnit.State.LOADED);
+              }
+    
+              commitRealmPackageUnits(unitArray);
+            });
+          }
+          catch (Exception ex)
+          {
+            throw WrappedException.wrap(ex);
+          }
+        }
+      }
+    }
+
+    private void commitRealmPackageUnits(InternalCDOPackageUnit[] packageUnits)
+    {
+      IStoreAccessor writer = getRepository().getStore().getWriter(null);
+      StoreThreadLocal.setAccessor(writer);
+
+      try
+      {
+        writer.writePackageUnits(packageUnits, new Monitor());
+        writer.commit(new Monitor());
+      }
+      finally
+      {
+        StoreThreadLocal.release();
+      }
+    }
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server.security/src/org/eclipse/emf/cdo/server/security/ISecurityManager.java b/plugins/org.eclipse.emf.cdo.server.security/src/org/eclipse/emf/cdo/server/security/ISecurityManager.java
index 0534955..9214661 100644
--- a/plugins/org.eclipse.emf.cdo.server.security/src/org/eclipse/emf/cdo/server/security/ISecurityManager.java
+++ b/plugins/org.eclipse.emf.cdo.server.security/src/org/eclipse/emf/cdo/server/security/ISecurityManager.java
@@ -32,6 +32,11 @@
 
   public IRepository getRepository();
 
+  /**
+   * @since 4.6
+   */
+  public IRepository[] getSecondaryRepositories();
+
   public Realm getRealm();
 
   /**
@@ -56,6 +61,7 @@
    *
    * @author Eike Stepper
    */
+  @FunctionalInterface
   public interface RealmOperation
   {
     public void execute(Realm realm);
diff --git a/plugins/org.eclipse.emf.cdo.server.security/src/org/eclipse/emf/cdo/server/spi/security/InternalSecurityManager.java b/plugins/org.eclipse.emf.cdo.server.security/src/org/eclipse/emf/cdo/server/spi/security/InternalSecurityManager.java
index 5083618..4bee9af 100644
--- a/plugins/org.eclipse.emf.cdo.server.security/src/org/eclipse/emf/cdo/server/spi/security/InternalSecurityManager.java
+++ b/plugins/org.eclipse.emf.cdo.server.security/src/org/eclipse/emf/cdo/server/spi/security/InternalSecurityManager.java
@@ -31,6 +31,22 @@
 
   public void setRepository(InternalRepository repository);
 
+  /**
+   * @since 4.6
+   */
+  @Override
+  public InternalRepository[] getSecondaryRepositories();
+
+  /**
+   * @since 4.6
+   */
+  public void addSecondaryRepository(InternalRepository repository);
+
+  /**
+   * @since 4.6
+   */
+  public void removeSecondaryRepository(InternalRepository repository);
+
   public String getRealmPath();
 
   public CommitHandler[] getCommitHandlers();
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/TransactionCommitContext.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/TransactionCommitContext.java
index 9098dd3..319fe9d 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/TransactionCommitContext.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/TransactionCommitContext.java
@@ -1807,7 +1807,7 @@
         {
           InternalCDOPackageUnit packageUnit = newPackageUnits[i];
           packageUnit.setState(CDOPackageUnit.State.LOADED);
-          packageUnit.setPackageRegistry(repositoryPackageRegistry);
+
           repositoryPackageRegistry.putPackageUnit(packageUnit);
           monitor.worked();
         }
@@ -1921,6 +1921,7 @@
     {
       LifecycleUtil.checkActive(this);
       packageUnit.setPackageRegistry(this);
+
       for (InternalCDOPackageInfo packageInfo : packageUnit.getPackageInfos())
       {
         EPackage ePackage = packageInfo.getEPackage();
diff --git a/plugins/org.eclipse.emf.cdo.ui/CDOClient1.launch b/plugins/org.eclipse.emf.cdo.ui/CDOClient1.launch
index ef17470..eacf457 100644
--- a/plugins/org.eclipse.emf.cdo.ui/CDOClient1.launch
+++ b/plugins/org.eclipse.emf.cdo.ui/CDOClient1.launch
@@ -2,7 +2,7 @@
 <launchConfiguration type="org.eclipse.pde.ui.RuntimeWorkbench">
     <setAttribute key="additional_plugins">
         <setEntry value="javax.servlet:3.1.0.v201410161800:default:false:default:default"/>
-        <setEntry value="org.eclipse.emf.cdo.edit:4.5.3.qualifier:default:true:default:default"/>
+        <setEntry value="org.eclipse.emf.cdo.edit:4.5.4.qualifier:default:true:default:default"/>
         <setEntry value="org.eclipse.emf.cdo.examples.company.edit:4.1.0.qualifier:default:true:default:default"/>
         <setEntry value="org.eclipse.emf.cdo.expressions.edit:4.4.0.qualifier:default:true:default:default"/>
         <setEntry value="org.eclipse.emf.cdo.security.edit:4.5.0.qualifier:default:true:default:default"/>
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/event/IListener.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/event/IListener.java
index 451c6a1..473e25d 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/event/IListener.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/event/IListener.java
@@ -17,6 +17,7 @@
  *
  * @author Eike Stepper
  */
+@FunctionalInterface
 public interface IListener extends EventListener
 {
   public void notifyEvent(IEvent event);