[418454] [Admin] Client API and UI for managing repositories in a server
https://bugs.eclipse.org/bugs/show_bug.cgi?id=418454

UI for deletion of a repository, including confirmation dialog and error handling.
UI for creation of a repository.
Both actions require authorization by providing the credentials of the admin repository's administrator user.
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/admin/CDOAdmin.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/admin/CDOAdmin.java
index be08e1c..13d3bfa 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/admin/CDOAdmin.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/admin/CDOAdmin.java
@@ -29,6 +29,12 @@
  */
 public interface CDOAdmin extends IContainer<CDOAdminRepository>, Closeable
 {
+  /**
+   * The default admin-handler type, which is guaranteed to exist on the server.
+   * 
+   * @since 4.3
+   */
+  public static final String TYPE_DEFAULT = "default"; //$NON-NLS-1$
 
   /**
    * The name of the boolean property that indicates whether to configure the
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/.settings/org.eclipse.core.resources.prefs b/plugins/org.eclipse.emf.cdo.ui.admin/.settings/org.eclipse.core.resources.prefs
index dd2bd57..d950894 100644
--- a/plugins/org.eclipse.emf.cdo.ui.admin/.settings/org.eclipse.core.resources.prefs
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/.settings/org.eclipse.core.resources.prefs
@@ -1,3 +1,2 @@
-#Mon Jul 04 12:55:06 CEST 2011
 eclipse.preferences.version=1
-
+encoding//src/org/eclipse/emf/cdo/ui/internal/admin/messages/messages.properties=ISO-8859-1
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.cdo.ui.admin/META-INF/MANIFEST.MF
index 73c1b58..f85dfe7 100644
--- a/plugins/org.eclipse.emf.cdo.ui.admin/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/META-INF/MANIFEST.MF
@@ -16,4 +16,7 @@
  org.eclipse.emf.cdo.ui.shared;bundle-version="[4.0.0,5.0.0)",
  org.eclipse.net4j.ui.shared;bundle-version="[4.0.0,5.0.0)"
 Export-Package: org.eclipse.emf.cdo.ui.internal.admin;version="4.1.200";x-internal:=true,
- org.eclipse.emf.cdo.ui.internal.admin.bundle;version="4.1.200";x-internal:=true
+ org.eclipse.emf.cdo.ui.internal.admin.actions;version="4.1.200";x-internal:=true,
+ org.eclipse.emf.cdo.ui.internal.admin.bundle;version="4.1.200";x-internal:=true,
+ org.eclipse.emf.cdo.ui.internal.admin.messages;version="4.1.200";x-internal:=true,
+ org.eclipse.emf.cdo.ui.internal.admin.wizards;version="4.1.200";x-internal:=true
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/build.properties b/plugins/org.eclipse.emf.cdo.ui.admin/build.properties
index 3cf0877..549cade 100644
--- a/plugins/org.eclipse.emf.cdo.ui.admin/build.properties
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/build.properties
@@ -6,6 +6,7 @@
 #
 # Contributors:
 #    Eike Stepper - initial API and implementation
+#    Christian W. Damus (CEA LIST) - bug 418454
 
 # NLS_MESSAGEFORMAT_VAR
 
@@ -17,6 +18,7 @@
                about.html,\
                copyright.txt,\
                .options
+bin.excludes = icons/**/*.pxm
 jars.compile.order = .
 source.. = src/
 output.. = bin/
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/icons/full/ctool16/create_repo.gif b/plugins/org.eclipse.emf.cdo.ui.admin/icons/full/ctool16/create_repo.gif
new file mode 100644
index 0000000..2a3a2ba
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/icons/full/ctool16/create_repo.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/icons/full/ctool16/create_repo.pxm b/plugins/org.eclipse.emf.cdo.ui.admin/icons/full/ctool16/create_repo.pxm
new file mode 100644
index 0000000..fe244f6
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/icons/full/ctool16/create_repo.pxm
Binary files differ
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/icons/admin_view.gif b/plugins/org.eclipse.emf.cdo.ui.admin/icons/full/view16/admin_view.gif
similarity index 100%
rename from plugins/org.eclipse.emf.cdo.ui.admin/icons/admin_view.gif
rename to plugins/org.eclipse.emf.cdo.ui.admin/icons/full/view16/admin_view.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/icons/full/wizban/new_repo.png b/plugins/org.eclipse.emf.cdo.ui.admin/icons/full/wizban/new_repo.png
new file mode 100644
index 0000000..a5b302f
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/icons/full/wizban/new_repo.png
Binary files differ
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/icons/full/wizban/new_repo.pxm b/plugins/org.eclipse.emf.cdo.ui.admin/icons/full/wizban/new_repo.pxm
new file mode 100644
index 0000000..91ce322
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/icons/full/wizban/new_repo.pxm
Binary files differ
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/plugin.xml b/plugins/org.eclipse.emf.cdo.ui.admin/plugin.xml
index ac7059f..be4092a 100644
--- a/plugins/org.eclipse.emf.cdo.ui.admin/plugin.xml
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/plugin.xml
@@ -9,6 +9,7 @@
 
 	Contributors:
 	  Eike Stepper - initial API and implementation
+	  Christian W. Damus (CEA LIST) - bug 418454
 -->
 
 <plugin>
@@ -18,7 +19,7 @@
       <view
             category="org.eclipse.emf.cdo"
             class="org.eclipse.emf.cdo.ui.internal.admin.CDOAdminView"
-            icon="icons/admin_view.gif"
+            icon="icons/full/view16/admin_view.gif"
             id="org.eclipse.emf.cdo.ui.admin.CDOAdminView"
             name="%view.name.0">
       </view>
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/CDOAdminView.java b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/CDOAdminView.java
index 0bee3e9..f833025 100644
--- a/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/CDOAdminView.java
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/CDOAdminView.java
@@ -18,20 +18,21 @@
 import org.eclipse.emf.cdo.common.model.CDOPackageRegistryPopulator;
 import org.eclipse.emf.cdo.net4j.CDONet4jSession;
 import org.eclipse.emf.cdo.net4j.CDONet4jSessionConfiguration;
+import org.eclipse.emf.cdo.ui.internal.admin.actions.AdminAction;
+import org.eclipse.emf.cdo.ui.internal.admin.actions.CreateRepositoryAction;
+import org.eclipse.emf.cdo.ui.internal.admin.actions.DeleteRepositoryAction;
 import org.eclipse.emf.cdo.ui.internal.admin.bundle.OM;
+import org.eclipse.emf.cdo.ui.internal.admin.messages.Messages;
 import org.eclipse.emf.cdo.ui.shared.SharedIcons;
 
 import org.eclipse.emf.internal.cdo.session.CDOSessionFactory;
 
-import org.eclipse.net4j.signal.RemoteException;
 import org.eclipse.net4j.ui.Net4jItemProvider.RemoveAction;
 import org.eclipse.net4j.util.container.IContainer;
 import org.eclipse.net4j.util.container.IManagedContainer;
 import org.eclipse.net4j.util.security.CredentialsProviderFactory;
 import org.eclipse.net4j.util.security.IPasswordCredentialsProvider;
-import org.eclipse.net4j.util.security.NotAuthenticatedException;
 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;
 
@@ -47,6 +48,8 @@
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.Image;
 
+import java.text.MessageFormat;
+
 /**
  * @author Eike Stepper
  */
@@ -92,7 +95,8 @@
         if (obj instanceof CDOAdminRepository)
         {
           CDOAdminRepository repository = (CDOAdminRepository)obj;
-          return repository.getName() + " [" + repository.getType() + ", " + repository.getState() + "]";
+          return MessageFormat.format(Messages.CDOAdminView_0, repository.getName(), repository.getType(),
+              repository.getState());
         }
 
         return super.getText(obj);
@@ -160,12 +164,14 @@
       if (obj instanceof CDOAdminClient)
       {
         CDOAdminClient admin = (CDOAdminClient)obj;
+        manager.add(new CreateRepositoryAction(admin));
         manager.add(new RemoveConnectionAction(adminManager, admin));
       }
       else if (obj instanceof CDOAdminClientRepository)
       {
         CDOAdminClientRepository repository = (CDOAdminClientRepository)obj;
         manager.add(new OpenSessionAction(repository));
+        manager.add(new DeleteRepositoryAction(repository));
       }
     }
   }
@@ -181,7 +187,7 @@
         public void run()
         {
           String lastURL = OM.getLastURL();
-          InputDialog dialog = new InputDialog(getShell(), getText(), "Enter the connection URL:", lastURL, null);
+          InputDialog dialog = new InputDialog(getShell(), getText(), Messages.CDOAdminView_1, lastURL, null);
           if (dialog.open() == InputDialog.OK)
           {
             String url = dialog.getValue();
@@ -191,8 +197,8 @@
         }
       };
 
-      addConnectionAction.setText("Add Connection");
-      addConnectionAction.setToolTipText("Add a new connection");
+      addConnectionAction.setText(Messages.CDOAdminView_2);
+      addConnectionAction.setToolTipText(Messages.CDOAdminView_3);
       addConnectionAction.setImageDescriptor(org.eclipse.net4j.ui.shared.SharedIcons
           .getDescriptor(org.eclipse.net4j.ui.shared.SharedIcons.ETOOL_ADD));
     }
@@ -205,7 +211,7 @@
   {
     IManagedContainer container = adminManager.getContainer();
     String productGroup = CredentialsProviderFactory.PRODUCT_GROUP;
-    String factoryType = "interactive";
+    String factoryType = "interactive"; //$NON-NLS-1$
     IPasswordCredentialsProvider credentialsProvider = (IPasswordCredentialsProvider)container.getElement(productGroup,
         factoryType, null);
 
@@ -256,58 +262,31 @@
   /**
    * @author Eike Stepper
    */
-  public class OpenSessionAction extends LongRunningAction implements CDOAdminClientRepository.SessionConfigurator
+  public class OpenSessionAction extends AdminAction<CDOAdminClientRepository> implements
+      CDOAdminClientRepository.SessionConfigurator
   {
-    private CDOAdminClientRepository repository;
-
     public OpenSessionAction(CDOAdminClientRepository repository)
     {
-      super("Open Session", "Open a new session to this repository", SharedIcons
-          .getDescriptor(SharedIcons.ETOOL_OPEN_SESSION));
-      this.repository = repository;
+      super(Messages.CDOAdminView_4, Messages.CDOAdminView_5,
+          SharedIcons.getDescriptor(SharedIcons.ETOOL_OPEN_SESSION), repository);
     }
 
     public CDOAdminClientRepository getRepository()
     {
-      return repository;
+      return target;
     }
 
     @Override
-    protected void doRun(IProgressMonitor progressMonitor) throws Exception
+    protected void safeRun(IProgressMonitor progressMonitor) throws Exception
     {
-      try
+      CDONet4jSession session = target.openSession(this);
+      if (session != null)
       {
-        CDONet4jSession session = repository.openSession(this);
-        if (session != null)
-        {
-          CDOPackageRegistryPopulator.populate(session.getPackageRegistry());
+        CDOPackageRegistryPopulator.populate(session.getPackageRegistry());
 
-          IManagedContainer container = adminManager.getContainer();
-          String description = "session" + getNextSessionNumber();
-          container.putElement(CDOSessionFactory.PRODUCT_GROUP, "admin", description, session);
-        }
-      }
-      catch (RemoteException ex)
-      {
-        if (ex.getCause() instanceof NotAuthenticatedException)
-        {
-          // Skip silently because user has canceled the authentication
-        }
-        else
-        {
-          throw ex;
-        }
-      }
-      catch (Exception ex)
-      {
-        if (ex instanceof NotAuthenticatedException)
-        {
-          // Skip silently because user has canceled the authentication
-        }
-        else
-        {
-          throw ex;
-        }
+        IManagedContainer container = adminManager.getContainer();
+        String description = "session" + getNextSessionNumber(); //$NON-NLS-1$
+        container.putElement(CDOSessionFactory.PRODUCT_GROUP, "admin", description, session); //$NON-NLS-1$
       }
     }
 
@@ -316,5 +295,11 @@
       IPasswordCredentialsProvider credentialsProvider = getCredentialsProvider();
       configuration.setCredentialsProvider(credentialsProvider);
     }
+
+    @Override
+    protected String getErrorPattern()
+    {
+      return Messages.CDOAdminView_6;
+    }
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/StoreType.java b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/StoreType.java
new file mode 100644
index 0000000..187e4b9
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/StoreType.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2013 Eike Stepper (Berlin, 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:
+ *    Christian W. Damus (CEA LIST) - initial API and implementation
+ */
+package org.eclipse.emf.cdo.ui.internal.admin;
+
+import org.eclipse.emf.cdo.ui.internal.admin.messages.Messages;
+
+import org.eclipse.net4j.util.StringUtil;
+import org.eclipse.net4j.util.io.IOUtil;
+
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Christian W. Damus (CEA LIST)
+ */
+public abstract class StoreType
+{
+  private static final List<StoreType> INSTANCES = Collections.unmodifiableList(Arrays.asList(initStoreTypes()));
+
+  private final String id;
+
+  private final String name;
+
+  public StoreType(String id, String name)
+  {
+    this.id = id;
+    this.name = name;
+  }
+
+  public static List<StoreType> getInstances()
+  {
+    return INSTANCES;
+  }
+
+  public String getID()
+  {
+    return id;
+  }
+
+  public String getName()
+  {
+    return name;
+  }
+
+  public abstract String getStoreTypeID();
+
+  public String getStoreXML(Map<String, Object> storeProperties)
+  {
+    String template = getTemplate();
+    return fillTemplate(template, storeProperties);
+  }
+
+  protected abstract String fillTemplate(String xmlTemplate, Map<String, Object> storeProperties);
+
+  protected String replaceVariables(String template, Map<String, String> variables)
+  {
+    String result = template;
+    for (Map.Entry<String, String> entry : variables.entrySet())
+    {
+      result = result.replace("{{" + entry.getKey() + "}}", entry.getValue()); //$NON-NLS-1$ //$NON-NLS-2$
+    }
+    return result;
+  }
+
+  private String getTemplate()
+  {
+    URL url = getClass().getResource(getStoreTypeID() + ".template.xml"); //$NON-NLS-1$
+    return IOUtil.readText(url);
+  }
+
+  @Override
+  public String toString()
+  {
+    return getName();
+  }
+
+  private static StoreType[] initStoreTypes()
+  {
+    // TODO: Make these contributable by store UI plug-ins
+    return new StoreType[] { new Database("h2", Messages.StoreType_0, "org.h2.jdbcx.JdbcDataSource", "jdbc:h2:%s"), //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+    };
+  }
+
+  /**
+   * @author Christian W. Damus (CEA LIST)
+   */
+  public static class Database extends StoreType
+  {
+    public static final String PROPERTY_PATH = "path"; //$NON-NLS-1$
+
+    public static final String PROPERTY_CONNECTION_KEEP_ALIVE_PERIOD = "connectionKeepAlivePeriod"; //$NON-NLS-1$
+
+    public static final String DEFAULT_CONNECTION_KEEP_ALIVE_PERIOD = "60"; //$NON-NLS-1$
+
+    public static final String PROPERTY_READER_POOL_CAPACITY = "readerPoolCapacity"; //$NON-NLS-1$
+
+    public static final String DEFAULT_READER_POOL_CAPACITY = "15"; //$NON-NLS-1$
+
+    public static final String PROPERTY_WRITER_POOL_CAPACITY = "writerPoolCapacity"; //$NON-NLS-1$
+
+    public static final String DEFAULT_WRITER_POOL_CAPACITY = "15"; //$NON-NLS-1$
+
+    private final String adapter;
+
+    private final String dataSourceClassName;
+
+    private final String urlPattern;
+
+    public Database(String id, String name, String dataSourceClassName, String urlPattern)
+    {
+      super("db." + id, name); //$NON-NLS-1$
+      adapter = id;
+      this.dataSourceClassName = dataSourceClassName;
+      this.urlPattern = urlPattern;
+    }
+
+    @Override
+    public String getStoreTypeID()
+    {
+      return "db"; //$NON-NLS-1$
+    }
+
+    public String getAdapter()
+    {
+      return adapter;
+    }
+
+    public String getDataSourceClassName()
+    {
+      return dataSourceClassName;
+    }
+
+    public String getDataSourceURL(String storePath)
+    {
+      return String.format(urlPattern, storePath);
+    }
+
+    @Override
+    protected String fillTemplate(String xmlTemplate, Map<String, Object> storeProperties)
+    {
+      Map<String, String> variables = new java.util.HashMap<String, String>();
+      variables.put("adapter", getAdapter()); //$NON-NLS-1$
+      variables.put("dataSource.class", getDataSourceClassName()); //$NON-NLS-1$
+      variables.put("dataSource.url", getDataSourceURL((String)storeProperties.get(PROPERTY_PATH))); //$NON-NLS-1$
+      variables.put("keepAlive", //$NON-NLS-1$
+          defaultString(storeProperties, PROPERTY_CONNECTION_KEEP_ALIVE_PERIOD, DEFAULT_CONNECTION_KEEP_ALIVE_PERIOD));
+      variables.put("readerPool", //$NON-NLS-1$
+          defaultString(storeProperties, PROPERTY_READER_POOL_CAPACITY, DEFAULT_READER_POOL_CAPACITY));
+      variables.put("writerPool", //$NON-NLS-1$
+          defaultString(storeProperties, PROPERTY_WRITER_POOL_CAPACITY, DEFAULT_WRITER_POOL_CAPACITY));
+      return replaceVariables(xmlTemplate, variables);
+    }
+
+    private String defaultString(Map<String, Object> storeProperties, String key, String defaultValue)
+    {
+      String value = (String)storeProperties.get(key);
+      return StringUtil.isEmpty(value) ? defaultValue : value;
+    }
+  }
+}
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/actions/AdminAction.java b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/actions/AdminAction.java
new file mode 100644
index 0000000..ff43f00
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/actions/AdminAction.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2012-2013 Eike Stepper (Berlin, 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
+ *    Christian W. Damus (CEA LIST) - bug 418454 
+ */
+package org.eclipse.emf.cdo.ui.internal.admin.actions;
+
+import org.eclipse.emf.cdo.ui.internal.admin.bundle.OM;
+
+import org.eclipse.net4j.signal.RemoteException;
+import org.eclipse.net4j.util.security.NotAuthenticatedException;
+import org.eclipse.net4j.util.ui.actions.LongRunningAction;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.resource.ImageDescriptor;
+
+import java.text.MessageFormat;
+
+/**
+ * @param <T> the type of the target element of the action in the CDO Administration View
+ * 
+ * @author Christian W. Damus (CEA LIST)
+ */
+public abstract class AdminAction<T> extends LongRunningAction
+{
+  protected final T target;
+
+  protected AdminAction(String label, String tooltip, ImageDescriptor imageDescriptor, T target)
+  {
+    super(label, tooltip, imageDescriptor);
+    this.target = target;
+  }
+
+  @Override
+  protected final void doRun(IProgressMonitor progressMonitor) throws Exception
+  {
+    try
+    {
+      safeRun(progressMonitor);
+    }
+    catch (RemoteException e)
+    {
+      handleError(e.getCause() == null ? e : e.getCause());
+    }
+    catch (Exception e)
+    {
+      handleError(e);
+    }
+  }
+
+  protected abstract void safeRun(IProgressMonitor progressMonitor) throws Exception;
+
+  protected void handleError(Throwable ex)
+  {
+    if (ex instanceof NotAuthenticatedException)
+    {
+      // Skip silently because user has canceled the authentication
+    }
+    else
+    {
+      showError(ex);
+    }
+  }
+
+  protected void showError(final Throwable e)
+  {
+    OM.LOG.error(e);
+    getDisplay().asyncExec(new Runnable()
+    {
+      public void run()
+      {
+        MessageDialog.openError(getShell(), getText(), MessageFormat.format(getErrorPattern(), e.getLocalizedMessage()));
+      }
+    });
+  }
+
+  protected abstract String getErrorPattern();
+}
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/actions/CreateRepositoryAction.java b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/actions/CreateRepositoryAction.java
new file mode 100644
index 0000000..3c969b1
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/actions/CreateRepositoryAction.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2012-2013 Eike Stepper (Berlin, 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:
+ *    Christian W. Damus (CEA LIST) - initial API and implementation
+ */
+package org.eclipse.emf.cdo.ui.internal.admin.actions;
+
+import org.eclipse.emf.cdo.admin.CDOAdminClient;
+import org.eclipse.emf.cdo.common.admin.CDOAdmin;
+import org.eclipse.emf.cdo.ui.internal.admin.bundle.OM;
+import org.eclipse.emf.cdo.ui.internal.admin.messages.Messages;
+import org.eclipse.emf.cdo.ui.internal.admin.wizards.CreateRepositoryWizard;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.window.Window;
+import org.eclipse.jface.wizard.WizardDialog;
+
+import java.util.Map;
+
+/**
+ * @author Christian W. Damus (CEA LIST)
+ */
+public class CreateRepositoryAction extends AdminAction<CDOAdminClient>
+{
+  private String repositoryName;
+
+  private Map<String, Object> repositoryProperties;
+
+  public CreateRepositoryAction(CDOAdminClient admin)
+  {
+    super(Messages.CreateRepositoryAction_0, Messages.CreateRepositoryAction_1, OM
+        .getImageDescriptor("icons/full/ctool16/create_repo.gif"), admin); //$NON-NLS-1$
+  }
+
+  @Override
+  protected void preRun() throws Exception
+  {
+    CreateRepositoryWizard wizard = new CreateRepositoryWizard();
+    WizardDialog wizardDialog = new WizardDialog(getShell(), wizard);
+    if (wizardDialog.open() == Window.OK)
+    {
+      repositoryName = wizard.getRepositoryName();
+      repositoryProperties = wizard.getRepositoryProperties();
+    }
+    else
+    {
+      cancel();
+    }
+  }
+
+  @Override
+  protected void safeRun(IProgressMonitor progressMonitor) throws Exception
+  {
+    try
+    {
+      target.createRepository(repositoryName, CDOAdmin.TYPE_DEFAULT, repositoryProperties);
+    }
+    finally
+    {
+      repositoryName = null;
+      repositoryProperties = null;
+    }
+  }
+
+  @Override
+  protected String getErrorPattern()
+  {
+    return Messages.CreateRepositoryAction_2;
+  }
+}
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/actions/DeleteRepositoryAction.java b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/actions/DeleteRepositoryAction.java
new file mode 100644
index 0000000..b712b34
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/actions/DeleteRepositoryAction.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2012-2013 Eike Stepper (Berlin, 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:
+ *    Christian W. Damus (CEA LIST) - initial API and implementation
+ */
+package org.eclipse.emf.cdo.ui.internal.admin.actions;
+
+import org.eclipse.emf.cdo.admin.CDOAdminClientRepository;
+import org.eclipse.emf.cdo.common.admin.CDOAdmin;
+import org.eclipse.emf.cdo.ui.internal.admin.messages.Messages;
+
+import org.eclipse.net4j.ui.shared.SharedIcons;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.MessageDialog;
+
+import java.text.MessageFormat;
+
+/**
+ * @author Christian W. Damus (CEA LIST)
+ */
+public class DeleteRepositoryAction extends AdminAction<CDOAdminClientRepository>
+{
+  public DeleteRepositoryAction(CDOAdminClientRepository repository)
+  {
+    super(Messages.DeleteRepositoryAction_1, Messages.DeleteRepositoryAction_2, SharedIcons
+        .getDescriptor(SharedIcons.ETOOL_DELETE), repository);
+  }
+
+  @Override
+  protected void preRun() throws Exception
+  {
+    final int NO_BUTTON = 1;
+    String message = MessageFormat.format(Messages.DeleteRepositoryAction_4, target.getName());
+    MessageDialog dlg = new MessageDialog(getShell(), getText(), null, message, MessageDialog.WARNING, new String[] {
+        IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL }, NO_BUTTON);
+    if (dlg.open() == NO_BUTTON)
+    {
+      cancel();
+    }
+  }
+
+  @Override
+  protected void safeRun(IProgressMonitor progressMonitor) throws Exception
+  {
+    target.delete(CDOAdmin.TYPE_DEFAULT);
+  }
+
+  @Override
+  protected String getErrorPattern()
+  {
+    return Messages.DeleteRepositoryAction_3;
+  }
+}
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/bundle/OM.java b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/bundle/OM.java
index c525aad..fc236a1 100644
--- a/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/bundle/OM.java
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/bundle/OM.java
@@ -8,6 +8,7 @@
  * Contributors:
  *    Eike Stepper - initial API and implementation
  *    Victor Roldan Betancort - maintenance
+ *    Christian W. Damus (CEA LIST) - bug 418454
  */
 package org.eclipse.emf.cdo.ui.internal.admin.bundle;
 
@@ -69,6 +70,8 @@
    */
   public static final class Activator extends UIActivator.WithState
   {
+    public static Activator INSTANCE;
+
     public Activator()
     {
       super(BUNDLE);
@@ -77,6 +80,8 @@
     @Override
     protected void doStartWithState(Object state) throws Exception
     {
+      INSTANCE = this;
+
       LifecycleUtil.activate(adminManager);
       if (state instanceof List<?>)
       {
@@ -98,6 +103,9 @@
       urls.add(0, lastURL);
 
       LifecycleUtil.deactivate(adminManager);
+
+      INSTANCE = null;
+
       return urls;
     }
   }
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/db.template.xml b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/db.template.xml
new file mode 100644
index 0000000..bc3b27a
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/db.template.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<store type="db">
+
+  <property name="connectionKeepAlivePeriod" value="{{keepAlive}}"/>
+  <property name="readerPoolCapacity" value="{{readerPool}}"/>
+  <property name="writerPoolCapacity" value="{{writerPool}}"/>
+
+  <mappingStrategy type="horizontal">
+    <property name="qualifiedNames" value="true"/>
+  </mappingStrategy>
+
+  <dbAdapter name="{{adapter}}"/>
+  <dataSource
+      class="{{dataSource.class}}"
+      uRL="{{dataSource.url}}"/>
+
+</store>
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/messages/Messages.java b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/messages/Messages.java
new file mode 100644
index 0000000..251f2b8
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/messages/Messages.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2004-2013 Eike Stepper (Berlin, 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:
+ *    Christian W. Damus (CEA LIST) - initial API and implementation
+ */
+package org.eclipse.emf.cdo.ui.internal.admin.messages;
+
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * @author Christian W. Damus (CEA LIST)
+ */
+public class Messages extends NLS
+{
+  private static final String BUNDLE_NAME = "org.eclipse.emf.cdo.ui.internal.admin.messages.messages"; //$NON-NLS-1$
+
+  public static String CDOAdminView_0;
+
+  public static String CDOAdminView_1;
+
+  public static String CDOAdminView_2;
+
+  public static String CDOAdminView_3;
+
+  public static String CDOAdminView_4;
+
+  public static String CDOAdminView_5;
+
+  public static String CDOAdminView_6;
+
+  public static String CreateRepositoryAction_0;
+
+  public static String CreateRepositoryAction_1;
+
+  public static String CreateRepositoryAction_2;
+
+  public static String CreateRepositoryWizard_0;
+
+  public static String CreateRepositoryGeneralPage_0;
+
+  public static String CreateRepositoryGeneralPage_1;
+
+  public static String CreateRepositoryGeneralPage_10;
+
+  public static String CreateRepositoryGeneralPage_11;
+
+  public static String CreateRepositoryGeneralPage_12;
+
+  public static String CreateRepositoryGeneralPage_13;
+
+  public static String CreateRepositoryGeneralPage_14;
+
+  public static String CreateRepositoryGeneralPage_15;
+
+  public static String CreateRepositoryGeneralPage_2;
+
+  public static String CreateRepositoryGeneralPage_3;
+
+  public static String CreateRepositoryGeneralPage_4;
+
+  public static String CreateRepositoryGeneralPage_5;
+
+  public static String CreateRepositoryGeneralPage_6;
+
+  public static String CreateRepositoryGeneralPage_7;
+
+  public static String CreateRepositoryGeneralPage_8;
+
+  public static String CreateRepositoryGeneralPage_9;
+
+  public static String CreateRepositoryStorePage_0;
+
+  public static String CreateRepositoryStorePage_1;
+
+  public static String CreateRepositoryStorePage_10;
+
+  public static String CreateRepositoryStorePage_11;
+
+  public static String CreateRepositoryStorePage_12;
+
+  public static String CreateRepositoryStorePage_2;
+
+  public static String CreateRepositoryStorePage_3;
+
+  public static String CreateRepositoryStorePage_4;
+
+  public static String CreateRepositoryStorePage_5;
+
+  public static String CreateRepositoryStorePage_6;
+
+  public static String CreateRepositoryStorePage_7;
+
+  public static String CreateRepositoryStorePage_8;
+
+  public static String CreateRepositoryStorePage_9;
+
+  public static String DeleteRepositoryAction_1;
+
+  public static String DeleteRepositoryAction_2;
+
+  public static String DeleteRepositoryAction_3;
+
+  public static String DeleteRepositoryAction_4;
+
+  public static String StoreType_0;
+
+  static
+  {
+    // initialize resource bundle
+    NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+  }
+
+  private Messages()
+  {
+  }
+}
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/messages/messages.properties b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/messages/messages.properties
new file mode 100644
index 0000000..58ca552
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/messages/messages.properties
@@ -0,0 +1,55 @@
+# Copyright (c) 2012-2013 Eike Stepper (Berlin, 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
+#    Christian W. Damus (CEA) - bug 418454
+
+CDOAdminView_0={0} [{1}, {2}]
+CDOAdminView_1=Enter the connection URL:
+CDOAdminView_2=Add Connection
+CDOAdminView_3=Add a new connection
+CDOAdminView_4=Open Session
+CDOAdminView_5=Open a new session to this repository
+CDOAdminView_6=Could not open a session: {0}
+CreateRepositoryAction_0=Create Repository...
+CreateRepositoryAction_1=Create a repository on the server
+CreateRepositoryAction_2=Could not create repository: {0}
+CreateRepositoryWizard_0=Create Repository
+CreateRepositoryGeneralPage_0=Enter the configuration details of the new repository.
+CreateRepositoryGeneralPage_1=Name:
+CreateRepositoryGeneralPage_10=Ensure referential integrity
+CreateRepositoryGeneralPage_11=Allow running queries to be interrupted
+CreateRepositoryGeneralPage_12=Override UUID:
+CreateRepositoryGeneralPage_13=ID generation:
+CreateRepositoryGeneralPage_14=Security Manager
+CreateRepositoryGeneralPage_15=Store
+CreateRepositoryGeneralPage_2=Enable security management
+CreateRepositoryGeneralPage_3=Automatic home folders for users
+CreateRepositoryGeneralPage_4=Store type:
+CreateRepositoryGeneralPage_5=General Settings
+CreateRepositoryGeneralPage_6=Repository Properties
+CreateRepositoryGeneralPage_7=Enable auditing (version history)
+CreateRepositoryGeneralPage_8=Enable branching
+CreateRepositoryGeneralPage_9=Enable storage of Ecore models
+CreateRepositoryStorePage_0=Store path:
+CreateRepositoryStorePage_1={0} Store Settings
+CreateRepositoryStorePage_10=The connection keep-alive period must be a positive integer.
+CreateRepositoryStorePage_11=The reader pool size must be a positive integer.
+CreateRepositoryStorePage_12=The writer pool size must be a positive integer.
+CreateRepositoryStorePage_2=Enter configuration parameters for the repository store.
+CreateRepositoryStorePage_3=General Properties
+CreateRepositoryStorePage_4=Performance Tuning
+CreateRepositoryStorePage_5=Keep-alive period (minutes):
+CreateRepositoryStorePage_6=Reader pool size:
+CreateRepositoryStorePage_7=Writer pool size:
+CreateRepositoryStorePage_8=The store path is required.
+CreateRepositoryStorePage_9=The connection keep-alive period is required.
+DeleteRepositoryAction_1=Delete Repository
+DeleteRepositoryAction_2=Remove this repository from the server
+DeleteRepositoryAction_3=Could not delete repository: {0}
+DeleteRepositoryAction_4=This action shuts down and removes the repository "{0}" on the server.  It cannot be undone.  You must provide your administrator credentials to complete this action.  Are you certain you want to delete this repository?
+StoreType_0=H2 Database
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/wizards/AbstractCreateRepositoryWizardPage.java b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/wizards/AbstractCreateRepositoryWizardPage.java
new file mode 100644
index 0000000..da1bc92
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/wizards/AbstractCreateRepositoryWizardPage.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2013 Eike Stepper (Berlin, 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:
+ *    Christian W. Damus (CEA LIST) - initial API and implementation
+ */
+package org.eclipse.emf.cdo.ui.internal.admin.wizards;
+
+import org.eclipse.emf.cdo.ui.internal.admin.bundle.OM;
+
+import org.eclipse.net4j.util.ui.UIUtil;
+
+import org.eclipse.jface.dialogs.DialogSettings;
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.ComboViewer;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Text;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Christian W. Damus (CEA LIST)
+ */
+public abstract class AbstractCreateRepositoryWizardPage extends WizardPage
+{
+  private Whiteboard whiteboard;
+
+  public AbstractCreateRepositoryWizardPage(String pageName)
+  {
+    super(pageName);
+  }
+
+  public void createControl(Composite parent)
+  {
+    Composite composite = new Composite(parent, SWT.NONE);
+    composite.setLayout(UIUtil.createGridLayout(2));
+    createContents(composite);
+    setControl(composite);
+
+    loadSettings(getDialogSettings());
+    hookListeners();
+  }
+
+  protected abstract void createContents(Composite parent);
+
+  protected void hookListeners()
+  {
+    hookListeners(new Listener()
+    {
+
+      public void handleEvent(Event event)
+      {
+        updateEnablement(false);
+      }
+    });
+
+    updateEnablement(true);
+  }
+
+  protected void hookListeners(Listener updateListener)
+  {
+  }
+
+  protected void updateEnablement(boolean firstTime)
+  {
+    setPageComplete(true);
+  }
+
+  boolean performFinish(Map<String, Object> repositoryProperties)
+  {
+    boolean result = collectRepositoryProperties(repositoryProperties);
+    saveSettings(getDialogSettings());
+    return result;
+  }
+
+  protected abstract boolean collectRepositoryProperties(Map<String, Object> repositoryProperties);
+
+  protected void loadSettings(IDialogSettings pageSettings)
+  {
+  }
+
+  @Override
+  protected IDialogSettings getDialogSettings()
+  {
+    IDialogSettings wizardSettings = super.getDialogSettings();
+
+    return wizardSettings == null ? null : DialogSettings.getOrCreateSection(wizardSettings, getName());
+  }
+
+  protected String getSetting(IDialogSettings pageSettings, String key, String defaultValue)
+  {
+    return pageSettings.get(key) == null ? defaultValue : pageSettings.get(key);
+  }
+
+  protected boolean getSetting(IDialogSettings pageSettings, String key, boolean defaultValue)
+  {
+    return pageSettings.get(key) == null ? defaultValue : pageSettings.getBoolean(key);
+  }
+
+  protected void saveSettings(IDialogSettings pageSettings)
+  {
+  }
+
+  protected Group group(Composite parent, String label)
+  {
+    Group result = new Group(parent, SWT.BORDER);
+    result.setText(label);
+    result.setLayoutData(UIUtil.createGridData(true, false));
+    result.setLayout(new GridLayout(2, false));
+    return result;
+  }
+
+  protected Text text(Composite parent, String label)
+  {
+    new Label(parent, SWT.NONE).setText(label);
+    Text result = new Text(parent, SWT.BORDER);
+    result.setLayoutData(UIUtil.createGridData(true, false));
+    return result;
+  }
+
+  protected Button checkbox(Composite parent, String label)
+  {
+    Button result = new Button(parent, SWT.CHECK);
+    result.setText(label);
+    result.setLayoutData(UIUtil.createGridData(2, 1));
+    return result;
+  }
+
+  protected ComboViewer combo(Composite parent, String label, Object input)
+  {
+    new Label(parent, SWT.NONE).setText(label);
+    ComboViewer result = new ComboViewer(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
+    result.setContentProvider(new ArrayContentProvider());
+    result.setInput(input);
+    return result;
+  }
+
+  protected boolean checked(Button checkbox)
+  {
+    return checkbox.isEnabled() && checkbox.getSelection();
+  }
+
+  protected String text(Text text)
+  {
+    return text.getText().trim();
+  }
+
+  protected boolean positiveInteger(String value)
+  {
+    try
+    {
+      return Integer.parseInt(value) > 0;
+    }
+    catch (NumberFormatException e)
+    {
+      return false;
+    }
+  }
+
+  protected final void publish(Object topic)
+  {
+    if (whiteboard != null && topic != null)
+    {
+      whiteboard.publish(topic);
+    }
+  }
+
+  final void bind(Whiteboard whiteboard)
+  {
+    if (this.whiteboard != null)
+    {
+      throw new IllegalStateException("already bound to a whiteboard"); //$NON-NLS-1$
+    }
+    this.whiteboard = whiteboard;
+    whiteboard.subscribe(this);
+  }
+
+  /**
+   * @author Christian W. Damus (CEA LIST)
+   */
+  @Target({ ElementType.METHOD })
+  @Retention(RetentionPolicy.RUNTIME)
+  public static @interface Subscribe
+  {
+  }
+
+  /**
+   * @author Christian W. Damus (CEA LIST)
+   */
+  public static final class Whiteboard
+  {
+    private List<Object> subscribers = new java.util.ArrayList<Object>();
+
+    /**
+     * Publish a topic to interested subscribers.
+     */
+    public void publish(Object topic)
+    {
+      for (Object subscriber : subscribers)
+      {
+        Method handler = getHandler(subscriber, topic.getClass());
+        if (handler != null)
+        {
+          try
+          {
+            handler.invoke(subscriber, topic);
+          }
+          catch (Exception e)
+          {
+            OM.LOG.error(e);
+          }
+        }
+      }
+    }
+
+    void subscribe(Object subscriber)
+    {
+      subscribers.add(subscriber);
+    }
+
+    private Method getHandler(Object subscriber, Class<?> topicType)
+    {
+      for (Method method : subscriber.getClass().getMethods())
+      {
+        if (method.isAnnotationPresent(Subscribe.class))
+        {
+          Class<?>[] parameterTypes = method.getParameterTypes();
+          if (parameterTypes.length == 1 && parameterTypes[0].isAssignableFrom(topicType))
+          {
+            return method;
+          }
+        }
+      }
+
+      return null;
+    }
+  }
+}
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/wizards/CreateRepositoryGeneralPage.java b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/wizards/CreateRepositoryGeneralPage.java
new file mode 100644
index 0000000..bf67bf9
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/wizards/CreateRepositoryGeneralPage.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2013 Eike Stepper (Berlin, 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:
+ *    Christian W. Damus (CEA LIST) - initial API and implementation
+ */
+package org.eclipse.emf.cdo.ui.internal.admin.wizards;
+
+import org.eclipse.emf.cdo.common.CDOCommonRepository.IDGenerationLocation;
+import org.eclipse.emf.cdo.common.admin.CDOAdmin;
+import org.eclipse.emf.cdo.ui.internal.admin.StoreType;
+import org.eclipse.emf.cdo.ui.internal.admin.messages.Messages;
+
+import org.eclipse.net4j.util.StringUtil;
+import org.eclipse.net4j.util.ui.UIUtil;
+
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.jface.viewers.ComboViewer;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Text;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Christian W. Damus (CEA LIST)
+ */
+public class CreateRepositoryGeneralPage extends AbstractCreateRepositoryWizardPage
+{
+  public static final String PROPERTY_NAME = "name"; //$NON-NLS-1$
+
+  private final Map<String, StoreType> storeTypes;
+
+  private Text nameText;
+
+  private Button enableAuditingCheckbox;
+
+  private Button enableBranchingCheckbox;
+
+  private Button enableEcoreModelsCheckbox;
+
+  private Button referentialIntegrityCheckbox;
+
+  private Button allowInterruptQueriesCheckbox;
+
+  private Text overrideUUIDText;
+
+  private ComboViewer idGenerationLocationCombo;
+
+  private Button securityCheckbox;
+
+  private Button homeFoldersCheckbox;
+
+  private ComboViewer storeCombo;
+
+  private StoreType storeType;
+
+  public CreateRepositoryGeneralPage(String pageName, List<StoreType> storeTypes)
+  {
+    super(pageName);
+    setTitle(Messages.CreateRepositoryGeneralPage_5);
+    setMessage(Messages.CreateRepositoryGeneralPage_0);
+    this.storeTypes = mapStoreTypes(storeTypes);
+  }
+
+  @Override
+  protected void createContents(Composite parent)
+  {
+    Composite composite = new Composite(parent, SWT.NONE);
+    composite.setLayoutData(UIUtil.createGridData());
+    composite.setLayout(new GridLayout());
+
+    Group properties = group(composite, Messages.CreateRepositoryGeneralPage_6);
+    nameText = text(properties, Messages.CreateRepositoryGeneralPage_1);
+    enableAuditingCheckbox = checkbox(properties, Messages.CreateRepositoryGeneralPage_7);
+    enableBranchingCheckbox = checkbox(properties, Messages.CreateRepositoryGeneralPage_8);
+    enableEcoreModelsCheckbox = checkbox(properties, Messages.CreateRepositoryGeneralPage_9);
+    referentialIntegrityCheckbox = checkbox(properties, Messages.CreateRepositoryGeneralPage_10);
+    allowInterruptQueriesCheckbox = checkbox(properties, Messages.CreateRepositoryGeneralPage_11);
+    overrideUUIDText = text(properties, Messages.CreateRepositoryGeneralPage_12);
+    idGenerationLocationCombo = combo(properties, Messages.CreateRepositoryGeneralPage_13, IDGenerationLocation.values());
+
+    Group security = group(composite, Messages.CreateRepositoryGeneralPage_14);
+    securityCheckbox = checkbox(security, Messages.CreateRepositoryGeneralPage_2);
+    homeFoldersCheckbox = checkbox(security, Messages.CreateRepositoryGeneralPage_3);
+
+    Group store = group(composite, Messages.CreateRepositoryGeneralPage_15);
+    storeCombo = combo(store, Messages.CreateRepositoryGeneralPage_4, storeTypes.values());
+  }
+
+  @Override
+  protected void hookListeners(Listener updateListener)
+  {
+    enableAuditingCheckbox.addListener(SWT.Selection, updateListener);
+    securityCheckbox.addListener(SWT.Selection, updateListener);
+    nameText.addListener(SWT.Modify, updateListener);
+    storeCombo.getCombo().addListener(SWT.Selection, updateListener);
+  }
+
+  @Override
+  protected void updateEnablement(boolean firstTime)
+  {
+    homeFoldersCheckbox.setEnabled(securityCheckbox.getSelection());
+    enableBranchingCheckbox.setEnabled(enableAuditingCheckbox.getSelection());
+
+    StoreType newStoreType = UIUtil.getElement(storeCombo.getSelection(), StoreType.class);
+    if (storeType != newStoreType)
+    {
+      storeType = newStoreType;
+      publish(storeType);
+    }
+
+    boolean nameOK = !StringUtil.isEmpty(nameText.getText().trim());
+    boolean storeTypeOK = storeType != null;
+
+    setPageComplete(nameOK && storeTypeOK);
+  }
+
+  protected IDGenerationLocation getIDGenerationLocation()
+  {
+    return UIUtil.getElement(idGenerationLocationCombo.getSelection(), IDGenerationLocation.class);
+  }
+
+  @Override
+  protected boolean collectRepositoryProperties(Map<String, Object> repositoryProperties)
+  {
+    repositoryProperties.put(PROPERTY_NAME, nameText.getText().trim());
+
+    // Additional properties that are standard across store types
+    repositoryProperties.put("supportingAudits", checked(enableAuditingCheckbox)); //$NON-NLS-1$
+    repositoryProperties.put("supportingBranches", checked(enableBranchingCheckbox)); //$NON-NLS-1$
+    repositoryProperties.put("supportingEcore", checked(enableEcoreModelsCheckbox)); //$NON-NLS-1$
+    repositoryProperties.put("ensureReferentialIntegrity", checked(referentialIntegrityCheckbox)); //$NON-NLS-1$
+    repositoryProperties.put("allowInterruptRunningQueries", checked(allowInterruptQueriesCheckbox)); //$NON-NLS-1$
+    repositoryProperties.put("overrideUUID", text(overrideUUIDText)); //$NON-NLS-1$
+    repositoryProperties.put("idGenerationLocation", getIDGenerationLocation().name()); //$NON-NLS-1$
+
+    repositoryProperties.put(CDOAdmin.PROPERTY_SECURITY_MANAGER, checked(securityCheckbox));
+    repositoryProperties.put(CDOAdmin.PROPERTY_SECURITY_HOME_FOLDERS, checked(homeFoldersCheckbox));
+
+    return true;
+  }
+
+  @Override
+  protected void loadSettings(IDialogSettings pageSettings)
+  {
+    // The repository names and override-UUIDs are unique, so we don't remember them
+
+    enableAuditingCheckbox.setSelection(getSetting(pageSettings, "enableAuditing", true)); //$NON-NLS-1$
+    enableBranchingCheckbox.setSelection(getSetting(pageSettings, "enableBranching", true)); //$NON-NLS-1$
+    enableEcoreModelsCheckbox.setSelection(getSetting(pageSettings, "enableEcore", true)); //$NON-NLS-1$
+    referentialIntegrityCheckbox.setSelection(getSetting(pageSettings, "referentialIntegrity", false)); //$NON-NLS-1$
+    allowInterruptQueriesCheckbox.setSelection(getSetting(pageSettings, "allowInterruptQueries", true)); //$NON-NLS-1$
+
+    IDGenerationLocation idGen = IDGenerationLocation.valueOf(getSetting(pageSettings, "idGeneration", //$NON-NLS-1$
+        IDGenerationLocation.STORE.name()));
+    idGenerationLocationCombo.setSelection(new StructuredSelection(idGen));
+
+    securityCheckbox.setSelection(getSetting(pageSettings, "security", true)); //$NON-NLS-1$
+    homeFoldersCheckbox.setSelection(getSetting(pageSettings, "homeFolders", true)); //$NON-NLS-1$
+
+    StoreType storeType = storeTypes.get(getSetting(pageSettings, "storeType", null)); //$NON-NLS-1$
+    if (storeType != null)
+    {
+      storeCombo.setSelection(new StructuredSelection(storeType));
+    }
+    else
+    {
+      storeCombo.getCombo().select(0);
+    }
+  }
+
+  @Override
+  protected void saveSettings(IDialogSettings pageSettings)
+  {
+    // The repository names and override-UUIDs are unique, so we don't remember them
+
+    pageSettings.put("enableAuditing", enableAuditingCheckbox.getSelection()); //$NON-NLS-1$
+    pageSettings.put("enableBranching", enableBranchingCheckbox.getSelection()); //$NON-NLS-1$
+    pageSettings.put("enableEcore", enableEcoreModelsCheckbox.getSelection()); //$NON-NLS-1$
+    pageSettings.put("referentialIntegrity", referentialIntegrityCheckbox.getSelection()); //$NON-NLS-1$
+    pageSettings.put("allowInterruptQueries", allowInterruptQueriesCheckbox.getSelection()); //$NON-NLS-1$
+    pageSettings.put("idGeneration", getIDGenerationLocation().name()); //$NON-NLS-1$
+
+    pageSettings.put("security", securityCheckbox.getSelection()); //$NON-NLS-1$
+    pageSettings.put("homeFolders", homeFoldersCheckbox.getSelection()); //$NON-NLS-1$
+
+    pageSettings.put("storeType", storeType.getID()); //$NON-NLS-1$
+  }
+
+  private static Map<String, StoreType> mapStoreTypes(List<StoreType> storeTypes)
+  {
+    Map<String, StoreType> result = new java.util.HashMap<String, StoreType>();
+
+    for (StoreType storeType : storeTypes)
+    {
+      result.put(storeType.getID(), storeType);
+    }
+
+    return result;
+  }
+
+}
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/wizards/CreateRepositoryStorePage.java b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/wizards/CreateRepositoryStorePage.java
new file mode 100644
index 0000000..bbc9d25
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/wizards/CreateRepositoryStorePage.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2013 Eike Stepper (Berlin, 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:
+ *    Christian W. Damus (CEA LIST) - initial API and implementation
+ */
+package org.eclipse.emf.cdo.ui.internal.admin.wizards;
+
+import static org.eclipse.emf.cdo.ui.internal.admin.StoreType.Database.DEFAULT_CONNECTION_KEEP_ALIVE_PERIOD;
+import static org.eclipse.emf.cdo.ui.internal.admin.StoreType.Database.DEFAULT_READER_POOL_CAPACITY;
+import static org.eclipse.emf.cdo.ui.internal.admin.StoreType.Database.DEFAULT_WRITER_POOL_CAPACITY;
+import static org.eclipse.emf.cdo.ui.internal.admin.StoreType.Database.PROPERTY_CONNECTION_KEEP_ALIVE_PERIOD;
+import static org.eclipse.emf.cdo.ui.internal.admin.StoreType.Database.PROPERTY_PATH;
+import static org.eclipse.emf.cdo.ui.internal.admin.StoreType.Database.PROPERTY_READER_POOL_CAPACITY;
+import static org.eclipse.emf.cdo.ui.internal.admin.StoreType.Database.PROPERTY_WRITER_POOL_CAPACITY;
+
+import org.eclipse.emf.cdo.common.admin.CDOAdmin;
+import org.eclipse.emf.cdo.ui.internal.admin.StoreType;
+import org.eclipse.emf.cdo.ui.internal.admin.messages.Messages;
+
+import org.eclipse.net4j.util.StringUtil;
+import org.eclipse.net4j.util.ui.UIUtil;
+
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Text;
+
+import java.text.MessageFormat;
+import java.util.Map;
+
+/**
+ * @author Christian W. Damus (CEA LIST)
+ */
+public class CreateRepositoryStorePage extends AbstractCreateRepositoryWizardPage
+{
+  private static final String TITLE_PATTERN = Messages.CreateRepositoryStorePage_1;
+
+  private Text storePathText;
+
+  private Text connectionKeepAlivePeriodText;
+
+  private Text readerPoolCapacityText;
+
+  private Text writerPoolCapacityText;
+
+  private StoreType storeType;
+
+  public CreateRepositoryStorePage(String pageName)
+  {
+    super(pageName);
+    setTitle(TITLE_PATTERN);
+    setMessage(Messages.CreateRepositoryStorePage_2);
+  }
+
+  @Subscribe
+  public void setStoreType(StoreType storeType)
+  {
+    this.storeType = storeType;
+    setTitle(MessageFormat.format(TITLE_PATTERN, storeType.getName()));
+    // TODO: When we support multiple store types, change a paged UI
+  }
+
+  @Override
+  protected void createContents(Composite parent)
+  {
+    Composite composite = new Composite(parent, SWT.NONE);
+    composite.setLayoutData(UIUtil.createGridData());
+    composite.setLayout(new GridLayout());
+
+    Group general = group(composite, Messages.CreateRepositoryStorePage_3);
+    storePathText = text(general, Messages.CreateRepositoryStorePage_0);
+
+    Group performance = group(composite, Messages.CreateRepositoryStorePage_4);
+    connectionKeepAlivePeriodText = text(performance, Messages.CreateRepositoryStorePage_5);
+    readerPoolCapacityText = text(performance, Messages.CreateRepositoryStorePage_6);
+    writerPoolCapacityText = text(performance, Messages.CreateRepositoryStorePage_7);
+  }
+
+  @Override
+  protected void hookListeners(Listener updateListener)
+  {
+    storePathText.addListener(SWT.Modify, updateListener);
+    connectionKeepAlivePeriodText.addListener(SWT.Modify, updateListener);
+    readerPoolCapacityText.addListener(SWT.Modify, updateListener);
+    writerPoolCapacityText.addListener(SWT.Modify, updateListener);
+  }
+
+  @Override
+  protected void updateEnablement(boolean firstTime)
+  {
+    boolean storePathOK = !StringUtil.isEmpty(text(storePathText));
+
+    if (firstTime)
+    {
+      setPageComplete(storePathOK);
+      return;
+    }
+
+    if (!storePathOK)
+    {
+      setErrorMessage(Messages.CreateRepositoryStorePage_8);
+      setPageComplete(false);
+      return;
+    }
+
+    // Don't need to worry about other properties the first time because they have defaults
+
+    String keepAlive = text(connectionKeepAlivePeriodText);
+    String readerPool = text(readerPoolCapacityText);
+    String writerPool = text(writerPoolCapacityText);
+
+    if (StringUtil.isEmpty(keepAlive))
+    {
+      setErrorMessage(Messages.CreateRepositoryStorePage_9);
+      setPageComplete(false);
+      return;
+    }
+
+    if (!positiveInteger(keepAlive))
+    {
+      setErrorMessage(Messages.CreateRepositoryStorePage_10);
+      setPageComplete(false);
+      return;
+    }
+
+    if (!StringUtil.isEmpty(readerPool) && !positiveInteger(readerPool))
+    {
+      setErrorMessage(Messages.CreateRepositoryStorePage_11);
+      setPageComplete(false);
+      return;
+    }
+
+    if (!StringUtil.isEmpty(writerPool) && !positiveInteger(writerPool))
+    {
+      setErrorMessage(Messages.CreateRepositoryStorePage_12);
+      setPageComplete(false);
+      return;
+    }
+
+    setErrorMessage(null);
+    setPageComplete(true);
+  }
+
+  @Override
+  protected void loadSettings(IDialogSettings pageSettings)
+  {
+    connectionKeepAlivePeriodText.setText(getSetting(pageSettings, PROPERTY_CONNECTION_KEEP_ALIVE_PERIOD,
+        DEFAULT_CONNECTION_KEEP_ALIVE_PERIOD));
+    readerPoolCapacityText
+        .setText(getSetting(pageSettings, PROPERTY_READER_POOL_CAPACITY, DEFAULT_READER_POOL_CAPACITY));
+    writerPoolCapacityText
+        .setText(getSetting(pageSettings, PROPERTY_WRITER_POOL_CAPACITY, DEFAULT_WRITER_POOL_CAPACITY));
+  }
+
+  @Override
+  protected void saveSettings(IDialogSettings pageSettings)
+  {
+    pageSettings.put(PROPERTY_CONNECTION_KEEP_ALIVE_PERIOD, text(connectionKeepAlivePeriodText));
+    pageSettings.put(PROPERTY_READER_POOL_CAPACITY, text(readerPoolCapacityText));
+    pageSettings.put(PROPERTY_WRITER_POOL_CAPACITY, text(writerPoolCapacityText));
+  }
+
+  @Override
+  protected boolean collectRepositoryProperties(Map<String, Object> repositoryProperties)
+  {
+    repositoryProperties.put(CDOAdmin.PROPERTY_STORE_XML_CONFIG, createStoreXML(storeType));
+    return true;
+  }
+
+  protected String createStoreXML(StoreType storeType)
+  {
+    Map<String, Object> storeProperties = new java.util.HashMap<String, Object>();
+
+    storeProperties.put(PROPERTY_PATH, text(storePathText));
+    storeProperties.put(PROPERTY_CONNECTION_KEEP_ALIVE_PERIOD, text(connectionKeepAlivePeriodText));
+    storeProperties.put(PROPERTY_READER_POOL_CAPACITY, text(readerPoolCapacityText));
+    storeProperties.put(PROPERTY_WRITER_POOL_CAPACITY, text(writerPoolCapacityText));
+
+    return storeType.getStoreXML(storeProperties);
+  }
+
+}
diff --git a/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/wizards/CreateRepositoryWizard.java b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/wizards/CreateRepositoryWizard.java
new file mode 100644
index 0000000..589319c
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.ui.admin/src/org/eclipse/emf/cdo/ui/internal/admin/wizards/CreateRepositoryWizard.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2013 Eike Stepper (Berlin, 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:
+ *    Christian W. Damus (CEA LIST) - initial API and implementation
+ */
+package org.eclipse.emf.cdo.ui.internal.admin.wizards;
+
+import org.eclipse.emf.cdo.ui.internal.admin.StoreType;
+import org.eclipse.emf.cdo.ui.internal.admin.bundle.OM;
+import org.eclipse.emf.cdo.ui.internal.admin.messages.Messages;
+import org.eclipse.emf.cdo.ui.internal.admin.wizards.AbstractCreateRepositoryWizardPage.Whiteboard;
+
+import org.eclipse.jface.wizard.Wizard;
+
+import java.util.Map;
+
+/**
+ * @author Christian W. Damus (CEA LIST)
+ */
+public class CreateRepositoryWizard extends Wizard
+{
+  private CreateRepositoryGeneralPage generalPage;
+
+  private CreateRepositoryStorePage storePage;
+
+  private String repositoryName;
+
+  private Map<String, Object> repositoryProperties;
+
+  public CreateRepositoryWizard()
+  {
+    setWindowTitle(Messages.CreateRepositoryWizard_0);
+    setDefaultPageImageDescriptor(OM.getImageDescriptor("icons/full/wizban/new_repo.png")); //$NON-NLS-1$
+    setDialogSettings(OM.Activator.INSTANCE.getDialogSettings(getClass()));
+    setHelpAvailable(false);
+  }
+
+  @Override
+  public void addPages()
+  {
+    super.addPages();
+
+    final Whiteboard whiteboard = new Whiteboard();
+    generalPage = new CreateRepositoryGeneralPage("general", StoreType.getInstances()); //$NON-NLS-1$
+    generalPage.bind(whiteboard);
+    addPage(generalPage);
+    storePage = new CreateRepositoryStorePage("store"); //$NON-NLS-1$
+    storePage.bind(whiteboard);
+    addPage(storePage);
+  }
+
+  @Override
+  public boolean performFinish()
+  {
+    Map<String, Object> repositoryProperties = new java.util.HashMap<String, Object>();
+    boolean result = generalPage.performFinish(repositoryProperties);
+    result = result && storePage.performFinish(repositoryProperties);
+
+    if (result)
+    {
+      repositoryName = (String)repositoryProperties.remove(CreateRepositoryGeneralPage.PROPERTY_NAME);
+      this.repositoryProperties = repositoryProperties;
+    }
+
+    return result;
+  }
+
+  public String getRepositoryName()
+  {
+    return repositoryName;
+  }
+
+  public Map<String, Object> getRepositoryProperties()
+  {
+    return repositoryProperties;
+  }
+
+}
diff --git a/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/UIActivator.java b/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/UIActivator.java
index e36b502..c92eda6 100644
--- a/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/UIActivator.java
+++ b/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/UIActivator.java
@@ -7,6 +7,7 @@
  *
  * Contributors:
  *    Eike Stepper - initial API and implementation
+ *    Christian W. Damus (CEA LIST) - bug 418454
  */
 package org.eclipse.net4j.util.ui;
 
@@ -15,6 +16,8 @@
 import org.eclipse.net4j.util.om.OSGiActivator;
 import org.eclipse.net4j.util.om.OSGiActivator.StateHandler;
 
+import org.eclipse.jface.dialogs.DialogSettings;
+import org.eclipse.jface.dialogs.IDialogSettings;
 import org.eclipse.jface.resource.ImageDescriptor;
 import org.eclipse.ui.plugin.AbstractUIPlugin;
 
@@ -50,6 +53,22 @@
     return imageDescriptorFromPlugin(omBundle.getBundleID(), path);
   }
 
+  /**
+   * @since 3.4
+   */
+  public IDialogSettings getDialogSettings(Class<?> clazz)
+  {
+    return getDialogSettings(clazz.getName());
+  }
+
+  /**
+   * @since 3.4
+   */
+  public IDialogSettings getDialogSettings(String section)
+  {
+    return DialogSettings.getOrCreateSection(getDialogSettings(), section);
+  }
+
   @Override
   public final void start(BundleContext context) throws Exception
   {
diff --git a/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/UIUtil.java b/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/UIUtil.java
index 69de2b6..4ce619e 100644
--- a/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/UIUtil.java
+++ b/plugins/org.eclipse.net4j.util.ui/src/org/eclipse/net4j/util/ui/UIUtil.java
@@ -8,6 +8,7 @@
  * Contributors:
  *    Eike Stepper - initial API and implementation
  *    Victor Roldan Betancort - maintenance
+ *    Christian W. Damus (CEA LIST) - bug 418454
  */
 package org.eclipse.net4j.util.ui;
 
@@ -338,6 +339,17 @@
   }
 
   /**
+   * @since 3.4
+   */
+  public static GridData createGridData(int horizontalSpan, int verticalSpan)
+  {
+    GridData result = new GridData();
+    result.horizontalSpan = horizontalSpan;
+    result.verticalSpan = verticalSpan;
+    return result;
+  }
+
+  /**
    * @since 3.0
    */
   public static GridData createEmptyGridData()
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/io/IOUtil.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/io/IOUtil.java
index 448c68e..4bfca63 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/io/IOUtil.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/io/IOUtil.java
@@ -7,6 +7,7 @@
  *
  * Contributors:
  *    Eike Stepper - initial API and implementation
+ *    Christian W. Damus (CEA LIST) - bug 418454
  */
 package org.eclipse.net4j.util.io;
 
@@ -32,10 +33,14 @@
 import java.io.Flushable;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.PrintStream;
 import java.io.Reader;
 import java.io.Writer;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -633,6 +638,54 @@
   }
 
   /**
+   * @since 3.4
+   */
+  public static String readText(URL url) throws IORuntimeException
+  {
+    Reader input = null;
+
+    try
+    {
+      URLConnection connection = url.openConnection();
+      connection.setDoInput(true);
+      connection.setUseCaches(true);
+      connection.connect();
+
+      Charset charset;
+      String encoding = connection.getContentEncoding();
+      if (encoding == null)
+      {
+        charset = Charset.defaultCharset();
+      }
+      else
+      {
+        charset = Charset.forName(encoding);
+      }
+
+      input = new InputStreamReader(connection.getInputStream(), charset);
+    }
+    catch (IOException ex)
+    {
+      throw new IORuntimeException(ex);
+    }
+
+    try
+    {
+      CharArrayWriter output = new CharArrayWriter();
+      copyCharacter(input, output);
+      return output.toString();
+    }
+    catch (IOException ex)
+    {
+      throw new IORuntimeException(ex);
+    }
+    finally
+    {
+      closeSilent(input);
+    }
+  }
+
+  /**
    * @since 3.1
    */
   public static String readTextFile(File file) throws IORuntimeException