[571768] Make SecurityManager multitenant

https://bugs.eclipse.org/bugs/show_bug.cgi?id=571768
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/CDOCommonSession.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/CDOCommonSession.java
index 14405a1..2681b72 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/CDOCommonSession.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/CDOCommonSession.java
@@ -31,6 +31,11 @@
 {
   public int getSessionID();
 
+  /**
+   * @since 4.13
+   */
+  public long getOpeningTime();
+
   public CDOCommonView[] getViews();
 
   public CDOCommonView getView(int viewID);
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/commit/CDOCommitInfoImpl.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/commit/CDOCommitInfoImpl.java
index ae07d13..05233b3 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/commit/CDOCommitInfoImpl.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/commit/CDOCommitInfoImpl.java
@@ -145,6 +145,7 @@
   @Override
   public boolean isInitialCommit()
   {
+    int xxx;
     return CDOCommonUtil.SYSTEM_USER_ID.equals(userID);
   }
 
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.explorer.ui/plugin.xml b/plugins/org.eclipse.emf.cdo.explorer.ui/plugin.xml
index c51de2b..da788cf 100644
--- a/plugins/org.eclipse.emf.cdo.explorer.ui/plugin.xml
+++ b/plugins/org.eclipse.emf.cdo.explorer.ui/plugin.xml
@@ -715,10 +715,24 @@
                </with>
             </visibleWhen>
          </command>
+         <command
+               commandId="org.eclipse.emf.cdo.explorer.ui.CheckoutDuplicate"
+               icon="icons/duplicate.gif"
+               label="Duplicate Checkout"
+               style="push"
+               tooltip="Duplicate the checkout">
+            <visibleWhen checkEnabled="false">
+               <with variable="activeMenuSelection">
+                  <iterate ifEmpty="false">
+                    <instanceof value="org.eclipse.emf.cdo.explorer.checkouts.CDOCheckout"/>
+                  </iterate>
+               </with>
+            </visibleWhen>
+         </command>
       </menuContribution>
       <menuContribution
             allPopups="false"
-            locationURI="popup:org.eclipse.ui.popup.any?after=group.build">
+            locationURI="popup:org.eclipse.ui.popup.any?after=group.close">
          <command
                commandId="org.eclipse.emf.cdo.explorer.ui.RepositoryDisconnect"
                icon="icons/disconnect.gif"
@@ -736,6 +750,10 @@
                </with>
             </visibleWhen>
          </command>
+      </menuContribution>
+      <menuContribution
+            allPopups="false"
+            locationURI="popup:org.eclipse.ui.popup.any?after=group.build">
          <command
                commandId="org.eclipse.emf.cdo.explorer.ui.CheckoutClose"
                icon="icons/checkout_closed.gif"
@@ -753,20 +771,6 @@
                </with>
             </visibleWhen>
          </command>
-         <command
-               commandId="org.eclipse.emf.cdo.explorer.ui.CheckoutDuplicate"
-               icon="icons/duplicate.gif"
-               label="Duplicate Checkout"
-               style="push"
-               tooltip="Duplicate the checkout">
-            <visibleWhen checkEnabled="false">
-               <with variable="activeMenuSelection">
-                  <iterate ifEmpty="false">
-                    <instanceof value="org.eclipse.emf.cdo.explorer.checkouts.CDOCheckout"/>
-                  </iterate>
-               </with>
-            </visibleWhen>
-         </command>
       </menuContribution>
       <menuContribution
             allPopups="false"
diff --git a/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/checkouts/CDOCheckoutLabelProvider.java b/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/checkouts/CDOCheckoutLabelProvider.java
index 3dc2643..bfeaa37 100644
--- a/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/checkouts/CDOCheckoutLabelProvider.java
+++ b/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/checkouts/CDOCheckoutLabelProvider.java
@@ -229,6 +229,11 @@
       return ContainerItemProvider.PENDING_IMAGE;
     }
 
+    if (object instanceof ViewerUtil.Error)
+    {
+      return ContainerItemProvider.ERROR_IMAGE;
+    }
+
     try
     {
       ILabelProvider provider = getStateManager().getLabelProvider(object);
diff --git a/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/checkouts/CDOCheckoutState.java b/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/checkouts/CDOCheckoutState.java
index 6758572..09d884b 100644
--- a/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/checkouts/CDOCheckoutState.java
+++ b/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/checkouts/CDOCheckoutState.java
@@ -11,6 +11,7 @@
 package org.eclipse.emf.cdo.explorer.ui.checkouts;
 
 import org.eclipse.emf.cdo.CDOElement;
+import org.eclipse.emf.cdo.CDOObject;
 import org.eclipse.emf.cdo.eresource.CDOResourceLeaf;
 import org.eclipse.emf.cdo.eresource.CDOResourceNode;
 import org.eclipse.emf.cdo.explorer.CDOExplorerUtil;
@@ -21,6 +22,7 @@
 import org.eclipse.emf.cdo.internal.ui.editor.CDOEditor;
 import org.eclipse.emf.cdo.ui.CDOEditorUtil;
 import org.eclipse.emf.cdo.ui.CDOLabelDecorator;
+import org.eclipse.emf.cdo.ui.CDOLabelProvider;
 import org.eclipse.emf.cdo.ui.CDOTreeExpansionAgent;
 import org.eclipse.emf.cdo.view.CDOView;
 
@@ -362,6 +364,15 @@
           return ContainerItemProvider.PENDING_COLOR;
         }
 
+        if (object instanceof CDOObject)
+        {
+          Color color = CDOLabelProvider.getColor((CDOObject)object);
+          if (color != null)
+          {
+            return color;
+          }
+        }
+
         return super.getForeground(object);
       }
     }
diff --git a/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/checkouts/actions/ShowInActionProvider.java b/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/checkouts/actions/ShowInActionProvider.java
index 747102c..af00979 100644
--- a/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/checkouts/actions/ShowInActionProvider.java
+++ b/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/checkouts/actions/ShowInActionProvider.java
@@ -17,10 +17,12 @@
 import org.eclipse.emf.cdo.explorer.CDOExplorerUtil;
 import org.eclipse.emf.cdo.explorer.checkouts.CDOCheckout;
 import org.eclipse.emf.cdo.explorer.repositories.CDORepository;
+import org.eclipse.emf.cdo.explorer.repositories.CDORepositoryManager.RepositoryConnectionEvent;
 import org.eclipse.emf.cdo.explorer.ui.bundle.OM;
 import org.eclipse.emf.cdo.explorer.ui.checkouts.CDOCheckoutContentProvider;
 import org.eclipse.emf.cdo.internal.explorer.AbstractElement;
 import org.eclipse.emf.cdo.internal.explorer.checkouts.OfflineCDOCheckout;
+import org.eclipse.emf.cdo.internal.explorer.repositories.CDORepositoryImpl;
 import org.eclipse.emf.cdo.internal.explorer.repositories.LocalCDORepository;
 import org.eclipse.emf.cdo.internal.ui.views.CDOSessionsView;
 import org.eclipse.emf.cdo.internal.ui.views.CDOTimeMachineView;
@@ -38,9 +40,17 @@
 
 import org.eclipse.emf.internal.cdo.session.CDOSessionFactory;
 
+import org.eclipse.net4j.util.container.ContainerEventAdapter;
+import org.eclipse.net4j.util.container.FactoryNotFoundException;
+import org.eclipse.net4j.util.container.IContainer;
 import org.eclipse.net4j.util.container.IManagedContainer;
 import org.eclipse.net4j.util.container.IPluginContainer;
+import org.eclipse.net4j.util.event.IEvent;
+import org.eclipse.net4j.util.event.IListener;
+import org.eclipse.net4j.util.factory.ProductCreationException;
 import org.eclipse.net4j.util.io.IOUtil;
+import org.eclipse.net4j.util.lifecycle.LifecycleEvent;
+import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
 import org.eclipse.net4j.util.ui.MenuFiller;
 
 import org.eclipse.emf.ecore.EObject;
@@ -259,18 +269,18 @@
     return false;
   }
 
-  public static void showDashboard(final StructuredViewer viewer, ISelectionService selectionService)
+  public static void showDashboard(StructuredViewer viewer, ISelectionService selectionService)
   {
-    final CDOCheckoutDashboard[] dashboard = { (CDOCheckoutDashboard)viewer.getData(DASHBOARD_KEY) };
+    CDOCheckoutDashboard[] dashboard = { (CDOCheckoutDashboard)viewer.getData(DASHBOARD_KEY) };
     if (dashboard[0] == null)
     {
-      final Control control = viewer.getControl();
-      final Object controlLayoutData = control.getLayoutData();
+      Control control = viewer.getControl();
+      Object controlLayoutData = control.getLayoutData();
 
-      final Composite parent = control.getParent();
-      final Layout parentLayout = parent.getLayout();
+      Composite parent = control.getParent();
+      Layout parentLayout = parent.getLayout();
 
-      final int[] minimumHeight = { 0 };
+      int[] minimumHeight = { 0 };
 
       GridLayout layout = new GridLayout(1, false);
       layout.marginWidth = 0;
@@ -280,7 +290,7 @@
       parent.setLayout(layout);
       control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
 
-      final Sash sash = new Sash(parent, SWT.HORIZONTAL | SWT.SMOOTH);
+      Sash sash = new Sash(parent, SWT.HORIZONTAL | SWT.SMOOTH);
       sash.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
       sash.addListener(SWT.Selection, new Listener()
       {
@@ -547,10 +557,10 @@
     @Override
     protected void run(IViewPart viewPart) throws Exception
     {
-      final Object select = show();
+      Object select = show();
       if (select != null)
       {
-        final TreeViewer viewer = ((CDOSessionsView)viewPart).getViewer();
+        TreeViewer viewer = ((CDOSessionsView)viewPart).getViewer();
         viewer.getControl().getDisplay().asyncExec(new Runnable()
         {
           @Override
@@ -584,7 +594,7 @@
             CDOBranch branch = checkoutView.getBranch();
             for (CDOTransaction transaction : session.getTransactions())
             {
-              if (branch.equals(transaction.getBranch()))
+              if (branch.getID() == transaction.getBranch().getID())
               {
                 return transaction;
               }
@@ -617,13 +627,64 @@
     {
       repository.connect();
 
-      String description = repository.getURI();
-      int lastSlash = description.lastIndexOf('/');
-      description = description.substring(0, lastSlash) + "?repositoryName=" + repository.getName() + "&automaticPackageRegistry=true&repositoryID="
-          + repository.getID();
+      String factoryType = "cdo-explorer";
+      String description = repository.getID();
 
-      return (CDOSession)IPluginContainer.INSTANCE.getElement(CDOSessionFactory.PRODUCT_GROUP, "cdo", repository.getConnectorType() + "://"
-          + repository.getConnectorDescription() + "?repositoryName=" + repository.getName() + "&repositoryID=" + repository.getID());
+      try
+      {
+        return (CDOSession)IPluginContainer.INSTANCE.getElement(CDOSessionFactory.PRODUCT_GROUP, factoryType, description);
+      }
+      catch (FactoryNotFoundException | ProductCreationException ex)
+      {
+        CDOSession session = ((CDORepositoryImpl)repository).openSession();
+
+        IListener listener = new ContainerEventAdapter<Object>()
+        {
+          @Override
+          protected void onRemoved(IContainer<Object> container, Object element)
+          {
+            if (element == repository)
+            {
+              dispose(session);
+            }
+          }
+
+          @Override
+          protected void notifyOtherEvent(IEvent event)
+          {
+            if (event instanceof RepositoryConnectionEvent)
+            {
+              RepositoryConnectionEvent e = (RepositoryConnectionEvent)event;
+              if (e.getRepository() == repository)
+              {
+                dispose(session);
+              }
+            }
+            else if (event instanceof LifecycleEvent)
+            {
+              LifecycleEvent e = (LifecycleEvent)event;
+              if (e.getSource() == session && e.getKind() == LifecycleEvent.Kind.DEACTIVATED)
+              {
+                dispose(session);
+              }
+            }
+          }
+
+          private void dispose(CDOSession session)
+          {
+            session.removeListener(this);
+            CDOExplorerUtil.getRepositoryManager().removeListener(this);
+            LifecycleUtil.deactivate(session);
+          }
+        };
+
+        session.addListener(listener);
+        CDOExplorerUtil.getRepositoryManager().addListener(listener);
+
+        IPluginContainer.INSTANCE.putElement(CDOSessionFactory.PRODUCT_GROUP, factoryType, description, session);
+
+        return session;
+      }
     }
   }
 
diff --git a/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/properties/AbstractPropertyPage.java b/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/properties/AbstractPropertyPage.java
index 2d00676..7344ebb 100644
--- a/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/properties/AbstractPropertyPage.java
+++ b/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/properties/AbstractPropertyPage.java
@@ -28,11 +28,11 @@
 import org.eclipse.ui.dialogs.PropertyPage;
 
 import java.io.File;
-import java.net.URI;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 
 /**
  * @author Eike Stepper
@@ -139,7 +139,7 @@
     Label labelControl = new Label(parent, SWT.NONE);
     labelControl.setText(label + ":");
 
-    Control control = createControl(parent, name, label, description, value);
+    Control control = createControl(parent, name, description, value);
     if (control == null)
     {
       control = new Label(parent, SWT.NONE);
@@ -147,14 +147,21 @@
 
     if (!StringUtil.isEmpty(description))
     {
-      labelControl.setToolTipText(description);
-      control.setToolTipText(description);
+      if (StringUtil.isEmpty(labelControl.getToolTipText()))
+      {
+        labelControl.setToolTipText(description);
+      }
+
+      if (StringUtil.isEmpty(control.getToolTipText()))
+      {
+        control.setToolTipText(description);
+      }
     }
 
     return control;
   }
 
-  protected Control createControl(Composite parent, String name, String label, String description, String value)
+  protected Control createControl(Composite parent, String name, String description, String value)
   {
     Label control = new Label(parent, SWT.NONE);
     control.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
@@ -163,25 +170,34 @@
     return control;
   }
 
-  protected Link createLink(Composite parent, String name, String label, String description, String value)
+  protected Link createFileLink(Composite parent, String name, String description, String value)
   {
     File file = new File(value);
-    URI uri = file.toURI();
+    return createLink(parent, file.toString(), file.toURI().toString(), uri -> IOUtil.openSystemBrowser(uri));
+  }
 
+  protected Link createLink(Composite parent, String label, String uri, Consumer<String> consumer)
+  {
     Link link = new Link(parent, SWT.NONE);
     link.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-    link.setText("<a href=\"" + uri + "\">" + file + "</a>");
+    setLinkText(link, label, uri);
+
     link.addSelectionListener(new SelectionAdapter()
     {
       @Override
       public void widgetSelected(SelectionEvent e)
       {
-        IOUtil.openSystemBrowser(uri.toString());
+        consumer.accept(uri);
       }
     });
 
     return link;
   }
 
+  protected void setLinkText(Link link, String label, String uri)
+  {
+    link.setText("<a href=\"" + uri + "\">" + label + "</a>");
+  }
+
   protected abstract T convertElement(IAdaptable element);
 }
diff --git a/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/properties/CheckoutPropertyPage.java b/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/properties/CheckoutPropertyPage.java
index 430307f..20a8dab 100644
--- a/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/properties/CheckoutPropertyPage.java
+++ b/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/properties/CheckoutPropertyPage.java
@@ -4,7 +4,7 @@
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
- * 
+ *
  * Contributors:
  *    Eike Stepper - initial API and implementation
  */
@@ -36,13 +36,13 @@
   }
 
   @Override
-  protected Control createControl(Composite parent, String name, String label, String description, String value)
+  protected Control createControl(Composite parent, String name, String description, String value)
   {
     if ("folder".equals(name))
     {
-      return createLink(parent, name, label, description, value);
+      return createFileLink(parent, name, description, value);
     }
 
-    return super.createControl(parent, name, label, description, value);
+    return super.createControl(parent, name, description, value);
   }
-}
\ No newline at end of file
+}
diff --git a/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/properties/RepositoryPropertyPage.java b/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/properties/RepositoryPropertyPage.java
index d5b6781..c0ea1fa 100644
--- a/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/properties/RepositoryPropertyPage.java
+++ b/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/properties/RepositoryPropertyPage.java
@@ -4,7 +4,7 @@
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
- * 
+ *
  * Contributors:
  *    Eike Stepper - initial API and implementation
  */
@@ -12,19 +12,35 @@
 
 import org.eclipse.emf.cdo.explorer.checkouts.CDOCheckout;
 import org.eclipse.emf.cdo.explorer.repositories.CDORepository;
+import org.eclipse.emf.cdo.explorer.ui.bundle.OM;
+import org.eclipse.emf.cdo.internal.explorer.repositories.CDORepositoryImpl;
 import org.eclipse.emf.cdo.internal.explorer.repositories.CDORepositoryProperties;
 
 import org.eclipse.net4j.util.AdapterUtil;
+import org.eclipse.net4j.util.StringUtil;
+import org.eclipse.net4j.util.security.IPasswordCredentials;
+import org.eclipse.net4j.util.ui.security.CredentialsDialog;
 
 import org.eclipse.core.runtime.IAdaptable;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Link;
+import org.eclipse.swt.widgets.Shell;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 /**
  * @author Eike Stepper
  */
 public final class RepositoryPropertyPage extends AbstractPropertyPage<CDORepository>
 {
+  private static final String USER_ID_LINK_URI = "security:change_login_crendentials";
+
+  private Link userIDLink;
+
   public RepositoryPropertyPage()
   {
     super(CDORepositoryProperties.INSTANCE, CDORepositoryProperties.CATEGORY_REPOSITORY, "id", "type", "label", "folder");
@@ -42,13 +58,120 @@
   }
 
   @Override
-  protected Control createControl(Composite parent, String name, String label, String description, String value)
+  protected Control createControl(Composite parent, String name, String description, String value)
   {
     if ("folder".equals(name))
     {
-      return createLink(parent, name, label, description, value);
+      return createFileLink(parent, name, description, value);
     }
 
-    return super.createControl(parent, name, label, description, value);
+    if ("userID".equals(name))
+    {
+      userIDLink = createLink(parent, value, USER_ID_LINK_URI, uri -> changeLoginCrendentials());
+      userIDLink.setToolTipText("Change login credentials");
+      return userIDLink;
+    }
+
+    return super.createControl(parent, name, description, value);
   }
-}
\ No newline at end of file
+
+  private void changeLoginCrendentials()
+  {
+    CDORepositoryImpl repository = (CDORepositoryImpl)getInput();
+    String realm = repository.getURI();
+    IPasswordCredentials credentials = repository.getCredentials();
+    String currentUserID = credentials == null ? null : credentials.getUserID();
+
+    ChangeCredentialsDialog dialog = new ChangeCredentialsDialog(getShell(), realm, currentUserID);
+    if (dialog.open() == ChangeCredentialsDialog.OK)
+    {
+      List<CDOCheckout> openCheckouts = null;
+      boolean connected = repository.isConnected();
+
+      if (connected)
+      {
+        try
+        {
+          openCheckouts = new ArrayList<>();
+          for (CDOCheckout checkout : repository.getCheckouts())
+          {
+            if (checkout.isOpen())
+            {
+              openCheckouts.add(checkout);
+            }
+          }
+
+          repository.disconnect(true);
+        }
+        catch (Exception ex)
+        {
+          OM.LOG.error(ex);
+        }
+      }
+
+      IPasswordCredentials newCredentials = dialog.getCredentials();
+      setLinkText(userIDLink, newCredentials.getUserID(), USER_ID_LINK_URI);
+
+      repository.setCredentials(newCredentials);
+
+      if (connected)
+      {
+        try
+        {
+          repository.connect();
+
+          if (repository.isConnected() && openCheckouts != null)
+          {
+            for (CDOCheckout checkout : openCheckouts)
+            {
+              checkout.open();
+            }
+          }
+        }
+        catch (Exception ex)
+        {
+          OM.LOG.error(ex);
+        }
+      }
+    }
+  }
+
+  /**
+   * @author Eike Stepper
+   */
+  private static final class ChangeCredentialsDialog extends CredentialsDialog
+  {
+    private final String currentUserID;
+
+    public ChangeCredentialsDialog(Shell shell, String realm, String currentUserID)
+    {
+      super(shell, realm, "Change Login Credentials", "Enter your user ID and password for the connection.");
+      this.currentUserID = currentUserID;
+    }
+
+    @Override
+    protected String getInitialUserID()
+    {
+      if (!StringUtil.isEmpty(currentUserID))
+      {
+        return currentUserID;
+      }
+
+      return super.getInitialUserID();
+    }
+
+    @Override
+    protected List<String> loadUsers()
+    {
+      Set<String> userIDs = new HashSet<>(super.loadUsers());
+      if (!StringUtil.isEmpty(currentUserID))
+      {
+        userIDs.add(currentUserID);
+      }
+
+      List<String> result = new ArrayList<>(userIDs);
+      result.sort(null);
+      return result;
+    }
+  }
+}
diff --git a/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/repositories/CDORepositoriesView.java b/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/repositories/CDORepositoriesView.java
index a2d3d13..40d41fe 100644
--- a/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/repositories/CDORepositoriesView.java
+++ b/plugins/org.eclipse.emf.cdo.explorer.ui/src/org/eclipse/emf/cdo/explorer/ui/repositories/CDORepositoriesView.java
@@ -31,7 +31,9 @@
 import org.eclipse.emf.cdo.util.ConcurrentAccessException;
 
 import org.eclipse.net4j.util.container.IContainer;
+import org.eclipse.net4j.util.security.PasswordCredentials;
 import org.eclipse.net4j.util.ui.UIUtil;
+import org.eclipse.net4j.util.ui.actions.LongRunningAction;
 import org.eclipse.net4j.util.ui.views.ContainerItemProvider;
 import org.eclipse.net4j.util.ui.views.ContainerView;
 
@@ -41,12 +43,14 @@
 import org.eclipse.emf.ecore.EPackage;
 import org.eclipse.emf.ecore.util.EcoreUtil;
 
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.jface.action.Action;
 import org.eclipse.jface.action.GroupMarker;
 import org.eclipse.jface.action.IMenuManager;
 import org.eclipse.jface.action.IToolBarManager;
 import org.eclipse.jface.action.MenuManager;
 import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.jface.viewers.ITreeSelection;
 import org.eclipse.jface.viewers.ITreeViewerListener;
 import org.eclipse.jface.viewers.TreeExpansionEvent;
@@ -77,6 +81,8 @@
 
   public static final String SHOW_IN_MENU_ID = ID + ".ShowInMenu";
 
+  private static final String GROUP_SECURITY = "group.security";
+
   private final ActivityDetector activityDetector = new ActivityDetector();
 
   private CDORepositoryItemProvider itemProvider;
@@ -233,6 +239,8 @@
     manager.add(new Separator("group.port"));
     manager.add(new Separator("group.build"));
     manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
+    manager.add(new Separator(GROUP_SECURITY));
+    manager.add(new Separator("group.close"));
     manager.add(new Separator("group.properties"));
 
     IWorkbenchPage page = getSite().getPage();
@@ -247,6 +255,16 @@
       manager.appendToGroup(ICommonMenuConstants.GROUP_OPEN, showInMenu);
     }
 
+    if (selectedElement instanceof CDORepository)
+    {
+      CDORepository repository = (CDORepository)selectedElement;
+      CDOSession session = repository.getSession();
+      if (session != null)
+      {
+        manager.appendToGroup(GROUP_SECURITY, new ChangeServerPasswordAction(page, repository));
+      }
+    }
+
     if (propertiesAction.isApplicableForSelection())
     {
       manager.appendToGroup("group.properties", propertiesAction);
@@ -397,4 +415,39 @@
       newRepository(shell);
     }
   }
+
+  /**
+   * @author Eike Stepper
+   */
+  private static final class ChangeServerPasswordAction extends LongRunningAction
+  {
+    private static final String TITLE = "Change Server Password";
+
+    private final CDORepository repository;
+
+    public ChangeServerPasswordAction(IWorkbenchPage page, CDORepository repository)
+    {
+      super(page, TITLE + INTERACTIVE);
+      this.repository = repository;
+    }
+
+    @Override
+    protected void doRun(IProgressMonitor progressMonitor) throws Exception
+    {
+      CDOSession session = repository.getSession();
+      if (session != null)
+      {
+        // Opens the org.eclipse.net4j.util.ui.security.CredentialsUpdateDialog.
+        char[] newPassword = session.changeServerPassword();
+
+        UIUtil.asyncExec(getDisplay(), () -> {
+          if (MessageDialog.openQuestion(getShell(), TITLE, "Adjust login credentials, accordingly?"))
+          {
+            String userID = repository.getCredentials().getUserID();
+            repository.setCredentials(new PasswordCredentials(userID, newPassword));
+          }
+        });
+      }
+    }
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/explorer/repositories/CDORepository.java b/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/explorer/repositories/CDORepository.java
index 96005e9..9ba9e41 100644
--- a/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/explorer/repositories/CDORepository.java
+++ b/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/explorer/repositories/CDORepository.java
@@ -24,6 +24,7 @@
 import org.eclipse.net4j.util.container.IManagedContainer;
 import org.eclipse.net4j.util.security.IPasswordCredentials;
 import org.eclipse.net4j.util.security.IPasswordCredentialsProvider2;
+import org.eclipse.net4j.util.security.IPasswordCredentialsUpdateProvider;
 
 /**
  * A CDO server independent representation of a repository.
@@ -33,8 +34,13 @@
  * @noextend This interface is not intended to be extended by clients.
  * @noimplement This interface is not intended to be implemented by clients.
  */
-public interface CDORepository
-    extends CDOExplorerElement, IContainer<CDOBranch>, IPasswordCredentialsProvider2, CDOSessionProvider, CDOViewOpener, CDOTransactionOpener
+public interface CDORepository extends CDOExplorerElement, //
+    IContainer<CDOBranch>, //
+    IPasswordCredentialsProvider2, //
+    IPasswordCredentialsUpdateProvider, //
+    CDOSessionProvider, //
+    CDOViewOpener, //
+    CDOTransactionOpener
 {
   public static final String TYPE_REMOTE = "remote";
 
diff --git a/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/internal/explorer/repositories/CDORepositoryImpl.java b/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/internal/explorer/repositories/CDORepositoryImpl.java
index 3f466aa..ffefa0c 100644
--- a/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/internal/explorer/repositories/CDORepositoryImpl.java
+++ b/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/internal/explorer/repositories/CDORepositoryImpl.java
@@ -38,6 +38,8 @@
 
 import org.eclipse.net4j.Net4jUtil;
 import org.eclipse.net4j.connector.IConnector;
+import org.eclipse.net4j.util.ConsumerWithException;
+import org.eclipse.net4j.util.ReflectUtil;
 import org.eclipse.net4j.util.StringUtil;
 import org.eclipse.net4j.util.UUIDGenerator;
 import org.eclipse.net4j.util.container.ContainerEvent;
@@ -49,8 +51,14 @@
 import org.eclipse.net4j.util.lifecycle.ILifecycle;
 import org.eclipse.net4j.util.lifecycle.LifecycleEventAdapter;
 import org.eclipse.net4j.util.om.OMPlatform;
+import org.eclipse.net4j.util.security.CredentialsProviderFactory;
+import org.eclipse.net4j.util.security.CredentialsUpdateOperation;
+import org.eclipse.net4j.util.security.ICredentialsProvider;
 import org.eclipse.net4j.util.security.IPasswordCredentials;
+import org.eclipse.net4j.util.security.IPasswordCredentialsUpdate;
+import org.eclipse.net4j.util.security.IPasswordCredentialsUpdateProvider;
 import org.eclipse.net4j.util.security.PasswordCredentials;
+import org.eclipse.net4j.util.security.SecurityUtil;
 
 import org.eclipse.emf.ecore.EClass;
 import org.eclipse.emf.ecore.EObject;
@@ -197,54 +205,80 @@
   @Override
   public IPasswordCredentials getCredentials(String realm)
   {
-    try
-    {
-      ISecurePreferences securePreferences = getSecurePreferences();
-      if (securePreferences != null)
+    IPasswordCredentials[] result = { null };
+
+    withSecureNode(false, node -> {
+      String userID = node.get("username", null);
+      if (!StringUtil.isEmpty(userID))
       {
-        String path = getSecurePath(securePreferences);
-        if (securePreferences.nodeExists(path))
-        {
-          ISecurePreferences node = securePreferences.node(path);
-          String userID = node.get("username", null);
-
-          if (!StringUtil.isEmpty(userID))
-          {
-            String password = node.get("password", null);
-            return new PasswordCredentials(userID, password);
-          }
-        }
+        String password = node.get("password", null);
+        result[0] = new PasswordCredentials(userID, password);
       }
-    }
-    catch (Exception ex)
-    {
-      OM.LOG.error(ex);
-    }
+    });
 
-    return null;
+    return result[0];
   }
 
   @Override
   public void setCredentials(IPasswordCredentials credentials)
   {
-    try
+    if (credentials == null)
     {
-      ISecurePreferences securePreferences = getSecurePreferences();
-      if (securePreferences != null)
-      {
-        String path = getSecurePath(securePreferences);
-        ISecurePreferences node = securePreferences.node(path);
-
-        node.put("uri", getURI(), false);
-        node.put("username", credentials.getUserID(), false);
-        node.put("password", new String(credentials.getPassword()), true);
+      // Delete node.
+      withSecureNode(false, node -> {
+        node.removeNode();
         node.flush();
+      });
+
+      return;
+    }
+
+    String password = SecurityUtil.toString(credentials.getPassword());
+
+    withSecureNode(true, node -> {
+      node.put("uri", getURI(), false);
+      node.put("username", credentials.getUserID(), false);
+
+      if (password == null)
+      {
+        node.remove("password");
+      }
+      else
+      {
+        node.put("password", password, true);
+      }
+
+      node.flush();
+    });
+  }
+
+  @Override
+  public IPasswordCredentialsUpdate getCredentialsUpdate(String userID, CredentialsUpdateOperation operation)
+  {
+    return getCredentialsUpdate(null, userID, operation);
+  }
+
+  @Override
+  public IPasswordCredentialsUpdate getCredentialsUpdate(String realm, String userID, CredentialsUpdateOperation operation)
+  {
+    IManagedContainer container = getContainer();
+
+    ICredentialsProvider provider = container.getElementOrNull(CredentialsProviderFactory.PRODUCT_GROUP, "cdo-explorer", null);
+    if (provider == null)
+    {
+      provider = container.getElementOrNull(CredentialsProviderFactory.PRODUCT_GROUP, "interactive", null);
+      if (provider == null)
+      {
+        provider = container.getElementOrNull(CredentialsProviderFactory.PRODUCT_GROUP, "default", null);
       }
     }
-    catch (Exception ex)
+
+    if (provider instanceof IPasswordCredentialsUpdateProvider)
     {
-      OM.LOG.error(ex);
+      return ((IPasswordCredentialsUpdateProvider)provider).getCredentialsUpdate(realm, userID, operation);
     }
+
+    return null;
   }
 
   @Override
@@ -329,8 +363,13 @@
   @Override
   public final void disconnect()
   {
+    disconnect(false);
+  }
+
+  public final void disconnect(boolean force)
+  {
     explicitlyConnected = false;
-    doDisconnect(false);
+    doDisconnect(force);
   }
 
   protected void doDisconnect(boolean force)
@@ -452,6 +491,9 @@
   {
     disconnect();
 
+    // Delete secure preference node.
+    setCredentials(null);
+
     CDORepositoryManagerImpl manager = getManager();
     if (manager != null)
     {
@@ -739,7 +781,7 @@
     return config;
   }
 
-  protected CDOSession openSession()
+  public CDOSession openSession()
   {
     CDOSessionConfiguration sessionConfiguration = createSessionConfiguration();
     sessionConfiguration.setPassiveUpdateEnabled(true);
@@ -766,6 +808,27 @@
     session.close();
   }
 
+  private void withSecureNode(boolean createOnDemand, ConsumerWithException<ISecurePreferences, Exception> consumer)
+  {
+    try
+    {
+      ISecurePreferences securePreferences = getSecurePreferences();
+      if (securePreferences != null)
+      {
+        String path = getSecurePath(securePreferences);
+        if (createOnDemand || securePreferences.nodeExists(path))
+        {
+          ISecurePreferences node = securePreferences.node(path);
+          consumer.accept(node);
+        }
+      }
+    }
+    catch (Exception ex)
+    {
+      OM.LOG.error(ex);
+    }
+  }
+
   private String getSecurePath(ISecurePreferences securePreferences)
   {
     String stateLocation = OM.getStateLocation().replace('/', '\\');
@@ -779,6 +842,38 @@
     Map<Object, Object> options = new HashMap<>();
     options.put(IProviderHints.PROMPT_USER, Boolean.FALSE);
 
-    return SecurePreferencesFactory.open(null, options);
+    ISecurePreferences result = SecurePreferencesFactory.open(null, options);
+    if (result != null)
+    {
+      // Try to refresh the entire secure storage, if needed.
+      try
+      {
+        // Fetch the root node.
+        Object root = ReflectUtil.getValue("node", result); //$NON-NLS-1$
+
+        // Just to be sure...
+        root = ReflectUtil.invokeMethod("getRoot", root); //$NON-NLS-1$
+
+        // Check if it has been modified, i.e., whether it is dirty from unsaved changes.
+        boolean modified = ReflectUtil.invokeMethod("isModified", root); //$NON-NLS-1$
+        if (!modified)
+        {
+          // If it's not dirty, check if the expected time stamp is different from the timestamp on disk.
+          long lastModified = ReflectUtil.invokeMethod("getLastModified", root); //$NON-NLS-1$
+          long timestamp = ReflectUtil.getValue("timestamp", root); //$NON-NLS-1$
+          if (lastModified != timestamp)
+          {
+            // If so, reload the secure storage from disk.
+            ReflectUtil.invokeMethod("load", root); //$NON-NLS-1$
+          }
+        }
+      }
+      catch (Throwable ex)
+      {
+        OM.LOG.error(ex);
+      }
+    }
+
+    return result;
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/internal/explorer/repositories/CDORepositoryManagerImpl.java b/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/internal/explorer/repositories/CDORepositoryManagerImpl.java
index 2214167..2e97c13 100644
--- a/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/internal/explorer/repositories/CDORepositoryManagerImpl.java
+++ b/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/internal/explorer/repositories/CDORepositoryManagerImpl.java
@@ -79,7 +79,7 @@
   public CDORepository addRepository(Properties properties, IPasswordCredentials credentials)
   {
     CDORepository repository = newElement(properties);
-    if (repository != null && credentials != null)
+    if (repository != null)
     {
       repository.setCredentials(credentials);
     }
diff --git a/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/internal/explorer/repositories/CloneCDORepository.java b/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/internal/explorer/repositories/CloneCDORepository.java
index b63143a..111e826 100644
--- a/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/internal/explorer/repositories/CloneCDORepository.java
+++ b/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/internal/explorer/repositories/CloneCDORepository.java
@@ -145,7 +145,7 @@
   }
 
   @Override
-  protected CDOSession openSession()
+  public CDOSession openSession()
   {
     final String repositoryName = getName();
     File folder = new File(getFolder(), "db");
diff --git a/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/internal/explorer/repositories/LocalCDORepository.java b/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/internal/explorer/repositories/LocalCDORepository.java
index bdb2631..8410d0c 100644
--- a/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/internal/explorer/repositories/LocalCDORepository.java
+++ b/plugins/org.eclipse.emf.cdo.explorer/src/org/eclipse/emf/cdo/internal/explorer/repositories/LocalCDORepository.java
@@ -124,7 +124,7 @@
   }
 
   @Override
-  protected CDOSession openSession()
+  public CDOSession openSession()
   {
     String repositoryName = getName();
     File folder = new File(getFolder(), "db");
diff --git a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/CDONet4jSessionImpl.java b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/CDONet4jSessionImpl.java
index 540eb7e..726ad33 100644
--- a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/CDONet4jSessionImpl.java
+++ b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/CDONet4jSessionImpl.java
@@ -47,6 +47,8 @@
 import org.eclipse.emf.spi.cdo.CDOSessionProtocol;
 import org.eclipse.emf.spi.cdo.CDOSessionProtocol.OpenSessionResult;
 
+import java.util.concurrent.atomic.AtomicReference;
+
 /**
  * @author Eike Stepper
  */
@@ -113,14 +115,28 @@
   }
 
   @Override
+  @Deprecated
   public void changeCredentials()
   {
+    changeServerPassword();
     // Send a request to the server to initiate (from the server) the password change protocol
     CDOSessionProtocol sessionProtocol = getSessionProtocol();
     sessionProtocol.requestChangeCredentials();
   }
 
   @Override
+  public char[] changeServerPassword()
+  {
+    AtomicReference<char[]> result = new AtomicReference<>();
+
+    // Send a request to the server to initiate (from the server) the password change protocol
+    CDOSessionProtocol sessionProtocol = getSessionProtocol();
+    sessionProtocol.requestChangeServerPassword(result);
+
+    return result.getAndSet(null);
+  }
+
+  @Override
   public void resetCredentials(String userID)
   {
     // Send a request to the server to initiate (from the server) the password reset protocol
@@ -170,6 +186,7 @@
       setSessionID(result.getSessionID());
       setUserID(result.getUserID());
       setLastUpdateTime(result.getLastUpdateTime());
+      setOpeningTime(result.getOpeningTime());
       setRepositoryInfo(new RepositoryInfo(this, result));
       return result;
     }
diff --git a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/CDOClientProtocol.java b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/CDOClientProtocol.java
index 07ec238..3c7626a 100644
--- a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/CDOClientProtocol.java
+++ b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/CDOClientProtocol.java
@@ -65,7 +65,6 @@
 import org.eclipse.net4j.util.om.monitor.Monitor;
 import org.eclipse.net4j.util.om.monitor.OMMonitor;
 import org.eclipse.net4j.util.om.trace.PerfTracer;
-import org.eclipse.net4j.util.security.CredentialsUpdateOperation;
 
 import org.eclipse.emf.ecore.EClass;
 import org.eclipse.emf.ecore.EPackage;
@@ -83,6 +82,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 
 /**
@@ -605,15 +605,22 @@
   }
 
   @Override
+  @Deprecated
   public void requestChangeCredentials()
   {
-    send(new ChangeCredentialsRequest(this, CredentialsUpdateOperation.CHANGE_PASSWORD, null), new Monitor());
+    requestChangeServerPassword(null);
+  }
+
+  @Override
+  public void requestChangeServerPassword(AtomicReference<char[]> receiver)
+  {
+    send(new ChangeCredentialsRequest(this, receiver), new Monitor());
   }
 
   @Override
   public void requestResetCredentials(String userID)
   {
-    send(new ChangeCredentialsRequest(this, CredentialsUpdateOperation.RESET_PASSWORD, userID), new Monitor());
+    send(new ChangeCredentialsRequest(this, userID), new Monitor());
   }
 
   @Override
diff --git a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/ChangeCredentialsRequest.java b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/ChangeCredentialsRequest.java
index 2e7dd55..8efde92 100644
--- a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/ChangeCredentialsRequest.java
+++ b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/ChangeCredentialsRequest.java
@@ -14,12 +14,16 @@
 import org.eclipse.emf.cdo.common.protocol.CDODataOutput;
 import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants;
 import org.eclipse.emf.cdo.internal.net4j.bundle.OM;
+import org.eclipse.emf.cdo.session.CDOSession;
 
 import org.eclipse.net4j.util.om.monitor.OMMonitor;
 import org.eclipse.net4j.util.om.trace.ContextTracer;
 import org.eclipse.net4j.util.security.CredentialsUpdateOperation;
 
 import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Request from the client to the server to initiate (from the server) the change-credentials protocol.
@@ -28,23 +32,40 @@
  */
 public class ChangeCredentialsRequest extends CDOClientRequestWithMonitoring<Boolean>
 {
+  static final ConcurrentMap<CDOSession, AtomicReference<char[]>> NEW_PASSWORD_RECEIVERS = new ConcurrentHashMap<>();
+
   private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_PROTOCOL, ChangeCredentialsRequest.class);
 
   private final CredentialsUpdateOperation operation;
 
+  private final AtomicReference<char[]> newPasswordReceiver;
+
   private final String userID;
 
-  public ChangeCredentialsRequest(CDOClientProtocol protocol, CredentialsUpdateOperation operation, String userID)
+  public ChangeCredentialsRequest(CDOClientProtocol protocol, AtomicReference<char[]> newPasswordReceiver)
   {
     super(protocol, CDOProtocolConstants.SIGNAL_CHANGE_CREDENTIALS);
+    operation = CredentialsUpdateOperation.CHANGE_PASSWORD;
+    this.newPasswordReceiver = newPasswordReceiver;
+    userID = null;
+  }
 
-    this.operation = operation;
+  public ChangeCredentialsRequest(CDOClientProtocol protocol, String userID)
+  {
+    super(protocol, CDOProtocolConstants.SIGNAL_CHANGE_CREDENTIALS);
+    operation = CredentialsUpdateOperation.RESET_PASSWORD;
+    newPasswordReceiver = null;
     this.userID = userID;
   }
 
   @Override
   protected void requesting(CDODataOutput out, OMMonitor monitor) throws IOException
   {
+    if (newPasswordReceiver != null)
+    {
+      NEW_PASSWORD_RECEIVERS.put(getSession(), newPasswordReceiver);
+    }
+
     if (TRACER.isEnabled())
     {
       TRACER.format("Requesting %s of user credentials", operation); //$NON-NLS-1$
@@ -57,6 +78,13 @@
   @Override
   protected Boolean confirming(CDODataInput in, OMMonitor monitor) throws IOException
   {
-    return in.readBoolean();
+    try
+    {
+      return in.readBoolean();
+    }
+    finally
+    {
+      NEW_PASSWORD_RECEIVERS.remove(getSession());
+    }
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/CredentialsChallengeIndication.java b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/CredentialsChallengeIndication.java
index 527cffd..8526c84 100644
--- a/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/CredentialsChallengeIndication.java
+++ b/plugins/org.eclipse.emf.cdo.net4j/src/org/eclipse/emf/cdo/internal/net4j/protocol/CredentialsChallengeIndication.java
@@ -16,7 +16,6 @@
 
 import org.eclipse.net4j.signal.IndicationWithMonitoring;
 import org.eclipse.net4j.signal.SignalProtocol;
-import org.eclipse.net4j.util.StringUtil;
 import org.eclipse.net4j.util.io.ExtendedDataInputStream;
 import org.eclipse.net4j.util.io.ExtendedDataOutputStream;
 import org.eclipse.net4j.util.om.monitor.OMMonitor;
@@ -28,10 +27,12 @@
 import org.eclipse.net4j.util.security.IPasswordCredentialsProvider;
 import org.eclipse.net4j.util.security.IPasswordCredentialsUpdate;
 import org.eclipse.net4j.util.security.IPasswordCredentialsUpdateProvider;
+import org.eclipse.net4j.util.security.SecurityUtil;
 
 import org.eclipse.emf.spi.cdo.InternalCDOSession;
 
 import java.io.ByteArrayOutputStream;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Implementation of the CDO client handler for the server-initiated change-credentials protocol.
@@ -40,6 +41,8 @@
  */
 public class CredentialsChallengeIndication extends IndicationWithMonitoring
 {
+  private AtomicReference<char[]> newPasswordReceiver;
+
   private Challenge challenge;
 
   private CredentialsUpdateOperation operation;
@@ -49,6 +52,7 @@
   public CredentialsChallengeIndication(SignalProtocol<?> protocol)
   {
     super(protocol, CDOProtocolConstants.SIGNAL_CREDENTIALS_CHALLENGE);
+    newPasswordReceiver = ChangeCredentialsRequest.NEW_PASSWORD_RECEIVERS.remove(getSession());
   }
 
   @Override
@@ -99,12 +103,8 @@
       }
 
       String authUserID = credentials.getUserID();
-      String authPassword = new String(credentials.getPassword());
-      String newPassword = new String(credentials.getNewPassword());
-      if (StringUtil.isEmpty(newPassword))
-      {
-        throw new IllegalStateException("No new password provided"); //$NON-NLS-1$
-      }
+      String authPassword = SecurityUtil.toString(credentials.getPassword());
+      String newPassword = SecurityUtil.toString(credentials.getNewPassword());
 
       ByteArrayOutputStream baos = new ByteArrayOutputStream();
       ExtendedDataOutputStream stream = new ExtendedDataOutputStream(baos);
@@ -115,7 +115,15 @@
         stream.writeString(authUserID);
         stream.writeString(authPassword);
         stream.writeString(newPassword);
+
+        if (newPasswordReceiver != null)
+        {
+          newPasswordReceiver.set(credentials.getNewPassword());
+          newPasswordReceiver = null;
+        }
+
         break;
+
       case RESET_PASSWORD:
         stream.writeString(authUserID);
         stream.writeString(authPassword);
diff --git a/plugins/org.eclipse.emf.cdo.security.ui/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.cdo.security.ui/META-INF/MANIFEST.MF
index 9fd34d5..44a264c 100644
--- a/plugins/org.eclipse.emf.cdo.security.ui/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.emf.cdo.security.ui/META-INF/MANIFEST.MF
@@ -1,19 +1,19 @@
 Manifest-Version: 1.0
 Bundle-ManifestVersion: 2
 Bundle-SymbolicName: org.eclipse.emf.cdo.security.ui;singleton:=true
-Bundle-Version: 4.5.0.qualifier
+Bundle-Version: 4.5.1.qualifier
 Bundle-Localization: plugin
 Bundle-Name: %pluginName
 Bundle-Vendor: %providerName
 Bundle-ClassPath: .
 Bundle-RequiredExecutionEnvironment: JavaSE-11
-Export-Package: org.eclipse.emf.cdo.security.internal.ui.bundle;version="4.5.0";x-internal:=true,
- org.eclipse.emf.cdo.security.internal.ui.dialogs;version="4.5.0";x-internal:=true,
- org.eclipse.emf.cdo.security.internal.ui.editor;version="4.5.0";x-internal:=true,
- org.eclipse.emf.cdo.security.internal.ui.handlers;version="4.5.0";x-internal:=true,
- org.eclipse.emf.cdo.security.internal.ui.messages;version="4.5.0";x-internal:=true,
- org.eclipse.emf.cdo.security.internal.ui.util;version="4.5.0";x-internal:=true,
- org.eclipse.emf.cdo.security.ui;version="4.5.0"
+Export-Package: org.eclipse.emf.cdo.security.internal.ui.bundle;version="4.5.1";x-internal:=true,
+ org.eclipse.emf.cdo.security.internal.ui.dialogs;version="4.5.1";x-internal:=true,
+ org.eclipse.emf.cdo.security.internal.ui.editor;version="4.5.1";x-internal:=true,
+ org.eclipse.emf.cdo.security.internal.ui.handlers;version="4.5.1";x-internal:=true,
+ org.eclipse.emf.cdo.security.internal.ui.messages;version="4.5.1";x-internal:=true,
+ org.eclipse.emf.cdo.security.internal.ui.util;version="4.5.1";x-internal:=true,
+ org.eclipse.emf.cdo.security.ui;version="4.5.1"
 Require-Bundle: org.eclipse.emf.cdo.security;bundle-version="[4.3.0,5.0.0)",
  org.eclipse.emf.cdo.security.edit;bundle-version="[4.1.0,5.0.0)",
  org.eclipse.emf.cdo.ui;bundle-version="[4.3.0,5.0.0)",
diff --git a/plugins/org.eclipse.emf.cdo.security.ui/plugin.xml b/plugins/org.eclipse.emf.cdo.security.ui/plugin.xml
index aac0298..8a5a852 100644
--- a/plugins/org.eclipse.emf.cdo.security.ui/plugin.xml
+++ b/plugins/org.eclipse.emf.cdo.security.ui/plugin.xml
@@ -13,39 +13,60 @@
 
 <plugin>
 
-   <extension
-         point="org.eclipse.ui.editors">
+   <extension point="org.eclipse.ui.editors">
       <editor
             class="org.eclipse.emf.cdo.security.internal.ui.editor.CDOSecurityFormEditor"
             contributorClass="org.eclipse.emf.cdo.security.internal.ui.editor.CDOSecurityFormActionBarContributor"
             default="false"
             icon="icons/full/obj16/SecurityRealmResource.png"
             id="org.eclipse.emf.cdo.security.ui.CDOSecurityFormEditor"
-            name="%editor.name">
-      </editor>
+            name="%editor.name"/>
    </extension>
 
-   <extension
-         point="org.eclipse.ui.menus">
-      <menuContribution
-            locationURI="popup:org.eclipse.ui.popup.any?after=additions">
+   <extension point="org.eclipse.ui.commands">
+      <category
+            id="org.eclipse.emf.cdo.security.ui.management" 
+            name="%category.name"/>
+      <command
+            id="org.eclipse.emf.cdo.security.ui.resetPassword"
+            categoryId="org.eclipse.emf.cdo.security.ui.management"
+            name="%command.name"
+            description="%command.description"
+            defaultHandler="org.eclipse.emf.cdo.security.internal.ui.handlers.ResetPasswordHandler"/>
+      <command
+            id="org.eclipse.emf.cdo.security.ui.openEditor"
+            name="%action.label.1"
+            categoryId="org.eclipse.emf.cdo.security.ui.management"
+            defaultHandler="org.eclipse.emf.cdo.security.internal.ui.handlers.ManageSecurityHandler"/>
+   </extension>
+
+   <extension point="org.eclipse.ui.handlers">
+      <handler
+            commandId="org.eclipse.emf.cdo.security.ui.openEditor"
+            class="org.eclipse.emf.cdo.security.internal.ui.handlers.ManageSecurityHandler$Sessionless">
+         <activeWhen>
+            <with variable="activeMenuSelection">
+               <iterate ifEmpty="false">
+                  <adapt type="org.eclipse.emf.cdo.admin.CDOAdminClientRepository"/>
+               </iterate>
+            </with>
+         </activeWhen>
+      </handler>
+   </extension>
+   
+   <extension point="org.eclipse.ui.menus">
+      <menuContribution locationURI="popup:org.eclipse.ui.popup.any?after=group.security">
          <command
                id="org.eclipse.emf.cdo.security.ui.ResetPasswordCommand"
                commandId="org.eclipse.emf.cdo.security.ui.resetPassword"
                label="%action.label"
                tooltip="%action.tooltip"
                style="push">
-            <visibleWhen
-                  checkEnabled="false">
-               <with
-                     variable="activeMenuSelection">
-                  <count
-                        value="1">
-                  </count>
-                  <iterate >
-                     <adapt
-                           type="org.eclipse.emf.cdo.security.User">
-                     </adapt>
+            <visibleWhen checkEnabled="false">
+               <with variable="activeMenuSelection">
+                  <count value="1"/>
+                  <iterate>
+                     <adapt type="org.eclipse.emf.cdo.security.User"/>
                   </iterate>
                </with>
             </visibleWhen>
@@ -55,22 +76,15 @@
                icon="$nl$/icons/full/ctool16/ManageSecurity.png"
                label="%action.label.1"
                style="push">
-            <visibleWhen
-                  checkEnabled="false">
-               <with
-                     variable="activeMenuSelection">
-                  <count
-                        value="1">
-                  </count>
-                  <iterate >
+            <visibleWhen checkEnabled="false">
+               <with variable="activeMenuSelection">
+                  <count value="1"/>
+                  <iterate>
                      <or>
-                        <adapt
-                              type="org.eclipse.emf.cdo.session.CDOSession">
-                           <test property="org.eclipse.emf.cdo.session.userAuthenticated" />
+                        <adapt type="org.eclipse.emf.cdo.session.CDOSession">
+                           <test property="org.eclipse.emf.cdo.session.userAuthenticated"/>
                         </adapt>
-                        <adapt
-                              type="org.eclipse.emf.cdo.admin.CDOAdminClientRepository">
-                        </adapt>
+                        <adapt type="org.eclipse.emf.cdo.admin.CDOAdminClientRepository"/>
                      </or>
                   </iterate>
                </with>
@@ -79,42 +93,4 @@
       </menuContribution>
    </extension>
 
-   <extension
-         point="org.eclipse.ui.commands">
-      <category
-            id="org.eclipse.emf.cdo.security.ui.management"
-            name="%category.name">
-      </category>
-      <command
-            id="org.eclipse.emf.cdo.security.ui.resetPassword"
-            categoryId="org.eclipse.emf.cdo.security.ui.management"
-            name="%command.name"
-            description="%command.description"
-            defaultHandler="org.eclipse.emf.cdo.security.internal.ui.handlers.ResetPasswordHandler">
-      </command>
-      <command
-            id="org.eclipse.emf.cdo.security.ui.openEditor"
-            name="%action.label.1"
-            categoryId="org.eclipse.emf.cdo.security.ui.management"
-            defaultHandler="org.eclipse.emf.cdo.security.internal.ui.handlers.ManageSecurityHandler">
-      </command>
-   </extension>
-
-   <extension
-         point="org.eclipse.ui.handlers">
-      <handler
-            commandId="org.eclipse.emf.cdo.security.ui.openEditor"
-            class="org.eclipse.emf.cdo.security.internal.ui.handlers.ManageSecurityHandler$Sessionless">
-         <activeWhen>
-            <with
-                  variable="activeMenuSelection">
-               <iterate ifEmpty="false">
-                  <adapt
-                        type="org.eclipse.emf.cdo.admin.CDOAdminClientRepository">
-                  </adapt>
-               </iterate>
-            </with>
-         </activeWhen>
-      </handler>
-   </extension>
 </plugin>
diff --git a/plugins/org.eclipse.emf.cdo.security.ui/pom.xml b/plugins/org.eclipse.emf.cdo.security.ui/pom.xml
index 5d9a79a..d8fabdf 100644
--- a/plugins/org.eclipse.emf.cdo.security.ui/pom.xml
+++ b/plugins/org.eclipse.emf.cdo.security.ui/pom.xml
@@ -25,7 +25,7 @@
 
   <groupId>org.eclipse.emf.cdo</groupId>
   <artifactId>org.eclipse.emf.cdo.security.ui</artifactId>
-  <version>4.5.0-SNAPSHOT</version>
+  <version>4.5.1-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 
 </project>
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..db90e7a 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.5.0.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.5.0";
   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.5.0";x-internal:=true,
+ org.eclipse.emf.cdo.security;version="4.5.0",
+ org.eclipse.emf.cdo.security.impl;version="4.5.0",
+ org.eclipse.emf.cdo.security.util;version="4.5.0"
 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..5b53e49 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.5.0-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.security/src/org/eclipse/emf/cdo/security/Role.java b/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/security/Role.java
index e19f672..53ed395 100644
--- a/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/security/Role.java
+++ b/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/security/Role.java
@@ -37,11 +37,6 @@
   /**
    * @since 4.3
    */
-  public static final String ADMINISTRATION = "Administration";
-
-  /**
-   * @since 4.3
-   */
   public static final String RESOURCE_TREE_WRITER = "Resource Tree Writer";
 
   /**
@@ -60,6 +55,21 @@
   public static final String ALL_OBJECTS_READER = "All Objects Reader";
 
   /**
+   * @since 4.5
+   */
+  public static final String NORMAL_OBJECTS_WRITER = "Normal Objects Writer";
+
+  /**
+   * @since 4.5
+   */
+  public static final String NORMAL_OBJECTS_READER = "Normal Objects Reader";
+
+  /**
+   * @since 4.3
+   */
+  public static final String ADMINISTRATION = "Administration";
+
+  /**
    * Returns the value of the '<em><b>Assignees</b></em>' reference list.
    * The list contents are of type {@link org.eclipse.emf.cdo.security.Assignee}.
    * It is bidirectional and its opposite is '{@link org.eclipse.emf.cdo.security.Assignee#getRoles <em>Roles</em>}'.
diff --git a/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/security/impl/DirectoryImpl.java b/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/security/impl/DirectoryImpl.java
index 22b7fa1..8f1e2b8 100644
--- a/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/security/impl/DirectoryImpl.java
+++ b/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/security/impl/DirectoryImpl.java
@@ -20,6 +20,7 @@
 import org.eclipse.emf.cdo.security.User;
 
 import org.eclipse.net4j.util.security.IPasswordCredentials;
+import org.eclipse.net4j.util.security.SecurityUtil;
 
 import org.eclipse.emf.common.util.EList;
 import org.eclipse.emf.ecore.EClass;
@@ -172,7 +173,7 @@
   @Override
   public User addUser(IPasswordCredentials credentials)
   {
-    return addUser(credentials.getUserID(), new String(credentials.getPassword()));
+    return addUser(credentials.getUserID(), SecurityUtil.toString(credentials.getPassword()));
   }
 
   /**
diff --git a/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/security/impl/RealmImpl.java b/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/security/impl/RealmImpl.java
index cba059c..5f4f753 100644
--- a/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/security/impl/RealmImpl.java
+++ b/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/security/impl/RealmImpl.java
@@ -23,7 +23,9 @@
 import org.eclipse.emf.cdo.security.User;
 import org.eclipse.emf.cdo.security.UserPassword;
 
+import org.eclipse.net4j.util.StringUtil;
 import org.eclipse.net4j.util.security.IPasswordCredentials;
+import org.eclipse.net4j.util.security.SecurityUtil;
 
 import org.eclipse.emf.common.util.EList;
 import org.eclipse.emf.ecore.EClass;
@@ -441,7 +443,7 @@
   @Override
   public User addUser(IPasswordCredentials credentials)
   {
-    return addUser(credentials.getUserID(), new String(credentials.getPassword()));
+    return addUser(credentials.getUserID(), SecurityUtil.toString(credentials.getPassword()));
   }
 
   /**
@@ -461,7 +463,7 @@
   public User setPassword(String id, String password)
   {
     UserPassword userPassword = null;
-    if (password != null && password.length() != 0)
+    if (!StringUtil.isEmpty(password))
     {
       userPassword = SecurityFactory.eINSTANCE.createUserPassword();
       userPassword.setEncrypted(password);
diff --git a/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/security/impl/ResourceFilterImpl.java b/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/security/impl/ResourceFilterImpl.java
index de10f9c..5ba7590 100644
--- a/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/security/impl/ResourceFilterImpl.java
+++ b/plugins/org.eclipse.emf.cdo.security/src/org/eclipse/emf/cdo/security/impl/ResourceFilterImpl.java
@@ -14,6 +14,7 @@
 import org.eclipse.emf.cdo.common.id.CDOID;
 import org.eclipse.emf.cdo.common.id.CDOIDUtil;
 import org.eclipse.emf.cdo.common.revision.CDORevision;
+import org.eclipse.emf.cdo.common.revision.CDORevisionData;
 import org.eclipse.emf.cdo.common.revision.CDORevisionProvider;
 import org.eclipse.emf.cdo.common.revision.CDORevisionUtil;
 import org.eclipse.emf.cdo.common.revision.delta.CDOContainerFeatureDelta;
@@ -397,7 +398,9 @@
     {
       resourceNode = true;
 
-      boolean rootResource = CDOIDUtil.isNull((CDOID)((InternalCDORevision)revision).getContainerID());
+      CDORevisionData revisionData = revision.data();
+      boolean rootResource = CDOIDUtil.isNull((CDOID)revisionData.getContainerID()) && CDOIDUtil.isNull(revisionData.getResourceID());
+
       if (rootResource)
       {
         if (!isIncludeRoot())
diff --git a/plugins/org.eclipse.emf.cdo.server.net4j/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.cdo.server.net4j/META-INF/MANIFEST.MF
index 7fd682d..86b62a2 100644
--- a/plugins/org.eclipse.emf.cdo.server.net4j/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.emf.cdo.server.net4j/META-INF/MANIFEST.MF
@@ -1,7 +1,7 @@
 Manifest-Version: 1.0
 Bundle-ManifestVersion: 2
 Bundle-SymbolicName: org.eclipse.emf.cdo.server.net4j;singleton:=true
-Bundle-Version: 4.3.0.qualifier
+Bundle-Version: 4.3.1.qualifier
 Bundle-Name: %pluginName
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin
@@ -12,7 +12,7 @@
 Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.5.0,4.0.0)";resolution:=optional,
  org.eclipse.emf.cdo.server;bundle-version="[4.0.0,5.0.0)";visibility:=reexport,
  org.eclipse.net4j;bundle-version="[4.0.0,5.0.0)";visibility:=reexport
-Export-Package: org.eclipse.emf.cdo.server.internal.net4j.bundle;version="4.3.0";x-internal:=true,
- org.eclipse.emf.cdo.server.internal.net4j.protocol;version="4.3.0";x-friends:="org.eclipse.emf.cdo.tests",
- org.eclipse.emf.cdo.server.net4j;version="4.3.0"
+Export-Package: org.eclipse.emf.cdo.server.internal.net4j.bundle;version="4.3.1";x-internal:=true,
+ org.eclipse.emf.cdo.server.internal.net4j.protocol;version="4.3.1";x-friends:="org.eclipse.emf.cdo.tests",
+ org.eclipse.emf.cdo.server.net4j;version="4.3.1"
 Automatic-Module-Name: org.eclipse.emf.cdo.server.net4j
diff --git a/plugins/org.eclipse.emf.cdo.server.net4j/pom.xml b/plugins/org.eclipse.emf.cdo.server.net4j/pom.xml
index 7d63a70..f3c5b94 100644
--- a/plugins/org.eclipse.emf.cdo.server.net4j/pom.xml
+++ b/plugins/org.eclipse.emf.cdo.server.net4j/pom.xml
@@ -25,7 +25,7 @@
 
   <groupId>org.eclipse.emf.cdo</groupId>
   <artifactId>org.eclipse.emf.cdo.server.net4j</artifactId>
-  <version>4.3.0-SNAPSHOT</version>
+  <version>4.3.1-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 
 </project>
diff --git a/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/bundle/Net4jAppExtension.java b/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/bundle/Net4jAppExtension.java
index c368ba9..0744a35 100644
--- a/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/bundle/Net4jAppExtension.java
+++ b/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/bundle/Net4jAppExtension.java
@@ -10,7 +10,7 @@
  */
 package org.eclipse.emf.cdo.server.internal.net4j.bundle;
 
-import org.eclipse.emf.cdo.spi.server.IAppExtension;
+import org.eclipse.emf.cdo.spi.server.IAppExtension4;
 
 import org.eclipse.net4j.TransportConfigurator;
 import org.eclipse.net4j.acceptor.IAcceptor;
@@ -22,7 +22,7 @@
 /**
  * @author Eike Stepper
  */
-public class Net4jAppExtension implements IAppExtension
+public class Net4jAppExtension implements IAppExtension4
 {
   private IAcceptor[] acceptors;
 
@@ -31,6 +31,12 @@
   }
 
   @Override
+  public int getPriority()
+  {
+    return PRIORITY_NETWORK;
+  }
+
+  @Override
   public void start(File configFile) throws Exception
   {
     OM.LOG.info("Net4j extension starting"); //$NON-NLS-1$
diff --git a/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/OpenSessionIndication.java b/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/OpenSessionIndication.java
index cb1c877..52faaf9 100644
--- a/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/OpenSessionIndication.java
+++ b/plugins/org.eclipse.emf.cdo.server.net4j/src/org/eclipse/emf/cdo/server/internal/net4j/protocol/OpenSessionIndication.java
@@ -201,6 +201,7 @@
 
       out.writeXLong(repository.getCreationTime());
       out.writeXLong(session.getFirstUpdateTime());
+      out.writeXLong(session.getOpeningTime());
       out.writeXInt(repository.getBranchManager().getTagModCount());
       out.writeCDOID(repository.getRootResourceID());
       out.writeBoolean(repository.isAuthenticating());
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..89a7da1 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,23 +2,23 @@
 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
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.5.0,4.0.0)",
- org.eclipse.emf.cdo.server;bundle-version="[4.1.0,5.0.0)",
- org.eclipse.emf.cdo.security;bundle-version="[4.1.0,5.0.0)",
+ org.eclipse.emf.cdo.server;bundle-version="[4.1.0,5.0.0)";visibility:=reexport,
+ org.eclipse.emf.cdo.security;bundle-version="[4.1.0,5.0.0)";visibility:=reexport,
  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/SecurityExtension.java b/plugins/org.eclipse.emf.cdo.server.security/src/org/eclipse/emf/cdo/server/internal/security/SecurityExtension.java
index 67c34fe..12eb0ed 100644
--- a/plugins/org.eclipse.emf.cdo.server.security/src/org/eclipse/emf/cdo/server/internal/security/SecurityExtension.java
+++ b/plugins/org.eclipse.emf.cdo.server.security/src/org/eclipse/emf/cdo/server/internal/security/SecurityExtension.java
@@ -15,6 +15,7 @@
 import org.eclipse.emf.cdo.server.internal.security.bundle.OM;
 import org.eclipse.emf.cdo.server.spi.security.SecurityManagerFactory;
 import org.eclipse.emf.cdo.spi.server.IAppExtension2;
+import org.eclipse.emf.cdo.spi.server.IAppExtension4;
 import org.eclipse.emf.cdo.spi.server.InternalRepository;
 import org.eclipse.emf.cdo.spi.server.RepositoryFactory;
 
@@ -38,7 +39,7 @@
 /**
  * @author Eike Stepper
  */
-public class SecurityExtension implements IAppExtension2
+public class SecurityExtension implements IAppExtension2, IAppExtension4
 {
   public static final String DEFAULT_REALM_PATH = "security";
 
@@ -47,6 +48,12 @@
   }
 
   @Override
+  public int getPriority()
+  {
+    return PRIORITY_SECURITY;
+  }
+
+  @Override
   public void start(File configFile) throws Exception
   {
     start(getDocument(configFile));
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..22e91db 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,8 @@
 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.StringUtil;
 import org.eclipse.net4j.util.WrappedException;
 import org.eclipse.net4j.util.collection.HashBag;
 import org.eclipse.net4j.util.concurrent.TimeoutRuntimeException;
@@ -80,10 +88,13 @@
 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.OMPlatform;
+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;
 import org.eclipse.net4j.util.security.IPasswordCredentials;
+import org.eclipse.net4j.util.security.SecurityUtil;
 
 import org.eclipse.emf.common.util.BasicDiagnostic;
 import org.eclipse.emf.common.util.Diagnostic;
@@ -93,18 +104,36 @@
 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;
+import java.util.function.Consumer;
 
 /**
  * @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 boolean DISABLE_DETACH_CHECKS = OMPlatform.INSTANCE.isProperty("org.eclipse.emf.cdo.server.security.DISABLE_DETACH_CHECKS");
+
+  private static final boolean ALLOW_EMPTY_PASSWORDS = OMPlatform.INSTANCE.isProperty("org.eclipse.emf.cdo.server.security.ALLOW_EMPTY_PASSWORDS");
+
+  private static final Consumer<String> EMPTY_PASSWORD_PREVENTER = pw -> {
+    if (StringUtil.isEmpty(pw))
+    {
+      throw new SecurityException("Password is empty");
+    }
+  };
 
   private static final SecurityFactory SF = SecurityFactory.eINSTANCE;
 
@@ -119,7 +148,7 @@
     @Override
     protected void onDeactivated(ILifecycle lifecycle)
     {
-      SECURITY_MANAGERS.remove(getRepository());
+      unregister(repository);
       SecurityManager.this.deactivate();
     }
   };
@@ -127,13 +156,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 +187,7 @@
       {
         if (clearUserInfos)
         {
-          clearUserInfos();
+          clearUserInfos(true);
           clearUserInfos = false;
         }
       }
@@ -169,7 +204,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<>();
 
@@ -181,21 +218,25 @@
 
   private PermissionImpl[] permissionArray = {};
 
+  private Consumer<String> passwordValidator = ALLOW_EMPTY_PASSWORDS ? null : EMPTY_PASSWORD_PREVENTER;
+
   private InternalRepository repository;
 
   private IAcceptor acceptor;
 
   private IConnector connector;
 
-  private CDONet4jSession systemSession;
+  private CDONet4jSession realmSession;
 
-  private CDOView systemView;
+  private CDOView realmView;
 
   private Realm realm;
 
   private CDOID realmID;
 
-  private long lastRealmModification = CDOBranchPoint.UNSPECIFIED_DATE;
+  private volatile Long lastRealmModification;
+
+  private Object lastRealmModificationLock = new Object();
 
   public SecurityManager(String realmPath, IManagedContainer container)
   {
@@ -216,7 +257,7 @@
   }
 
   @Override
-  public final IRepository getRepository()
+  public final InternalRepository getRepository()
   {
     return repository;
   }
@@ -232,6 +273,41 @@
   }
 
   @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();
+    }
+  }
+
+  public Consumer<String> getPasswordValidator()
+  {
+    return passwordValidator;
+  }
+
+  public void setPasswordValidator(Consumer<String> passwordValidator)
+  {
+    checkInactive();
+    this.passwordValidator = passwordValidator;
+  }
+
+  @Override
   public Realm getRealm()
   {
     return realm;
@@ -274,66 +350,40 @@
   }
 
   @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)
+    User[] result = { null };
+    modify(realm -> {
+      result[0] = realm.addUser(id);
+
+      if (!StringUtil.isEmpty(password))
       {
         UserPassword userPassword = SF.createUserPassword();
         userPassword.setEncrypted(new String(password));
-
-        result[0] = realm.addUser(id);
         result[0].setPassword(userPassword);
       }
     });
@@ -344,70 +394,43 @@
   @Override
   public User addUser(IPasswordCredentials credentials)
   {
-    return addUser(credentials.getUserID(), new String(credentials.getPassword()));
+    return addUser(credentials.getUserID(), SecurityUtil.toString(credentials.getPassword()));
   }
 
   @Override
-  public User setPassword(final String id, final String password)
+  public User setPassword(String id, String password)
   {
-    final User[] result = { null };
-    modify(new RealmOperation()
+    if (passwordValidator != null)
     {
-      @Override
-      public void execute(Realm realm)
-      {
-        result[0] = realm.setPassword(id, password);
-      }
-    });
+      passwordValidator.accept(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 +457,7 @@
   public CDOCommitInfo modifyWithInfo(RealmOperation operation, boolean waitUntilReadable)
   {
     checkReady();
-    CDOTransaction transaction = systemSession.openTransaction();
+    CDOTransaction transaction = realmSession.openTransaction();
 
     try
     {
@@ -444,7 +467,7 @@
 
       if (waitUntilReadable)
       {
-        if (!systemView.waitForUpdate(info.getTimeStamp(), 10000))
+        if (!realmView.waitForUpdate(info.getTimeStamp(), 10000))
         {
           throw new TimeoutRuntimeException();
         }
@@ -566,7 +589,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 +628,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 +664,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();
@@ -653,7 +676,7 @@
     sessionManager.addListener(sessionManagerListener);
     repository.addHandler(writeAccessHandler);
 
-    SECURITY_MANAGERS.put(repository, this);
+    register(repository);
     initCommitHandlers(firstTime);
   }
 
@@ -672,6 +695,14 @@
     Role allWriterRole = realm.addRole(Role.ALL_OBJECTS_WRITER);
     allWriterRole.getPermissions().add(SF.createFilterPermission(Access.WRITE, SF.createResourceFilter(".*", PatternStyle.REGEX)));
 
+    Role normalReaderRole = realm.addRole(Role.NORMAL_OBJECTS_READER);
+    normalReaderRole.getPermissions()
+        .add(SF.createFilterPermission(Access.READ, SF.createNotFilter(SF.createResourceFilter(realmPath, PatternStyle.EXACT, false))));
+
+    Role normalWriterRole = realm.addRole(Role.NORMAL_OBJECTS_WRITER);
+    normalWriterRole.getPermissions()
+        .add(SF.createFilterPermission(Access.WRITE, SF.createNotFilter(SF.createResourceFilter(realmPath, PatternStyle.EXACT, false))));
+
     Role treeReaderRole = realm.addRole(Role.RESOURCE_TREE_READER);
     treeReaderRole.getPermissions().add(SF.createFilterPermission(Access.READ, SF.createPackageFilter(EresourcePackage.eINSTANCE)));
 
@@ -704,11 +735,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;
@@ -724,15 +755,7 @@
   protected CDOPermission authorize(CDORevision revision, CDORevisionProvider revisionProvider, CDOBranchPoint securityContext, ISession session,
       Access defaultAccess, Permission[] permissions)
   {
-    if (lastRealmModification != CDOBranchPoint.UNSPECIFIED_DATE)
-    {
-      if (!systemView.waitForUpdate(lastRealmModification, 10000L))
-      {
-        throw new TimeoutRuntimeException();
-      }
-
-      lastRealmModification = CDOBranchPoint.UNSPECIFIED_DATE;
-    }
+    waitForRealmUpdate(securityContext);
 
     boolean setUser = defaultAccess == null;
     if (setUser)
@@ -786,80 +809,219 @@
     }
   }
 
-  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);
-
-    synchronized (userInfos)
-    {
-      userInfos.put(session, userInfo);
-
-      Permission[] permissions = userInfo.getPermissions();
-      for (int i = 0; i < permissions.length; i++)
+      if (!DISABLE_DETACH_CHECKS)
       {
-        Permission permission = permissions[i];
-        permissionBag.add((PermissionImpl)permission);
+        CDOID[] detachedObjects = commitContext.getDetachedObjects();
+        if (detachedObjects != null && detachedObjects.length != 0)
+        {
+          for (int i = 0; i < detachedObjects.length; i++)
+          {
+            CDOID id = detachedObjects[i];
+            CDORevision revision = transaction.getRevision(id);
+
+            CDOPermission permission = authorize(revision, transaction, securityContext, session, userDefaultAccess, userPermissions);
+            if (permission != CDOPermission.WRITE)
+            {
+              throw new SecurityException("User " + commitContext.getUserID() + " is not allowed to detach " + revision);
+            }
+          }
+        }
       }
 
-      // Atomic update
-      permissionArray = permissionBag.toArray(new PermissionImpl[permissionBag.size()]);
+      InternalCDORevision[] revisions = commitContext.getDirtyObjects();
+      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();
+    }
+  }
+
+  protected void sessionAdded(ISession session)
+  {
+    String userID = session.getUserID();
+    if (userID != null)
+    {
+      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();
+    if (userID != null)
+    {
+      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 +1057,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;
@@ -913,6 +1075,60 @@
     super.doDeactivate();
   }
 
+  private void rememberRealmCommit(CDOBranchPoint commitBranchPoint)
+  {
+    synchronized (lastRealmModificationLock)
+    {
+      lastRealmModification = commitBranchPoint.getTimeStamp();
+    }
+  }
+
+  private void waitForRealmUpdate(CDOBranchPoint securityContext)
+  {
+    if (lastRealmModification != null)
+    {
+      long updateTime;
+
+      synchronized (lastRealmModificationLock)
+      {
+        if (lastRealmModification != null)
+        {
+          updateTime = lastRealmModification;
+          lastRealmModification = null;
+        }
+        else
+        {
+          updateTime = CDOBranchPoint.UNSPECIFIED_DATE;
+        }
+      }
+
+      if (updateTime != CDOBranchPoint.UNSPECIFIED_DATE)
+      {
+        long contextTime = securityContext.getTimeStamp();
+        if (contextTime == CDOBranchPoint.UNSPECIFIED_DATE || contextTime < updateTime)
+        {
+          if (!realmView.waitForUpdate(updateTime, 10000L))
+          {
+            throw new TimeoutRuntimeException();
+          }
+        }
+      }
+    }
+  }
+
+  private void register(InternalRepository repository)
+  {
+    if (SECURITY_MANAGERS.putIfAbsent(repository, this) != null)
+    {
+      throw new IllegalStateException("A security manager is already associated with repository " + repository);
+    }
+  }
+
+  private void unregister(InternalRepository repository)
+  {
+    SECURITY_MANAGERS.remove(repository);
+  }
+
   public static InternalSecurityManager get(IRepository repository)
   {
     return SECURITY_MANAGERS.get(repository);
@@ -921,17 +1137,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 +1156,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,27 +1199,34 @@
    */
   private final class Authenticator implements IAuthenticator2
   {
+    public Authenticator()
+    {
+    }
+
     @Override
     public void authenticate(String userID, char[] password) throws SecurityException
     {
       User user = getUser(userID);
-      UserPassword userPassword = user.getPassword();
-
-      if (userPassword != null)
+      if (!user.isLocked())
       {
-        String encrypted = userPassword.getEncrypted();
-        if (!Arrays.equals(password, encrypted == null ? null : encrypted.toCharArray()))
+        UserPassword userPassword = user.getPassword();
+
+        String encrypted = userPassword == null ? null : userPassword.getEncrypted();
+        if (Arrays.equals(password, SecurityUtil.toCharArray(encrypted)))
         {
-          throw new SecurityException("Access denied"); //$NON-NLS-1$
+          // Access granted.
+          return;
         }
       }
+
+      throw new SecurityException("Access denied"); //$NON-NLS-1$
     }
 
     @Override
     public void updatePassword(String userID, char[] oldPassword, char[] newPassword)
     {
       authenticate(userID, oldPassword);
-      setPassword(userID, new String(newPassword));
+      setPassword(userID, SecurityUtil.toString(newPassword));
     }
 
     @Override
@@ -984,7 +1240,7 @@
         throw new SecurityException("Password reset requires administrator privilege"); //$NON-NLS-1$
       }
 
-      setPassword(userID, new String(newPassword));
+      setPassword(userID, SecurityUtil.toString(newPassword));
     }
 
     @Override
@@ -1008,6 +1264,10 @@
    */
   private final class PermissionManager implements IPermissionManager
   {
+    public PermissionManager()
+    {
+    }
+
     @Override
     @Deprecated
     public CDOPermission getPermission(CDORevision revision, CDOBranchPoint securityContext, String userID)
@@ -1016,7 +1276,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 +1346,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,123 +1362,25 @@
       }
 
       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)
       {
-        lastRealmModification = commitContext.getBranchPoint().getTimeStamp();
+        CDOBranchPoint commitBranchPoint = commitContext.getBranchPoint();
+
+        rememberRealmCommit(commitBranchPoint);
       }
 
       handleCommitted(commitContext);
@@ -1243,11 +1398,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 +1437,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 +1474,108 @@
       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);
+      register(delegate);
+    }
+
+    @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()
+    {
+      unregister(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/ServerCDOView.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/ServerCDOView.java
index dfcf42f..0da5976 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/ServerCDOView.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/ServerCDOView.java
@@ -615,16 +615,19 @@
       }
     };
 
+    private final InternalSession internalSession;
+
+    private final InternalRepository repository;
+
+    private final long openingTime;
+
     private boolean generatedPackageEmulationEnabled;
 
-    private InternalSession internalSession;
-
-    private InternalRepository repository;
-
     public ServerCDOSession(InternalSession internalSession)
     {
       this.internalSession = internalSession;
       repository = internalSession.getManager().getRepository();
+      openingTime = repository.getTimeStamp();
     }
 
     @Override
@@ -726,16 +729,28 @@
      * be done client-side by interaction with the user.
      *
      * @since 4.3
+     * @deprecated
      */
     @Override
+    @Deprecated
     public void changeCredentials()
     {
       throw new UnsupportedOperationException();
     }
 
     /**
+     * Server sessions may not be used to change the user's credentials: it must
+     * be done client-side by interaction with the user.
+     */
+    @Override
+    public char[] changeServerPassword()
+    {
+      return null;
+    }
+
+    /**
      * Server sessions may not be used to reset a user's credentials: it must
-     * be done client-side by interaction with an adminstrator.
+     * be done client-side by interaction with an administrator.
      *
      * @since 4.3
      */
@@ -1027,6 +1042,12 @@
     }
 
     @Override
+    public long getOpeningTime()
+    {
+      return openingTime;
+    }
+
+    @Override
     public long getLastUpdateTime()
     {
       return getBranchPoint().getTimeStamp();
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Session.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Session.java
index 3a56802..965e425 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Session.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Session.java
@@ -53,6 +53,7 @@
 import org.eclipse.emf.cdo.spi.server.InternalView;
 
 import org.eclipse.net4j.util.AdapterUtil;
+import org.eclipse.net4j.util.CheckUtil;
 import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump;
 import org.eclipse.net4j.util.collection.IndexedList;
 import org.eclipse.net4j.util.container.Container;
@@ -98,6 +99,8 @@
 
   private boolean openOnClientSide;
 
+  private long openingTime;
+
   private long firstUpdateTime;
 
   private long lastUpdateTime;
@@ -284,6 +287,19 @@
   }
 
   @Override
+  public long getOpeningTime()
+  {
+    return openingTime;
+  }
+
+  @Override
+  public void setOpeningTime(long openingTime)
+  {
+    CheckUtil.checkState(this.openingTime == 0, "Opening time is already set");
+    this.openingTime = openingTime;
+  }
+
+  @Override
   @Deprecated
   public long getLastUpdateTime()
   {
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/SessionManager.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/SessionManager.java
index 8c47f1a..495638a 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/SessionManager.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/SessionManager.java
@@ -26,6 +26,7 @@
 import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants;
 import org.eclipse.emf.cdo.internal.server.bundle.OM;
 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.session.remote.CDORemoteSessionMessage;
 import org.eclipse.emf.cdo.spi.common.branch.InternalCDOBranch;
@@ -50,10 +51,12 @@
 import org.eclipse.net4j.util.security.IAuthenticator;
 import org.eclipse.net4j.util.security.IAuthenticator2;
 import org.eclipse.net4j.util.security.IUserManager;
+import org.eclipse.net4j.util.security.SecurityUtil;
 import org.eclipse.net4j.util.security.UserManagerAuthenticator;
 
 import java.io.ByteArrayInputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -93,6 +96,8 @@
     }
   };
 
+  private InternalSession[] sessionsArray = {};
+
   /**
    * @since 2.0
    */
@@ -190,10 +195,33 @@
   @Override
   public InternalSession[] getSessions()
   {
-    synchronized (sessions)
-    {
-      return sessions.values().toArray(new InternalSession[sessions.size()]);
-    }
+    return sessionsArray;
+  }
+
+  /**
+   * This method is (and must be) called while holding the "sessions" monitor lock.
+   */
+  private void buildSessionsArray()
+  {
+    sessionsArray = sessions.values().toArray(new InternalSession[sessions.size()]);
+
+    Arrays.sort(sessionsArray, (s1, s2) -> {
+      // Iterating system sessions first is very important for the SecurityManager.
+      // In particular realmView.waitForUpdate(lastRealmModification) may deadlock without this order.
+      int sys1 = IRepository.SYSTEM_USER_ID.equals(s1.getUserID()) ? 0 : 1;
+      int sys2 = IRepository.SYSTEM_USER_ID.equals(s2.getUserID()) ? 0 : 1;
+
+      int result = Integer.compare(sys1, sys2);
+      if (result == 0)
+      {
+        long t1 = s1.getOpeningTime();
+        long t2 = s2.getOpeningTime();
+
+        result = Long.compare(t1, t2);
+      }
+
+      return result;
+    });
   }
 
   /**
@@ -230,14 +258,14 @@
   @Override
   public InternalSession openSession(ISessionProtocol sessionProtocol)
   {
-    final int id = lastSessionID.incrementAndGet();
+    int id = lastSessionID.incrementAndGet();
     if (TRACER.isEnabled())
     {
       TRACER.trace("Opening session " + id); //$NON-NLS-1$
     }
 
     String userID = authenticateUser(sessionProtocol);
-    final InternalSession session = createSession(id, userID, sessionProtocol);
+    InternalSession session = createSession(id, userID, sessionProtocol);
     LifecycleUtil.activate(session);
 
     synchronized (sessions)
@@ -247,10 +275,14 @@
         @Override
         public void run()
         {
+          long openingTime = repository.getTimeStamp();
+          session.setOpeningTime(openingTime);
+
           long firstUpdateTime = repository.getLastCommitTimeStamp();
           session.setFirstUpdateTime(firstUpdateTime);
 
           sessions.put(id, session);
+          buildSessionsArray();
         }
       });
     }
@@ -273,6 +305,7 @@
     synchronized (sessions)
     {
       removeSession = sessions.remove(sessionID);
+      buildSessionsArray();
     }
 
     if (removeSession != null)
@@ -390,7 +423,9 @@
   public void sendCommitNotification(CommitNotificationInfo info)
   {
     CDOCommonSession sender = info.getSender();
-    for (InternalSession session : getSessions())
+    InternalSession[] sessions = getSessions();
+
+    for (InternalSession session : sessions)
     {
       if (session != sender || info.isModifiedByServer())
       {
@@ -571,7 +606,7 @@
 
       ExtendedDataInputStream stream = new ExtendedDataInputStream(bais);
       String userID = stream.readString();
-      char[] password = stream.readString().toCharArray();
+      char[] password = SecurityUtil.toCharArray(stream.readString());
 
       authenticator.authenticate(userID, password);
       return userID;
@@ -637,12 +672,13 @@
       if (operation == CredentialsUpdateOperation.RESET_PASSWORD)
       {
         String adminID = stream.readString();
-        char[] adminPassword = stream.readString().toCharArray();
+        char[] adminPassword = SecurityUtil.toCharArray(stream.readString());
         if (!ObjectUtil.equals(userID, stream.readString()))
         {
           throw new SecurityException("Attempt to reset password of a different user than requested"); //$NON-NLS-1$
         }
-        char[] newPassword = stream.readString().toCharArray();
+
+        char[] newPassword = SecurityUtil.toCharArray(stream.readString());
 
         // this will throw if the current credentials are not authenticated as an administrator
         ((IAuthenticator2)authenticator).resetPassword(adminID, adminPassword, userID, newPassword);
@@ -650,8 +686,8 @@
       else
       {
         userID = stream.readString(); // user can change any password that she can authenticate on the old password
-        char[] password = stream.readString().toCharArray();
-        char[] newPassword = stream.readString().toCharArray();
+        char[] password = SecurityUtil.toCharArray(stream.readString());
+        char[] newPassword = SecurityUtil.toCharArray(stream.readString());
 
         // this will throw if the "old password" provided by the user is not correct
         ((IAuthenticator2)authenticator).updatePassword(userID, password, newPassword);
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.server/src/org/eclipse/emf/cdo/internal/server/bundle/CDOServerApplication.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/bundle/CDOServerApplication.java
index 30dc783..dd1d603 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/bundle/CDOServerApplication.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/bundle/CDOServerApplication.java
@@ -14,6 +14,7 @@
 import org.eclipse.emf.cdo.server.IRepository;
 import org.eclipse.emf.cdo.spi.server.IAppExtension;
 import org.eclipse.emf.cdo.spi.server.IAppExtension3;
+import org.eclipse.emf.cdo.spi.server.IAppExtension4;
 import org.eclipse.emf.cdo.spi.server.RepositoryConfigurator;
 
 import org.eclipse.net4j.util.container.IManagedContainer;
@@ -102,6 +103,9 @@
   protected void doStop() throws Exception
   {
     OM.LOG.info(Messages.getString("CDOServerApplication.7")); //$NON-NLS-1$
+
+    extensions.sort(IAppExtension4.COMPARATOR.reversed());
+
     for (IAppExtension extension : extensions)
     {
       try
@@ -141,24 +145,14 @@
   {
     IExtensionRegistry registry = Platform.getExtensionRegistry();
     IConfigurationElement[] elements = registry.getConfigurationElementsFor(OM.BUNDLE_ID, IAppExtension.EXT_POINT);
-    for (final IConfigurationElement element : elements)
+
+    for (IConfigurationElement element : elements)
     {
       if ("appExtension".equals(element.getName())) //$NON-NLS-1$
       {
         try
         {
           IAppExtension extension = (IAppExtension)element.createExecutableExtension("class"); //$NON-NLS-1$
-
-          if (extension instanceof IAppExtension3)
-          {
-            IAppExtension3 extension3 = (IAppExtension3)extension;
-            extension3.start(repositories, configFile);
-          }
-          else
-          {
-            extension.start(configFile);
-          }
-
           extensions.add(extension);
         }
         catch (Exception ex)
@@ -167,6 +161,28 @@
         }
       }
     }
+
+    extensions.sort(IAppExtension4.COMPARATOR);
+
+    for (IAppExtension extension : extensions)
+    {
+      try
+      {
+        if (extension instanceof IAppExtension3)
+        {
+          IAppExtension3 extension3 = (IAppExtension3)extension;
+          extension3.start(repositories, configFile);
+        }
+        else
+        {
+          extension.start(configFile);
+        }
+      }
+      catch (Exception ex)
+      {
+        OM.LOG.error(ex);
+      }
+    }
   }
 
   public static IManagedContainer getContainer()
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/IAppExtension4.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/IAppExtension4.java
new file mode 100644
index 0000000..cac968a
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/IAppExtension4.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020 Eike Stepper (Loehne, Germany) and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Eike Stepper - initial API and implementation
+ */
+package org.eclipse.emf.cdo.spi.server;
+
+import java.util.Comparator;
+
+/**
+ * An optional extension of the {@link IAppExtension} interface for  {@link #getPriority() prioritized} app extensions.
+ *
+ * @author Eike Stepper
+ * @since 4.12
+ */
+public interface IAppExtension4 extends IAppExtension
+{
+  public static final int PRIORITY_NETWORK = 10;
+
+  public static final int PRIORITY_SECURITY = 100;
+
+  public static final int PRIORITY_DEFAULT = 1000;
+
+  public static final Comparator<IAppExtension> COMPARATOR = (e1, e2) -> {
+    int p1 = getPriority(e1);
+    int p2 = getPriority(e2);
+    return Integer.compare(p1, p2);
+  };
+
+  public int getPriority();
+
+  public static int getPriority(IAppExtension appExtension)
+  {
+    if (appExtension instanceof IAppExtension4)
+    {
+      return ((IAppExtension4)appExtension).getPriority();
+    }
+
+    return PRIORITY_DEFAULT;
+  }
+}
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/InternalSession.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/InternalSession.java
index 9f7a3f8..fa73248 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/InternalSession.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/InternalSession.java
@@ -52,6 +52,11 @@
   public void setUserID(String userID);
 
   /**
+   * @since 4.12
+   */
+  public void setOpeningTime(long openingTime);
+
+  /**
    * @since 4.5
    */
   public long getFirstUpdateTime();
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/SessionTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/SessionTest.java
index b09ad36..43a7017 100644
--- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/SessionTest.java
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/SessionTest.java
@@ -67,7 +67,6 @@
   {
     super.setUp();
     enableConsole();
-
   }
 
   public void testIsSupportingAudits() throws Exception
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.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/CDOContentProvider.java b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/CDOContentProvider.java
index a774396..d8309f0 100644
--- a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/CDOContentProvider.java
+++ b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/CDOContentProvider.java
@@ -16,6 +16,7 @@
 import org.eclipse.emf.cdo.common.revision.CDOList;
 import org.eclipse.emf.cdo.common.revision.CDORevision;
 import org.eclipse.emf.cdo.common.revision.CDORevisionManager;
+import org.eclipse.emf.cdo.common.security.NoPermissionException;
 import org.eclipse.emf.cdo.eresource.CDOResource;
 import org.eclipse.emf.cdo.internal.ui.bundle.OM;
 import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
@@ -220,7 +221,7 @@
         return contentProvider.hasChildren(object);
       }
     }
-    catch (LifecycleException ex)
+    catch (LifecycleException | NoPermissionException ex)
     {
       //$FALL-THROUGH$
     }
@@ -366,17 +367,20 @@
             }
             catch (Exception ex)
             {
-              childrenCache.remove(originalObject);
+              childrenCache.put(originalObject, new Object[] { new ViewerUtil.Error(originalObject, ex.getMessage()) });
 
               if (finalOpeningContext != null)
               {
                 closeContext(finalOpeningContext);
               }
 
-              if (view == null)
+              if (view == null && finalObject instanceof EObject)
               {
                 CDOObject cdoObject = getCDOObject((EObject)finalObject);
-                view = cdoObject.cdoView();
+                if (cdoObject != null)
+                {
+                  view = cdoObject.cdoView();
+                }
               }
 
               if (view == null || !view.isClosed())
@@ -471,7 +475,7 @@
 
       return new Object[] { new ViewerUtil.Pending(originalObject, text) };
     }
-    catch (LifecycleException ex)
+    catch (LifecycleException | NoPermissionException ex)
     {
       //$FALL-THROUGH$
     }
@@ -521,7 +525,7 @@
         }
       }
     }
-    catch (LifecycleException ex)
+    catch (LifecycleException | NoPermissionException ex)
     {
       //$FALL-THROUGH$
     }
diff --git a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/ViewerUtil.java b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/ViewerUtil.java
index cdc9956..816bbaa 100644
--- a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/ViewerUtil.java
+++ b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/ViewerUtil.java
@@ -202,4 +202,36 @@
       return text;
     }
   }
+
+  /**
+   * @author Eike Stepper
+   */
+  public static final class Error
+  {
+    private final Object parent;
+
+    private final String text;
+
+    public Error(Object parent, String text)
+    {
+      this.parent = parent;
+      this.text = text;
+    }
+
+    public Object getParent()
+    {
+      return parent;
+    }
+
+    public String getText()
+    {
+      return text;
+    }
+
+    @Override
+    public String toString()
+    {
+      return text;
+    }
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/actions/ChangePasswordAction.java b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/actions/ChangePasswordAction.java
index 268376a..b5b6789 100644
--- a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/actions/ChangePasswordAction.java
+++ b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/actions/ChangePasswordAction.java
@@ -35,6 +35,6 @@
   @Override
   protected void doRun(IProgressMonitor progressMonitor) throws Exception
   {
-    getSession().changeCredentials();
+    getSession().changeServerPassword();
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/editor/CDOEditor.java b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/editor/CDOEditor.java
index 33761fe..608b134 100644
--- a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/editor/CDOEditor.java
+++ b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/internal/ui/editor/CDOEditor.java
@@ -3010,6 +3010,7 @@
         return;
       }
 
+      CDOView view = CDOEditor.this.view;
       if (view != null)
       {
         setView(view);
diff --git a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/ui/CDOLabelProvider.java b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/ui/CDOLabelProvider.java
index 5b32cf1..0c0cf96 100644
--- a/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/ui/CDOLabelProvider.java
+++ b/plugins/org.eclipse.emf.cdo.ui/src/org/eclipse/emf/cdo/ui/CDOLabelProvider.java
@@ -49,11 +49,11 @@
  */
 public class CDOLabelProvider extends AdapterFactoryLabelProvider implements IColorProvider, IFontProvider
 {
-  private static final Color GRAY = UIUtil.getDisplay().getSystemColor(SWT.COLOR_GRAY);
+  private static final Color COLOR_PERMISSION_NONE = UIUtil.getDisplay().getSystemColor(SWT.COLOR_GRAY);
 
-  private static final Color YELLOW = UIUtil.getDisplay().getSystemColor(SWT.COLOR_DARK_YELLOW);
+  private static final Color COLOR_PERMISSION_READ = UIUtil.getDisplay().getSystemColor(SWT.COLOR_DARK_CYAN);
 
-  private static final Color RED = UIUtil.getDisplay().getSystemColor(SWT.COLOR_RED);
+  private static final Color COLOR_CONFLICT = UIUtil.getDisplay().getSystemColor(SWT.COLOR_RED);
 
   private static final Image ERROR_IMAGE = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_ERROR_TSK);
 
@@ -217,17 +217,17 @@
     {
       if (object.cdoConflict())
       {
-        return RED;
+        return COLOR_CONFLICT;
       }
 
       CDOPermission permission = object.cdoPermission();
       switch (permission)
       {
       case NONE:
-        return GRAY;
+        return COLOR_PERMISSION_NONE;
 
       case READ:
-        return YELLOW;
+        return COLOR_PERMISSION_READ;
 
       default:
         //$FALL-THROUGH$
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/session/CDOSession.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/session/CDOSession.java
index 5b5c530..a388f64 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/session/CDOSession.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/session/CDOSession.java
@@ -190,11 +190,25 @@
    * @throws UnsupportedOperationException if the session implementation does not permit changing credentials
    *
    * @since 4.3
+   * @deprecated As of 4.13 use {@link #changeServerPassword()}.
+   *
    * @see #getCredentialsProvider()
    */
+  @Deprecated
   public void changeCredentials();
 
   /**
+   * Initiates (possibly interactive) changing of credentials for the user logged in in this session.
+   * This is an optional operation of the session.
+   *
+   * @throws UnsupportedOperationException if the session implementation does not permit changing credentials
+   *
+   * @since 4.13
+   * @see #getCredentialsProvider()
+   */
+  public char[] changeServerPassword();
+
+  /**
    * @since 4.13
    */
   public CDOClob newClob(Reader contents) throws IOException;
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/ObjectProperties.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/ObjectProperties.java
index a63efad..deebef1 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/ObjectProperties.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/ObjectProperties.java
@@ -19,6 +19,7 @@
 import org.eclipse.emf.cdo.common.lock.CDOLockState;
 import org.eclipse.emf.cdo.common.revision.CDORevision;
 import org.eclipse.emf.cdo.common.security.CDOPermission;
+import org.eclipse.emf.cdo.common.security.NoPermissionException;
 import org.eclipse.emf.cdo.common.util.CDOCommonUtil;
 import org.eclipse.emf.cdo.transaction.CDOTransaction;
 import org.eclipse.emf.cdo.util.CDOUtil;
@@ -285,7 +286,14 @@
       @Override
       protected Object eval(EObject object)
       {
-        return !object.eContents().isEmpty();
+        try
+        {
+          return !object.eContents().isEmpty();
+        }
+        catch (NoPermissionException ex)
+        {
+          return false;
+        }
       }
     });
 
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java
index 0c7eae4..d4011ff 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java
@@ -196,6 +196,8 @@
 
   private String userID;
 
+  private long openingTime;
+
   private long lastUpdateTime;
 
   @ExcludeFromDump
@@ -861,6 +863,17 @@
   }
 
   @Override
+  public long getOpeningTime()
+  {
+    return openingTime;
+  }
+
+  public void setOpeningTime(long openingTime)
+  {
+    this.openingTime = openingTime;
+  }
+
+  @Override
   public long getLastUpdateTime()
   {
     synchronized (lastUpdateTimeLock)
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/DelegatingSessionProtocol.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/DelegatingSessionProtocol.java
index 4bf5391..73747ae 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/DelegatingSessionProtocol.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/DelegatingSessionProtocol.java
@@ -75,6 +75,7 @@
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 
 /**
@@ -1126,14 +1127,21 @@
   }
 
   @Override
+  @Deprecated
   public void requestChangeCredentials()
   {
+    requestChangeServerPassword(null);
+  }
+
+  @Override
+  public void requestChangeServerPassword(AtomicReference<char[]> receiver)
+  {
     int attempt = 0;
     for (;;)
     {
       try
       {
-        delegate.requestChangeCredentials();
+        delegate.requestChangeServerPassword(receiver);
         return;
       }
       catch (Exception ex)
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/CDOSessionProtocol.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/CDOSessionProtocol.java
index 7c79346..2158912 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/CDOSessionProtocol.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/CDOSessionProtocol.java
@@ -76,6 +76,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * If the meaning of this type isn't clear, there really should be more of a description here...
@@ -330,13 +331,26 @@
    * This is an optional session protocol operation.
    *
    * @since 4.3
+   * @deprecated As of 4.13 use {@link #requestChangeServerPassword(AtomicReference)}.
    *
    * @throws UnsupportedOperationException if the session protocol implementation does
    *         not support requesting change of credentials
    */
+  @Deprecated
   public void requestChangeCredentials();
 
   /**
+   * Requests that the server initiate the change-credentials protocol.
+   * This is an optional session protocol operation.
+   *
+   * @since 4.13
+   *
+   * @throws UnsupportedOperationException if the session protocol implementation does
+   *         not support requesting change of credentials
+   */
+  public void requestChangeServerPassword(AtomicReference<char[]> receiver);
+
+  /**
    * Requests that the server initiate the reset-credentials protocol.
    * This is an optional session protocol operation.
    *
@@ -383,6 +397,8 @@
 
     private long lastUpdateTime;
 
+    private long openingTime;
+
     private int tagModCount;
 
     private RepositoryTimeResult repositoryTimeResult;
@@ -430,6 +446,7 @@
 
       repositoryCreationTime = in.readXLong();
       lastUpdateTime = in.readXLong();
+      openingTime = in.readXLong();
       tagModCount = in.readXInt();
       rootResourceID = in.readCDOID();
       authenticating = in.readBoolean();
@@ -638,6 +655,14 @@
     }
 
     /**
+     * @since 4.13
+     */
+    public long getOpeningTime()
+    {
+      return openingTime;
+    }
+
+    /**
      * @since 3.0
      */
     public long getLastUpdateTime()
diff --git a/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/internal/ui/messages/messages.properties b/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/internal/ui/messages/messages.properties
index 2d0f663..d48eba1 100644
--- a/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/internal/ui/messages/messages.properties
+++ b/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/internal/ui/messages/messages.properties
@@ -25,7 +25,7 @@
 CredentialsDialog_0=Login
 CredentialsResetDialog_0=Reset Password
 CredentialsResetDialog_1=Provide your administrator login to reset the password for user "{0}".
-CredentialsUpdateDialog_0=Change Password
+CredentialsUpdateDialog_0=Change Server Password
 CredentialsUpdateDialog_1=Authenticate and enter your new password.
 CredentialsUpdateDialog_2=New Password:
 CredentialsUpdateDialog_3=Repeat Password:
diff --git a/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/security/CredentialsDialog.java b/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/security/CredentialsDialog.java
index 088b56d..f087a33 100644
--- a/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/security/CredentialsDialog.java
+++ b/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/security/CredentialsDialog.java
@@ -15,9 +15,11 @@
 import org.eclipse.net4j.util.internal.ui.messages.Messages;
 import org.eclipse.net4j.util.security.IPasswordCredentials;
 import org.eclipse.net4j.util.security.PasswordCredentials;
+import org.eclipse.net4j.util.security.SecurityUtil;
 import org.eclipse.net4j.util.ui.UIUtil;
 import org.eclipse.net4j.util.ui.widgets.BaseDialog;
 
+import org.eclipse.jface.dialogs.IDialogConstants;
 import org.eclipse.jface.dialogs.IDialogSettings;
 import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.swt.SWT;
@@ -50,7 +52,7 @@
 
   private final String realm;
 
-  private final List<String> users;
+  private List<String> users;
 
   private Control userIDControl;
 
@@ -79,7 +81,6 @@
     super(shell, DEFAULT_SHELL_STYLE | SWT.APPLICATION_MODAL, title, message, OM.Activator.INSTANCE.getDialogSettings(),
         OM.getImageDescriptor("icons/credentials_wiz.gif")); //$NON-NLS-1$
     this.realm = realm;
-    users = loadUsers();
   }
 
   /**
@@ -124,8 +125,17 @@
   }
 
   @Override
+  protected Control createButtonBar(Composite parent)
+  {
+    Control buttonBar = super.createButtonBar(parent);
+    updateOkButton();
+    return buttonBar;
+  }
+
+  @Override
   protected void createUI(Composite parent)
   {
+    users = loadUsers();
     createCredentialsArea(parent);
   }
 
@@ -161,12 +171,16 @@
   {
     if (users.isEmpty())
     {
-      return new Text(composite, SWT.BORDER);
+      Text text = new Text(composite, SWT.BORDER);
+      text.addModifyListener(e -> updateOkButton());
+
+      return text;
     }
 
     Combo combo = new Combo(composite, SWT.BORDER);
     combo.setItems(users.toArray(new String[users.size()]));
-    combo.setText(users.get(0));
+    combo.setText(getInitialUserID());
+    combo.addModifyListener(e -> updateOkButton());
 
     return combo;
   }
@@ -174,18 +188,10 @@
   @Override
   protected void okPressed()
   {
-    String userID;
-    if (userIDControl instanceof Combo)
-    {
-      userID = ((Combo)userIDControl).getText();
-    }
-    else
-    {
-      userID = ((Text)userIDControl).getText();
-    }
+    String userID = getUserID();
 
     String password = passwordControl.getText();
-    credentials = createCredentials(userID, password.toCharArray());
+    credentials = createCredentials(userID, SecurityUtil.toCharArray(password));
 
     users.remove(userID);
     users.add(0, userID);
@@ -232,6 +238,24 @@
     settings.put(key, users.toArray(new String[users.size()]));
   }
 
+  /**
+   * @since 3.10
+   */
+  protected String getInitialUserID()
+  {
+    return users.get(0);
+  }
+
+  private String getUserID()
+  {
+    if (userIDControl instanceof Combo)
+    {
+      return ((Combo)userIDControl).getText();
+    }
+
+    return ((Text)userIDControl).getText();
+  }
+
   private String getRealmKey()
   {
     String key = "realm";
@@ -247,4 +271,9 @@
   {
     return getDialogSettings("users");
   }
+
+  private void updateOkButton()
+  {
+    getButton(IDialogConstants.OK_ID).setEnabled(getUserID().length() != 0);
+  }
 }
diff --git a/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/security/CredentialsUpdateDialog.java b/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/security/CredentialsUpdateDialog.java
index fa3f2bc..30988f6 100644
--- a/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/security/CredentialsUpdateDialog.java
+++ b/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/security/CredentialsUpdateDialog.java
@@ -15,6 +15,7 @@
 import org.eclipse.net4j.util.security.IPasswordCredentials;
 import org.eclipse.net4j.util.security.IPasswordCredentialsUpdate;
 import org.eclipse.net4j.util.security.PasswordCredentialsUpdate;
+import org.eclipse.net4j.util.security.SecurityUtil;
 import org.eclipse.net4j.util.ui.UIUtil;
 
 import org.eclipse.jface.dialogs.IDialogConstants;
@@ -72,7 +73,7 @@
   protected IPasswordCredentials createCredentials(String userID, char[] password)
   {
     String newPassword = newPasswordControl.getText();
-    return new PasswordCredentialsUpdate(userID, password, newPassword.toCharArray());
+    return new PasswordCredentialsUpdate(userID, password, SecurityUtil.toCharArray(newPassword));
   }
 
   @Override
diff --git a/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/security/InteractiveCredentialsProvider.java b/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/security/InteractiveCredentialsProvider.java
index a7d9a74..09f1a75 100644
--- a/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/security/InteractiveCredentialsProvider.java
+++ b/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/security/InteractiveCredentialsProvider.java
@@ -17,6 +17,7 @@
 import org.eclipse.net4j.util.security.IPasswordCredentialsProvider2;
 import org.eclipse.net4j.util.security.IPasswordCredentialsUpdate;
 import org.eclipse.net4j.util.security.IPasswordCredentialsUpdateProvider;
+import org.eclipse.net4j.util.security.SecurityUtil;
 import org.eclipse.net4j.util.ui.UIUtil;
 
 import org.eclipse.jface.dialogs.IDialogConstants;
@@ -25,7 +26,6 @@
 import org.eclipse.swt.dnd.Clipboard;
 import org.eclipse.swt.dnd.TextTransfer;
 import org.eclipse.swt.dnd.Transfer;
-import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Shell;
 
 import java.text.MessageFormat;
@@ -56,20 +56,15 @@
    * @since 3.3
    */
   @Override
-  public IPasswordCredentials getCredentials(final String realm)
+  public IPasswordCredentials getCredentials(String realm)
   {
-    final IPasswordCredentials[] credentials = new IPasswordCredentials[1];
-    final Display display = UIUtil.getDisplay();
-    display.syncExec(new Runnable()
-    {
-      @Override
-      public void run()
+    IPasswordCredentials[] credentials = new IPasswordCredentials[1];
+
+    UIUtil.syncExec(() -> {
+      CredentialsDialog dialog = new CredentialsDialog(UIUtil.getShell(), realm);
+      if (dialog.open() == CredentialsDialog.OK)
       {
-        CredentialsDialog dialog = new CredentialsDialog(UIUtil.getShell(), realm);
-        if (dialog.open() == CredentialsDialog.OK)
-        {
-          credentials[0] = dialog.getCredentials();
-        }
+        credentials[0] = dialog.getCredentials();
       }
     });
 
@@ -89,73 +84,68 @@
    * @since 3.4
    */
   @Override
-  public IPasswordCredentialsUpdate getCredentialsUpdate(final String realm, final String userID, final CredentialsUpdateOperation operation)
+  public IPasswordCredentialsUpdate getCredentialsUpdate(String realm, String userID, CredentialsUpdateOperation operation)
   {
-    final IPasswordCredentialsUpdate[] update = { null };
-    final Display display = UIUtil.getDisplay();
-    display.syncExec(new Runnable()
-    {
-      @Override
-      public void run()
+    IPasswordCredentialsUpdate[] update = { null };
+
+    UIUtil.syncExec(() -> {
+      Shell shell = UIUtil.getShell();
+
+      if (operation == CredentialsUpdateOperation.CHANGE_PASSWORD)
       {
-        Shell shell = UIUtil.getShell();
-
-        if (operation == CredentialsUpdateOperation.CHANGE_PASSWORD)
+        CredentialsUpdateDialog dialog = new CredentialsUpdateDialog(shell, realm, userID);
+        if (dialog.open() == Window.OK)
         {
-          CredentialsUpdateDialog dialog = new CredentialsUpdateDialog(shell, realm, userID);
-          if (dialog.open() == Window.OK)
-          {
-            update[0] = dialog.getCredentials();
-          }
+          update[0] = dialog.getCredentials();
         }
-        else
+      }
+      else
+      {
+        CredentialsResetDialog dialog = new CredentialsResetDialog(shell, realm, userID);
+        if (dialog.open() == Window.OK)
         {
-          CredentialsResetDialog dialog = new CredentialsResetDialog(shell, realm, userID);
-          if (dialog.open() == Window.OK)
+          update[0] = dialog.getCredentials();
+          String newPassword = SecurityUtil.toString(update[0].getNewPassword());
+
+          MessageDialog msg = new MessageDialog(shell, Messages.getString("InteractiveCredentialsProvider.0"), null, //$NON-NLS-1$
+              MessageFormat.format(Messages.getString("InteractiveCredentialsProvider.1"), //$NON-NLS-1$
+                  userID, newPassword),
+              MessageDialog.INFORMATION, new String[] { Messages.getString("InteractiveCredentialsProvider.2"), //$NON-NLS-1$
+                  IDialogConstants.OK_LABEL },
+              0)
           {
-            update[0] = dialog.getCredentials();
-            final String newPassword = new String(update[0].getNewPassword());
 
-            MessageDialog msg = new MessageDialog(shell, Messages.getString("InteractiveCredentialsProvider.0"), null, //$NON-NLS-1$
-                MessageFormat.format(Messages.getString("InteractiveCredentialsProvider.1"), //$NON-NLS-1$
-                    userID, newPassword),
-                MessageDialog.INFORMATION, new String[] { Messages.getString("InteractiveCredentialsProvider.2"), //$NON-NLS-1$
-                    IDialogConstants.OK_LABEL },
-                0)
+            @Override
+            protected void buttonPressed(int buttonId)
             {
-
-              @Override
-              protected void buttonPressed(int buttonId)
+              if (buttonId == 0)
               {
-                if (buttonId == 0)
-                {
-                  copyToClipboard();
-                  // Don't close the dialog
-                }
-                else
-                {
-                  // Close the dialog in the usual way
-                  super.buttonPressed(IDialogConstants.OK_ID);
-                }
+                copyToClipboard();
+                // Don't close the dialog
               }
-
-              private void copyToClipboard()
+              else
               {
-                Clipboard clipboard = new Clipboard(getShell().getDisplay());
-
-                try
-                {
-                  clipboard.setContents(new Object[] { newPassword }, new Transfer[] { TextTransfer.getInstance() });
-                }
-                finally
-                {
-                  clipboard.dispose();
-                }
+                // Close the dialog in the usual way
+                super.buttonPressed(IDialogConstants.OK_ID);
               }
-            };
+            }
 
-            msg.open();
-          }
+            private void copyToClipboard()
+            {
+              Clipboard clipboard = new Clipboard(getShell().getDisplay());
+
+              try
+              {
+                clipboard.setContents(new Object[] { newPassword }, new Transfer[] { TextTransfer.getInstance() });
+              }
+              finally
+              {
+                clipboard.dispose();
+              }
+            }
+          };
+
+          msg.open();
         }
       }
     });
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/ReflectUtil.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/ReflectUtil.java
index 2858f0d..0c89342 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/ReflectUtil.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/ReflectUtil.java
@@ -40,6 +40,7 @@
  * Various static helper methods for dealing with Java reflection.
  *
  * @author Eike Stepper
+ * @since 3.14
  */
 public final class ReflectUtil
 {
@@ -115,7 +116,7 @@
     }
     catch (Exception ex)
     {
-      throw WrappedException.wrap(ex);
+      throw ReflectionException.wrap(ex);
     }
   }
 
@@ -127,10 +128,24 @@
     }
     catch (Exception ex)
     {
-      throw WrappedException.wrap(ex);
+      throw ReflectionException.wrap(ex);
     }
   }
 
+  /**
+   * @since 3.14
+   */
+  @SuppressWarnings("unchecked")
+  public static <T> T invokeMethod(String methodName, Object target)
+  {
+    if (target instanceof Class)
+    {
+      return (T)invokeMethod(getMethod((Class<?>)target, methodName), null);
+    }
+
+    return (T)invokeMethod(getMethod(target.getClass(), methodName), target);
+  }
+
   public static Field getField(Class<?> c, String fieldName)
   {
     try
@@ -164,7 +179,7 @@
     }
     catch (Exception ex)
     {
-      throw WrappedException.wrap(ex);
+      throw ReflectionException.wrap(ex);
     }
   }
 
@@ -214,7 +229,7 @@
     }
     catch (Exception ex)
     {
-      throw WrappedException.wrap(ex);
+      throw ReflectionException.wrap(ex);
     }
   }
 
@@ -226,10 +241,36 @@
     }
     catch (Exception ex)
     {
-      throw WrappedException.wrap(ex);
+      throw ReflectionException.wrap(ex);
     }
   }
 
+  /**
+   * @since 3.14
+   */
+  @SuppressWarnings("unchecked")
+  public static <T> T getValue(String fieldName, Object target)
+  {
+    if (target instanceof Class)
+    {
+      Field field = getField((Class<?>)target, fieldName);
+      if (field == null)
+      {
+        throw new ReflectionException("No such field: " + fieldName);
+      }
+
+      return (T)getValue(field, null);
+    }
+
+    Field field = getField(target.getClass(), fieldName);
+    if (field == null)
+    {
+      throw new ReflectionException("No such field: " + fieldName);
+    }
+
+    return (T)getValue(field, target);
+  }
+
   public static void setValue(Field field, Object target, Object value)
   {
     setValue(field, target, value, false);
@@ -251,7 +292,7 @@
     }
     catch (Exception ex)
     {
-      throw WrappedException.wrap(ex);
+      throw ReflectionException.wrap(ex);
     }
   }
 
@@ -755,10 +796,61 @@
       return type;
     }
 
+    /**
+     * @since 3.14
+     */
     public static PrimitiveType forClass(Class<?> clazz)
     {
       PrimitiveType result = INSTANCES.get(clazz);
       return result == null ? NONE : result;
     }
   }
+
+  /**
+   * @author Eike Stepper
+   * @since 3.14
+   */
+  public static class ReflectionException extends RuntimeException
+  {
+    private static final long serialVersionUID = 1L;
+
+    public ReflectionException()
+    {
+    }
+
+    public ReflectionException(String message, Exception cause)
+    {
+      super(message, cause);
+    }
+
+    public ReflectionException(String message)
+    {
+      super(message);
+    }
+
+    public ReflectionException(Exception cause)
+    {
+      super(cause);
+    }
+
+    public static ReflectionException wrap(Exception exception)
+    {
+      if (exception instanceof ReflectionException)
+      {
+        return (ReflectionException)exception;
+      }
+
+      return new ReflectionException(exception);
+    }
+
+    public static Exception unwrap(Exception exception)
+    {
+      if (exception instanceof ReflectionException)
+      {
+        return (Exception)((ReflectionException)exception).getCause();
+      }
+
+      return exception;
+    }
+  }
 }
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/container/IManagedContainer.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/container/IManagedContainer.java
index 415e6cc..b16ca24 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/container/IManagedContainer.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/container/IManagedContainer.java
@@ -82,6 +82,11 @@
   public Object getElement(String productGroup, String factoryType, String description, boolean activate)
       throws FactoryNotFoundException, ProductCreationException;
 
+  /**
+   * @since 3.14
+   */
+  public <T> T getElementOrNull(String productGroup, String factoryType, String description);
+
   public Object removeElement(String productGroup, String factoryType, String description);
 
   /**
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/container/ManagedContainer.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/container/ManagedContainer.java
index 483334c..caba6da 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/container/ManagedContainer.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/container/ManagedContainer.java
@@ -365,6 +365,20 @@
     return element;
   }
 
+  @Override
+  @SuppressWarnings("unchecked")
+  public <T> T getElementOrNull(String productGroup, String factoryType, String description)
+  {
+    try
+    {
+      return (T)getElement(productGroup, factoryType, description);
+    }
+    catch (FactoryNotFoundException | ProductCreationException ex)
+    {
+      return null;
+    }
+  }
+
   /**
    * @since 3.2
    */
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);
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/FileUserManager.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/FileUserManager.java
index d00017c..044e493 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/FileUserManager.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/FileUserManager.java
@@ -159,7 +159,7 @@
     for (Entry<Object, Object> entry : properties.entrySet())
     {
       String userID = (String)entry.getKey();
-      char[] password = ((String)entry.getValue()).toCharArray();
+      char[] password = SecurityUtil.toCharArray((String)entry.getValue());
       users.put(userID, password);
     }
   }
@@ -193,7 +193,7 @@
 
     for (Entry<String, char[]> entry : users.entrySet())
     {
-      properties.put(entry.getKey(), new String(entry.getValue()));
+      properties.put(entry.getKey(), SecurityUtil.toString(entry.getValue()));
     }
 
     String comment = MessageFormat.format("User database {0,date} {0,time,HH:mm:ss:SSS}", System.currentTimeMillis());
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/PasswordCredentials.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/PasswordCredentials.java
index 1ec4089..0cf5dd1 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/PasswordCredentials.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/PasswordCredentials.java
@@ -28,7 +28,7 @@
    */
   public PasswordCredentials(String userID, String password)
   {
-    this(userID, password.toCharArray());
+    this(userID, SecurityUtil.toCharArray(password));
   }
 
   /**
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/PasswordCredentialsProvider.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/PasswordCredentialsProvider.java
index adda247..1d35069 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/PasswordCredentialsProvider.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/PasswordCredentialsProvider.java
@@ -42,7 +42,7 @@
    */
   public PasswordCredentialsProvider(String userID, String password)
   {
-    this(userID, password == null ? null : password.toCharArray());
+    this(userID, SecurityUtil.toCharArray(password));
   }
 
   @Override
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/PasswordCredentialsUpdate.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/PasswordCredentialsUpdate.java
index e01791a..8ac1d0c 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/PasswordCredentialsUpdate.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/PasswordCredentialsUpdate.java
@@ -28,7 +28,7 @@
 
   public PasswordCredentialsUpdate(String userID, String password, String newPassword)
   {
-    this(userID, password.toCharArray(), newPassword.toCharArray());
+    this(userID, SecurityUtil.toCharArray(password), SecurityUtil.toCharArray(newPassword));
   }
 
   public PasswordCredentialsUpdate(String userID)
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/SecurityUtil.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/SecurityUtil.java
index ed8e8b0..3b86804 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/SecurityUtil.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/SecurityUtil.java
@@ -94,4 +94,30 @@
   {
     return pbeEncrypt(data, password, algorithmName, salt, count);
   }
+
+  /**
+   * @since 3.14
+   */
+  public static String toString(char[] chars)
+  {
+    if (chars == null || chars.length == 0)
+    {
+      return null;
+    }
+
+    return new String(chars);
+  }
+
+  /**
+   * @since 3.14
+   */
+  public static char[] toCharArray(String str)
+  {
+    if (str == null || str.length() == 0)
+    {
+      return null;
+    }
+
+    return str.toCharArray();
+  }
 }
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/UserManager.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/UserManager.java
index 05c1241..17b0605 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/UserManager.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/UserManager.java
@@ -68,12 +68,12 @@
 
     if (userPassword == null)
     {
-      throw new SecurityException("No such user: " + userID); //$NON-NLS-1$
+      throw new NotAuthenticatedException("No such user: " + userID); //$NON-NLS-1$
     }
 
     if (!Arrays.equals(userPassword, password))
     {
-      throw new SecurityException("Wrong password for user: " + userID); //$NON-NLS-1$
+      throw new NotAuthenticatedException("Wrong password for user: " + userID); //$NON-NLS-1$
     }
   }
 
diff --git a/plugins/org.eclipse.net4j/src/org/eclipse/net4j/signal/security/AuthenticationIndication.java b/plugins/org.eclipse.net4j/src/org/eclipse/net4j/signal/security/AuthenticationIndication.java
index 581640e..6454321 100644
--- a/plugins/org.eclipse.net4j/src/org/eclipse/net4j/signal/security/AuthenticationIndication.java
+++ b/plugins/org.eclipse.net4j/src/org/eclipse/net4j/signal/security/AuthenticationIndication.java
@@ -23,6 +23,7 @@
 import org.eclipse.net4j.util.security.DiffieHellman.Server.Challenge;
 import org.eclipse.net4j.util.security.IPasswordCredentials;
 import org.eclipse.net4j.util.security.IPasswordCredentialsProvider;
+import org.eclipse.net4j.util.security.SecurityUtil;
 
 import org.eclipse.internal.net4j.bundle.OM;
 
@@ -103,7 +104,7 @@
         throw new IllegalStateException("No userID provided"); //$NON-NLS-1$
       }
 
-      String password = new String(credentials.getPassword());
+      String password = SecurityUtil.toString(credentials.getPassword());
 
       ByteArrayOutputStream baos = new ByteArrayOutputStream();
       ExtendedDataOutputStream stream = new ExtendedDataOutputStream(baos);