You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by ju...@apache.org on 2012/09/17 14:54:06 UTC

svn commit: r1386591 [6/7] - in /jackrabbit/oak/trunk: ./ oak-mongomk-api/ oak-mongomk-api/src/ oak-mongomk-api/src/main/ oak-mongomk-api/src/main/java/ oak-mongomk-api/src/main/java/org/ oak-mongomk-api/src/main/java/org/apache/ oak-mongomk-api/src/ma...

Added: jackrabbit/oak/trunk/oak-mongomk/src/test/java/com/mongodb/DBCollection.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/test/java/com/mongodb/DBCollection.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/test/java/com/mongodb/DBCollection.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk/src/test/java/com/mongodb/DBCollection.java Mon Sep 17 12:54:01 2012
@@ -0,0 +1,1460 @@
+// DBCollection.java
+
+/**
+ *      Copyright (C) 2008 10gen Inc.
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ */
+
+package com.mongodb;
+
+// Mongo
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.bson.types.ObjectId;
+
+/** This class provides a skeleton implementation of a database collection.
+ * <p>A typical invocation sequence is thus
+ * <blockquote><pre>
+ *     Mongo mongo = new Mongo( new DBAddress( "localhost", 127017 ) );
+ *     DB db = mongo.getDB( "mydb" );
+ *     DBCollection collection = db.getCollection( "test" );
+ * </pre></blockquote>
+ * @dochub collections
+ */
+@SuppressWarnings( {"unchecked", "rawtypes" })
+public abstract class DBCollection {
+
+    // THIS HAS BEEN PATCHED TO REMOVE SOME FINAL MODIFIER ON METHODS WHICH NEEDED TO BE MOCKED FOR TESTING
+
+    /**
+     * Saves document(s) to the database.
+     * if doc doesn't have an _id, one will be added
+     * you can get the _id that was added from doc after the insert
+     *
+     * @param arr  array of documents to save
+     * @param concern the write concern
+     * @return
+     * @throws MongoException
+     * @dochub insert
+     */
+    public WriteResult insert(DBObject[] arr , WriteConcern concern ) throws MongoException {
+        return insert( arr, concern, getDBEncoderFactory().create() );
+    }
+
+    /**
+     * Saves document(s) to the database.
+     * if doc doesn't have an _id, one will be added
+     * you can get the _id that was added from doc after the insert
+     *
+     * @param arr  array of documents to save
+     * @param concern the write concern
+     * @param encoder the DBEncoder to use
+     * @return
+     * @throws MongoException
+     * @dochub insert
+     */
+    public abstract WriteResult insert(DBObject[] arr , WriteConcern concern, DBEncoder encoder) throws MongoException;
+
+    /**
+     * Inserts a document into the database.
+     * if doc doesn't have an _id, one will be added
+     * you can get the _id that was added from doc after the insert
+     *
+     * @param o
+     * @param concern the write concern
+     * @return
+     * @throws MongoException
+     * @dochub insert
+     */
+    public WriteResult insert(DBObject o , WriteConcern concern )
+        throws MongoException {
+        return insert( new DBObject[]{ o } , concern );
+    }
+
+    /**
+     * Saves document(s) to the database.
+     * if doc doesn't have an _id, one will be added
+     * you can get the _id that was added from doc after the insert
+     *
+     * @param arr  array of documents to save
+     * @return
+     * @throws MongoException
+     * @dochub insert
+     */
+    public WriteResult insert(DBObject ... arr)
+        throws MongoException {
+        return insert( arr , getWriteConcern() );
+    }
+
+    /**
+     * Saves document(s) to the database.
+     * if doc doesn't have an _id, one will be added
+     * you can get the _id that was added from doc after the insert
+     *
+     * @param arr  array of documents to save
+     * @return
+     * @throws MongoException
+     * @dochub insert
+     */
+    public WriteResult insert(WriteConcern concern, DBObject ... arr)
+        throws MongoException {
+        return insert( arr, concern );
+    }
+
+    /**
+     * Saves document(s) to the database.
+     * if doc doesn't have an _id, one will be added
+     * you can get the _id that was added from doc after the insert
+     *
+     * @param list list of documents to save
+     * @return
+     * @throws MongoException
+     * @dochub insert
+     */
+    public WriteResult insert(List<DBObject> list )
+        throws MongoException {
+        return insert( list, getWriteConcern() );
+    }
+
+    /**
+     * Saves document(s) to the database.
+     * if doc doesn't have an _id, one will be added
+     * you can get the _id that was added from doc after the insert
+     *
+     * @param list list of documents to save
+     * @param concern the write concern
+     * @return
+     * @throws MongoException
+     * @dochub insert
+     */
+    public WriteResult insert(List<DBObject> list, WriteConcern concern )
+            throws MongoException {
+        return insert( list.toArray( new DBObject[list.size()] ) , concern );
+    }
+
+    /**
+     * Performs an update operation.
+     * @param q search query for old object to update
+     * @param o object with which to update <tt>q</tt>
+     * @param upsert if the database should create the element if it does not exist
+     * @param multi if the update should be applied to all objects matching (db version 1.1.3 and above). An object will
+     * not be inserted if it does not exist in the collection and upsert=true and multi=true.
+     * See <a href="http://www.mongodb.org/display/DOCS/Atomic+Operations">http://www.mongodb.org/display/DOCS/Atomic+Operations</a>
+     * @param concern the write concern
+     * @return
+     * @throws MongoException
+     * @dochub update
+     */
+    public WriteResult update( DBObject q , DBObject o , boolean upsert , boolean multi , WriteConcern concern ) throws MongoException {
+        return update( q, o, upsert, multi, concern, getDBEncoderFactory().create() );
+    }
+
+    /**
+     * Performs an update operation.
+     * @param q search query for old object to update
+     * @param o object with which to update <tt>q</tt>
+     * @param upsert if the database should create the element if it does not exist
+     * @param multi if the update should be applied to all objects matching (db version 1.1.3 and above). An object will
+     * not be inserted if it does not exist in the collection and upsert=true and multi=true.
+     * See <a href="http://www.mongodb.org/display/DOCS/Atomic+Operations">http://www.mongodb.org/display/DOCS/Atomic+Operations</a>
+     * @param concern the write concern
+     * @param encoder the DBEncoder to use
+     * @return
+     * @throws MongoException
+     * @dochub update
+     */
+    public abstract WriteResult update( DBObject q , DBObject o , boolean upsert , boolean multi , WriteConcern concern, DBEncoder encoder ) throws MongoException ;
+
+    /**
+     * calls {@link DBCollection#update(com.mongodb.DBObject, com.mongodb.DBObject, boolean, boolean, com.mongodb.WriteConcern)} with default WriteConcern.
+     * @param q search query for old object to update
+     * @param o object with which to update <tt>q</tt>
+     * @param upsert if the database should create the element if it does not exist
+     * @param multi if the update should be applied to all objects matching (db version 1.1.3 and above)
+     *              See http://www.mongodb.org/display/DOCS/Atomic+Operations
+     * @return
+     * @throws MongoException
+     * @dochub update
+     */
+    public WriteResult update( DBObject q , DBObject o , boolean upsert , boolean multi )
+        throws MongoException {
+        return update( q , o , upsert , multi , getWriteConcern() );
+    }
+
+    /**
+     * calls {@link DBCollection#update(com.mongodb.DBObject, com.mongodb.DBObject, boolean, boolean)} with upsert=false and multi=false
+     * @param q search query for old object to update
+     * @param o object with which to update <tt>q</tt>
+     * @return
+     * @throws MongoException
+     * @dochub update
+     */
+    public WriteResult update( DBObject q , DBObject o ) throws MongoException {
+        return update( q , o , false , false );
+    }
+
+    /**
+     * calls {@link DBCollection#update(com.mongodb.DBObject, com.mongodb.DBObject, boolean, boolean)} with upsert=false and multi=true
+     * @param q search query for old object to update
+     * @param o object with which to update <tt>q</tt>
+     * @return
+     * @throws MongoException
+     * @dochub update
+     */
+    public WriteResult updateMulti( DBObject q , DBObject o ) throws MongoException {
+        return update( q , o , false , true );
+    }
+
+    /**
+     * Adds any necessary fields to a given object before saving it to the collection.
+     * @param o object to which to add the fields
+     */
+    protected abstract void doapply( DBObject o );
+
+    /**
+     * Removes objects from the database collection.
+     * @param o the object that documents to be removed must match
+     * @param concern WriteConcern for this operation
+     * @return
+     * @throws MongoException
+     * @dochub remove
+     */
+    public WriteResult remove( DBObject o , WriteConcern concern ) throws MongoException {
+        return remove(  o, concern, getDBEncoderFactory().create() );
+    }
+
+    /**
+     * Removes objects from the database collection.
+     * @param o the object that documents to be removed must match
+     * @param concern WriteConcern for this operation
+     * @param encoder the DBEncoder to use
+     * @return
+     * @throws MongoException
+     * @dochub remove
+     */
+    public abstract WriteResult remove( DBObject o , WriteConcern concern, DBEncoder encoder ) throws MongoException ;
+
+    /**
+     * calls {@link DBCollection#remove(com.mongodb.DBObject, com.mongodb.WriteConcern)} with the default WriteConcern
+     * @param o the object that documents to be removed must match
+     * @return
+     * @throws MongoException
+     * @dochub remove
+     */
+    public WriteResult remove( DBObject o )
+        throws MongoException {
+        return remove( o , getWriteConcern() );
+    }
+
+
+    /**
+     * Finds objects
+     */
+    abstract Iterator<DBObject> __find( DBObject ref , DBObject fields , int numToSkip , int batchSize , int limit, int options, ReadPreference readPref, DBDecoder decoder ) throws MongoException ;
+
+    /**
+     * Calls {@link DBCollection#find(com.mongodb.DBObject, com.mongodb.DBObject, int, int)} and applies the query options
+     * @param query query used to search
+     * @param fields the fields of matching objects to return
+     * @param numToSkip number of objects to skip
+     * @param batchSize the batch size. This option has a complex behavior, see {@link DBCursor#batchSize(int) }
+     * @param options - see Bytes QUERYOPTION_*
+     * @return the cursor
+     * @throws MongoException
+     * @dochub find
+     */
+    @Deprecated
+    public final DBCursor find( DBObject query , DBObject fields , int numToSkip , int batchSize , int options ) throws MongoException{
+        return find(query, fields, numToSkip, batchSize).addOption(options);
+    }
+
+
+    /**
+     * Finds objects from the database that match a query.
+     * A DBCursor object is returned, that can be iterated to go through the results.
+     *
+     * @param query query used to search
+     * @param fields the fields of matching objects to return
+     * @param numToSkip number of objects to skip
+     * @param batchSize the batch size. This option has a complex behavior, see {@link DBCursor#batchSize(int) }
+     * @return the cursor
+     * @throws MongoException
+     * @dochub find
+     */
+    @Deprecated
+    public final DBCursor find( DBObject query , DBObject fields , int numToSkip , int batchSize ) {
+        DBCursor cursor = find(query, fields).skip(numToSkip).batchSize(batchSize);
+        return cursor;
+    }
+
+    // ------
+
+    /**
+     * Finds an object by its id.
+     * This compares the passed in value to the _id field of the document
+     *
+     * @param obj any valid object
+     * @return the object, if found, otherwise <code>null</code>
+     * @throws MongoException
+     */
+    public final DBObject findOne( Object obj )
+        throws MongoException {
+        return findOne(obj, null);
+    }
+
+
+    /**
+     * Finds an object by its id.
+     * This compares the passed in value to the _id field of the document
+     *
+     * @param obj any valid object
+     * @param fields fields to return
+     * @return the object, if found, otherwise <code>null</code>
+     * @dochub find
+     */
+    public final DBObject findOne( Object obj, DBObject fields ) {
+        Iterator<DBObject> iterator = __find(new BasicDBObject("_id", obj), fields, 0, -1, 0, getOptions(), getReadPreference(), _decoderFactory.create() );
+        return (iterator != null ? iterator.next() : null);
+    }
+
+    /**
+     * Finds the first document in the query and updates it.
+     * @param query query to match
+     * @param fields fields to be returned
+     * @param sort sort to apply before picking first document
+     * @param remove if true, document found will be removed
+     * @param update update to apply
+     * @param returnNew if true, the updated document is returned, otherwise the old document is returned (or it would be lost forever)
+     * @param upsert do upsert (insert if document not present)
+     * @return the document
+     */
+    public DBObject findAndModify(DBObject query, DBObject fields, DBObject sort, boolean remove, DBObject update, boolean returnNew, boolean upsert) {
+
+        BasicDBObject cmd = new BasicDBObject( "findandmodify", _name);
+        if (query != null && !query.keySet().isEmpty())
+            cmd.append( "query", query );
+        if (fields != null && !fields.keySet().isEmpty())
+            cmd.append( "fields", fields );
+        if (sort != null && !sort.keySet().isEmpty())
+            cmd.append( "sort", sort );
+
+        if (remove)
+            cmd.append( "remove", remove );
+        else {
+            if (update != null && !update.keySet().isEmpty()) {
+                // if 1st key doesnt start with $, then object will be inserted as is, need to check it
+                String key = update.keySet().iterator().next();
+                if (key.charAt(0) != '$')
+                    _checkObject(update, false, false);
+                cmd.append( "update", update );
+            }
+            if (returnNew)
+                cmd.append( "new", returnNew );
+            if (upsert)
+                cmd.append( "upsert", upsert );
+        }
+
+        if (remove && !(update == null || update.keySet().isEmpty() || returnNew))
+            throw new MongoException("FindAndModify: Remove cannot be mixed with the Update, or returnNew params!");
+
+        CommandResult res = this._db.command( cmd );
+        if (res.ok() || res.getErrorMessage().equals( "No matching object found" ))
+            return (DBObject) res.get( "value" );
+        res.throwOnError();
+        return null;
+    }
+
+
+    /**
+     * calls {@link DBCollection#findAndModify(com.mongodb.DBObject, com.mongodb.DBObject, com.mongodb.DBObject, boolean, com.mongodb.DBObject, boolean, boolean)}
+     * with fields=null, remove=false, returnNew=false, upsert=false
+     * @param query
+     * @param sort
+     * @param update
+     * @return the old document
+     */
+    public DBObject findAndModify( DBObject query , DBObject sort , DBObject update){
+        return findAndModify( query, null, sort, false, update, false, false);
+    }
+
+    /**
+     * calls {@link DBCollection#findAndModify(com.mongodb.DBObject, com.mongodb.DBObject, com.mongodb.DBObject, boolean, com.mongodb.DBObject, boolean, boolean)}
+     * with fields=null, sort=null, remove=false, returnNew=false, upsert=false
+     * @param query
+     * @param update
+     * @return the old document
+     */
+    public DBObject findAndModify( DBObject query , DBObject update ) {
+        return findAndModify( query, null, null, false, update, false, false );
+    }
+
+    /**
+     * calls {@link DBCollection#findAndModify(com.mongodb.DBObject, com.mongodb.DBObject, com.mongodb.DBObject, boolean, com.mongodb.DBObject, boolean, boolean)}
+     * with fields=null, sort=null, remove=true, returnNew=false, upsert=false
+     * @param query
+     * @return the removed document
+     */
+    public DBObject findAndRemove( DBObject query ) {
+        return findAndModify( query, null, null, true, null, false, false );
+    }
+
+    // --- START INDEX CODE ---
+
+    /**
+     * calls {@link DBCollection#createIndex(com.mongodb.DBObject, com.mongodb.DBObject)} with default index options
+     * @param keys an object with a key set of the fields desired for the index
+     * @throws MongoException
+     */
+    public final void createIndex( final DBObject keys )
+        throws MongoException {
+        createIndex( keys , defaultOptions( keys ) );
+    }
+
+    /**
+     * Forces creation of an index on a set of fields, if one does not already exist.
+     * @param keys
+     * @param options
+     * @throws MongoException
+     */
+    public void createIndex( DBObject keys , DBObject options ) throws MongoException {
+        createIndex( keys, options, getDBEncoderFactory().create() );
+    }
+
+    /**
+     * Forces creation of an index on a set of fields, if one does not already exist.
+     * @param keys
+     * @param options
+     * @param encoder the DBEncoder to use
+     * @throws MongoException
+     */
+    public abstract void createIndex( DBObject keys , DBObject options, DBEncoder encoder ) throws MongoException;
+
+    /**
+     * Creates an ascending index on a field with default options, if one does not already exist.
+     * @param name name of field to index on
+     */
+    public final void ensureIndex( final String name ){
+        ensureIndex( new BasicDBObject( name , 1 ) );
+    }
+
+    /**
+     * calls {@link DBCollection#ensureIndex(com.mongodb.DBObject, com.mongodb.DBObject)} with default options
+     * @param keys an object with a key set of the fields desired for the index
+     * @throws MongoException
+     */
+    public final void ensureIndex( final DBObject keys )
+        throws MongoException {
+        ensureIndex( keys , defaultOptions( keys ) );
+    }
+
+    /**
+     * calls {@link DBCollection#ensureIndex(com.mongodb.DBObject, java.lang.String, boolean)} with unique=false
+     * @param keys fields to use for index
+     * @param name an identifier for the index
+     * @throws MongoException
+     * @dochub indexes
+     */
+    public void ensureIndex( DBObject keys , String name )
+        throws MongoException {
+        ensureIndex( keys , name , false );
+    }
+
+    /**
+     * Ensures an index on this collection (that is, the index will be created if it does not exist).
+     * @param keys fields to use for index
+     * @param name an identifier for the index. If null or empty, the default name will be used.
+     * @param unique if the index should be unique
+     * @throws MongoException
+     */
+    public void ensureIndex( DBObject keys , String name , boolean unique )
+        throws MongoException {
+        DBObject options = defaultOptions( keys );
+        if (name != null && name.length()>0)
+            options.put( "name" , name );
+        if ( unique )
+            options.put( "unique" , Boolean.TRUE );
+        ensureIndex( keys , options );
+    }
+
+    /**
+     * Creates an index on a set of fields, if one does not already exist.
+     * @param keys an object with a key set of the fields desired for the index
+     * @param optionsIN options for the index (name, unique, etc)
+     * @throws MongoException
+     */
+    public final void ensureIndex( final DBObject keys , final DBObject optionsIN )
+        throws MongoException {
+
+        if ( checkReadOnly( false ) ) return;
+
+        final DBObject options = defaultOptions( keys );
+        for ( String k : optionsIN.keySet() )
+            options.put( k , optionsIN.get( k ) );
+
+        final String name = options.get( "name" ).toString();
+
+        if ( _createdIndexes.contains( name ) )
+            return;
+
+        createIndex( keys , options );
+        _createdIndexes.add( name );
+    }
+
+    /**
+     * Clears all indices that have not yet been applied to this collection.
+     */
+    public void resetIndexCache(){
+        _createdIndexes.clear();
+    }
+
+    DBObject defaultOptions( DBObject keys ){
+        DBObject o = new BasicDBObject();
+        o.put( "name" , genIndexName( keys ) );
+        o.put( "ns" , _fullName );
+        return o;
+    }
+
+    /**
+     * Convenience method to generate an index name from the set of fields it is over.
+     * @param keys the names of the fields used in this index
+     * @return a string representation of this index's fields
+     */
+    public static String genIndexName( DBObject keys ){
+        StringBuilder name = new StringBuilder();
+        for ( String s : keys.keySet() ){
+            if ( name.length() > 0 )
+                name.append( '_' );
+            name.append( s ).append( '_' );
+            Object val = keys.get( s );
+            if ( val instanceof Number || val instanceof String )
+                name.append( val.toString().replace( ' ', '_' ) );
+        }
+        return name.toString();
+    }
+
+    // --- END INDEX CODE ---
+
+    /**
+     * Set hint fields for this collection (to optimize queries).
+     * @param lst a list of <code>DBObject</code>s to be used as hints
+     */
+    public void setHintFields( List<DBObject> lst ){
+        _hintFields = lst;
+    }
+
+    /**
+     * Queries for an object in this collection.
+     * @param ref object for which to search
+     * @return an iterator over the results
+     * @dochub find
+     */
+    public DBCursor find( DBObject ref ){
+        return new DBCursor( this, ref, null, getReadPreference());
+    }
+
+    /**
+     * Queries for an object in this collection.
+     *
+     * <p>
+     * An empty DBObject will match every document in the collection.
+     * Regardless of fields specified, the _id fields are always returned.
+     * </p>
+     * <p>
+     * An example that returns the "x" and "_id" fields for every document
+     * in the collection that has an "x" field:
+     * </p>
+     * <blockquote><pre>
+     * BasicDBObject keys = new BasicDBObject();
+     * keys.put("x", 1);
+     *
+     * DBCursor cursor = collection.find(new BasicDBObject(), keys);
+     * </pre></blockquote>
+     *
+     * @param ref object for which to search
+     * @param keys fields to return
+     * @return a cursor to iterate over results
+     * @dochub find
+     */
+    public DBCursor find( DBObject ref , DBObject keys ){
+        return new DBCursor( this, ref, keys, getReadPreference());
+    }
+
+
+    /**
+     * Queries for all objects in this collection.
+     * @return a cursor which will iterate over every object
+     * @dochub find
+     */
+    public DBCursor find(){
+        return new DBCursor( this, null, null, getReadPreference());
+    }
+
+    /**
+     * Returns a single object from this collection.
+     * @return the object found, or <code>null</code> if the collection is empty
+     * @throws MongoException
+     */
+    public DBObject findOne()
+        throws MongoException {
+        return findOne( new BasicDBObject() );
+    }
+
+    /**
+     * Returns a single object from this collection matching the query.
+     * @param o the query object
+     * @return the object found, or <code>null</code> if no such object exists
+     * @throws MongoException
+     */
+    public DBObject findOne( DBObject o )
+        throws MongoException {
+        return findOne( o, null, getReadPreference());
+    }
+
+    /**
+     * Returns a single object from this collection matching the query.
+     * @param o the query object
+     * @param fields fields to return
+     * @return the object found, or <code>null</code> if no such object exists
+     * @dochub find
+     */
+    public DBObject findOne( DBObject o, DBObject fields ) {
+        return findOne( o, fields, getReadPreference());
+    }
+    /**
+     * Returns a single object from this collection matching the query.
+     * @param o the query object
+     * @param fields fields to return
+     * @return the object found, or <code>null</code> if no such object exists
+     * @dochub find
+     */
+    public DBObject findOne( DBObject o, DBObject fields, ReadPreference readPref ) {
+        Iterator<DBObject> i = __find( o , fields , 0 , -1 , 0, getOptions(), readPref, _decoderFactory.create() );
+        DBObject obj = (i == null ? null : i.next());
+        if ( obj != null && ( fields != null && fields.keySet().size() > 0 ) ){
+            obj.markAsPartialObject();
+        }
+        return obj;
+    }
+
+
+    /**
+     * calls {@link DBCollection#apply(com.mongodb.DBObject, boolean)} with ensureID=true
+     * @param o <code>DBObject</code> to which to add fields
+     * @return the modified parameter object
+     */
+    public final Object apply( DBObject o ){
+        return apply( o , true );
+    }
+
+    /**
+     * calls {@link DBCollection#doapply(com.mongodb.DBObject)}, optionally adding an automatic _id field
+     * @param jo object to add fields to
+     * @param ensureID whether to add an <code>_id</code> field
+     * @return the modified object <code>o</code>
+     */
+    public final Object apply( DBObject jo , boolean ensureID ){
+
+        Object id = jo.get( "_id" );
+        if ( ensureID && id == null ){
+            id = ObjectId.get();
+            jo.put( "_id" , id );
+        }
+
+        doapply( jo );
+
+        return id;
+    }
+
+    /**
+     * calls {@link DBCollection#save(com.mongodb.DBObject, com.mongodb.WriteConcern)} with default WriteConcern
+     * @param jo the <code>DBObject</code> to save
+     *        will add <code>_id</code> field to jo if needed
+     * @return
+     */
+    public final WriteResult save( DBObject jo ) {
+        return save(jo, getWriteConcern());
+    }
+
+    /**
+     * Saves an object to this collection (does insert or update based on the object _id).
+     * @param jo the <code>DBObject</code> to save
+     * @param concern the write concern
+     * @return
+     * @throws MongoException
+     */
+    public final WriteResult save( DBObject jo, WriteConcern concern )
+        throws MongoException {
+        if ( checkReadOnly( true ) )
+            return null;
+
+        _checkObject( jo , false , false );
+
+        Object id = jo.get( "_id" );
+
+        if ( id == null || ( id instanceof ObjectId && ((ObjectId)id).isNew() ) ){
+            if ( id != null && id instanceof ObjectId )
+                ((ObjectId)id).notNew();
+            if ( concern == null )
+                return insert( jo );
+            else
+                return insert( jo, concern );
+        }
+
+        DBObject q = new BasicDBObject();
+        q.put( "_id" , id );
+        if ( concern == null )
+            return update( q , jo , true , false );
+        else
+            return update( q , jo , true , false , concern );
+
+    }
+
+    // ---- DB COMMANDS ----
+    /**
+     * Drops all indices from this collection
+     * @throws MongoException
+     */
+    public void dropIndexes()
+        throws MongoException {
+        dropIndexes( "*" );
+    }
+
+
+    /**
+     * Drops an index from this collection
+     * @param name the index name
+     * @throws MongoException
+     */
+    public void dropIndexes( String name )
+        throws MongoException {
+        DBObject cmd = BasicDBObjectBuilder.start()
+            .add( "deleteIndexes" , getName() )
+            .add( "index" , name )
+            .get();
+
+        resetIndexCache();
+        CommandResult res = _db.command( cmd );
+        if (res.ok() || res.getErrorMessage().equals( "ns not found" ))
+            return;
+        res.throwOnError();
+    }
+
+    /**
+     * Drops (deletes) this collection. Use with care.
+     * @throws MongoException
+     */
+    public void drop()
+        throws MongoException {
+        resetIndexCache();
+        CommandResult res =_db.command( BasicDBObjectBuilder.start().add( "drop" , getName() ).get() );
+        if (res.ok() || res.getErrorMessage().equals( "ns not found" ))
+            return;
+        res.throwOnError();
+    }
+
+    /**
+     * returns the number of documents in this collection.
+     * @return
+     * @throws MongoException
+     */
+    public long count()
+        throws MongoException {
+        return getCount(new BasicDBObject(), null);
+    }
+
+    /**
+     * returns the number of documents that match a query.
+     * @param query query to match
+     * @return
+     * @throws MongoException
+     */
+    public long count(DBObject query)
+        throws MongoException {
+        return getCount(query, null);
+    }
+
+
+    /**
+     *  calls {@link DBCollection#getCount(com.mongodb.DBObject, com.mongodb.DBObject)} with an empty query and null fields.
+     *  @return number of documents that match query
+     * @throws MongoException
+     */
+    public long getCount()
+        throws MongoException {
+        return getCount(new BasicDBObject(), null);
+    }
+
+    /**
+     *  calls {@link DBCollection#getCount(com.mongodb.DBObject, com.mongodb.DBObject)} with null fields.
+     *  @param query query to match
+     *  @return
+     * @throws MongoException
+     */
+    public long getCount(DBObject query)
+        throws MongoException {
+        return getCount(query, null);
+    }
+
+    /**
+     *  calls {@link DBCollection#getCount(com.mongodb.DBObject, com.mongodb.DBObject, long, long)} with limit=0 and skip=0
+     *  @param query query to match
+     *  @param fields fields to return
+     *  @return
+     * @throws MongoException
+     */
+    public long getCount(DBObject query, DBObject fields)
+        throws MongoException {
+        return getCount( query , fields , 0 , 0 );
+    }
+
+    /**
+     *  Returns the number of documents in the collection
+     *  that match the specified query
+     *
+     *  @param query query to select documents to count
+     *  @param fields fields to return
+     *  @param limit limit the count to this value
+     * @param skip number of entries to skip
+     * @return number of documents that match query and fields
+     * @throws MongoException
+     */
+    public long getCount(DBObject query, DBObject fields, long limit, long skip )
+        throws MongoException {
+
+        BasicDBObject cmd = new BasicDBObject();
+        cmd.put("count", getName());
+        cmd.put("query", query);
+        if (fields != null) {
+            cmd.put("fields", fields);
+        }
+
+        if ( limit > 0 )
+            cmd.put( "limit" , limit );
+        if ( skip > 0 )
+            cmd.put( "skip" , skip );
+
+        CommandResult res = _db.command(cmd,getOptions());
+
+        if ( ! res.ok() ){
+            String errmsg = res.getErrorMessage();
+
+            if ( errmsg.equals("ns does not exist") ||
+                 errmsg.equals("ns missing" ) ){
+                // for now, return 0 - lets pretend it does exist
+                return 0;
+            }
+
+            res.throwOnError();
+        }
+
+        return res.getLong("n");
+    }
+
+    /**
+     * Calls {@link DBCollection#rename(java.lang.String, boolean)} with dropTarget=false
+     * @param newName new collection name (not a full namespace)
+     * @return the new collection
+     * @throws MongoException
+     */
+    public DBCollection rename( String newName )
+        throws MongoException {
+        return rename(newName, false);
+    }
+
+    /**
+     * renames of this collection to newName
+     * @param newName new collection name (not a full namespace)
+     * @param dropTarget if a collection with the new name exists, whether or not to drop it
+     * @return the new collection
+     * @throws MongoException
+     */
+    public DBCollection rename( String newName, boolean dropTarget )
+        throws MongoException {
+        CommandResult ret =
+            _db.getSisterDB( "admin" )
+            .command( BasicDBObjectBuilder.start()
+                      .add( "renameCollection" , _fullName )
+                      .add( "to" , _db._name + "." + newName )
+                      .add( "dropTarget" , dropTarget )
+                      .get() );
+        ret.throwOnError();
+        resetIndexCache();
+        return _db.getCollection( newName );
+    }
+
+    /**
+     * calls {@link DBCollection#group(com.mongodb.DBObject, com.mongodb.DBObject, com.mongodb.DBObject, java.lang.String, java.lang.String)} with finalize=null
+     * @param key - { a : true }
+     * @param cond - optional condition on query
+     * @param reduce javascript reduce function
+     * @param initial initial value for first match on a key
+     * @return
+     * @throws MongoException
+     * @see <a href="http://www.mongodb.org/display/DOCS/Aggregation">http://www.mongodb.org/display/DOCS/Aggregation</a>
+     */
+    public DBObject group( DBObject key , DBObject cond , DBObject initial , String reduce )
+        throws MongoException {
+        return group( key , cond , initial , reduce , null );
+    }
+
+    /**
+     * Applies a group operation
+     * @param key - { a : true }
+     * @param cond - optional condition on query
+     * @param reduce javascript reduce function
+     * @param initial initial value for first match on a key
+     * @param finalize An optional function that can operate on the result(s) of the reduce function.
+     * @return
+     * @throws MongoException
+     * @see <a href="http://www.mongodb.org/display/DOCS/Aggregation">http://www.mongodb.org/display/DOCS/Aggregation</a>
+     */
+    public DBObject group( DBObject key , DBObject cond , DBObject initial , String reduce , String finalize )
+        throws MongoException {
+        GroupCommand cmd = new GroupCommand(this, key, cond, initial, reduce, finalize);
+        return group( cmd );
+    }
+
+    /**
+     * Applies a group operation
+     * @param cmd the group command
+     * @return
+     * @throws MongoException
+     * @see <a href="http://www.mongodb.org/display/DOCS/Aggregation">http://www.mongodb.org/display/DOCS/Aggregation</a>
+     */
+    public DBObject group( GroupCommand cmd ) {
+        CommandResult res =  _db.command( cmd.toDBObject(), getOptions() );
+        res.throwOnError();
+        return (DBObject)res.get( "retval" );
+    }
+
+
+    /**
+     * @deprecated prefer the {@link DBCollection#group(com.mongodb.GroupCommand)} which is more standard
+     * Applies a group operation
+     * @param args object representing the arguments to the group function
+     * @return
+     * @throws MongoException
+     * @see <a href="http://www.mongodb.org/display/DOCS/Aggregation">http://www.mongodb.org/display/DOCS/Aggregation</a>
+     */
+    @Deprecated
+    public DBObject group( DBObject args )
+        throws MongoException {
+        args.put( "ns" , getName() );
+        CommandResult res =  _db.command( new BasicDBObject( "group" , args ), getOptions() );
+        res.throwOnError();
+        return (DBObject)res.get( "retval" );
+    }
+
+    /**
+     * find distinct values for a key
+     * @param key
+     * @return
+     */
+    public List distinct( String key ){
+        return distinct( key , new BasicDBObject() );
+    }
+
+    /**
+     * find distinct values for a key
+     * @param key
+     * @param query query to match
+     * @return
+     */
+    public List distinct( String key , DBObject query ){
+        DBObject c = BasicDBObjectBuilder.start()
+            .add( "distinct" , getName() )
+            .add( "key" , key )
+            .add( "query" , query )
+            .get();
+
+        CommandResult res = _db.command( c, getOptions() );
+        res.throwOnError();
+        return (List)(res.get( "values" ));
+    }
+
+    /**
+     * performs a map reduce operation
+     * Runs the command in REPLACE output mode (saves to named collection)
+     *
+     * @param map
+     *            map function in javascript code
+     * @param outputTarget
+     *            optional - leave null if want to use temp collection
+     * @param reduce
+     *            reduce function in javascript code
+     * @param query
+     *            to match
+     * @return
+     * @throws MongoException
+     * @dochub mapreduce
+     */
+    public MapReduceOutput mapReduce( String map , String reduce , String outputTarget , DBObject query ) throws MongoException{
+        return mapReduce( new MapReduceCommand( this , map , reduce , outputTarget , MapReduceCommand.OutputType.REPLACE, query ) );
+    }
+
+    /**
+     * performs a map reduce operation
+     * Specify an outputType to control job execution
+     * * INLINE - Return results inline
+     * * REPLACE - Replace the output collection with the job output
+     * * MERGE - Merge the job output with the existing contents of outputTarget
+     * * REDUCE - Reduce the job output with the existing contents of
+     * outputTarget
+     *
+     * @param map
+     *            map function in javascript code
+     * @param outputTarget
+     *            optional - leave null if want to use temp collection
+     * @param outputType
+     *            set the type of job output
+     * @param reduce
+     *            reduce function in javascript code
+     * @param query
+     *            to match
+     * @return
+     * @throws MongoException
+     * @dochub mapreduce
+     */
+    public MapReduceOutput mapReduce( String map , String reduce , String outputTarget , MapReduceCommand.OutputType outputType , DBObject query )
+            throws MongoException{
+        return mapReduce( new MapReduceCommand( this , map , reduce , outputTarget , outputType , query ) );
+    }
+
+    /**
+     * performs a map reduce operation
+     *
+     * @param command
+     *            object representing the parameters
+     * @return
+     * @throws MongoException
+     */
+    public MapReduceOutput mapReduce( MapReduceCommand command ) throws MongoException{
+        DBObject cmd = command.toDBObject();
+        // if type in inline, then query options like slaveOk is fine
+        CommandResult res = null;
+        if (command.getOutputType() == MapReduceCommand.OutputType.INLINE)
+            res = _db.command( cmd, getOptions(), command.getReadPreference() != null ? command.getReadPreference() : getReadPreference() );
+        else
+            res = _db.command( cmd );
+        res.throwOnError();
+        return new MapReduceOutput( this , cmd, res );
+    }
+
+    /**
+     * performs a map reduce operation
+     *
+     * @param command
+     *            object representing the parameters
+     * @return
+     * @throws MongoException
+     */
+    public MapReduceOutput mapReduce( DBObject command ) throws MongoException{
+        if ( command.get( "mapreduce" ) == null && command.get( "mapReduce" ) == null )
+            throw new IllegalArgumentException( "need mapreduce arg" );
+        CommandResult res = _db.command( command );
+        res.throwOnError();
+        return new MapReduceOutput( this , command, res );
+    }
+
+    /**
+     *   Return a list of the indexes for this collection.  Each object
+     *   in the list is the "info document" from MongoDB
+     *
+     *   @return list of index documents
+     */
+    public List<DBObject> getIndexInfo() {
+        BasicDBObject cmd = new BasicDBObject();
+        cmd.put("ns", getFullName());
+
+        DBCursor cur = _db.getCollection("system.indexes").find(cmd);
+
+        List<DBObject> list = new ArrayList<DBObject>();
+
+        while(cur.hasNext()) {
+            list.add(cur.next());
+        }
+
+        return list;
+    }
+
+    /**
+     * Drops an index from this collection
+     * @param keys keys of the index
+     * @throws MongoException
+     */
+    public void dropIndex( DBObject keys )
+        throws MongoException {
+        dropIndexes( genIndexName( keys ) );
+    }
+
+    /**
+     * Drops an index from this collection
+     * @param name name of index to drop
+     * @throws MongoException
+     */
+    public void dropIndex( String name )
+        throws MongoException {
+        dropIndexes( name );
+    }
+
+    /**
+     * gets the collections statistics ("collstats" command)
+     * @return
+     */
+    public CommandResult getStats() {
+        return getDB().command(new BasicDBObject("collstats", getName()), getOptions());
+    }
+
+    /**
+     * returns whether or not this is a capped collection
+     * @return
+     */
+    public boolean isCapped() {
+        CommandResult stats = getStats();
+        Object capped = stats.get("capped");
+        return(capped != null && (Integer)capped == 1);
+    }
+
+    // ------
+
+    /**
+     * Initializes a new collection. No operation is actually performed on the database.
+     * @param base database in which to create the collection
+     * @param name the name of the collection
+     */
+    protected DBCollection( DB base , String name ){
+        _db = base;
+        _name = name;
+        _fullName = _db.getName() + "." + name;
+        _options = new Bytes.OptionHolder( _db._options );
+        _decoderFactory = _db.getMongo().getMongoOptions().dbDecoderFactory;
+        _encoderFactory = _db.getMongo().getMongoOptions().dbEncoderFactory;
+    }
+
+    protected DBObject _checkObject( DBObject o , boolean canBeNull , boolean query ){
+        if ( o == null ){
+            if ( canBeNull )
+                return null;
+            throw new IllegalArgumentException( "can't be null" );
+        }
+
+        if ( o.isPartialObject() && ! query )
+            throw new IllegalArgumentException( "can't save partial objects" );
+
+        if ( ! query ){
+            _checkKeys(o);
+        }
+        return o;
+    }
+
+    /**
+     * Checks key strings for invalid characters.
+     */
+    private void _checkKeys( DBObject o ) {
+        for ( String s : o.keySet() ){
+            validateKey ( s );
+            Object inner = o.get( s );
+            if ( inner instanceof DBObject ) {
+                _checkKeys( (DBObject)inner );
+            } else if ( inner instanceof Map ) {
+                _checkKeys( (Map<String, Object>)inner );
+            }
+        }
+    }
+
+    /**
+     * Checks key strings for invalid characters.
+     */
+    private void _checkKeys( Map<String, Object> o ) {
+        for ( String s : o.keySet() ){
+            validateKey ( s );
+            Object inner = o.get( s );
+            if ( inner instanceof DBObject ) {
+                _checkKeys( (DBObject)inner );
+            } else if ( inner instanceof Map ) {
+                _checkKeys( (Map<String, Object>)inner );
+            }
+        }
+    }
+
+    /**
+     * Check for invalid key names
+     * @param s the string field/key to check
+     * @exception IllegalArgumentException if the key is not valid.
+     */
+    private void validateKey(String s ) {
+        if ( s.contains( "." ) )
+            throw new IllegalArgumentException( "fields stored in the db can't have . in them. (Bad Key: '" + s + "')" );
+        if ( s.startsWith( "$" ) )
+            throw new IllegalArgumentException( "fields stored in the db can't start with '$' (Bad Key: '" + s + "')" );
+    }
+
+    /**
+     * Finds a collection that is prefixed with this collection's name.
+     * A typical use of this might be
+     * <blockquote><pre>
+     *    DBCollection users = mongo.getCollection( "wiki" ).getCollection( "users" );
+     * </pre></blockquote>
+     * Which is equivalent to
+     * <pre><blockquote>
+     *   DBCollection users = mongo.getCollection( "wiki.users" );
+     * </pre></blockquote>
+     * @param n the name of the collection to find
+     * @return the matching collection
+     */
+    public DBCollection getCollection( String n ){
+        return _db.getCollection( _name + "." + n );
+    }
+
+    /**
+     * Returns the name of this collection.
+     * @return  the name of this collection
+     */
+    public String getName(){
+        return _name;
+    }
+
+    /**
+     * Returns the full name of this collection, with the database name as a prefix.
+     * @return  the name of this collection
+     */
+    public String getFullName(){
+        return _fullName;
+    }
+
+    /**
+     * Returns the database this collection is a member of.
+     * @return this collection's database
+     */
+    public DB getDB(){
+        return _db;
+    }
+
+    /**
+     * Returns if this collection's database is read-only
+     * @param strict if an exception should be thrown if the database is read-only
+     * @return if this collection's database is read-only
+     * @throws RuntimeException if the database is read-only and <code>strict</code> is set
+     */
+    protected boolean checkReadOnly( boolean strict ){
+        if ( ! _db._readOnly )
+            return false;
+
+        if ( ! strict )
+            return true;
+
+        throw new IllegalStateException( "db is read only" );
+    }
+
+    @Override
+    public int hashCode(){
+        return _fullName.hashCode();
+    }
+
+    @Override
+    public boolean equals( Object o ){
+        return o == this;
+    }
+
+    @Override
+    public String toString(){
+        return _name;
+    }
+
+    /**
+     * Sets a default class for objects in this collection; null resets the class to nothing.
+     * @param c the class
+     * @throws IllegalArgumentException if <code>c</code> is not a DBObject
+     */
+    public void setObjectClass( Class c ){
+        if ( c == null ){
+            // reset
+            _wrapper = null;
+            _objectClass = null;
+            return;
+        }
+
+        if ( ! DBObject.class.isAssignableFrom( c ) )
+            throw new IllegalArgumentException( c.getName() + " is not a DBObject" );
+        _objectClass = c;
+        if ( ReflectionDBObject.class.isAssignableFrom( c ) )
+            _wrapper = ReflectionDBObject.getWrapper( c );
+        else
+            _wrapper = null;
+    }
+
+    /**
+     * Gets the default class for objects in the collection
+     * @return the class
+     */
+    public Class getObjectClass(){
+        return _objectClass;
+    }
+
+    /**
+     * sets the internal class
+     * @param path
+     * @param c
+     */
+    public void setInternalClass( String path , Class c ){
+        _internalClass.put( path , c );
+    }
+
+    /**
+     * gets the internal class
+     * @param path
+     * @return
+     */
+    protected Class getInternalClass( String path ){
+        Class c = _internalClass.get( path );
+        if ( c != null )
+            return c;
+
+        if ( _wrapper == null )
+            return null;
+        return _wrapper.getInternalClass( path );
+    }
+
+    /**
+     * Set the write concern for this collection. Will be used for
+     * writes to this collection. Overrides any setting of write
+     * concern at the DB level. See the documentation for
+     * {@link WriteConcern} for more information.
+     *
+     * @param concern write concern to use
+     */
+    public void setWriteConcern( WriteConcern concern ){
+        _concern = concern;
+    }
+
+    /**
+     * Get the write concern for this collection.
+     * @return
+     */
+    public WriteConcern getWriteConcern(){
+        if ( _concern != null )
+            return _concern;
+        return _db.getWriteConcern();
+    }
+
+    /**
+     * Sets the read preference for this collection. Will be used as default
+     * for reads from this collection; overrides DB & Connection level settings.
+     * See the * documentation for {@link ReadPreference} for more information.
+     *
+     * @param preference Read Preference to use
+     */
+    public void setReadPreference( ReadPreference preference ){
+        _readPref = preference;
+    }
+
+    /**
+     * Gets the read preference
+     * @return
+     */
+    public ReadPreference getReadPreference(){
+        if ( _readPref != null )
+            return _readPref;
+        return _db.getReadPreference();
+    }
+    /**
+     * makes this query ok to run on a slave node
+     *
+     * @deprecated Replaced with ReadPreference.SECONDARY
+     * @see com.mongodb.ReadPreference.SECONDARY
+     */
+    @Deprecated
+    public void slaveOk(){
+        addOption( Bytes.QUERYOPTION_SLAVEOK );
+    }
+
+    /**
+     * adds a default query option
+     * @param option
+     */
+    public void addOption( int option ){
+        _options.add( option );
+    }
+
+    /**
+     * sets the default query options
+     * @param options
+     */
+    public void setOptions( int options ){
+        _options.set( options );
+    }
+
+    /**
+     * resets the default query options
+     */
+    public void resetOptions(){
+        _options.reset();
+    }
+
+    /**
+     * gets the default query options
+     * @return
+     */
+    public int getOptions(){
+        return _options.get();
+    }
+
+    public void setDBDecoderFactory(DBDecoderFactory fact) {
+        if (fact == null)
+            _decoderFactory = _db.getMongo().getMongoOptions().dbDecoderFactory;
+        else
+            _decoderFactory = fact;
+    }
+
+    public DBDecoderFactory getDBDecoderFactory() {
+        return _decoderFactory;
+    }
+
+    public void setDBEncoderFactory(DBEncoderFactory fact) {
+        if (fact == null)
+            _encoderFactory = _db.getMongo().getMongoOptions().dbEncoderFactory;
+        else
+            _encoderFactory = fact;
+    }
+
+    public DBEncoderFactory getDBEncoderFactory() {
+        return _encoderFactory;
+    }
+
+    final DB _db;
+
+    final protected String _name;
+    final protected String _fullName;
+
+    protected List<DBObject> _hintFields;
+    private WriteConcern _concern = null;
+    private ReadPreference _readPref = null;
+    private DBDecoderFactory _decoderFactory;
+    private DBEncoderFactory _encoderFactory;
+    final Bytes.OptionHolder _options;
+
+    protected Class _objectClass = null;
+    private Map<String,Class> _internalClass = Collections.synchronizedMap( new HashMap<String,Class>() );
+    private ReflectionDBObject.JavaWrapper _wrapper = null;
+
+    final private Set<String> _createdIndexes = new HashSet<String>();
+
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk/src/test/java/com/mongodb/DBCollection.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/BaseMongoTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/BaseMongoTest.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/BaseMongoTest.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/BaseMongoTest.java Mon Sep 17 12:54:01 2012
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.mongomk;
+
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.apache.jackrabbit.mongomk.MongoConnection;
+import org.apache.jackrabbit.mongomk.util.MongoUtil;
+import org.apache.log4j.BasicConfigurator;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+
+/**
+ * Base class for {@code MongoDB} tests.
+ *
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+@SuppressWarnings("javadoc")
+public class BaseMongoTest {
+
+    public static MongoConnection mongoConnection;
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        BasicConfigurator.configure();
+        createDefaultMongoConnection();
+        MongoAssert.setMongoConnection(mongoConnection);
+    }
+
+    @AfterClass
+    public static void tearDownAfterClass() throws Exception {
+        dropDefaultDatabase();
+    }
+
+    private static void createDefaultMongoConnection() throws Exception {
+        InputStream is = BaseMongoTest.class.getResourceAsStream("/config.cfg");
+        Properties properties = new Properties();
+        properties.load(is);
+
+        String host = properties.getProperty("host");
+        int port = Integer.parseInt(properties.getProperty("port"));
+        String database = properties.getProperty("db");
+
+        mongoConnection = new MongoConnection(host, port, database);
+    }
+
+    private static void dropDefaultDatabase() {
+        mongoConnection.getDB().dropDatabase();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MongoUtil.initDatabase(mongoConnection);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        MongoUtil.clearDatabase(mongoConnection);
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/BaseMongoTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/MongoAssert.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/MongoAssert.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/MongoAssert.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/MongoAssert.java Mon Sep 17 12:54:01 2012
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.mongomk;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.jackrabbit.mongomk.api.model.Commit;
+import org.apache.jackrabbit.mongomk.api.model.Node;
+import org.apache.jackrabbit.mongomk.model.CommitMongo;
+import org.apache.jackrabbit.mongomk.model.HeadMongo;
+import org.apache.jackrabbit.mongomk.model.NodeMongo;
+import org.apache.jackrabbit.mongomk.util.MongoUtil;
+import org.junit.Assert;
+
+import com.mongodb.DBCollection;
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
+
+/**
+ * Assertion utilities for {@code MongoDB} tests.
+ *
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+@SuppressWarnings("javadoc")
+public class MongoAssert {
+
+    private static MongoConnection mongoConnection;
+
+    public static void assertCommitContainsAffectedPaths(String revisionId, String... expectedPaths) {
+        DBCollection commitCollection = mongoConnection.getCommitCollection();
+        DBObject query = QueryBuilder.start(CommitMongo.KEY_REVISION_ID)
+                .is(MongoUtil.toMongoRepresentation(revisionId)).get();
+        CommitMongo result = (CommitMongo) commitCollection.findOne(query);
+        Assert.assertNotNull(result);
+
+        List<String> actualPaths = result.getAffectedPaths();
+        Assert.assertEquals(new HashSet<String>(Arrays.asList(expectedPaths)), new HashSet<String>(actualPaths));
+    }
+
+    public static void assertCommitExists(Commit commit) {
+        DBCollection commitCollection = mongoConnection.getCommitCollection();
+        DBObject query = QueryBuilder.start(CommitMongo.KEY_REVISION_ID)
+                .is(MongoUtil.toMongoRepresentation(commit.getRevisionId())).and(CommitMongo.KEY_MESSAGE)
+                .is(commit.getMessage()).and(CommitMongo.KEY_DIFF).is(commit.getDiff()).and(CommitMongo.KEY_PATH)
+                .is(commit.getPath()).and(CommitMongo.KEY_FAILED).notEquals(Boolean.TRUE).get();
+        CommitMongo result = (CommitMongo) commitCollection.findOne(query);
+        Assert.assertNotNull(result);
+    }
+
+    public static void assertHeadRevision(long revisionId) {
+        DBCollection headCollection = mongoConnection.getHeadCollection();
+        HeadMongo result = (HeadMongo) headCollection.findOne();
+        Assert.assertEquals(revisionId, result.getHeadRevisionId());
+    }
+
+    public static void assertNextRevision(long revisionId) {
+        DBCollection headCollection = mongoConnection.getHeadCollection();
+        HeadMongo result = (HeadMongo) headCollection.findOne();
+        Assert.assertEquals(revisionId, result.getNextRevisionId());
+    }
+
+    public static void assertNodeRevisionId(String path, String revisionId, boolean exists) {
+        DBCollection nodeCollection = mongoConnection.getNodeCollection();
+        DBObject query = QueryBuilder.start(NodeMongo.KEY_PATH).is(path).and(NodeMongo.KEY_REVISION_ID)
+                .is(MongoUtil.toMongoRepresentation(revisionId)).get();
+        NodeMongo nodeMongo = (NodeMongo) nodeCollection.findOne(query);
+
+        if (exists) {
+            Assert.assertNotNull(nodeMongo);
+        } else {
+            Assert.assertNull(nodeMongo);
+        }
+    }
+
+    public static void assertNodesExist(String parentPath, Node expected) {
+        DBCollection nodeCollection = mongoConnection.getNodeCollection();
+        QueryBuilder qb = QueryBuilder.start(NodeMongo.KEY_PATH).is(expected.getPath()).and(NodeMongo.KEY_REVISION_ID)
+                .is(MongoUtil.toMongoRepresentation(expected.getRevisionId()));
+        Map<String, Object> properties = expected.getProperties();
+        if (properties != null) {
+            for (Map.Entry<String, Object> entry : properties.entrySet()) {
+                qb.and(NodeMongo.KEY_PROPERTIES + "." + entry.getKey()).is(entry.getValue());
+            }
+        }
+
+        DBObject query = qb.get();
+
+        NodeMongo nodeMongo = (NodeMongo) nodeCollection.findOne(query);
+        Assert.assertNotNull(nodeMongo);
+
+        Set<Node> children = expected.getChildren();
+        if (children != null) {
+            List<String> childNames = nodeMongo.getChildren();
+            Assert.assertNotNull(childNames);
+            Assert.assertEquals(children.size(), childNames.size());
+            Assert.assertEquals(children.size(), new HashSet<String>(childNames).size());
+            for (Node child : children) {
+                assertNodesExist(expected.getPath(), child);
+                Assert.assertTrue(childNames.contains(child.getName()));
+            }
+        } else {
+            Assert.assertNull(nodeMongo.getChildren());
+        }
+    }
+
+    static void setMongoConnection(MongoConnection mongoConnection) {
+        // must be set prior to using this class.
+        MongoAssert.mongoConnection = mongoConnection;
+    }
+
+    private MongoAssert() {
+        // no instantiation
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/MongoAssert.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/command/CommitCommandMongoTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/command/CommitCommandMongoTest.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/command/CommitCommandMongoTest.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/command/CommitCommandMongoTest.java Mon Sep 17 12:54:01 2012
@@ -0,0 +1,377 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.mongomk.command;
+
+import static org.junit.Assert.fail;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.jackrabbit.mongomk.BaseMongoTest;
+import org.apache.jackrabbit.mongomk.MongoAssert;
+import org.apache.jackrabbit.mongomk.api.model.Commit;
+import org.apache.jackrabbit.mongomk.api.model.Instruction;
+import org.apache.jackrabbit.mongomk.api.model.Node;
+import org.apache.jackrabbit.mongomk.impl.builder.NodeBuilder;
+import org.apache.jackrabbit.mongomk.impl.model.AddNodeInstructionImpl;
+import org.apache.jackrabbit.mongomk.impl.model.AddPropertyInstructionImpl;
+import org.apache.jackrabbit.mongomk.impl.model.CommitImpl;
+import org.apache.jackrabbit.mongomk.impl.model.RemoveNodeInstructionImpl;
+import org.apache.jackrabbit.mongomk.scenario.SimpleNodeScenario;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+@SuppressWarnings("javadoc")
+public class CommitCommandMongoTest extends BaseMongoTest {
+
+    @Test
+    public void testAddIntermediataryNodes() throws Exception {
+        // Assert.fail("Do it");
+    }
+
+    @Test
+    public void testAddNewNodesToSameParent() throws Exception {
+        List<Instruction> instructions = new LinkedList<Instruction>();
+        instructions.add(new AddNodeInstructionImpl("/", "1"));
+
+        Commit commit = new CommitImpl("This is the 1st commit", "/", "+1 : {}", instructions);
+        CommitCommandMongo command = new CommitCommandMongo(mongoConnection, commit);
+        String firstRevisionId = command.execute();
+
+        instructions = new LinkedList<Instruction>();
+        instructions.add(new AddNodeInstructionImpl("/", "2"));
+
+        commit = new CommitImpl("This is the 2nd commit", "/", "+2 : {}", instructions);
+        command = new CommitCommandMongo(mongoConnection, commit);
+        String secondRevisionId = command.execute();
+
+        instructions = new LinkedList<Instruction>();
+        instructions.add(new AddNodeInstructionImpl("/", "3"));
+
+        commit = new CommitImpl("This is the 3rd commit", "/", "+3 : {}", instructions);
+        command = new CommitCommandMongo(mongoConnection, commit);
+        String thirdRevisionId = command.execute();
+
+        MongoAssert.assertNodesExist("", NodeBuilder.build(String.format(
+                "{ \"/#%3$s\" : { \"1#%1$s\" : { } , \"2#%2$s\" : { } , \"3#%3$s\" : { } } }",
+                firstRevisionId, secondRevisionId, thirdRevisionId)));
+    }
+
+    @Test
+    public void testCommitAddNodes() throws Exception {
+        List<Instruction> instructions = new LinkedList<Instruction>();
+        instructions.add(new AddNodeInstructionImpl("/", "a"));
+        instructions.add(new AddNodeInstructionImpl("/a", "b"));
+        instructions.add(new AddNodeInstructionImpl("/a", "c"));
+
+        Commit commit = new CommitImpl("This is a simple commit", "/", "+a : { b : {} , c : {} }", instructions);
+        CommitCommandMongo command = new CommitCommandMongo(mongoConnection, commit);
+        String revisionId = command.execute();
+
+        Assert.assertNotNull(revisionId);
+        MongoAssert.assertNodesExist("", NodeBuilder.build(String.format(
+                "{ \"/#%1$s\" : { \"a#%1$s\" : { \"b#%1$s\" : {} , \"c#%1$s\" : {} } } }", revisionId)));
+
+        MongoAssert.assertCommitExists(commit);
+        MongoAssert.assertCommitContainsAffectedPaths(commit.getRevisionId(), "/", "/a", "/a/b", "/a/c");
+        MongoAssert.assertHeadRevision(1);
+        MongoAssert.assertNextRevision(2);
+    }
+
+    @Test
+    public void testCommitAddNodesAndPropertiesOutOfOrder() throws Exception {
+        List<Instruction> instructions = new LinkedList<Instruction>();
+        instructions.add(new AddPropertyInstructionImpl("/a", "key1", "value1"));
+        instructions.add(new AddNodeInstructionImpl("/", "a"));
+        instructions.add(new AddNodeInstructionImpl("/a", "b"));
+        instructions.add(new AddPropertyInstructionImpl("/a/b", "key2", "value2"));
+        instructions.add(new AddPropertyInstructionImpl("/a/c", "key3", "value3"));
+        instructions.add(new AddNodeInstructionImpl("/a", "c"));
+
+        Commit commit = new CommitImpl("This is a simple commit", "/",
+                "+a : { \"key1\" : \"value1\" , \"key2\" : \"value2\" , \"key3\" : \"value3\" }", instructions);
+        CommitCommandMongo command = new CommitCommandMongo(mongoConnection, commit);
+        String revisionId = command.execute();
+
+        Assert.assertNotNull(revisionId);
+        MongoAssert
+                .assertNodesExist(
+                        "",
+                        NodeBuilder.build(String
+                                .format("{ \"/#%1$s\" : { \"a#%1$s\" : { \"key1\" : \"value1\", \"b#%1$s\" : { \"key2\" : \"value2\" } , \"c#%1$s\" : { \"key3\" : \"value3\" } } } }",
+                                        revisionId)));
+
+        MongoAssert.assertCommitExists(commit);
+        MongoAssert.assertCommitContainsAffectedPaths(commit.getRevisionId(), "/", "/a", "/a/b", "/a/c");
+        MongoAssert.assertHeadRevision(1);
+        MongoAssert.assertNextRevision(2);
+    }
+
+    @Test
+    public void testCommitAddNodesWhichAlreadyExist() throws Exception {
+        SimpleNodeScenario scenario1 = new SimpleNodeScenario(mongoConnection);
+        scenario1.create();
+
+        List<Instruction> instructions = new LinkedList<Instruction>();
+        instructions.add(new AddNodeInstructionImpl("/", "a"));
+        instructions.add(new AddPropertyInstructionImpl("/a", "key1", "value1"));
+        instructions.add(new AddNodeInstructionImpl("/a", "b"));
+        instructions.add(new AddPropertyInstructionImpl("/a/b", "key2", "value2"));
+        instructions.add(new AddNodeInstructionImpl("/a", "c"));
+        instructions.add(new AddPropertyInstructionImpl("/a/c", "key3", "value3"));
+
+        Commit commit = new CommitImpl("This is a simple commit", "/",
+                "+a : { \"key1\" : \"value1\" , \"key2\" : \"value2\" , \"key3\" : \"value3\" }", instructions);
+        CommitCommandMongo command = new CommitCommandMongo(mongoConnection, commit);
+        String revisionId = command.execute();
+
+        Assert.assertNotNull(revisionId);
+        MongoAssert
+                .assertNodesExist(
+                        "",
+                        NodeBuilder.build(String
+                                .format("{ \"/#%1$s\" : { \"a#%1$s\" : {  \"int\" : 1 , \"key1\" : \"value1\", \"b#%1$s\" : { \"string\" : \"foo\" , \"key2\" : \"value2\" } , \"c#%1$s\" : { \"bool\" : true , \"key3\" : \"value3\" } } } }",
+                                        revisionId)));
+
+        MongoAssert.assertCommitExists(commit);
+        // MongoAssert.assertCommitContainsAffectedPaths(commit.getRevisionId(), "/a", "/a/b", "/a/c"); TODO think about
+        // whether / should really be included since it already contained /a
+        MongoAssert.assertCommitContainsAffectedPaths(commit.getRevisionId(), "/", "/a", "/a/b", "/a/c");
+    }
+
+    @Test
+    public void testCommitAndMergeNodes() throws Exception {
+        SimpleNodeScenario scenario1 = new SimpleNodeScenario(mongoConnection);
+        String firstRevisionId = scenario1.create();
+        String secondRevisionId = scenario1.update_A_and_add_D_and_E();
+
+        SimpleNodeScenario scenario2 = new SimpleNodeScenario(mongoConnection);
+        String thirdRevisionId = scenario2.create();
+
+        MongoAssert
+                .assertNodesExist(
+                        "",
+                        NodeBuilder.build(String
+                                .format("{ \"/#%1$s\" : { \"a#%1$s\" : { \"int\" : 1 , \"b#%1$s\" : { \"string\" : \"foo\" } , \"c#%1$s\" : { \"bool\" : true } } } }",
+                                        firstRevisionId)));
+        MongoAssert
+                .assertNodesExist(
+                        "",
+                        NodeBuilder.build(String
+                                .format("{ \"/#%1$s\" : { \"a#%2$s\" : { \"int\" : 1 , \"double\" : 0.123 , \"b#%2$s\" : { \"string\" : \"foo\" , \"e#%2$s\" : { \"array\" : [ 123, null, 123.456, \"for:bar\", true ] } } , \"c#%1$s\" : { \"bool\" : true }, \"d#%2$s\" : { \"null\" : null } } } }",
+                                        firstRevisionId, secondRevisionId)));
+        MongoAssert
+                .assertNodesExist(
+                        "",
+                        NodeBuilder.build(String
+                                .format("{ \"/#%3$s\" : { \"a#%3$s\" : { \"int\" : 1 , \"double\" : 0.123 , \"b#%3$s\" : { \"string\" : \"foo\" , \"e#%2$s\" : { \"array\" : [ 123, null, 123.456, \"for:bar\", true ] } } , \"c#%3$s\" : { \"bool\" : true }, \"d#%2$s\" : { \"null\" : null } } } }",
+                                        firstRevisionId, secondRevisionId,
+                                        thirdRevisionId)));
+    }
+
+    @Test
+    public void testCommitContainsAllAffectedNodes() throws Exception {
+        SimpleNodeScenario scenario = new SimpleNodeScenario(mongoConnection);
+        String firstRevisionId = scenario.create();
+        String secondRevisionId = scenario.update_A_and_add_D_and_E();
+
+        MongoAssert.assertCommitContainsAffectedPaths(firstRevisionId, "/", "/a", "/a/b", "/a/c");
+        MongoAssert.assertCommitContainsAffectedPaths(secondRevisionId, "/a", "/a/b", "/a/d", "/a/b/e");
+    }
+
+    @Test
+    public void testRemoveNode() throws Exception {
+        List<Instruction> instructions = new LinkedList<Instruction>();
+        instructions.add(new AddNodeInstructionImpl("/", "a"));
+        instructions.add(new AddNodeInstructionImpl("/a", "b"));
+        instructions.add(new AddNodeInstructionImpl("/a", "c"));
+
+        Commit commit = new CommitImpl("This is a simple commit", "/", "+a : { b : {} , c : {} }", instructions);
+        CommitCommandMongo command = new CommitCommandMongo(mongoConnection, commit);
+        String revisionId = command.execute();
+        Assert.assertNotNull(revisionId);
+
+        instructions = new LinkedList<Instruction>();
+        instructions.add(new RemoveNodeInstructionImpl("/", "a"));
+
+        commit = new CommitImpl("This is a simple commit", "/", "-a", instructions);
+        command = new CommitCommandMongo(mongoConnection, commit);
+        revisionId = command.execute();
+        Assert.assertNotNull(revisionId);
+
+        MongoAssert.assertNodesExist("",
+                NodeBuilder.build(String.format("{ \"/#%1$s\" : {} }", revisionId)));
+
+        MongoAssert.assertCommitExists(commit);
+        MongoAssert.assertCommitContainsAffectedPaths(commit.getRevisionId(), "/");
+    }
+
+    @Test
+    @Ignore // FIXME
+    public void testRemoveNonExistentNode() throws Exception {
+        List<Instruction> instructions = new LinkedList<Instruction>();
+        instructions.add(new AddNodeInstructionImpl("/", "a"));
+        instructions.add(new AddNodeInstructionImpl("/a", "b"));
+
+        Commit commit = new CommitImpl("Add nodes", "/", "+a : { b : {}  }", instructions);
+        CommitCommandMongo command = new CommitCommandMongo(mongoConnection, commit);
+        command.execute();
+
+        instructions = new LinkedList<Instruction>();
+        instructions.add(new RemoveNodeInstructionImpl("/a", "c"));
+
+        commit = new CommitImpl("Non-existent node delete", "/a", "-c", instructions);
+        command = new CommitCommandMongo(mongoConnection, commit);
+        try {
+            command.execute();
+            fail("Exception expected");
+        } catch (Exception expected) {
+
+        }
+    }
+
+    @Test
+    public void testExistingParentContainsChildren() throws Exception {
+        List<Instruction> instructions = new LinkedList<Instruction>();
+        instructions.add(new AddNodeInstructionImpl("/", "a"));
+        instructions.add(new AddNodeInstructionImpl("/", "b"));
+        instructions.add(new AddNodeInstructionImpl("/", "c"));
+
+        Commit commit = new CommitImpl("This is a simple commit", "/", "+a : { b : {} , c : {} }", instructions);
+        CommitCommandMongo command = new CommitCommandMongo(mongoConnection, commit);
+        String revisionId = command.execute();
+
+        Assert.assertNotNull(revisionId);
+        MongoAssert.assertNodesExist("", NodeBuilder.build(String.format(
+                "{ \"/#%1$s\" : { \"a#%1$s\" : {}, \"b#%1$s\" : {} , \"c#%1$s\" : {} } }", revisionId)));
+
+        GetNodesCommandMongo command2 = new GetNodesCommandMongo(mongoConnection, "/", revisionId, 0);
+        Node rootOfPath = command2.execute();
+        Assert.assertEquals(3, rootOfPath.getChildCount());
+    }
+
+    @Test
+    public void testMergePropertiesAndChildren_noneExistedAndNewAdded() throws Exception {
+        List<Instruction> instructions = new LinkedList<Instruction>();
+        instructions.add(new AddNodeInstructionImpl("/", "a"));
+        instructions.add(new AddPropertyInstructionImpl("/a", "key1", "value1"));
+        instructions.add(new AddPropertyInstructionImpl("/a", "key2", "value2"));
+        instructions.add(new AddPropertyInstructionImpl("/a", "key3", "value3"));
+
+        Commit commit = new CommitImpl("This is a simple commit", "/",
+                "+a : { \"key1\" : \"value1\" , \"key2\" : \"value2\" , \"key3\" : \"value3\" }", instructions);
+        CommitCommandMongo command = new CommitCommandMongo(mongoConnection, commit);
+        String revisionId = command.execute();
+
+        MongoAssert.assertNodesExist("", NodeBuilder.build(String.format("{ \"/#%1$s\" : {} }", "0")));
+        MongoAssert
+                .assertNodesExist(
+                        "",
+                        NodeBuilder.build(String
+                                .format("{ \"/#%1$s\" : { \"a#%1$s\" : { \"key1\" : \"value1\", \"key2\" : \"value2\", \"key3\" : \"value3\" } } }",
+                                        revisionId)));
+    }
+
+    @Test
+    public void testMergePropertiesAndChildren_someExistedAndNewAdded() throws Exception {
+        List<Instruction> instructions = new LinkedList<Instruction>();
+        instructions.add(new AddNodeInstructionImpl("/", "a"));
+        instructions.add(new AddPropertyInstructionImpl("/a", "existed_key1", "value1"));
+        instructions.add(new AddPropertyInstructionImpl("/a", "existed_key2", "value2"));
+        instructions.add(new AddPropertyInstructionImpl("/a", "existed_key3", "value3"));
+
+        Commit commit = new CommitImpl(
+                "This is a simple commit",
+                "/",
+                "+a : { \"existed_key1\" : \"value1\" , \"existed_key2\" : \"value2\" , \"existed_key3\" : \"value3\" }",
+                instructions);
+        CommitCommandMongo command = new CommitCommandMongo(mongoConnection, commit);
+        String revisionId = command.execute();
+
+        instructions = new LinkedList<Instruction>();
+        instructions.add(new AddNodeInstructionImpl("/", "a"));
+        instructions.add(new AddPropertyInstructionImpl("/a", "key1", "value1"));
+        instructions.add(new AddPropertyInstructionImpl("/a", "key2", "value2"));
+        instructions.add(new AddPropertyInstructionImpl("/a", "key3", "value3"));
+
+        commit = new CommitImpl("This is a simple commit", "/",
+                "+a : { \"key1\" : \"value1\" , \"key2\" : \"value2\" , \"key3\" : \"value3\" }", instructions);
+        command = new CommitCommandMongo(mongoConnection, commit);
+        revisionId = command.execute();
+
+        MongoAssert.assertNodesExist("", NodeBuilder.build(String.format("{ \"/#%1$s\" : {} }", "0")));
+        MongoAssert
+                .assertNodesExist(
+                        "",
+                        NodeBuilder.build(String
+                                .format("{ \"/#%1$s\" : { \"a#%1$s\" : { \"existed_key1\" : \"value1\", \"existed_key2\" : \"value2\", \"existed_key3\" : \"value3\", \"key1\" : \"value1\", \"key2\" : \"value2\", \"key3\" : \"value3\" } } }",
+                                        revisionId)));
+    }
+
+    @Test
+    public void testNoOtherNodesTouched() throws Exception {
+        List<Instruction> instructions = new LinkedList<Instruction>();
+        instructions.add(new AddNodeInstructionImpl("/", "a"));
+        instructions.add(new AddNodeInstructionImpl("/", "b"));
+        instructions.add(new AddNodeInstructionImpl("/", "c"));
+
+        Commit commit = new CommitImpl("This is a simple commit", "/", "+a : { b : {} , c : {} }", instructions);
+        CommitCommandMongo command = new CommitCommandMongo(mongoConnection, commit);
+        String firstRevisionId = command.execute();
+
+        instructions = new LinkedList<Instruction>();
+        instructions.add(new AddNodeInstructionImpl("/a", "d"));
+        instructions.add(new AddNodeInstructionImpl("/a", "e"));
+
+        commit = new CommitImpl("This is a simple commit", "/a", "+d: {} \n+e : {}", instructions);
+        command = new CommitCommandMongo(mongoConnection, commit);
+        String secondRevisionId = command.execute();
+
+        MongoAssert.assertNodeRevisionId("/", firstRevisionId, true);
+        MongoAssert.assertNodeRevisionId("/a", firstRevisionId, true);
+        MongoAssert.assertNodeRevisionId("/b", firstRevisionId, true);
+        MongoAssert.assertNodeRevisionId("/c", firstRevisionId, true);
+        MongoAssert.assertNodeRevisionId("/a/d", firstRevisionId, false);
+        MongoAssert.assertNodeRevisionId("/a/e", firstRevisionId, false);
+
+        MongoAssert.assertNodeRevisionId("/", secondRevisionId, false);
+        MongoAssert.assertNodeRevisionId("/a", secondRevisionId, true);
+        MongoAssert.assertNodeRevisionId("/b", secondRevisionId, false);
+        MongoAssert.assertNodeRevisionId("/c", secondRevisionId, false);
+        MongoAssert.assertNodeRevisionId("/a/d", secondRevisionId, true);
+        MongoAssert.assertNodeRevisionId("/a/e", secondRevisionId, true);
+    }
+
+    @Test
+    @Ignore /// FIXME
+    public void testRootNodeHasEmptyRootPath() throws Exception {
+        List<Instruction> instructions = new LinkedList<Instruction>();
+        instructions.add(new AddNodeInstructionImpl("", "/"));
+
+        Commit commit = new CommitImpl("This is the root commit", "", "+/ : {}", instructions);
+        CommitCommandMongo command = new CommitCommandMongo(mongoConnection, commit);
+        String revisionId = command.execute();
+
+        Assert.assertNotNull(revisionId);
+        MongoAssert.assertNodesExist("",
+                NodeBuilder.build(String.format("{ \"/#%1$s\" : {} }", revisionId)));
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/command/CommitCommandMongoTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/command/ConcurrentCommitCommandMongoTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/command/ConcurrentCommitCommandMongoTest.java?rev=1386591&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/command/ConcurrentCommitCommandMongoTest.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/command/ConcurrentCommitCommandMongoTest.java Mon Sep 17 12:54:01 2012
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.mongomk.command;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.jackrabbit.mongomk.BaseMongoTest;
+import org.apache.jackrabbit.mongomk.api.command.CommandExecutor;
+import org.apache.jackrabbit.mongomk.api.model.Commit;
+import org.apache.jackrabbit.mongomk.api.model.Instruction;
+import org.apache.jackrabbit.mongomk.api.model.Node;
+import org.apache.jackrabbit.mongomk.impl.command.CommandExecutorImpl;
+import org.apache.jackrabbit.mongomk.impl.model.AddNodeInstructionImpl;
+import org.apache.jackrabbit.mongomk.impl.model.CommitImpl;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:pmarx@adobe.com>Philipp Marx</a>
+ */
+@SuppressWarnings("javadoc")
+public class ConcurrentCommitCommandMongoTest extends BaseMongoTest {
+
+    @Test
+    public void testConflictingConcurrentUpdate() throws Exception {
+        int numOfConcurrentThreads = 5;
+        final Object waitLock = new Object();
+
+        // create the commands
+        List<CommitCommandMongo> commands = new ArrayList<CommitCommandMongo>(numOfConcurrentThreads);
+        for (int i = 0; i < numOfConcurrentThreads; ++i) {
+            List<Instruction> instructions = new LinkedList<Instruction>();
+            instructions.add(new AddNodeInstructionImpl("/", String.valueOf(i)));
+            Commit commit = new CommitImpl("This is a concurrent commit", "/", "+" + i + " : {}", instructions);
+            CommitCommandMongo command = new CommitCommandMongo(mongoConnection, commit) {
+                @Override
+                protected boolean saveAndSetHeadRevision() throws Exception {
+                    try {
+                        synchronized (waitLock) {
+                            waitLock.wait();
+                        }
+
+                        return super.saveAndSetHeadRevision();
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                        return false;
+                    }
+                };
+            };
+            commands.add(command);
+        }
+
+        // execute the commands
+        final CommandExecutor commandExecutor = new CommandExecutorImpl();
+        ExecutorService executorService = Executors.newFixedThreadPool(numOfConcurrentThreads);
+        final List<String> revisionIds = new LinkedList<String>();
+        for (int i = 0; i < numOfConcurrentThreads; ++i) {
+            final CommitCommandMongo command = commands.get(i);
+            Runnable runnable = new Runnable() {
+
+                @Override
+                public void run() {
+                    try {
+                        String revisionId = commandExecutor.execute(command);
+                        revisionIds.add(revisionId);
+                    } catch (Exception e) {
+                        revisionIds.add(null);
+                    }
+                }
+            };
+            executorService.execute(runnable);
+        }
+
+        // notify the wait lock to execute the command concurrently
+        do {
+            Thread.sleep(1500);
+            synchronized (waitLock) {
+                waitLock.notifyAll();
+            }
+        } while (revisionIds.size() < numOfConcurrentThreads);
+
+        // verify the result by sorting the revision ids and verifying that all children are contained in the next
+        // revision
+        Collections.sort(revisionIds, new Comparator<String>() {
+            @Override
+            public int compare(String o1, String o2) {
+                return Long.valueOf(o1).compareTo(Long.valueOf(o2));
+            }
+        });
+        List<String> lastChildren = new LinkedList<String>();
+        for (int i = 0; i < numOfConcurrentThreads; ++i) {
+            String revisionId = revisionIds.get(i);
+
+            GetNodesCommandMongo command2 = new GetNodesCommandMongo(mongoConnection, "/", revisionId, 0);
+            Node root = command2.execute();
+            Set<Node> children = root.getChildren();
+            for (String lastChild : lastChildren) {
+                boolean contained = false;
+                for (Node childNode : children) {
+                    if (childNode.getName().equals(lastChild)) {
+                        contained = true;
+                        break;
+                    }
+                }
+                Assert.assertTrue(contained);
+            }
+            lastChildren.clear();
+            for (Node childNode : children) {
+                lastChildren.add(childNode.getName());
+            }
+        }
+
+        // TODO Assert the number of commits
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/command/ConcurrentCommitCommandMongoTest.java
------------------------------------------------------------------------------
    svn:eol-style = native