[333260] Backport repo export feature
https://bugs.eclipse.org/bugs/show_bug.cgi?id=333260

diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/id/CDOIDUtil.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/id/CDOIDUtil.java
index 9cba651..e81cc85 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/id/CDOIDUtil.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/id/CDOIDUtil.java
@@ -8,7 +8,8 @@
  * Contributors:
  *    Eike Stepper - initial API and implementation
  *    Simon McDuff - http://bugs.eclipse.org/226778    
- *    Simon McDuff - http://bugs.eclipse.org/213402   
+ *    Simon McDuff - http://bugs.eclipse.org/213402
+ *    Caspar De Groot - https://bugs.eclipse.org/333260
  */
 package org.eclipse.emf.cdo.common.id;
 
@@ -227,6 +228,29 @@
     builder.append("/" + id.toURIFragment()); //$NON-NLS-1$
   }
 
+  /**
+   * Backport of 4.0's implementation of write(StringBuilder,CDOID),
+   * or more accurately: a limited emulation of the 4.0 behavior
+   */
+  public static void write40(StringBuilder builder, CDOID id)
+  {
+    if (id == null)
+    {
+      id = CDOID.NULL;
+    }
+
+    if (id instanceof CDOIDLongImpl)
+    {
+      builder.append('L');
+    }
+    else
+    {
+      throw new RuntimeException("Runtime type of CDOID not supported: " + id.getClass().getName());
+    }
+
+    builder.append(id.toURIFragment());
+  }
+
   public static CDOIDMeta createMeta(long value)
   {
     return new CDOIDMetaImpl(value);
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/revision/CDORevisionHandler.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/revision/CDORevisionHandler.java
new file mode 100644
index 0000000..9d8c591
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/revision/CDORevisionHandler.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2004 - 2010 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
+ */
+package org.eclipse.emf.cdo.common.revision;
+
+/**
+ * Backported from 3.0/4.0
+ * 
+ * @author Eike Stepper
+ * @since 3.0
+ */
+public interface CDORevisionHandler
+{
+  /**
+   * Handles a revision.
+   * 
+   * @return <code>true</code> to indicate that the caller may pass more revisions, <code>false</code> otherwise.
+   * @since 4.0
+   */
+  public boolean handleRevision(CDORevision revision);
+}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBRevisionHandler.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBRevisionHandler.java
new file mode 100644
index 0000000..53b017c
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBRevisionHandler.java
@@ -0,0 +1,22 @@
+package org.eclipse.emf.cdo.server.internal.db;
+
+import org.eclipse.emf.cdo.common.revision.CDORevision;
+import org.eclipse.emf.cdo.common.revision.CDORevisionHandler;
+
+/**
+ * @author Eike Stepper
+ */
+public class DBRevisionHandler implements CDORevisionHandler
+{
+  private CDORevisionHandler delegate;
+
+  public DBRevisionHandler(CDORevisionHandler delegate)
+  {
+    this.delegate = delegate;
+  }
+
+  public boolean handleRevision(CDORevision revision)
+  {
+    return delegate.handleRevision(revision);
+  }
+}
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStore.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStore.java
index fd943c6..9a7ffe7 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStore.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStore.java
@@ -11,12 +11,17 @@
  *    Stefan Winkler - 271444: [DB] Multiple refactorings https://bugs.eclipse.org/bugs/show_bug.cgi?id=271444
  *    Stefan Winkler - 249610: [DB] Support external references (Implementation)
  *    Victor Roldan - 289237: [DB] [maintenance] Support external references
+ *    Caspar De Groot - https://bugs.eclipse.org/333260
  */
 package org.eclipse.emf.cdo.server.internal.db;
 
+import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.revision.CDORevision;
 import org.eclipse.emf.cdo.server.ISession;
+import org.eclipse.emf.cdo.server.IStoreAccessor;
 import org.eclipse.emf.cdo.server.ITransaction;
 import org.eclipse.emf.cdo.server.IView;
+import org.eclipse.emf.cdo.server.StoreThreadLocal;
 import org.eclipse.emf.cdo.server.db.IMetaDataManager;
 import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy;
 import org.eclipse.emf.cdo.server.internal.db.bundle.OM;
@@ -40,6 +45,8 @@
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.text.MessageFormat;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -65,6 +72,8 @@
 
   private IExternalReferenceManager.Internal externalReferenceManager;
 
+  private CDOID rootResourceID;
+
   @ExcludeFromDump
   private transient ProgressDistributor accessorWriteDistributor = new ProgressDistributor.Geometric()
   {
@@ -238,8 +247,8 @@
     checkNull(dbAdapter, Messages.getString("DBStore.1")); //$NON-NLS-1$
     checkNull(dbConnectionProvider, Messages.getString("DBStore.0")); //$NON-NLS-1$
 
-    checkState(getRevisionTemporality() == RevisionTemporality.AUDITING == mappingStrategy.hasAuditSupport(), Messages
-        .getString("DBStore.7")); //$NON-NLS-1$
+    checkState(getRevisionTemporality() == RevisionTemporality.AUDITING == mappingStrategy.hasAuditSupport(),
+        Messages.getString("DBStore.7")); //$NON-NLS-1$
   }
 
   @Override
@@ -407,4 +416,56 @@
   {
     return System.currentTimeMillis();
   }
+
+  @Override
+  public CDOID getRootResourceID()
+  {
+    if (rootResourceID != null)
+    {
+      return rootResourceID;
+    }
+
+    IStoreAccessor accessor = StoreThreadLocal.getAccessor();
+    final List<CDOID> resourceIDs = new LinkedList<CDOID>();
+    accessor.queryResources(new IStoreAccessor.QueryResourcesContext()
+    {
+      public long getTimeStamp()
+      {
+        return CDORevision.UNSPECIFIED_DATE;
+      }
+
+      public CDOID getFolderID()
+      {
+        return null;
+      }
+
+      public String getName()
+      {
+        return null;
+      }
+
+      public boolean exactMatch()
+      {
+        return true;
+      }
+
+      public int getMaxResults()
+      {
+        return 2;
+      }
+
+      public boolean addResource(CDOID resourceID)
+      {
+        return resourceIDs.add(resourceID);
+      }
+    });
+
+    if (resourceIDs.size() != 1)
+    {
+      throw new RuntimeException("Could not deduce rootResourceID");
+    }
+
+    rootResourceID = resourceIDs.get(0);
+    return rootResourceID;
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreAccessor.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreAccessor.java
index 70ce861..2c310f2 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreAccessor.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/DBStoreAccessor.java
@@ -9,6 +9,7 @@
  *    Eike Stepper - initial API and implementation
  *    Stefan Winkler - https://bugs.eclipse.org/bugs/show_bug.cgi?id=259402
  *    Stefan Winkler - 271444: [DB] Multiple refactorings https://bugs.eclipse.org/bugs/show_bug.cgi?id=271444
+ *    Caspar De Groot - https://bugs.eclipse.org/333260
  */
 package org.eclipse.emf.cdo.server.internal.db;
 
@@ -16,6 +17,7 @@
 import org.eclipse.emf.cdo.common.id.CDOID;
 import org.eclipse.emf.cdo.common.model.CDOClassifierRef;
 import org.eclipse.emf.cdo.common.model.CDOPackageRegistry;
+import org.eclipse.emf.cdo.common.revision.CDORevisionHandler;
 import org.eclipse.emf.cdo.common.revision.CDORevisionUtil;
 import org.eclipse.emf.cdo.server.IQueryHandler;
 import org.eclipse.emf.cdo.server.IRepository;
@@ -30,6 +32,7 @@
 import org.eclipse.emf.cdo.server.db.mapping.IClassMappingDeltaSupport;
 import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy;
 import org.eclipse.emf.cdo.server.internal.db.bundle.OM;
+import org.eclipse.emf.cdo.server.internal.db.mapping.AbstractMappingStrategy;
 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;
@@ -41,9 +44,9 @@
 import org.eclipse.net4j.util.collection.CloseableIterator;
 import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
 import org.eclipse.net4j.util.om.monitor.OMMonitor;
+import org.eclipse.net4j.util.om.monitor.OMMonitor.Async;
 import org.eclipse.net4j.util.om.monitor.ProgressDistributable;
 import org.eclipse.net4j.util.om.monitor.ProgressDistributor;
-import org.eclipse.net4j.util.om.monitor.OMMonitor.Async;
 import org.eclipse.net4j.util.om.trace.ContextTracer;
 
 import org.eclipse.emf.ecore.EClass;
@@ -501,6 +504,13 @@
     }
   }
 
+  @Override
+  public void handleRevisions(CDORevisionHandler handler)
+  {
+    IMappingStrategy mappingStrategy = getStore().getMappingStrategy();
+    ((AbstractMappingStrategy)mappingStrategy).handleRevisions(this, new DBRevisionHandler(handler));
+  }
+
   private class ConnectionKeepAliveTask extends TimerTask
   {
     public static final long EXECUTION_PERIOD = 1000 * 60 * 60 * 4; // 4 hours
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/AbstractMappingStrategy.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/AbstractMappingStrategy.java
index 9e88cf3..8eb8e79 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/AbstractMappingStrategy.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/AbstractMappingStrategy.java
@@ -10,13 +10,18 @@
  *    Stefan Winkler - major refactoring
  *    Stefan Winkler - 271444: [DB] Multiple refactorings https://bugs.eclipse.org/bugs/show_bug.cgi?id=271444
  *    Victor Roldan Betancort - 289360: [DB] [maintenance] Support FeatureMaps
+ *    Caspar De Groot - https://bugs.eclipse.org/333260
  */
 package org.eclipse.emf.cdo.server.internal.db.mapping;
 
 import org.eclipse.emf.cdo.common.id.CDOID;
 import org.eclipse.emf.cdo.common.model.CDOClassifierRef;
 import org.eclipse.emf.cdo.common.model.CDOModelUtil;
+import org.eclipse.emf.cdo.common.model.CDOPackageInfo;
+import org.eclipse.emf.cdo.common.model.CDOPackageRegistry;
 import org.eclipse.emf.cdo.common.model.EMFUtil;
+import org.eclipse.emf.cdo.common.revision.CDORevisionHandler;
+import org.eclipse.emf.cdo.server.IRepository;
 import org.eclipse.emf.cdo.server.IStoreAccessor.QueryResourcesContext;
 import org.eclipse.emf.cdo.server.db.IDBStore;
 import org.eclipse.emf.cdo.server.db.IDBStoreAccessor;
@@ -26,6 +31,7 @@
 import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy;
 import org.eclipse.emf.cdo.server.db.mapping.ITypeMapping;
 import org.eclipse.emf.cdo.server.internal.db.ObjectIDIterator;
+import org.eclipse.emf.cdo.server.internal.db.mapping.horizontal.AbstractHorizontalClassMapping;
 import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageInfo;
 import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageUnit;
 
@@ -41,6 +47,7 @@
 import org.eclipse.net4j.util.om.monitor.OMMonitor.Async;
 
 import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EClassifier;
 import org.eclipse.emf.ecore.ENamedElement;
 import org.eclipse.emf.ecore.EPackage;
 import org.eclipse.emf.ecore.EStructuralFeature;
@@ -85,6 +92,8 @@
 
   private ConcurrentMap<EClass, IClassMapping> classMappings;
 
+  private boolean allClassMappingsCreated;
+
   public AbstractMappingStrategy()
   {
     classMappings = new ConcurrentHashMap<EClass, IClassMapping>();
@@ -367,9 +376,49 @@
 
   public final Map<EClass, IClassMapping> getClassMappings()
   {
+    return getClassMappings(true);
+  }
+
+  public final Map<EClass, IClassMapping> getClassMappings(boolean createOnDemand)
+  {
+    if (createOnDemand)
+    {
+      synchronized (classMappings)
+      {
+        if (!allClassMappingsCreated)
+        {
+          createAllClassMappings();
+          allClassMappingsCreated = true;
+        }
+      }
+    }
+
     return classMappings;
   }
 
+  private void createAllClassMappings()
+  {
+    IRepository repository = getStore().getRepository();
+    CDOPackageRegistry packageRegistry = repository.getPackageRegistry();
+    for (CDOPackageInfo packageInfo : packageRegistry.getPackageInfos())
+    {
+      if (!packageInfo.isSystemPackage())
+      {
+        for (EClassifier eClassifier : packageInfo.getEPackage().getEClassifiers())
+        {
+          if (eClassifier instanceof EClass)
+          {
+            EClass eClass = (EClass)eClassifier;
+            if (!eClass.isAbstract() && !eClass.isInterface())
+            {
+              getClassMapping(eClass); // Get or create it
+            }
+          }
+        }
+      }
+    }
+  }
+
   public final IClassMapping getClassMapping(EClass eClass)
   {
     // Try without synchronization first; this will almost always succeed, so it avoids the
@@ -416,4 +465,13 @@
   public abstract IListMapping doCreateListMapping(EClass containingClass, EStructuralFeature feature);
 
   public abstract IListMapping doCreateFeatureMapMapping(EClass containingClass, EStructuralFeature feature);
+
+  public void handleRevisions(IDBStoreAccessor accessor, CDORevisionHandler handler)
+  {
+    Collection<IClassMapping> values = getClassMappings().values();
+    for (IClassMapping mapping : values)
+    {
+      ((AbstractHorizontalClassMapping)mapping).handleRevisions(accessor, handler);
+    }
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractHorizontalClassMapping.java b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractHorizontalClassMapping.java
index fa79523..f6e133c 100644
--- a/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractHorizontalClassMapping.java
+++ b/plugins/org.eclipse.emf.cdo.server.db/src/org/eclipse/emf/cdo/server/internal/db/mapping/horizontal/AbstractHorizontalClassMapping.java
@@ -11,15 +11,22 @@
  *    Stefan Winkler - 249610: [DB] Support external references (Implementation)
  *    Victor Roldan - 289237: [DB] [maintenance] Support external references
  *    Victor Roldan Betancort - 289360: [DB] [maintenance] Support FeatureMaps
+ *    Caspar De Groot - https://bugs.eclipse.org/333260
  */
 package org.eclipse.emf.cdo.server.internal.db.mapping.horizontal;
 
 import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.id.CDOIDUtil;
 import org.eclipse.emf.cdo.common.model.CDOModelUtil;
 import org.eclipse.emf.cdo.common.revision.CDORevision;
+import org.eclipse.emf.cdo.common.revision.CDORevisionHandler;
 import org.eclipse.emf.cdo.eresource.EresourcePackage;
+import org.eclipse.emf.cdo.server.IRepository;
+import org.eclipse.emf.cdo.server.IRevisionManager;
 import org.eclipse.emf.cdo.server.db.IDBStoreAccessor;
 import org.eclipse.emf.cdo.server.db.IMetaDataManager;
+import org.eclipse.emf.cdo.server.db.IPreparedStatementCache;
+import org.eclipse.emf.cdo.server.db.IPreparedStatementCache.ReuseProbability;
 import org.eclipse.emf.cdo.server.db.mapping.IClassMapping;
 import org.eclipse.emf.cdo.server.db.mapping.IListMapping;
 import org.eclipse.emf.cdo.server.db.mapping.IMappingStrategy;
@@ -71,6 +78,8 @@
 
   private List<IListMapping> listMappings;
 
+  private String sqlSelectForHandle;
+
   public AbstractHorizontalClassMapping(AbstractHorizontalMappingStrategy mappingStrategy, EClass eClass)
   {
     this.mappingStrategy = mappingStrategy;
@@ -78,6 +87,7 @@
 
     initTable();
     initFeatures();
+    initSQLStrings();
   }
 
   private void initTable()
@@ -186,10 +196,10 @@
       revision.setVersion(resultSet.getInt(i++));
       revision.setCreated(resultSet.getLong(i++));
       revision.setRevised(resultSet.getLong(i++));
-      revision.setResourceID(InternalCDODBUtil.convertLongToCDOID(getExternalReferenceManager(), accessor, resultSet
-          .getLong(i++)));
-      revision.setContainerID(InternalCDODBUtil.convertLongToCDOID(getExternalReferenceManager(), accessor, resultSet
-          .getLong(i++)));
+      revision.setResourceID(InternalCDODBUtil.convertLongToCDOID(getExternalReferenceManager(), accessor,
+          resultSet.getLong(i++)));
+      revision.setContainerID(InternalCDODBUtil.convertLongToCDOID(getExternalReferenceManager(), accessor,
+          resultSet.getLong(i++)));
       revision.setContainingFeatureID(resultSet.getInt(i++));
 
       for (ITypeMapping mapping : valueMappings)
@@ -389,4 +399,66 @@
   protected abstract void writeValues(IDBStoreAccessor accessor, InternalCDORevision revision);
 
   protected abstract void reviseObject(IDBStoreAccessor accessor, CDOID id, long revised);
+
+  public void handleRevisions(IDBStoreAccessor accessor, CDORevisionHandler handler)
+  {
+    IPreparedStatementCache statementCache = accessor.getStatementCache();
+    IRepository repository = accessor.getStore().getRepository();
+    IRevisionManager revisionManager = repository.getRevisionManager();
+
+    PreparedStatement stmt = null;
+    ResultSet rs = null;
+
+    // TODO: test for timeStamp == INVALID_TIME and encode revision.isValid() as WHERE instead of fetching all revisions
+    // in order to increase performance
+
+    StringBuilder builder = new StringBuilder(sqlSelectForHandle);
+
+    int timeParameters = 0;
+
+    try
+    {
+      stmt = statementCache.getPreparedStatement(builder.toString(), ReuseProbability.LOW);
+      for (int i = 0; i < timeParameters; i++)
+      {
+        stmt.setLong(i + 1, -1/* CDOBranchPoint.INVALID_DATE */);
+      }
+
+      rs = stmt.executeQuery();
+      while (rs.next())
+      {
+        long id = rs.getLong(1);
+        int version = rs.getInt(2);
+
+        InternalCDORevision revision = (InternalCDORevision)revisionManager.getRevisionByVersion(
+            CDOIDUtil.createLong(id), CDORevision.UNCHUNKED, version, true);
+
+        if (!handler.handleRevision(revision))
+        {
+          break;
+        }
+      }
+    }
+    catch (SQLException e)
+    {
+      throw new DBException(e);
+    }
+    finally
+    {
+      DBUtil.close(rs);
+      statementCache.releasePreparedStatement(stmt);
+    }
+  }
+
+  private void initSQLStrings()
+  {
+    // ----------- Select all revisions (for handleRevisions) ---
+    StringBuilder builder = new StringBuilder("SELECT "); //$NON-NLS-1$
+    builder.append(CDODBSchema.ATTRIBUTES_ID);
+    builder.append(", "); //$NON-NLS-1$
+    builder.append(CDODBSchema.ATTRIBUTES_VERSION);
+    builder.append(" FROM "); //$NON-NLS-1$
+    builder.append(getTable());
+    sqlSelectForHandle = builder.toString();
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Repository.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Repository.java
index b2f3b0a..a5bf30c 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Repository.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Repository.java
@@ -11,15 +11,18 @@
  *    Simon McDuff - http://bugs.eclipse.org/233273
  *    Simon McDuff - http://bugs.eclipse.org/233490
  *    Stefan Winkler - changed order of determining audit and revision delta support.
+ *    Caspar De Groot - https://bugs.eclipse.org/333260
  */
 package org.eclipse.emf.cdo.internal.server;
 
 import org.eclipse.emf.cdo.common.CDOQueryInfo;
+import org.eclipse.emf.cdo.common.id.CDOID;
 import org.eclipse.emf.cdo.common.id.CDOIDMetaRange;
 import org.eclipse.emf.cdo.common.model.CDOPackageUnit;
 import org.eclipse.emf.cdo.common.model.EMFUtil;
 import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants;
 import org.eclipse.emf.cdo.common.revision.CDORevision;
+import org.eclipse.emf.cdo.common.revision.CDORevisionHandler;
 import org.eclipse.emf.cdo.eresource.EresourcePackage;
 import org.eclipse.emf.cdo.internal.common.model.CDOPackageRegistryImpl;
 import org.eclipse.emf.cdo.server.IQueryHandler;
@@ -32,9 +35,11 @@
 import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageRegistry;
 import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageUnit;
 import org.eclipse.emf.cdo.spi.server.ContainerQueryHandlerProvider;
+import org.eclipse.emf.cdo.spi.server.Store;
+import org.eclipse.emf.cdo.spi.server.StoreAccessor;
 
-import org.eclipse.net4j.util.StringUtil;
 import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump;
+import org.eclipse.net4j.util.StringUtil;
 import org.eclipse.net4j.util.concurrent.ConcurrencyUtil;
 import org.eclipse.net4j.util.container.Container;
 import org.eclipse.net4j.util.container.IPluginContainer;
@@ -668,6 +673,24 @@
     }
   }
 
+  public void handleRevisions(final CDORevisionHandler handler)
+  {
+    CDORevisionHandler wrapper = handler;
+
+    IStoreAccessor accessor = StoreThreadLocal.getAccessor();
+    ((StoreAccessor)accessor).handleRevisions(wrapper);
+  }
+
+  public CDOID getRootResourceID()
+  {
+    return ((Store)getStore()).getRootResourceID();
+  }
+
+  public long getLastCommitTimeStamp()
+  {
+    return System.currentTimeMillis() - 1;
+  }
+
   /**
    * @author Eike Stepper
    * @since 2.0
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 081d30c..6ce02bf 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
@@ -11,6 +11,7 @@
  *    Simon McDuff - http://bugs.eclipse.org/230832
  *    Simon McDuff - http://bugs.eclipse.org/233490
  *    Simon McDuff - http://bugs.eclipse.org/213402
+ *    Caspar De Groot - https://bugs.eclipse.org/333260
  */
 package org.eclipse.emf.cdo.internal.server;
 
@@ -38,6 +39,7 @@
 import org.eclipse.net4j.channel.IChannel;
 import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump;
 import org.eclipse.net4j.util.container.Container;
+import org.eclipse.net4j.util.event.EventUtil;
 import org.eclipse.net4j.util.event.IListener;
 import org.eclipse.net4j.util.lifecycle.ILifecycle;
 import org.eclipse.net4j.util.lifecycle.LifecycleEventAdapter;
@@ -96,7 +98,7 @@
     this.protocol = protocol;
     this.sessionID = sessionID;
     this.userID = userID;
-    protocol.addListener(protocolListener);
+    EventUtil.addListener(protocol, protocolListener);
 
     try
     {
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/bundle/CDOCommandProvider.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/bundle/CDOCommandProvider.java
new file mode 100644
index 0000000..69c6dc2
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/bundle/CDOCommandProvider.java
@@ -0,0 +1,167 @@
+/**
+ * Copyright (c) 2004 - 2010 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
+ *    Caspar De Groot - https://bugs.eclipse.org/333260
+ */
+package org.eclipse.emf.cdo.internal.server.bundle;
+
+import org.eclipse.emf.cdo.server.CDOServerExporter;
+import org.eclipse.emf.cdo.server.IRepository;
+import org.eclipse.emf.cdo.spi.server.RepositoryFactory;
+
+import org.eclipse.net4j.util.container.IManagedContainer;
+import org.eclipse.net4j.util.container.IPluginContainer;
+import org.eclipse.net4j.util.io.IOUtil;
+
+import org.eclipse.osgi.framework.console.CommandInterpreter;
+import org.eclipse.osgi.framework.console.CommandProvider;
+
+import org.osgi.framework.BundleContext;
+
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+
+/**
+ * @author Eike Stepper
+ */
+public class CDOCommandProvider implements CommandProvider
+{
+  public CDOCommandProvider(BundleContext bundleContext)
+  {
+    bundleContext.registerService(CommandProvider.class.getName(), this, null);
+  }
+
+  public String getHelp()
+  {
+    StringBuffer buffer = new StringBuffer();
+    buffer.append("---CDO commands---\n");
+    buffer.append("\tcdo list - list all active repositories\n");
+    buffer.append("\tcdo export - export the contents of a repository to an XML file\n");
+    return buffer.toString();
+  }
+
+  public Object _cdo(CommandInterpreter interpreter)
+  {
+    try
+    {
+      String cmd = interpreter.nextArgument();
+      if ("list".equals(cmd))
+      {
+        list(interpreter);
+        return null;
+      }
+
+      if ("export".equals(cmd))
+      {
+        exportXML(interpreter);
+        return null;
+      }
+
+      interpreter.println(getHelp());
+    }
+    catch (CommandException ex)
+    {
+      interpreter.println(ex.getMessage());
+    }
+    catch (Exception ex)
+    {
+      interpreter.printStackTrace(ex);
+    }
+
+    return null;
+  }
+
+  protected void list(CommandInterpreter interpreter) throws Exception
+  {
+    IManagedContainer container = IPluginContainer.INSTANCE;
+    for (Object element : container.getElements(RepositoryFactory.PRODUCT_GROUP))
+    {
+      if (element instanceof IRepository)
+      {
+        IRepository repository = (IRepository)element;
+        interpreter.println(repository.getName());
+      }
+    }
+  }
+
+  protected void exportXML(CommandInterpreter interpreter) throws Exception
+  {
+    String syntax = "Syntax: cdo export <repository-name> <export-file>";
+    IRepository repository = getRepository(interpreter, syntax);
+    String exportFile = nextArgument(interpreter, syntax);
+    OutputStream out = null;
+
+    try
+    {
+      out = new FileOutputStream(exportFile);
+
+      CDOServerExporter.XML exporter = new CDOServerExporter.XML(repository);
+      exporter.exportRepository(out);
+      interpreter.println("Repository exported");
+    }
+    finally
+    {
+      IOUtil.close(out);
+    }
+  }
+
+  private String nextArgument(CommandInterpreter interpreter, String syntax)
+  {
+    String argument = interpreter.nextArgument();
+    if (argument == null)
+    {
+      throw new CommandException(syntax);
+    }
+
+    return argument;
+  }
+
+  private IRepository getRepository(CommandInterpreter interpreter, String syntax)
+  {
+    String repositoryName = nextArgument(interpreter, syntax);
+    IRepository repository = getRepository(repositoryName);
+    if (repository == null)
+    {
+      throw new CommandException("Repository not found: " + repositoryName);
+    }
+
+    return repository;
+  }
+
+  private IRepository getRepository(String name)
+  {
+    IManagedContainer container = IPluginContainer.INSTANCE;
+    for (Object element : container.getElements(RepositoryFactory.PRODUCT_GROUP))
+    {
+      if (element instanceof IRepository)
+      {
+        IRepository repository = (IRepository)element;
+        if (repository.getName().equals(name))
+        {
+          return repository;
+        }
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * @author Eike Stepper
+   */
+  private static final class CommandException extends RuntimeException
+  {
+    private static final long serialVersionUID = 1L;
+
+    public CommandException(String message)
+    {
+      super(message);
+    }
+  }
+}
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/bundle/OM.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/bundle/OM.java
index a5275d9..23bd811 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/bundle/OM.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/bundle/OM.java
@@ -7,6 +7,7 @@
  * 
  * Contributors:
  *    Eike Stepper - initial API and implementation
+ *    Caspar De Groot - https://bugs.eclipse.org/333260
  */
 package org.eclipse.emf.cdo.internal.server.bundle;
 
@@ -56,5 +57,11 @@
     {
       super(BUNDLE);
     }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+      new CDOCommandProvider(bundleContext);
+    }
   }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/mem/MEMStore.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/mem/MEMStore.java
index dddedc3..a67cc03 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/mem/MEMStore.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/mem/MEMStore.java
@@ -16,6 +16,8 @@
 import org.eclipse.emf.cdo.common.id.CDOIDUtil;
 import org.eclipse.emf.cdo.common.model.CDOModelConstants;
 import org.eclipse.emf.cdo.common.revision.CDORevision;
+import org.eclipse.emf.cdo.common.revision.CDORevisionHandler;
+import org.eclipse.emf.cdo.eresource.EresourcePackage;
 import org.eclipse.emf.cdo.server.IMEMStore;
 import org.eclipse.emf.cdo.server.ISession;
 import org.eclipse.emf.cdo.server.IStoreAccessor;
@@ -28,6 +30,9 @@
 
 import org.eclipse.net4j.util.ObjectUtil;
 
+import org.eclipse.emf.ecore.EAttribute;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EReference;
 import org.eclipse.emf.ecore.EStructuralFeature;
 
 import java.util.ArrayList;
@@ -49,6 +54,8 @@
 
   private int listLimit;
 
+  private CDOID rootResourceID;
+
   /**
    * @param listLimit
    *          See {@link #setListLimit(int)}.
@@ -382,4 +389,54 @@
       list.remove(0);
     }
   }
+
+  public synchronized void handleRevisions(CDORevisionHandler handler)
+  {
+    for (List<InternalCDORevision> list : revisions.values())
+    {
+      for (InternalCDORevision revision : list)
+      {
+        if (!handleRevision(revision, handler))
+        {
+          return;
+        }
+      }
+    }
+  }
+
+  private boolean handleRevision(InternalCDORevision revision, CDORevisionHandler handler)
+  {
+    return handler.handleRevision(revision);
+  }
+
+  public CDOID getRootResourceID()
+  {
+    if (rootResourceID != null)
+    {
+      return rootResourceID;
+    }
+
+    EClass eResourceEClass = EresourcePackage.eINSTANCE.getCDOResource();
+    EAttribute nameAttr = EresourcePackage.eINSTANCE.getCDOResourceNode_Name();
+    EReference folderRef = EresourcePackage.eINSTANCE.getCDOResourceNode_Folder();
+    for (List<InternalCDORevision> list : revisions.values())
+    {
+      InternalCDORevision firstRev = list.get(0);
+      if (firstRev.getEClass() == eResourceEClass)
+      {
+        CDOID folderID = (CDOID)firstRev.get(folderRef, 0);
+        if (folderID.isNull())
+        {
+          String name = (String)firstRev.get(nameAttr, 0);
+          if (name == null)
+          {
+            rootResourceID = firstRev.getID();
+            return rootResourceID;
+          }
+        }
+      }
+    }
+
+    throw new RuntimeException("Could not deduce rootResourceID");
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/mem/MEMStoreAccessor.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/mem/MEMStoreAccessor.java
index a749905..ade249b 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/mem/MEMStoreAccessor.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/mem/MEMStoreAccessor.java
@@ -14,6 +14,7 @@
 
 import org.eclipse.emf.cdo.common.CDOQueryInfo;
 import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.revision.CDORevisionHandler;
 import org.eclipse.emf.cdo.server.IQueryContext;
 import org.eclipse.emf.cdo.server.IQueryHandler;
 import org.eclipse.emf.cdo.server.ISession;
@@ -324,4 +325,9 @@
   {
     // Pooling of store accessors not supported
   }
+
+  public void handleRevisions(CDORevisionHandler handler)
+  {
+    getStore().handleRevisions(handler);
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/server/CDOServerExporter.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/server/CDOServerExporter.java
new file mode 100644
index 0000000..b571e0c
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/server/CDOServerExporter.java
@@ -0,0 +1,570 @@
+/**
+ * Copyright (c) 2004 - 2010 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
+ *    Caspar De Groot - https://bugs.eclipse.org/333260
+ */
+package org.eclipse.emf.cdo.server;
+
+import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.id.CDOIDUtil;
+import org.eclipse.emf.cdo.common.model.CDOClassInfo;
+import org.eclipse.emf.cdo.common.model.CDOClassifierRef;
+import org.eclipse.emf.cdo.common.model.CDOModelUtil;
+import org.eclipse.emf.cdo.common.model.CDOPackageInfo;
+import org.eclipse.emf.cdo.common.model.CDOPackageRegistry;
+import org.eclipse.emf.cdo.common.model.CDOPackageUnit;
+import org.eclipse.emf.cdo.common.model.EMFUtil;
+import org.eclipse.emf.cdo.common.revision.CDORevision;
+import org.eclipse.emf.cdo.common.revision.CDORevisionHandler;
+import org.eclipse.emf.cdo.internal.server.Repository;
+import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
+
+import org.eclipse.net4j.util.WrappedException;
+import org.eclipse.net4j.util.io.XMLOutput;
+import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
+
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.emf.ecore.util.FeatureMap;
+import org.eclipse.emf.ecore.util.FeatureMapUtil;
+
+import org.xml.sax.SAXException;
+
+import java.io.OutputStream;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author Eike Stepper
+ * @since 4.0
+ */
+public abstract class CDOServerExporter<OUT>
+{
+  private IRepository repository;
+
+  public CDOServerExporter(IRepository repository)
+  {
+    this.repository = repository;
+  }
+
+  public final IRepository getRepository()
+  {
+    return repository;
+  }
+
+  public final void exportRepository(OutputStream out) throws Exception
+  {
+    boolean wasActive = LifecycleUtil.isActive(repository);
+    if (!wasActive)
+    {
+      LifecycleUtil.activate(repository);
+    }
+
+    ISession session = repository.getSessionManager().openSession(null);
+    StoreThreadLocal.setSession(session);
+
+    try
+    {
+      OUT output = createOutput(out);
+      exportAll(output);
+    }
+    finally
+    {
+      StoreThreadLocal.release();
+      if (!wasActive)
+      {
+        LifecycleUtil.deactivate(repository);
+      }
+
+      repository = null;
+    }
+  }
+
+  protected abstract OUT createOutput(OutputStream out) throws Exception;
+
+  protected void exportAll(final OUT out) throws Exception
+  {
+    try
+    {
+      exportPackages(out);
+      exportBranches(out);
+      exportLobs(out);
+      exportCommits(out);
+    }
+    catch (WrappedException ex)
+    {
+      throw WrappedException.unwrap(ex);
+    }
+  }
+
+  protected void exportPackages(OUT out) throws Exception
+  {
+    CDOPackageRegistry packageRegistry = repository.getPackageRegistry();
+    CDOPackageUnit[] packageUnits = packageRegistry.getPackageUnits();
+    for (CDOPackageUnit packageUnit : packageUnits)
+    {
+      String id = packageUnit.getID();
+
+      // Don't export the system packages, as 4.0 does not expect these
+      if (CDOModelUtil.CORE_PACKAGE_URI.equals(id) || CDOModelUtil.RESOURCE_PACKAGE_URI.equals(id))
+      {
+        continue;
+      }
+
+      CDOPackageUnit.Type type = packageUnit.getOriginalType();
+      long time = packageUnit.getTimeStamp();
+
+      EPackage ePackage = packageUnit.getTopLevelPackageInfo().getEPackage();
+      String data = new String(EMFUtil.getEPackageBytes(ePackage, false, packageRegistry));
+
+      startPackageUnit(out, id, type, time, data);
+      for (CDOPackageInfo packageInfo : packageUnit.getPackageInfos())
+      {
+        String packageURI = packageInfo.getPackageURI();
+        exportPackageInfo(out, packageURI);
+      }
+
+      endPackageUnit(out);
+    }
+  }
+
+  protected abstract void startPackageUnit(OUT out, String id, CDOPackageUnit.Type type, long time, String data)
+      throws Exception;
+
+  protected abstract void endPackageUnit(OUT out) throws Exception;
+
+  protected abstract void exportPackageInfo(OUT out, String packageURI) throws Exception;
+
+  protected void exportBranches(final OUT out) throws Exception
+  {
+    exportBranch(out);
+  }
+
+  protected void exportBranch(OUT out) throws Exception
+  {
+    exportRevisions(out);
+  }
+
+  protected void exportRevisions(final OUT out) throws Exception
+  {
+    ((Repository)repository).handleRevisions(new CDORevisionHandler()
+    {
+      public boolean handleRevision(CDORevision revision)
+      {
+        try
+        {
+          exportRevision(out, revision);
+          return true;
+        }
+        catch (Exception ex)
+        {
+          throw WrappedException.wrap(ex);
+        }
+      }
+    });
+  }
+
+  protected abstract void exportRevision(OUT out, CDORevision revision) throws Exception;
+
+  protected void exportLobs(final OUT out) throws Exception
+  {
+  }
+
+  protected void exportCommits(final OUT out) throws Exception
+  {
+  }
+
+  /**
+   * @author Eike Stepper
+   */
+  public static interface XMLConstants
+  {
+    public static final String REPOSITORY = "repository";
+
+    public static final String REPOSITORY_NAME = "name";
+
+    public static final String REPOSITORY_UUID = "uuid";
+
+    public static final String REPOSITORY_ROOT = "root";
+
+    public static final String REPOSITORY_CREATED = "created";
+
+    public static final String REPOSITORY_COMMITTED = "committed";
+
+    public static final String MODELS = "models";
+
+    public static final String PACKAGE_UNIT = "packageUnit";
+
+    public static final String PACKAGE_UNIT_ID = "id";
+
+    public static final String PACKAGE_UNIT_TYPE = "type";
+
+    public static final String PACKAGE_UNIT_TIME = "time";
+
+    public static final String PACKAGE_UNIT_DATA = "data";
+
+    public static final String PACKAGE_INFO = "packageInfo";
+
+    public static final String PACKAGE_INFO_URI = "uri";
+
+    public static final String INSTANCES = "instances";
+
+    public static final String BRANCH = "branch";
+
+    public static final String BRANCH_ID = "id";
+
+    public static final String BRANCH_NAME = "name";
+
+    public static final String BRANCH_TIME = "time";
+
+    public static final String BRANCH_PARENT = "parent";
+
+    public static final String REVISION = "revision";
+
+    public static final String REVISION_ID = "id";
+
+    public static final String REVISION_CLASS = "class";
+
+    public static final String REVISION_VERSION = "version";
+
+    public static final String REVISION_TIME = "time";
+
+    public static final String REVISION_REVISED = "revised";
+
+    public static final String REVISION_RESOURCE = "resource";
+
+    public static final String REVISION_CONTAINER = "container";
+
+    public static final String REVISION_FEATURE = "feature";
+
+    public static final String FEATURE = "feature";
+
+    public static final String FEATURE_NAME = "name";
+
+    public static final String FEATURE_TYPE = "type";
+
+    public static final String FEATURE_INNER_FEATURE = "innerFeature";
+
+    public static final String FEATURE_INNER_TYPE = "innerType";
+
+    public static final String FEATURE_VALUE = "value";
+
+    public static final String FEATURE_ID = "id";
+
+    public static final String FEATURE_SIZE = "size";
+
+    public static final String TYPE_BLOB = "Blob";
+
+    public static final String TYPE_CLOB = "Clob";
+
+    public static final String TYPE_FEATURE_MAP = "FeatureMap";
+
+    public static final String LOBS = "lobs";
+
+    public static final String LOB_ID = "id";
+
+    public static final String LOB_SIZE = "size";
+
+    public static final String BLOB = "blob";
+
+    public static final String CLOB = "clob";
+
+    public static final String COMMITS = "commits";
+
+    public static final String COMMIT = "commit";
+
+    public static final String COMMIT_TIME = "time";
+
+    public static final String COMMIT_PREVIOUS = "previous";
+
+    public static final String COMMIT_BRANCH = "branch";
+
+    public static final String COMMIT_USER = "user";
+
+    public static final String COMMIT_COMMENT = "comment";
+  }
+
+  /**
+   * @author Eike Stepper
+   */
+  public static class XML extends CDOServerExporter<XMLOutput> implements XMLConstants
+  {
+    public XML(IRepository repository)
+    {
+      super(repository);
+    }
+
+    @Override
+    protected final XMLOutput createOutput(OutputStream out) throws Exception
+    {
+      return new XMLOutput(out);
+    }
+
+    @Override
+    protected void exportAll(XMLOutput out) throws Exception
+    {
+      out.element(REPOSITORY);
+      out.attribute(REPOSITORY_NAME, getRepository().getName());
+      out.attribute(REPOSITORY_UUID, getRepository().getUUID());
+      out.attribute(REPOSITORY_ROOT, str(((Repository)getRepository()).getRootResourceID()));
+      out.attribute(REPOSITORY_CREATED, getRepository().getStore().getCreationTime());
+      out.attribute(REPOSITORY_COMMITTED, ((Repository)getRepository()).getLastCommitTimeStamp());
+
+      out.push();
+      super.exportAll(out);
+      out.done();
+    }
+
+    @Override
+    protected void exportPackages(XMLOutput out) throws Exception
+    {
+      out.element(MODELS);
+
+      out.push();
+      super.exportPackages(out);
+      out.pop();
+    }
+
+    @Override
+    protected void startPackageUnit(XMLOutput out, String id, CDOPackageUnit.Type type, long time, String data)
+        throws Exception
+    {
+      out.element(PACKAGE_UNIT);
+      out.attribute(PACKAGE_UNIT_ID, id);
+      out.attribute(PACKAGE_UNIT_TYPE, type);
+      out.attribute(PACKAGE_UNIT_TIME, time);
+      out.attribute(PACKAGE_UNIT_DATA, data);
+      out.push();
+    }
+
+    @Override
+    protected void endPackageUnit(XMLOutput out) throws Exception
+    {
+      out.pop();
+    }
+
+    @Override
+    protected void exportPackageInfo(XMLOutput out, String uri) throws Exception
+    {
+      out.element(PACKAGE_INFO);
+      out.attribute(PACKAGE_INFO_URI, uri);
+    }
+
+    @Override
+    protected void exportBranches(XMLOutput out) throws Exception
+    {
+      out.element(INSTANCES);
+
+      out.push();
+      super.exportBranches(out);
+      out.pop();
+    }
+
+    @Override
+    protected void exportBranch(XMLOutput out) throws Exception
+    {
+      out.element(BRANCH);
+      out.attribute(BRANCH_ID, 0);
+      out.attribute(BRANCH_NAME, "MAIN");
+      out.attribute(BRANCH_TIME, getRepository().getStore().getCreationTime());
+
+      out.push();
+      super.exportBranch(out);
+      out.pop();
+    }
+
+    private String getURI(CDOClassifierRef ref)
+    {
+      return ref.getPackageURI() + CDOClassifierRef.URI_SEPARATOR + ref.getClassifierName();
+    }
+
+    @Override
+    protected void exportRevision(XMLOutput out, CDORevision revision) throws Exception
+    {
+      InternalCDORevision rev = (InternalCDORevision)revision;
+
+      out.element(REVISION);
+      out.attribute(REVISION_ID, str(rev.getID()));
+      out.attribute(REVISION_CLASS, getURI(new CDOClassifierRef(rev.getEClass())));
+      out.attribute(REVISION_VERSION, rev.getVersion());
+      out.attribute(REVISION_TIME, rev.getCreated());
+
+      long revised = rev.getRevised();
+      if (revised != 0 /* CDOBranchPoint.UNSPECIFIED_DATE */)
+      {
+        out.attribute(REVISION_REVISED, revised);
+      }
+
+      CDOID resourceID = rev.getResourceID();
+      if (!CDOIDUtil.isNull(resourceID))
+      {
+        out.attribute(REVISION_RESOURCE, str(resourceID));
+      }
+
+      CDOID containerID = (CDOID)rev.getContainerID();
+      if (!CDOIDUtil.isNull(containerID))
+      {
+        out.attribute(REVISION_CONTAINER, str(containerID));
+      }
+
+      int containingFeatureID = rev.getContainingFeatureID();
+      if (containingFeatureID != 0)
+      {
+        out.attribute(REVISION_FEATURE, containingFeatureID);
+      }
+
+      out.push();
+      CDOClassInfo classInfo = CDOModelUtil.getClassInfo(rev.getEClass());
+      for (EStructuralFeature feature : classInfo.getAllPersistentFeatures())
+      {
+        if (feature.isMany())
+        {
+          @SuppressWarnings("unchecked")
+          List<Object> list = (List<Object>)rev.getValue(feature);
+          if (list != null)
+          {
+            for (Object value : list)
+            {
+              exportFeature(out, feature, value);
+            }
+          }
+        }
+        else
+        {
+          Object value = rev.getValue(feature);
+          if (value != null && !(value instanceof CDOID && CDOIDUtil.isNull((CDOID)value)))
+          {
+            exportFeature(out, feature, value);
+          }
+        }
+      }
+
+      out.pop();
+    }
+
+    protected void exportFeature(XMLOutput out, EStructuralFeature feature, Object value) throws Exception
+    {
+      out.element(FEATURE);
+      out.attribute(FEATURE_NAME, feature.getName());
+      exportFeature(out, feature, FEATURE_TYPE, value);
+    }
+
+    protected void exportFeature(XMLOutput out, EStructuralFeature feature, String featureType, Object value)
+        throws SAXException
+    {
+      if (value instanceof CDOID)
+      {
+        out.attribute(featureType, Object.class.getSimpleName());
+        out.attribute(FEATURE_VALUE, str((CDOID)value));
+      }
+      else if (value instanceof Date)
+      {
+        Date date = (Date)value;
+        out.attribute(featureType, Date.class.getSimpleName());
+        out.attribute(FEATURE_VALUE, date.getTime());
+      }
+      else if (FeatureMapUtil.isFeatureMap(feature))
+      {
+        FeatureMap.Entry entry = (FeatureMap.Entry)value;
+        EStructuralFeature innerFeature = entry.getEStructuralFeature();
+        Object innerValue = entry.getValue();
+
+        out.attribute(featureType, TYPE_FEATURE_MAP);
+        out.attribute(FEATURE_INNER_FEATURE, innerFeature.getName());
+        exportFeature(out, innerFeature, FEATURE_INNER_TYPE, innerValue);
+      }
+      else
+      {
+        if (!(value instanceof String))
+        {
+          out.attribute(featureType, type(value));
+        }
+
+        out.attributeOrNull(FEATURE_VALUE, value);
+      }
+    }
+
+    @Override
+    protected void exportLobs(XMLOutput out) throws Exception
+    {
+      out.element(LOBS);
+
+      out.push();
+      super.exportLobs(out);
+      out.pop();
+    }
+
+    @Override
+    protected void exportCommits(XMLOutput out) throws Exception
+    {
+      out.element(COMMITS);
+
+      out.push();
+      super.exportCommits(out);
+      out.pop();
+    }
+
+    protected final String str(CDOID id)
+    {
+      StringBuilder builder = new StringBuilder();
+      CDOIDUtil.write40(builder, id);
+      return builder.toString();
+    }
+
+    protected String type(Object value)
+    {
+      if (value instanceof Boolean)
+      {
+        return Boolean.class.getSimpleName();
+      }
+
+      if (value instanceof Character)
+      {
+        return Character.class.getSimpleName();
+      }
+
+      if (value instanceof Byte)
+      {
+        return Byte.class.getSimpleName();
+      }
+
+      if (value instanceof Short)
+      {
+        return Short.class.getSimpleName();
+      }
+
+      if (value instanceof Integer)
+      {
+        return Integer.class.getSimpleName();
+      }
+
+      if (value instanceof Long)
+      {
+        return Long.class.getSimpleName();
+      }
+
+      if (value instanceof Float)
+      {
+        return Float.class.getSimpleName();
+      }
+
+      if (value instanceof Double)
+      {
+        return Double.class.getSimpleName();
+      }
+
+      if (value instanceof String)
+      {
+        return String.class.getSimpleName();
+      }
+
+      throw new IllegalArgumentException("Invalid type: " + value.getClass().getName());
+    }
+  }
+}
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/Store.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/Store.java
index c8f8b97..ffb25c4 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/Store.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/Store.java
@@ -7,6 +7,7 @@
  * 
  * Contributors:
  *    Eike Stepper - initial API and implementation
+ *    Caspar De Groot - https://bugs.eclipse.org/333260    
  */
 package org.eclipse.emf.cdo.spi.server;
 
@@ -22,8 +23,8 @@
 import org.eclipse.emf.cdo.server.ITransaction;
 import org.eclipse.emf.cdo.server.IView;
 
-import org.eclipse.net4j.util.StringUtil;
 import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump;
+import org.eclipse.net4j.util.StringUtil;
 import org.eclipse.net4j.util.container.IContainerDelta;
 import org.eclipse.net4j.util.lifecycle.Lifecycle;
 import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
@@ -363,4 +364,9 @@
   {
     return Collections.unmodifiableSet(new HashSet<T>(Arrays.asList(elements)));
   }
+
+  public CDOID getRootResourceID()
+  {
+    throw new RuntimeException("Method getRootResourceID not supported");
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/StoreAccessor.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/StoreAccessor.java
index 97b6644..189ab87 100644
--- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/StoreAccessor.java
+++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/spi/server/StoreAccessor.java
@@ -9,11 +9,13 @@
  *    Eike Stepper - initial API and implementation
  *    Simon McDuff - http://bugs.eclipse.org/201266
  *    Simon McDuff - http://bugs.eclipse.org/213402
+ *    Caspar De Groot - https://bugs.eclipse.org/333260
  */
 package org.eclipse.emf.cdo.spi.server;
 
 import org.eclipse.emf.cdo.common.id.CDOID;
 import org.eclipse.emf.cdo.common.id.CDOIDTemp;
+import org.eclipse.emf.cdo.common.revision.CDORevisionHandler;
 import org.eclipse.emf.cdo.internal.server.bundle.OM;
 import org.eclipse.emf.cdo.server.ISession;
 import org.eclipse.emf.cdo.server.IStoreAccessor;
@@ -203,4 +205,9 @@
   protected abstract void doPassivate() throws Exception;
 
   protected abstract void doUnpassivate() throws Exception;
+
+  public void handleRevisions(CDORevisionHandler handler)
+  {
+    throw new RuntimeException("handleRevisions not supported");
+  }
 }
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllTestsAllConfigs.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllTestsAllConfigs.java
index 1a56903..0c80ba7 100644
--- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllTestsAllConfigs.java
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllTestsAllConfigs.java
@@ -115,6 +115,7 @@
     testClasses.add(MultiValuedOfAttributeTest.class);
     testClasses.add(AdapterManagerTest.class);
     testClasses.add(ConflictResolverTest.class);
+    testClasses.add(BackupTest.class);
 
     // Specific for MEMStore
     testClasses.add(MEMStoreQueryTest.class);
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/BackupTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/BackupTest.java
new file mode 100644
index 0000000..19aeef0
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/BackupTest.java
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2004 - 2010 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
+ *    Caspar De Groot - https://bugs.eclipse.org/333260
+ */
+package org.eclipse.emf.cdo.tests;
+
+import org.eclipse.emf.cdo.eresource.CDOResource;
+import org.eclipse.emf.cdo.server.CDOServerExporter;
+import org.eclipse.emf.cdo.server.IRepository;
+import org.eclipse.emf.cdo.session.CDOSession;
+import org.eclipse.emf.cdo.tests.model1.Customer;
+import org.eclipse.emf.cdo.tests.model1.PurchaseOrder;
+import org.eclipse.emf.cdo.transaction.CDOTransaction;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Date;
+
+/**
+ * @author Eike Stepper
+ */
+public class BackupTest extends AbstractCDOTest
+{
+  @Override
+  protected void doSetUp() throws Exception
+  {
+    disableConsole();
+    super.doSetUp();
+  }
+
+  @Override
+  protected void doTearDown() throws Exception
+  {
+    disableConsole();
+    super.doTearDown();
+  }
+
+  public void testExport() throws Exception
+  {
+    CDOSession session = openSession();
+    CDOTransaction transaction = session.openTransaction();
+    CDOResource resource = transaction.createResource("/res1");
+    resource.getContents().add(createCustomer("Eike"));
+    transaction.commit();
+    session.close();
+
+    IRepository repo1 = getRepository();
+
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    CDOServerExporter.XML exporter = new CDOServerExporter.XML(repo1);
+    exporter.exportRepository(baos);
+    System.out.println(baos.toString());
+  }
+
+  public void testExportDate() throws Exception
+  {
+    CDOSession session = openSession();
+    CDOTransaction transaction = session.openTransaction();
+    CDOResource resource = transaction.createResource("/res1");
+    PurchaseOrder purchaseOrder = getModel1Factory().createPurchaseOrder();
+    purchaseOrder.setDate(new Date(1234567));
+    resource.getContents().add(purchaseOrder);
+    transaction.commit();
+    session.close();
+
+    IRepository repo1 = getRepository();
+
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    CDOServerExporter.XML exporter = new CDOServerExporter.XML(repo1);
+    exporter.exportRepository(baos);
+    System.out.println(baos.toString());
+  }
+
+  private Customer createCustomer(String name)
+  {
+    Customer customer = getModel1Factory().createCustomer();
+    customer.setName(name);
+    return customer;
+  }
+}
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/io/XMLOutput.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/io/XMLOutput.java
new file mode 100644
index 0000000..ed8aa97
--- /dev/null
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/io/XMLOutput.java
@@ -0,0 +1,307 @@
+package org.eclipse.net4j.util.io;
+
+import org.eclipse.net4j.util.HexUtil;
+import org.eclipse.net4j.util.WrappedException;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.util.LinkedList;
+
+/**
+ * @author Eike Stepper
+ * @since 3.1
+ */
+public class XMLOutput
+{
+  private static final AttributesImpl NO_ATTRIBUTES = new AttributesImpl();
+
+  private TransformerHandler xmlHandler;
+
+  private char[] newLine;
+
+  private char[] indentation;
+
+  private LinkedList<Element> stack = new LinkedList<Element>();
+
+  private Element element;
+
+  public XMLOutput(OutputStream out) throws TransformerConfigurationException, SAXException
+  {
+    setNewLine("\n");
+    setIndentation("  ");
+    SAXTransformerFactory factory = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
+
+    xmlHandler = factory.newTransformerHandler();
+
+    Transformer transformer = xmlHandler.getTransformer();
+    transformer.setOutputProperty(OutputKeys.METHOD, "xml");
+    transformer.setOutputProperty(OutputKeys.VERSION, "1.0");
+    transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+
+    xmlHandler.setResult(new StreamResult(out));
+    xmlHandler.startDocument();
+  }
+
+  public void setNewLine(String newLine)
+  {
+    this.newLine = newLine.toCharArray();
+  }
+
+  public void setIndentation(String indentation)
+  {
+    this.indentation = indentation.toCharArray();
+  }
+
+  public XMLOutput element(String name) throws SAXException
+  {
+    flush();
+    element = new Element(name);
+    return this;
+  }
+
+  public XMLOutput attribute(String name, Object value) throws SAXException
+  {
+    if (value != null)
+    {
+      return attributeOrNull(name, value);
+    }
+
+    return this;
+  }
+
+  public XMLOutput attributeOrNull(String name, Object value) throws SAXException
+  {
+    checkElement();
+    element.addAttribute(name, value);
+    return this;
+  }
+
+  public Writer characters() throws SAXException
+  {
+    checkElement();
+    newLine();
+    element.start();
+    xmlHandler.startCDATA();
+
+    return new Writer()
+    {
+      @Override
+      public void write(char[] cbuf, int off, int len) throws IOException
+      {
+        try
+        {
+          xmlHandler.characters(cbuf, off, len);
+        }
+        catch (SAXException ex)
+        {
+          throw WrappedException.wrap(ex);
+        }
+      }
+
+      @Override
+      public void flush() throws IOException
+      {
+        // Do nothing
+      }
+
+      @Override
+      public void close() throws IOException
+      {
+        try
+        {
+          xmlHandler.endCDATA();
+          element.end();
+        }
+        catch (SAXException ex)
+        {
+          throw WrappedException.wrap(ex);
+        }
+        finally
+        {
+          element = null;
+        }
+      }
+    };
+  }
+
+  public OutputStream bytes() throws SAXException
+  {
+    checkElement();
+    newLine();
+    element.start();
+    xmlHandler.startCDATA();
+
+    return new OutputStream()
+    {
+      @Override
+      public void write(byte[] b, int off, int len) throws IOException
+      {
+        try
+        {
+          char[] cbuf = HexUtil.bytesToHex(b, off, len).toCharArray();
+          xmlHandler.characters(cbuf, 0, cbuf.length);
+        }
+        catch (SAXException ex)
+        {
+          throw WrappedException.wrap(ex);
+        }
+      }
+
+      @Override
+      public void write(int i) throws IOException
+      {
+        byte b = (byte)((i & 0xff) + Byte.MIN_VALUE);
+        byte[] bs = { b };
+        write(bs, 0, 1);
+      }
+
+      @Override
+      public void close() throws IOException
+      {
+        try
+        {
+          xmlHandler.endCDATA();
+          element.end();
+        }
+        catch (SAXException ex)
+        {
+          throw WrappedException.wrap(ex);
+        }
+        finally
+        {
+          element = null;
+        }
+      }
+    };
+  }
+
+  public XMLOutput push() throws SAXException
+  {
+    newLine();
+    element.start();
+
+    stack.add(element);
+    element = null;
+    return this;
+  }
+
+  public XMLOutput pop() throws SAXException
+  {
+    flush();
+    Element element = stack.removeLast();
+
+    if (element.hasChildren())
+    {
+      newLine();
+    }
+
+    element.end();
+    return this;
+  }
+
+  public void done() throws SAXException
+  {
+    while (!stack.isEmpty())
+    {
+      pop();
+    }
+
+    xmlHandler.endDocument();
+  }
+
+  private void flush() throws SAXException
+  {
+    if (element != null)
+    {
+      newLine();
+      element.start();
+      element.end();
+      element = null;
+    }
+  }
+
+  private void newLine() throws SAXException
+  {
+    xmlHandler.ignorableWhitespace(newLine, 0, newLine.length);
+    for (int i = 0; i < stack.size(); i++)
+    {
+      xmlHandler.ignorableWhitespace(indentation, 0, indentation.length);
+    }
+  }
+
+  private void checkElement()
+  {
+    if (element == null)
+    {
+      throw new IllegalStateException("No element");
+    }
+  }
+
+  /**
+   * @author Eike Stepper
+   */
+  private final class Element
+  {
+    private String name;
+
+    private AttributesImpl attributes;
+
+    private boolean children;
+
+    public Element(String name)
+    {
+      this.name = name;
+    }
+
+    public boolean hasChildren()
+    {
+      return children;
+    }
+
+    public void addChild()
+    {
+      children = true;
+    }
+
+    public void addAttribute(String name, Object value)
+    {
+      if (attributes == null)
+      {
+        attributes = new AttributesImpl();
+      }
+
+      if (value == null)
+      {
+        value = "";
+      }
+
+      attributes.addAttribute("", "", name, "", value.toString());
+    }
+
+    public void start() throws SAXException
+    {
+      if (!stack.isEmpty())
+      {
+        stack.getLast().addChild();
+      }
+
+      xmlHandler.startElement("", "", name, attributes == null ? NO_ATTRIBUTES : attributes);
+    }
+
+    public void end() throws SAXException
+    {
+      xmlHandler.endElement("", "", name);
+    }
+  }
+}