Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEike Stepper2011-02-16 09:40:34 +0000
committerEike Stepper2011-02-16 09:40:34 +0000
commit8b618a1471a8ea22951b87c8d1cadeab7daa1cce (patch)
tree598c9110ac25c3b3400a413c3f5a0a779bc099a8 /plugins/org.eclipse.emf.cdo.server.mongodb/src
parent4cc3c90d4ba00d0943caa8b3ccaa2f9849567a34 (diff)
downloadcdo-8b618a1471a8ea22951b87c8d1cadeab7daa1cce.tar.gz
cdo-8b618a1471a8ea22951b87c8d1cadeab7daa1cce.tar.xz
cdo-8b618a1471a8ea22951b87c8d1cadeab7daa1cce.zip
[337152] [Mongo] Create a MongoDBStore
https://bugs.eclipse.org/bugs/show_bug.cgi?id=337152
Diffstat (limited to 'plugins/org.eclipse.emf.cdo.server.mongodb/src')
-rw-r--r--plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/LongIDHandler.java122
-rw-r--r--plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/MongoDBBrowserPage.java2
-rw-r--r--plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/MongoDBStore.java182
-rw-r--r--plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/MongoDBStoreAccessor.java2
-rw-r--r--plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/mongodb/CDOMongoDBUtil.java2
-rw-r--r--plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/mongodb/IMongoDBStore.java38
-rw-r--r--plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/mongodb/IMongoDBStoreAccessor.java2
7 files changed, 325 insertions, 25 deletions
diff --git a/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/LongIDHandler.java b/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/LongIDHandler.java
new file mode 100644
index 0000000000..37cafaef6e
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/LongIDHandler.java
@@ -0,0 +1,122 @@
+/**
+ * Copyright (c) 2004 - 2011 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.server.internal.mongodb;
+
+import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.id.CDOID.ObjectType;
+import org.eclipse.emf.cdo.common.id.CDOIDUtil;
+import org.eclipse.emf.cdo.common.revision.CDORevision;
+import org.eclipse.emf.cdo.server.mongodb.IMongoDBStore.IDHandler;
+import org.eclipse.emf.cdo.spi.server.LongIDStore;
+
+import org.eclipse.net4j.util.lifecycle.Lifecycle;
+
+import java.util.Set;
+
+/**
+ * @author Eike Stepper
+ */
+public class LongIDHandler extends Lifecycle implements IDHandler
+{
+ public static final CDOID MIN = CDOID.NULL;
+
+ public static final CDOID MAX = create(Long.MAX_VALUE);
+
+ private MongoDBStore store;
+
+ private CDOID lastObjectID = MIN;
+
+ private CDOID nextLocalObjectID = MAX;
+
+ public LongIDHandler(MongoDBStore store)
+ {
+ this.store = store;
+ }
+
+ public MongoDBStore getStore()
+ {
+ return store;
+ }
+
+ public Set<ObjectType> getObjectIDTypes()
+ {
+ return LongIDStore.OBJECT_ID_TYPES;
+ }
+
+ public CDOID getMinCDOID()
+ {
+ return MIN;
+ }
+
+ public CDOID getMaxCDOID()
+ {
+ return MAX;
+ }
+
+ public int compare(CDOID id1, CDOID id2)
+ {
+ return id1.compareTo(id2);
+ }
+
+ public CDOID createCDOID(String val)
+ {
+ Long id = Long.valueOf(val);
+ return create(id);
+ }
+
+ public synchronized CDOID getLastObjectID()
+ {
+ return lastObjectID;
+ }
+
+ public synchronized void setLastObjectID(CDOID lastObjectID)
+ {
+ this.lastObjectID = lastObjectID;
+ }
+
+ public synchronized CDOID getNextLocalObjectID()
+ {
+ return nextLocalObjectID;
+ }
+
+ public synchronized void setNextLocalObjectID(CDOID nextLocalObjectID)
+ {
+ this.nextLocalObjectID = nextLocalObjectID;
+ }
+
+ public synchronized CDOID getNextCDOID(CDORevision revision)
+ {
+ if (revision.getBranch().isLocal())
+ {
+ CDOID result = nextLocalObjectID;
+ nextLocalObjectID = create(value(result) - 1);
+ return result;
+ }
+
+ lastObjectID = create(value(lastObjectID) + 1);
+ return lastObjectID;
+ }
+
+ public boolean isLocalCDOID(CDOID id)
+ {
+ return compare(id, nextLocalObjectID) > 0;
+ }
+
+ private static CDOID create(long id)
+ {
+ return CDOIDUtil.createLong(id);
+ }
+
+ private static long value(CDOID id)
+ {
+ return CDOIDUtil.getLong(id);
+ }
+}
diff --git a/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/MongoDBBrowserPage.java b/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/MongoDBBrowserPage.java
index 92d10902cc..9da127721f 100644
--- a/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/MongoDBBrowserPage.java
+++ b/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/MongoDBBrowserPage.java
@@ -2,7 +2,7 @@ package org.eclipse.emf.cdo.server.internal.mongodb;
import org.eclipse.emf.cdo.server.CDOServerBrowser;
import org.eclipse.emf.cdo.server.CDOServerBrowser.AbstractPage;
-import org.eclipse.emf.cdo.server.mongodbdb.IMongoDBStore;
+import org.eclipse.emf.cdo.server.mongodb.IMongoDBStore;
import org.eclipse.emf.cdo.spi.server.InternalRepository;
import org.eclipse.net4j.util.factory.ProductCreationException;
diff --git a/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/MongoDBStore.java b/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/MongoDBStore.java
index f0364e31f6..d71d7020e3 100644
--- a/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/MongoDBStore.java
+++ b/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/MongoDBStore.java
@@ -11,20 +11,32 @@
package org.eclipse.emf.cdo.server.internal.mongodb;
import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.util.CDOCommonUtil;
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.mongodbdb.IMongoDBStore;
-import org.eclipse.emf.cdo.server.mongodbdb.IMongoDBStoreAccessor;
+import org.eclipse.emf.cdo.server.internal.mongodb.bundle.OM;
+import org.eclipse.emf.cdo.server.mongodb.IMongoDBStore;
+import org.eclipse.emf.cdo.server.mongodb.IMongoDBStoreAccessor;
import org.eclipse.emf.cdo.spi.server.Store;
import org.eclipse.emf.cdo.spi.server.StoreAccessorPool;
+import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
+
+import com.mongodb.BasicDBObject;
import com.mongodb.DB;
+import com.mongodb.DBCollection;
+import com.mongodb.DBCursor;
+import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoURI;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
/**
@@ -34,17 +46,41 @@ public class MongoDBStore extends Store implements IMongoDBStore
{
public static final String TYPE = "mongodb"; //$NON-NLS-1$
+ private static final String PROP_REPOSITORY_CREATED = "org.eclipse.emf.cdo.server.mongodb.repositoryCreated"; //$NON-NLS-1$
+
+ private static final String PROP_REPOSITORY_STOPPED = "org.eclipse.emf.cdo.server.mongodb.repositoryStopped"; //$NON-NLS-1$
+
+ private static final String PROP_NEXT_LOCAL_CDOID = "org.eclipse.emf.cdo.server.mongodb.nextLocalCDOID"; //$NON-NLS-1$
+
+ private static final String PROP_LAST_CDOID = "org.eclipse.emf.cdo.server.mongodb.lastCDOID"; //$NON-NLS-1$
+
+ private static final String PROP_LAST_BRANCHID = "org.eclipse.emf.cdo.server.mongodb.lastBranchID"; //$NON-NLS-1$
+
+ private static final String PROP_LAST_LOCAL_BRANCHID = "org.eclipse.emf.cdo.server.mongodb.lastLocalBranchID"; //$NON-NLS-1$
+
+ private static final String PROP_LAST_COMMITTIME = "org.eclipse.emf.cdo.server.mongodb.lastCommitTime"; //$NON-NLS-1$
+
+ private static final String PROP_LAST_NONLOCAL_COMMITTIME = "org.eclipse.emf.cdo.server.mongodb.lastNonLocalCommitTime"; //$NON-NLS-1$
+
+ private static final String PROP_GRACEFULLY_SHUT_DOWN = "org.eclipse.emf.cdo.server.mongodb.gracefullyShutDown"; //$NON-NLS-1$
+
private MongoURI mongoURI;
private String dbName;
- private DB db;
-
private IsolationLevel isolationLevel;
private EmbeddingStrategy embeddingStrategy;
- private IDHandler idHandler;
+ private IDHandler idHandler = new LongIDHandler(this);
+
+ private DB db;
+
+ private DBCollection propertiesCollection;
+
+ private boolean firstStart;
+
+ private long creationTime;
public MongoDBStore()
{
@@ -75,12 +111,6 @@ public class MongoDBStore extends Store implements IMongoDBStore
this.dbName = dbName;
}
- public DB getDB()
- {
- checkActive();
- return db;
- }
-
public IsolationLevel getIsolationLevel()
{
return isolationLevel;
@@ -114,14 +144,52 @@ public class MongoDBStore extends Store implements IMongoDBStore
this.idHandler = idHandler;
}
+ public DB getDB()
+ {
+ return db;
+ }
+
+ public DBCollection getPropertiesCollection()
+ {
+ return propertiesCollection;
+ }
+
public Map<String, String> getPropertyValues(Set<String> names)
{
- throw new UnsupportedOperationException("Not yet implemented"); // TODO Implement me
+ Map<String, String> result = new HashMap<String, String>();
+ for (String name : names)
+ {
+ DBObject query = new BasicDBObject("_id", name);
+ DBCursor cursor = propertiesCollection.find(query);
+
+ try
+ {
+ if (cursor.hasNext())
+ {
+ DBObject doc = cursor.next();
+ result.put(name, (String)doc.get("v"));
+ }
+ }
+ finally
+ {
+ cursor.close();
+ }
+ }
+
+ return result;
}
public void setPropertyValues(Map<String, String> properties)
{
- throw new UnsupportedOperationException("Not yet implemented"); // TODO Implement me
+ for (Entry<String, String> property : properties.entrySet())
+ {
+ DBObject doc = new BasicDBObject();
+ doc.put("_id", property.getKey());
+ doc.put("v", property.getValue());
+
+ propertiesCollection.insert(doc);
+
+ }
}
public void removePropertyValues(Set<String> names)
@@ -129,19 +197,23 @@ public class MongoDBStore extends Store implements IMongoDBStore
throw new UnsupportedOperationException("Not yet implemented"); // TODO Implement me
}
- public boolean isFirstTime()
+ public boolean isFirstStart()
{
- throw new UnsupportedOperationException("Not yet implemented"); // TODO Implement me
+ return firstStart;
}
public long getCreationTime()
{
- throw new UnsupportedOperationException("Not yet implemented"); // TODO Implement me
+ return creationTime;
}
public void setCreationTime(long creationTime)
{
- throw new UnsupportedOperationException("Not yet implemented"); // TODO Implement me
+ this.creationTime = creationTime;
+
+ Map<String, String> map = new HashMap<String, String>();
+ map.put(PROP_REPOSITORY_CREATED, Long.toString(creationTime));
+ setPropertyValues(map);
}
public CDOID createObjectID(String val)
@@ -204,14 +276,44 @@ public class MongoDBStore extends Store implements IMongoDBStore
protected void doActivate() throws Exception
{
super.doActivate();
+ setObjectIDTypes(idHandler.getObjectIDTypes());
Mongo mongo = new Mongo(mongoURI);
db = mongo.getDB(dbName);
+
+ Set<String> collectionNames = db.getCollectionNames();
+ firstStart = !collectionNames.contains("cdo.properties");
+
+ propertiesCollection = db.getCollection("cdo.properties");
+
+ LifecycleUtil.activate(idHandler);
+
+ if (firstStart)
+ {
+ firstStart();
+ }
+ else
+ {
+ reStart();
+ }
}
@Override
protected void doDeactivate() throws Exception
{
+ Map<String, String> map = new HashMap<String, String>();
+ map.put(PROP_GRACEFULLY_SHUT_DOWN, Boolean.TRUE.toString());
+ map.put(PROP_REPOSITORY_STOPPED, Long.toString(getRepository().getTimeStamp()));
+ map.put(PROP_NEXT_LOCAL_CDOID, Store.idToString(idHandler.getNextLocalObjectID()));
+ map.put(PROP_LAST_CDOID, Store.idToString(idHandler.getLastObjectID()));
+ map.put(PROP_LAST_BRANCHID, Integer.toString(getLastBranchID()));
+ map.put(PROP_LAST_LOCAL_BRANCHID, Integer.toString(getLastLocalBranchID()));
+ map.put(PROP_LAST_COMMITTIME, Long.toString(getLastCommitTime()));
+ map.put(PROP_LAST_NONLOCAL_COMMITTIME, Long.toString(getLastNonLocalCommitTime()));
+ setPropertyValues(map);
+
+ LifecycleUtil.activate(idHandler);
+
if (db != null)
{
db.getMongo().close();
@@ -220,4 +322,50 @@ public class MongoDBStore extends Store implements IMongoDBStore
super.doDeactivate();
}
+
+ protected void firstStart()
+ {
+ setCreationTime(getRepository().getTimeStamp());
+ OM.LOG.info("First start: " + CDOCommonUtil.formatTimeStamp(creationTime));
+ }
+
+ protected void reStart()
+ {
+ Set<String> names = new HashSet<String>();
+ names.add(PROP_REPOSITORY_CREATED);
+ names.add(PROP_GRACEFULLY_SHUT_DOWN);
+
+ Map<String, String> map = getPropertyValues(names);
+ creationTime = Long.valueOf(map.get(PROP_REPOSITORY_CREATED));
+
+ if (map.containsKey(PROP_GRACEFULLY_SHUT_DOWN))
+ {
+ names.clear();
+ names.add(PROP_NEXT_LOCAL_CDOID);
+ names.add(PROP_LAST_CDOID);
+ names.add(PROP_LAST_BRANCHID);
+ names.add(PROP_LAST_LOCAL_BRANCHID);
+ names.add(PROP_LAST_COMMITTIME);
+ names.add(PROP_LAST_NONLOCAL_COMMITTIME);
+ map = getPropertyValues(names);
+
+ idHandler.setNextLocalObjectID(stringToID(map.get(PROP_NEXT_LOCAL_CDOID)));
+ idHandler.setLastObjectID(stringToID(map.get(PROP_LAST_CDOID)));
+ setLastBranchID(Integer.valueOf(map.get(PROP_LAST_BRANCHID)));
+ setLastLocalBranchID(Integer.valueOf(map.get(PROP_LAST_LOCAL_BRANCHID)));
+ setLastCommitTime(Long.valueOf(map.get(PROP_LAST_COMMITTIME)));
+ setLastNonLocalCommitTime(Long.valueOf(map.get(PROP_LAST_NONLOCAL_COMMITTIME)));
+ }
+ else
+ {
+ repairAfterCrash();
+ }
+
+ removePropertyValues(Collections.singleton(PROP_GRACEFULLY_SHUT_DOWN));
+ }
+
+ protected void repairAfterCrash()
+ {
+ throw new UnsupportedOperationException("Not yet implemented"); // TODO Implement me
+ }
}
diff --git a/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/MongoDBStoreAccessor.java b/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/MongoDBStoreAccessor.java
index a945776539..4217b3d6c8 100644
--- a/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/MongoDBStoreAccessor.java
+++ b/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/internal/mongodb/MongoDBStoreAccessor.java
@@ -25,7 +25,7 @@ import org.eclipse.emf.cdo.server.IQueryHandler;
import org.eclipse.emf.cdo.server.ISession;
import org.eclipse.emf.cdo.server.IStoreChunkReader;
import org.eclipse.emf.cdo.server.ITransaction;
-import org.eclipse.emf.cdo.server.mongodbdb.IMongoDBStoreAccessor;
+import org.eclipse.emf.cdo.server.mongodb.IMongoDBStoreAccessor;
import org.eclipse.emf.cdo.spi.common.commit.CDOChangeSetSegment;
import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageUnit;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
diff --git a/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/mongodb/CDOMongoDBUtil.java b/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/mongodb/CDOMongoDBUtil.java
index 7c08a601bb..b8e1e85a63 100644
--- a/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/mongodb/CDOMongoDBUtil.java
+++ b/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/mongodb/CDOMongoDBUtil.java
@@ -10,7 +10,7 @@
* Stefan Winkler - 271444: [DB] Multiple refactorings
* Stefan Winkler - 249610: [DB] Support external references (Implementation)
*/
-package org.eclipse.emf.cdo.server.mongodbdb;
+package org.eclipse.emf.cdo.server.mongodb;
import org.eclipse.emf.cdo.server.internal.mongodb.MongoDBBrowserPage;
import org.eclipse.emf.cdo.server.internal.mongodb.MongoDBStore;
diff --git a/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/mongodb/IMongoDBStore.java b/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/mongodb/IMongoDBStore.java
index be42766c13..5c2bd44cc4 100644
--- a/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/mongodb/IMongoDBStore.java
+++ b/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/mongodb/IMongoDBStore.java
@@ -10,15 +10,22 @@
* Stefan Winkler - 271444: [DB] Multiple refactorings
* Stefan Winkler - 249610: [DB] Support external references (Implementation)
*/
-package org.eclipse.emf.cdo.server.mongodbdb;
+package org.eclipse.emf.cdo.server.mongodb;
+import org.eclipse.emf.cdo.common.id.CDOID;
+import org.eclipse.emf.cdo.common.id.CDOID.ObjectType;
+import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.server.ISession;
import org.eclipse.emf.cdo.server.IStore;
import org.eclipse.emf.cdo.server.ITransaction;
import com.mongodb.DB;
+import com.mongodb.DBCollection;
import com.mongodb.MongoURI;
+import java.util.Comparator;
+import java.util.Set;
+
/**
* @author Eike Stepper
*/
@@ -28,8 +35,6 @@ public interface IMongoDBStore extends IStore
public String getDBName();
- public DB getDB();
-
public IsolationLevel getIsolationLevel();
public EmbeddingStrategy getEmbeddingStrategy();
@@ -40,6 +45,10 @@ public interface IMongoDBStore extends IStore
public IMongoDBStoreAccessor getWriter(ITransaction transaction);
+ public DB getDB();
+
+ public DBCollection getPropertiesCollection();
+
/**
* @author Eike Stepper
*/
@@ -57,7 +66,28 @@ public interface IMongoDBStore extends IStore
/**
* @author Eike Stepper
*/
- public interface IDHandler
+ public interface IDHandler extends Comparator<CDOID>
{
+ public IMongoDBStore getStore();
+
+ public Set<ObjectType> getObjectIDTypes();
+
+ public CDOID getMinCDOID();
+
+ public CDOID getMaxCDOID();
+
+ public boolean isLocalCDOID(CDOID id);
+
+ public CDOID getNextCDOID(CDORevision revision);
+
+ public CDOID createCDOID(String val);
+
+ public CDOID getNextLocalObjectID();
+
+ public void setNextLocalObjectID(CDOID nextLocalObjectID);
+
+ public CDOID getLastObjectID();
+
+ public void setLastObjectID(CDOID lastObjectID);
}
}
diff --git a/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/mongodb/IMongoDBStoreAccessor.java b/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/mongodb/IMongoDBStoreAccessor.java
index d9166d0155..6ad0e8be0f 100644
--- a/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/mongodb/IMongoDBStoreAccessor.java
+++ b/plugins/org.eclipse.emf.cdo.server.mongodb/src/org/eclipse/emf/cdo/server/mongodb/IMongoDBStoreAccessor.java
@@ -8,7 +8,7 @@
* Contributors:
* Eike Stepper - initial API and implementation
*/
-package org.eclipse.emf.cdo.server.mongodbdb;
+package org.eclipse.emf.cdo.server.mongodb;
import org.eclipse.emf.cdo.server.IStoreAccessor;

Back to the top