org.eclipse.emf.cdo.server.internal.mongodb.Commits.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.emf.cdo.server.internal.mongodb.Commits.java

Source

/*
 * Copyright (c) 2011, 2012 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.branch.CDOBranch;
import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
import org.eclipse.emf.cdo.common.branch.CDOBranchVersion;
import org.eclipse.emf.cdo.common.commit.CDOChangeKind;
import org.eclipse.emf.cdo.common.commit.CDOCommitInfo;
import org.eclipse.emf.cdo.common.commit.CDOCommitInfoHandler;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.model.CDOClassInfo;
import org.eclipse.emf.cdo.common.model.CDOClassifierRef;
import org.eclipse.emf.cdo.common.model.CDOModelConstants;
import org.eclipse.emf.cdo.common.model.CDOModelUtil;
import org.eclipse.emf.cdo.common.model.CDOPackageUnit;
import org.eclipse.emf.cdo.common.model.CDOType;
import org.eclipse.emf.cdo.common.model.EMFUtil;
import org.eclipse.emf.cdo.common.revision.CDOList;
import org.eclipse.emf.cdo.common.revision.CDORevisionCacheAdder;
import org.eclipse.emf.cdo.common.revision.CDORevisionData;
import org.eclipse.emf.cdo.common.util.CDOCommonUtil;
import org.eclipse.emf.cdo.server.IStoreAccessor;
import org.eclipse.emf.cdo.server.IStoreAccessor.QueryResourcesContext;
import org.eclipse.emf.cdo.server.StoreThreadLocal;
import org.eclipse.emf.cdo.server.internal.mongodb.MongoDBStore.ValueHandler;
import org.eclipse.emf.cdo.spi.common.branch.InternalCDOBranchManager;
import org.eclipse.emf.cdo.spi.common.commit.InternalCDOCommitInfoManager;
import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageInfo;
import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageRegistry;
import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageUnit;
import org.eclipse.emf.cdo.spi.common.revision.DetachedCDORevision;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
import org.eclipse.emf.cdo.spi.common.revision.SyntheticCDORevision;
import org.eclipse.emf.cdo.spi.server.InternalCommitContext;
import org.eclipse.emf.cdo.spi.server.InternalRepository;

import org.eclipse.net4j.util.ObjectUtil;
import org.eclipse.net4j.util.StringUtil;
import org.eclipse.net4j.util.om.monitor.OMMonitor;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.InternalEObject.EStore;
import org.eclipse.emf.ecore.resource.ResourceSet;

import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.QueryOperators;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * @author Eike Stepper
 */
public class Commits extends Coll {
    public static final String COMMITS = "commits";

    public static final String COMMITS_ID = "_id";

    public static final String COMMITS_PREVIOUS = "previous";

    public static final String COMMITS_BRANCH = "branch";

    public static final String COMMITS_USER = "user";

    public static final String COMMITS_COMMENT = "comment";

    public static final String UNITS = "units";

    public static final String UNITS_ID = "id";

    public static final String UNITS_TYPE = "type";

    public static final String UNITS_DATA = "data";

    public static final String PACKAGES = "packages";

    public static final String PACKAGES_URI = "uri";

    public static final String PACKAGES_PARENT = "parent";

    public static final String CLASSIFIER_PREFIX = "c";

    public static final String SET_SUFFIX = "___set";

    public static final String REVISIONS = "revisions";

    public static final String REVISIONS_ID = "cdo_id";

    public static final String REVISIONS_VERSION = "cdo_version";

    private static final String REVISIONS_REVISED = "cdo_revised";

    public static final String REVISIONS_CLASS = "cdo_class";

    public static final String REVISIONS_RESOURCE = "cdo_resource";

    public static final String REVISIONS_CONTAINER = "cdo_container";

    public static final String REVISIONS_FEATURE = "cdo_feature";

    private static final boolean ZIP_PACKAGE_BYTES = true;

    private InternalCDOPackageRegistry packageRegistry;

    private IDHandler idHandler;

    private InternalCDOPackageUnit[] systemPackageUnits;

    private EStructuralFeature resourceNameFeature;

    public Commits(MongoDBStore store) {
        super(store, COMMITS);
        ensureIndex(UNITS, UNITS_ID);

        if (store.isBranching()) {
            DBObject index = new BasicDBObject();
            index.put(REVISIONS + "." + REVISIONS_ID, 1);
            index.put(COMMITS_BRANCH, 1);
            index.put(REVISIONS + "." + REVISIONS_VERSION, 1);

            collection.ensureIndex(index);
        } else {
            ensureIndex(REVISIONS, REVISIONS_ID, REVISIONS_VERSION);
        }

        packageRegistry = store.getRepository().getPackageRegistry();
        idHandler = store.getIDHandler();
    }

    public void writePackageUnits(MongoDBStoreAccessor mongoDBStoreAccessor, InternalCDOPackageUnit[] packageUnits,
            OMMonitor monitor) {
        // This must be the first commit into the repo.
        systemPackageUnits = packageUnits;

        for (int i = 0; i < packageUnits.length; i++) {
            InternalCDOPackageUnit packageUnit = packageUnits[i];
            InternalCDOPackageInfo[] packageInfos = packageUnit.getPackageInfos();
            for (int j = 0; j < packageInfos.length; j++) {
                InternalCDOPackageInfo packageInfo = packageInfos[j];
                for (EClassifier classifier : packageInfo.getEPackage().getEClassifiers()) {
                    store.getClasses().mapNewClassifier(classifier);
                }
            }
        }
    }

    private DBObject[] marshallUnits(InternalCDOPackageUnit[] packageUnits) {
        DBObject[] result = new DBObject[packageUnits.length];
        InternalCDOPackageRegistry packageRegistry = store.getRepository().getPackageRegistry();

        for (int i = 0; i < packageUnits.length; i++) {
            InternalCDOPackageUnit packageUnit = packageUnits[i];
            EPackage ePackage = packageUnit.getTopLevelPackageInfo().getEPackage();
            byte[] bytes = EMFUtil.getEPackageBytes(ePackage, ZIP_PACKAGE_BYTES, packageRegistry);
            DBObject[] packages = marshallPackages(packageUnit.getPackageInfos());

            DBObject doc = new BasicDBObject();
            doc.put(UNITS_ID, packageUnit.getID());
            doc.put(UNITS_TYPE, packageUnit.getOriginalType().toString());
            doc.put(UNITS_DATA, bytes);
            doc.put(PACKAGES, packages);

            result[i] = doc;
        }

        return result;
    }

    private DBObject[] marshallPackages(InternalCDOPackageInfo[] packageInfos) {
        DBObject[] result = new DBObject[packageInfos.length];
        for (int i = 0; i < packageInfos.length; i++) {
            InternalCDOPackageInfo packageInfo = packageInfos[i];

            DBObject doc = new BasicDBObject();
            doc.put(PACKAGES_URI, packageInfo.getPackageURI());
            String parent = packageInfo.getParentURI();
            if (!StringUtil.isEmpty(parent)) {
                doc.put(PACKAGES_PARENT, parent);
            }

            for (EClassifier classifier : packageInfo.getEPackage().getEClassifiers()) {
                int classifierID = store.getClasses().mapNewClassifier(classifier);
                doc.put(CLASSIFIER_PREFIX + classifierID, classifier.getName());
            }

            result[i] = doc;
        }

        return result;
    }

    public Collection<InternalCDOPackageUnit> readPackageUnits() {
        final Collection<InternalCDOPackageUnit> packageUnits = new ArrayList<InternalCDOPackageUnit>();

        DBObject query = new BasicDBObject();
        query.put(UNITS, new BasicDBObject("$exists", true));

        new QueryEmbeddedUnits<Object>(query) {
            @Override
            protected Object handleEmbedded(DBObject doc, DBObject embedded) {
                long time = (Long) doc.get(COMMITS_ID);
                CDOPackageUnit.Type type = CDOPackageUnit.Type.valueOf((String) embedded.get(UNITS_TYPE));
                InternalCDOPackageInfo[] infos = readPackageInfos(embedded);

                InternalCDOPackageUnit packageUnit = createPackageUnit();
                packageUnit.setOriginalType(type);
                packageUnit.setTimeStamp(time);
                packageUnit.setPackageInfos(infos);

                packageUnits.add(packageUnit);
                return null;
            }

            private InternalCDOPackageInfo[] readPackageInfos(DBObject embedded) {
                BasicDBList infos = (BasicDBList) embedded.get(PACKAGES);
                InternalCDOPackageInfo[] result = new InternalCDOPackageInfo[infos.size()];
                int i = 0;

                for (Object info : infos) {
                    DBObject infoObject = (DBObject) info;
                    String uri = (String) infoObject.get(PACKAGES_URI);
                    String parent = (String) infoObject.get(PACKAGES_PARENT);

                    InternalCDOPackageInfo packageInfo = createPackageInfo();
                    packageInfo.setPackageURI(uri);
                    packageInfo.setParentURI(parent);

                    result[i++] = packageInfo;
                }

                return result;
            }

            private InternalCDOPackageUnit createPackageUnit() {
                return (InternalCDOPackageUnit) CDOModelUtil.createPackageUnit();
            }

            private InternalCDOPackageInfo createPackageInfo() {
                return (InternalCDOPackageInfo) CDOModelUtil.createPackageInfo();
            }
        }.execute();

        return packageUnits;
    }

    public EPackage[] loadPackageUnit(final InternalCDOPackageUnit packageUnit) {
        DBObject query = new BasicDBObject();
        query.put(UNITS + "." + UNITS_ID, packageUnit.getID());

        return new QueryEmbeddedUnits<EPackage[]>(query) {
            @Override
            protected EPackage[] handleEmbedded(DBObject doc, DBObject embedded) {
                byte[] data = (byte[]) embedded.get(UNITS_DATA);
                EPackage ePackage = createEPackage(packageUnit, data);
                return EMFUtil.getAllPackages(ePackage);
            }

            private EPackage createEPackage(InternalCDOPackageUnit packageUnit, byte[] bytes) {
                ResourceSet resourceSet = EMFUtil.newEcoreResourceSet(packageRegistry);
                return EMFUtil.createEPackage(packageUnit.getID(), bytes, ZIP_PACKAGE_BYTES, resourceSet, false);
            }
        }.execute();
    }

    public void initializeClassifiers() {
        final Classes classes = store.getClasses();

        DBObject query = new BasicDBObject();
        query.put(UNITS, new BasicDBObject("$exists", true));

        new QueryEmbeddedUnits<Object>(query) {
            @Override
            protected Object handleEmbedded(DBObject doc, DBObject embedded) {
                BasicDBList infos = (BasicDBList) embedded.get(PACKAGES);
                for (Object info : infos) {
                    DBObject infoObject = (DBObject) info;
                    String uri = (String) infoObject.get(PACKAGES_URI);
                    handleClassifiers(infoObject, uri);
                }

                return null;
            }

            private void handleClassifiers(DBObject embedded, String packageURI) {
                Set<String> keys = embedded.keySet();
                for (String key : keys) {
                    if (key.startsWith(CLASSIFIER_PREFIX)) {
                        int id = Integer.parseInt(key.substring(CLASSIFIER_PREFIX.length()));
                        String classifierName = (String) embedded.get(key);

                        CDOClassifierRef classifierRef = new CDOClassifierRef(packageURI, classifierName);
                        EClassifier classifier = classifierRef.resolve(packageRegistry);

                        classes.mapClassifier(classifier, id);
                    }
                }
            }
        }.execute();
    }

    public void write(MongoDBStoreAccessor accessor, InternalCommitContext context, OMMonitor monitor) {
        try {
            monitor.begin(104);
            CDOBranchPoint branchPoint = context.getBranchPoint();

            DBObject doc = new BasicDBObject();
            doc.put(COMMITS_ID, branchPoint.getTimeStamp());

            long previous = context.getPreviousTimeStamp();
            boolean firstCommit = previous == CDOBranchPoint.UNSPECIFIED_DATE;
            if (!firstCommit) {
                doc.put(COMMITS_PREVIOUS, previous);
            }

            if (store.isBranching()) {
                doc.put(COMMITS_BRANCH, branchPoint.getBranch().getID());
            }

            String user = context.getUserID();
            if (!StringUtil.isEmpty(user)) {
                doc.put(COMMITS_USER, user);
            }

            String comment = context.getCommitComment();
            if (!StringUtil.isEmpty(comment)) {
                doc.put(COMMITS_COMMENT, comment);
            }

            InternalCDOPackageUnit[] newPackageUnits = firstCommit ? systemPackageUnits
                    : context.getNewPackageUnits();
            if (!ObjectUtil.isEmpty(newPackageUnits)) {
                doc.put(UNITS, marshallUnits(newPackageUnits));
            }

            monitor.worked();
            accessor.addIDMappings(context, monitor.fork());
            context.applyIDMappings(monitor.fork());

            List<DBObject> docs = new ArrayList<DBObject>();
            marshalRevisions(docs, context, context.getNewObjects(), CDOChangeKind.NEW);
            marshalRevisions(docs, context, context.getDirtyObjects(), CDOChangeKind.CHANGED);
            marshalRevisions(docs, context, context.getDetachedRevisions(), CDOChangeKind.DETACHED);

            if (!docs.isEmpty()) {
                doc.put(REVISIONS, docs);
            }

            monitor.worked();

            collection.insert(doc);
            monitor.worked(100);
        } finally {
            monitor.done();
        }
    }

    private void marshalRevisions(List<DBObject> docs, InternalCommitContext context,
            InternalCDORevision[] revisions, CDOChangeKind changeKind) {
        for (InternalCDORevision revision : revisions) {
            DBObject doc = marshallRevision(context, revision, changeKind);
            docs.add(doc);
        }
    }

    private DBObject marshallRevision(InternalCommitContext context, InternalCDORevision revision,
            CDOChangeKind changeKind) {
        boolean resource = !(revision instanceof SyntheticCDORevision) && revision.isResource();
        if (resource && resourceNameFeature == null) {
            resourceNameFeature = revision.getEClass()
                    .getEStructuralFeature(CDOModelConstants.RESOURCE_NODE_NAME_ATTRIBUTE);
        }

        DBObject doc = new BasicDBObject();
        idHandler.write(doc, REVISIONS_ID, revision.getID());

        EClass eClass = revision.getEClass();
        doc.put(REVISIONS_CLASS, store.getClasses().getClassifierID(eClass));

        if (changeKind == CDOChangeKind.DETACHED) {
            doc.put(REVISIONS_VERSION, -revision.getVersion() - 1);
            return doc;
        }

        doc.put(REVISIONS_VERSION, revision.getVersion());

        CDOID resourceID = revision.getResourceID();
        idHandler.write(doc, REVISIONS_RESOURCE, resourceID);

        CDOID containerID = (CDOID) revision.getContainerID();
        idHandler.write(doc, REVISIONS_CONTAINER, containerID);

        int featureID = revision.getContainingFeatureID();
        doc.put(REVISIONS_FEATURE, featureID);

        if (resource && changeKind != CDOChangeKind.DETACHED) {
            String name = (String) revision.data().get(resourceNameFeature, 0);
            IStoreAccessor accessor = StoreThreadLocal.getAccessor();

            CDOID existingID = accessor.readResourceID(containerID, name, revision);
            if (existingID != null && !existingID.equals(revision.getID())
                    && !isBeingDetached(context, existingID)) {
                throw new IllegalStateException("Duplicate resource: name=" + name + ", folderID=" + containerID); //$NON-NLS-1$ //$NON-NLS-2$
            }
        }

        CDOClassInfo classInfo = revision.getClassInfo();
        for (EStructuralFeature feature : classInfo.getAllPersistentFeatures()) {
            Object value = revision.getValue(feature);

            CDOType type = CDOModelUtil.getType(feature);
            ValueHandler valueHandler = store.getValueHandler(type);

            if (feature.isUnsettable()) {
                if (value == null) {
                    doc.put(feature.getName() + SET_SUFFIX, false);
                    doc.put(feature.getName(), valueHandler.getMongoDefaultValue(feature));
                    continue;
                }

                doc.put(feature.getName() + SET_SUFFIX, true);
            }

            if (value == CDORevisionData.NIL) {
                doc.put(feature.getName(), null);
            } else if (value == null) {
                if (feature.isMany() || feature.getDefaultValue() == null) {
                    doc.put(feature.getName(), null);
                } else {
                    doc.put(feature.getName(), valueHandler.getMongoDefaultValue(feature));
                }
            } else {
                if (feature.isMany()) {
                    List<?> cdoList = (List<?>) value;
                    BasicDBList mongoList = new BasicDBList();
                    for (Object element : cdoList) {
                        element = valueHandler.toMongo(element);
                        mongoList.add(element);
                    }

                    value = mongoList;
                } else {
                    value = valueHandler.toMongo(value);
                }

                doc.put(feature.getName(), value);
            }
        }

        return doc;
    }

    private boolean isBeingDetached(InternalCommitContext context, CDOID id) {
        for (CDOID idBeingDetached : context.getDetachedObjects()) {
            if (id.equals(idBeingDetached)) {
                return true;
            }
        }

        return false;
    }

    public void queryResources(final QueryResourcesContext context) {
        Classes classes = getStore().getClasses();
        final int folderCID = classes.getResourceFolderClassID();
        final int resourceCID = classes.getResourceClassID();

        final CDOID folderID = context.getFolderID();
        final String name = context.getName();
        final boolean exactMatch = context.exactMatch();
        final long timeStamp = context.getTimeStamp();

        DBObject query = new BasicDBObject();

        query.put(REVISIONS + "." + REVISIONS_CLASS,
                new BasicDBObject("$in", new int[] { folderCID, resourceCID }));

        addToQuery(query, context);
        query.put(REVISIONS + "." + REVISIONS_CONTAINER, idHandler.toValue(folderID));

        if (name == null || exactMatch) {
            query.put(REVISIONS + "." + "name", name);
        } else {
            query.put(REVISIONS + "." + "name", Pattern.compile("^" + name));
        }

        new QueryEmbeddedRevisions<Boolean>(query) {
            @Override
            protected Boolean handleEmbedded(DBObject doc, DBObject embedded) {
                int classID = (Integer) embedded.get(REVISIONS_CLASS);
                if (classID != folderCID && classID != resourceCID) {
                    return null;
                }

                int version = (Integer) embedded.get(REVISIONS_VERSION);
                if (version <= 0) {
                    return null;
                }

                CDOID container = idHandler.read(embedded, REVISIONS_CONTAINER);
                if (!ObjectUtil.equals(container, folderID)) {
                    return null;
                }

                String revisionName = (String) embedded.get("name");
                if (name == null || exactMatch) {
                    if (!ObjectUtil.equals(revisionName, name)) {
                        return null;
                    }
                } else {
                    if (!revisionName.startsWith(name)) {
                        return null;
                    }
                }

                CDOID id = idHandler.read(embedded, REVISIONS_ID);

                long created = (Long) doc.get(COMMITS_ID);
                long revised = getRevised(id, context.getBranch(), version, doc, embedded);
                if (!CDOCommonUtil.isValidTimeStamp(timeStamp, created, revised)) {
                    return null;
                }

                if (!context.addResource(id)) {
                    // No more results allowed
                    return true;
                }

                return null;
            }
        }.execute();
    }

    private long getRevised(CDOID id, CDOBranch branch, int version, DBObject doc, DBObject revision) {
        Object value = revision.get(REVISIONS_REVISED);
        if (value instanceof Long) {
            return (Long) value;
        }

        DBObject query = new BasicDBObject();
        idHandler.write(query, REVISIONS + "." + REVISIONS_ID, id);
        if (store.isBranching()) {
            query.put(COMMITS_BRANCH, branch.getID());
        }

        int nextVersion = version + 1;
        query.put(REVISIONS + "." + REVISIONS_VERSION,
                new BasicDBObject("$in", new int[] { nextVersion, -nextVersion }));

        Long result = new Query<Long>(query) {
            @Override
            protected Long handleDoc(DBObject doc) {
                return (Long) doc.get(COMMITS_ID);
            }
        }.execute();

        if (result != null) {
            long revised = result - 1;

            // try
            // {
            // revision.put(REVISIONS_REVISED, revised);
            // collection.save(doc);
            // }
            // catch (Exception ex)
            // {
            // OM.LOG.warn(ex);
            // }

            return revised;
        }

        return CDOBranchPoint.UNSPECIFIED_DATE;
    }

    public InternalCDORevision readRevision(final CDOID id, final CDOBranchPoint branchPoint, int listChunk,
            CDORevisionCacheAdder cache) {
        final CDOBranch branch = branchPoint.getBranch();
        final long timeStamp = branchPoint.getTimeStamp();

        DBObject query = new BasicDBObject();
        idHandler.write(query, REVISIONS + "." + REVISIONS_ID, id);

        addToQuery(query, branchPoint);

        return new QueryEmbeddedRevisions<InternalCDORevision>(query) {
            @Override
            public InternalCDORevision execute() {
                return execute(collection.find(getRef()).sort(new BasicDBObject(COMMITS_ID, -1)).limit(1));
            }

            @Override
            protected InternalCDORevision handleEmbedded(DBObject doc, DBObject embedded) {
                CDOID embeddedID = idHandler.read(embedded, REVISIONS_ID);
                if (!ObjectUtil.equals(embeddedID, id)) {
                    return null;
                }

                long created = (Long) doc.get(COMMITS_ID);
                CDOBranchPoint revisionBranchPoint = branch.getPoint(created);

                InternalCDORevision revision = unmarshallRevision(doc, embedded, id, revisionBranchPoint);

                long revised = revision.getRevised();
                if (!CDOCommonUtil.isValidTimeStamp(timeStamp, created, revised)) {
                    return null;
                }

                return revision;
            }
        }.execute();
    }

    private void addToQuery(DBObject query, CDOBranchPoint branchPoint) {
        // Exclude detached objects
        query.put(REVISIONS + "." + REVISIONS_VERSION, new BasicDBObject("$gte", CDOBranchVersion.FIRST_VERSION));

        long timeStamp = branchPoint.getTimeStamp();
        if (timeStamp != CDOBranchPoint.UNSPECIFIED_DATE) {
            query.put(COMMITS_ID, new BasicDBObject("$lte", timeStamp));
        }

        if (store.isBranching()) {
            int branch = branchPoint.getBranch().getID();
            query.put(COMMITS_BRANCH, branch);
        }
    }

    public InternalCDORevision readRevisionByVersion(final CDOID id, CDOBranchVersion branchVersion, int listChunk,
            CDORevisionCacheAdder cache) {
        DBObject query = new BasicDBObject();
        idHandler.write(query, REVISIONS + "." + REVISIONS_ID, id);

        int version = branchVersion.getVersion();
        query.put(REVISIONS + "." + REVISIONS_VERSION, new BasicDBObject("$in", new int[] { version, -version }));

        final CDOBranch branch = branchVersion.getBranch();
        if (store.isBranching()) {
            query.put(COMMITS_BRANCH, branch.getID());
        }

        return new QueryEmbeddedRevisions<InternalCDORevision>(query) {
            @Override
            protected InternalCDORevision handleEmbedded(DBObject doc, DBObject embedded) {
                CDOID revisionID = idHandler.read(embedded, REVISIONS_ID);
                if (!ObjectUtil.equals(revisionID, id)) {
                    return null;
                }

                long revisionTime = (Long) doc.get(COMMITS_ID);
                CDOBranchPoint branchPoint = branch.getPoint(revisionTime);

                return unmarshallRevision(doc, embedded, id, branchPoint);
            }
        }.execute();
    }

    private InternalCDORevision unmarshallRevision(DBObject doc, DBObject embedded, CDOID id,
            CDOBranchPoint branchPoint) {
        int classID = (Integer) embedded.get(REVISIONS_CLASS);
        EClass eClass = store.getClasses().getClass(classID);

        CDOBranch branch = branchPoint.getBranch();
        int version = (Integer) embedded.get(REVISIONS_VERSION);
        long revised = getRevised(id, branch, Math.abs(version), doc, embedded);

        if (version < CDOBranchVersion.FIRST_VERSION) {
            long timeStamp = branchPoint.getTimeStamp();
            return new DetachedCDORevision(eClass, id, branch, -version, timeStamp, revised);
        }

        CDOID resourceID = idHandler.read(embedded, REVISIONS_RESOURCE);
        CDOID containerID = idHandler.read(embedded, REVISIONS_CONTAINER);
        int featureID = (Integer) embedded.get(REVISIONS_FEATURE);

        InternalCDORevision result = store.createRevision(eClass, id);
        result.setBranchPoint(branchPoint);
        result.setRevised(revised);
        result.setVersion(version);
        result.setResourceID(resourceID);
        result.setContainerID(containerID);
        result.setContainingFeatureID(featureID);

        unmarshallRevision(embedded, result);

        return result;
    }

    private void unmarshallRevision(DBObject doc, InternalCDORevision revision) {
        CDOClassInfo classInfo = revision.getClassInfo();
        for (EStructuralFeature feature : classInfo.getAllPersistentFeatures()) {
            Object value = doc.get(feature.getName());

            if (feature.isUnsettable()) {
                boolean set = (Boolean) doc.get(feature.getName() + SET_SUFFIX);
                if (!set) {
                    continue;
                }
            }

            if (value == null) {
                if (!feature.isMany()) {
                    if (feature.getDefaultValue() != null) {
                        value = CDORevisionData.NIL;
                    }
                }
            }

            CDOType type = CDOModelUtil.getType(feature);
            ValueHandler valueHandler = store.getValueHandler(type);

            if (feature.isMany()) {
                if (value != null) {
                    List<?> list = (List<?>) value;
                    CDOList revisionList = revision.getList(feature, list.size());
                    for (Object element : list) {
                        element = valueHandler.fromMongo(element);
                        revisionList.add(element);
                    }
                }
            } else {
                value = valueHandler.fromMongo(value);
                revision.set(feature, EStore.NO_INDEX, value);
            }
        }
    }

    public void loadCommitInfos(CDOBranch branch, long startTime, long endTime,
            final CDOCommitInfoHandler handler) {
        if (endTime < CDOBranchPoint.UNSPECIFIED_DATE) {
            throw new IllegalArgumentException("Counting not supported");
        }

        DBObject query = new BasicDBObject();

        if (branch != null && store.isBranching()) {
            query.put(COMMITS_BRANCH, branch.getID());
        }

        BasicDBList list = new BasicDBList();
        if (startTime != CDOBranchPoint.UNSPECIFIED_DATE) {
            list.add(new BasicDBObject(QueryOperators.GTE, startTime));
        }

        if (endTime != CDOBranchPoint.UNSPECIFIED_DATE) {
            list.add(new BasicDBObject(QueryOperators.LTE, endTime));
        }

        int size = list.size();
        if (size == 2) {
            query.put(COMMITS_ID, list);
        } else if (size == 1) {
            query.put(COMMITS_ID, list.get(0));
        }

        InternalRepository repository = store.getRepository();
        final InternalCDOBranchManager branchManager = repository.getBranchManager();
        final InternalCDOCommitInfoManager commitManager = repository.getCommitInfoManager();

        new Query<Object>(query) {
            @Override
            public Object execute() {
                return execute(collection.find(getRef()).sort(new BasicDBObject(COMMITS_ID, 1)));
            }

            @Override
            protected Object handleDoc(DBObject doc) {
                long time = (Long) doc.get(COMMITS_ID);
                Object value = doc.get(COMMITS_PREVIOUS);
                long previous = value == null ? 0L : (Long) value;

                CDOBranch commitBranch;
                if (store.isBranching()) {
                    int branchID = (Integer) doc.get(COMMITS_BRANCH);
                    commitBranch = branchManager.getBranch(branchID);
                } else {
                    commitBranch = branchManager.getMainBranch();
                }

                String user = (String) doc.get(COMMITS_USER);
                String comment = (String) doc.get(COMMITS_COMMENT);

                CDOCommitInfo commitInfo = commitManager.createCommitInfo(commitBranch, time, previous, user,
                        comment, null);
                handler.handleCommitInfo(commitInfo);
                return null;
            }
        }.execute();
    }

    /**
     * @author Eike Stepper
     */
    public abstract class QueryEmbedded<RESULT> extends Query<RESULT> {
        private String field;

        public QueryEmbedded(DBObject ref, String field) {
            super(ref);
            this.field = field;
        }

        @Override
        protected RESULT handleDoc(DBObject doc) {
            BasicDBList list = (BasicDBList) doc.get(field);
            for (Object object : list) {
                DBObject embedded = (DBObject) object;
                RESULT result = handleEmbedded(doc, embedded);
                if (result != null) {
                    return result;
                }
            }

            return null;
        }

        protected abstract RESULT handleEmbedded(DBObject doc, DBObject embedded);
    }

    /**
     * @author Eike Stepper
     */
    public abstract class QueryEmbeddedUnits<RESULT> extends QueryEmbedded<RESULT> {
        public QueryEmbeddedUnits(DBObject ref) {
            super(ref, UNITS);
        }
    }

    /**
     * @author Eike Stepper
     */
    public abstract class QueryEmbeddedRevisions<RESULT> extends QueryEmbedded<RESULT> {
        public QueryEmbeddedRevisions(DBObject ref) {
            super(ref, REVISIONS);
        }
    }
}