clazzes) {
+ if (clazzes != null) {
+ for (final Class> clazz : clazzes) {
+ if (ClassInfo.isModel(clazz)) {
+ final ClassInfo info = ClassInfo.getClassInfo(clazz);
+ final DBCollection collection = persistenceManager.getDatabase().getCollection(info.tableName);
+ for (final Field field : info.updateFields) {
+
+ final Index index = field.getAnnotation(Index.class);
+ if (index != null) {
+ final String[] names = index.value();
+ final DBObject newIndex = new BasicDBObject();
+ for (final String name : names) {
+ if (newIndex.containsField(name) == false) {
+ newIndex.put(name, 1);
+ }
+ }
+ collection.ensureIndex(newIndex);
+ }
+
+ final Unique unique = field.getAnnotation(Unique.class);
+ if (unique != null) {
+ final String[] names = unique.value();
+ final DBObject newIndex = new BasicDBObject();
+ for (final String name : names) {
+ if (newIndex.containsField(name) == false) {
+ newIndex.put(name, 1);
+ }
+ }
+ final DBObject uniqueIndex = new BasicDBObject();
+ uniqueIndex.put("unique", true);
+ collection.ensureIndex(newIndex, uniqueIndex);
+ }
+
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the persistence manager being used by this generator.
+ *
+ * @return the persistenceManager
+ */
+ protected MongoDBPersistenceManager getPersistenceManager() {
+ return persistenceManager;
+ }
+
+}
diff --git a/source/src/main/java/siena/mongodb/MongoDBPersistenceManager.java b/source/src/main/java/siena/mongodb/MongoDBPersistenceManager.java
new file mode 100644
index 0000000..4928603
--- /dev/null
+++ b/source/src/main/java/siena/mongodb/MongoDBPersistenceManager.java
@@ -0,0 +1,899 @@
+package siena.mongodb;
+
+import java.lang.reflect.Field;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.bson.types.ObjectId;
+
+import siena.AbstractPersistenceManager;
+import siena.ClassInfo;
+import siena.Json;
+import siena.Query;
+import siena.QueryFilter;
+import siena.QueryFilterSimple;
+import siena.QueryOrder;
+import siena.SienaException;
+import siena.Util;
+import siena.core.async.PersistenceManagerAsync;
+import siena.embed.Embedded;
+import siena.embed.JsonSerializer;
+import siena.gae.Unindexed;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DB;
+import com.mongodb.DBCollection;
+import com.mongodb.DBCursor;
+import com.mongodb.DBObject;
+import com.mongodb.Mongo;
+import com.mongodb.MongoOptions;
+import com.mongodb.ServerAddress;
+
+/**
+ * Persistence Manager for working with a Mongo DB. Each Siena model will become it's own collection.
+ *
+ * The following properties can be configured on Mongo database. A database name and at least one host name are required.
+ *
+ * - siena.mongodb.hostnames - a comma-delimited list of host names (or IP addresses) with or without port numbers (i.e. 127.0.0.1:27000,192.168.0.1)
+ * - siena.mongodb.databaseName - name of the database (will be created automatically if it doesn't already exist)
+ * - siena.mongodb.userName - required if auth is enabled
+ * - siena.mongodb.password - required if auth is enabled
+ * - siena.mongodb.autoConnectRetry
+ * - siena.mongodb.connectionsPerHost
+ * - siena.mongodb.connectTimeout
+ * - siena.mongodb.maxWaitTime
+ * - siena.mongodb.socketTimeout
+ * - siena.mongodb.threadsAllowedToBlockForConnectionMultiplier
+ *
+ *
+ * The Query operators supported are:
+ *
+ * - =
+ * - !=
+ * - <
+ * - >
+ * - <=
+ * - >=
+ * - IN
+ * - NOT IN
+ * - MOD
+ * - ALL
+ * - EXISTS
+ * - TYPE
+ * - SIZE
+ * - OR
+ * - NOR
+ *
+ *
+ * @author pjarrell
+ * @author (modified by) T.hagikura
+ */
+public class MongoDBPersistenceManager extends AbstractPersistenceManager {
+
+ private static final String ID_FIELD = "_id";
+ private static final String PREFIX = "siena.mongodb.";
+ private static final Log LOGGER = LogFactory.getLog(MongoDBPersistenceManager.class);
+ private static final Map operators = new HashMap() {
+ private static final long serialVersionUID = 1L;
+ {
+ put("=", null);
+ put("!=", "$ne");
+ put("<", "$lt");
+ put(">", "$gt");
+ put("<=", "$lte");
+ put(">=", "$gte");
+ put(" IN", "$in");
+ put(" NOT IN", "$nin");
+ put(" MOD", "$mod");
+ put(" ALL", "$all");
+ put(" EXISTS", "$exists");
+ put(" TYPE", "$type");
+ put(" SIZE", "$size");
+ put(" OR", "$or");
+ put(" NOR", "$nor");
+ }
+ };
+ private static String[] supportedOperators;
+
+ static {
+ supportedOperators = operators.keySet().toArray(new String[0]);
+ }
+
+ private Mongo mongo;
+ private String databaseName;
+ private String userName;
+ private String password;
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#beginTransaction(int)
+ */
+ @Override
+ public void beginTransaction(final int isolationLevel) {
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#closeConnection()
+ */
+ @Override
+ public void closeConnection() {
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#commitTransaction()
+ */
+ @Override
+ public void commitTransaction() {
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#count(siena.Query)
+ */
+ @Override
+ public int count(final Query query) {
+ return prepare(query, true).count();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#delete(java.lang.Object)
+ */
+ @Override
+ public void delete(final Object obj) {
+ final Class> clazz = obj.getClass();
+
+ ClassInfo info = ClassInfo.getClassInfo(clazz);
+ Field idField = info.getIdField();
+ Object idVal = Util.readField(obj, idField);
+ if (idVal == null) {
+ throw new SienaException("Object does not exist : " + obj);
+ }
+
+ final DBCollection collection = getDatabase().getCollection(ClassInfo.getClassInfo(clazz).tableName);
+ final Object id = getId(obj);
+ final DBObject query = new BasicDBObject(ID_FIELD, new ObjectId(id.toString()));
+ // LOGGER.info("Query to delete (by object): " + query);
+ collection.remove(query);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#delete(siena.Query)
+ */
+ @Override
+ public int delete(final Query query) {
+ int count = 0;
+ final Class> clazz = query.getQueriedClass();
+ final DBCollection collection = getDatabase().getCollection(ClassInfo.getClassInfo(clazz).tableName);
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace("Query to delete: " + query);
+ }
+ final DBCursor cursor = prepare(query, true);
+ while (cursor.hasNext()) {
+ final DBObject entity = cursor.next();
+ collection.remove(entity);
+ count += 1;
+ }
+ return count;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#fetch(siena.Query)
+ */
+ @Override
+ public List fetch(final Query query) {
+ return map(query, 0, prepare(query, false));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#fetch(siena.Query, int)
+ */
+ @Override
+ public List fetch(final Query query, final int limit) {
+ return map(query, 0, prepare(query, false).limit(limit));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#fetch(siena.Query, int, java.lang.Object)
+ */
+ @Override
+ public List fetch(final Query query, final int limit, final Object offset) {
+ return map(query, 0, prepare(query, false).skip((Integer) offset).limit(limit));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#fetchKeys(siena.Query)
+ */
+ @Override
+ public List fetchKeys(final Query query) {
+ return map(query, 0, prepare(query, true));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#fetchKeys(siena.Query, int)
+ */
+ @Override
+ public List fetchKeys(final Query query, final int limit) {
+ return map(query, 0, prepare(query, true).limit(limit));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#fetchKeys(siena.Query, int, java.lang.Object)
+ */
+ @Override
+ public List fetchKeys(final Query query, final int limit, final Object offset) {
+ return map(query, 0, prepare(query, true).skip((Integer) offset).limit(limit));
+ }
+
+ /**
+ * Fill in the properties in the entity using the model object.
+ *
+ * @param obj
+ * is the model to use as the source
+ * @param entity
+ * is the entity to store as the target
+ */
+ protected void fillEntity(final Object obj, final DBObject entity) {
+ final Class> clazz = obj.getClass();
+
+ for (final Field field : ClassInfo.getClassInfo(clazz).updateFields) {
+ final String property = ClassInfo.getColumnNames(field)[0];
+ Object value = readField(obj, field);
+ final Class> fieldClass = field.getType();
+ if (ClassInfo.isModel(fieldClass)) {
+ if (value == null) {
+ entity.put(property, null);
+ }
+ else {
+ final Object id = getId(value);
+ entity.put(property, id);
+ }
+ }
+ else {
+ if (value != null) {
+ if (field.getType() == Json.class) {
+ value = value.toString();
+ }
+ else if (field.getAnnotation(Embedded.class) != null) {
+ value = JsonSerializer.serialize(value).toString();
+ }
+ }
+ final Unindexed ui = field.getAnnotation(Unindexed.class);
+ if (ui == null) {
+ entity.put(property, value);
+ }
+ else {
+ entity.put(property, value);
+ }
+ }
+ }
+ }
+
+ /**
+ * Fill in the properties of the model from stored entity.
+ *
+ * @param obj
+ * is model to fill in as the target
+ * @param entity
+ * is the entity used to fill the model as the source
+ */
+ protected void fillModel(final Object obj, final DBObject entity) {
+ final Class> clazz = obj.getClass();
+
+ for (final Field field : ClassInfo.getClassInfo(clazz).updateFields) {
+ field.setAccessible(true);
+ final String property = ClassInfo.getColumnNames(field)[0];
+ try {
+ final Class> fieldClass = field.getType();
+ if (ClassInfo.isModel(fieldClass)) {
+ final Object objectId = entity.get(property);
+ if (objectId != null) {
+ final Object value = fieldClass.newInstance();
+ final Field id = ClassInfo.getIdField(fieldClass);
+ setId(id, value, objectId);
+ field.set(obj, value);
+ }
+ }
+ else {
+ setFromObject(obj, field, entity.get(property));
+ }
+ }
+ catch (final Exception e) {
+ throw new SienaException(e);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#get(java.lang.Object)
+ */
+ @Override
+ public void get(final Object obj) {
+ final Class> clazz = obj.getClass();
+ final DBCollection collection = getDatabase().getCollection(ClassInfo.getClassInfo(clazz).tableName);
+ Object id = getId(obj);
+ try {
+ id = new ObjectId(id.toString());
+ final DBObject entity = collection.findOne(id);
+ fillModel(obj, entity);
+ }
+ catch (final Exception e) {
+ throw new SienaException(e);
+ }
+ }
+
+ /**
+ * Gets the database specified. MongoDB will create the database if it doesn't already exist.
+ *
+ * @return
+ */
+ protected DB getDatabase() {
+ if (mongo != null) {
+ final DB db = mongo.getDB(databaseName);
+ if (db != null && db.isAuthenticated() == false && userName != null && password != null) {
+ if (db.authenticate(userName.trim(), password.trim().toCharArray()) == false) {
+ throw new RuntimeException("Authentication failed using the configured credentials");
+ }
+ }
+ return db;
+ }
+ else {
+ throw new RuntimeException("MongoDB not initialized");
+ }
+ }
+
+ /**
+ * Get the ID of the entity.
+ *
+ * @param entity
+ * is the stored entity with the ID
+ * @return
+ */
+ protected Object getId(final DBObject entity) {
+ return entity.get(ID_FIELD);
+ }
+
+ /**
+ * Get the ID of the model.
+ *
+ * @param obj
+ * is the the model with the ID
+ * @return
+ */
+ protected Object getId(final Object obj) {
+ try {
+ final Field f = ClassInfo.getIdField(obj.getClass());
+ return f.get(obj);
+ }
+ catch (final Exception e) {
+ throw new SienaException(e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#init(java.util.Properties)
+ */
+ @Override
+ public void init(final Properties p) {
+ final String hostNames = p.getProperty(PREFIX + "hostnames");
+ final List addresses = new LinkedList();
+ if (hostNames != null && hostNames.length() > 0) {
+ final String[] hosts = hostNames.trim().split(",");
+ for (final String host : hosts) {
+ ServerAddress address = null;
+ if (host.contains(":")) {
+ final String[] parts = host.trim().split(":");
+ if (parts.length == 2) {
+ try {
+ address = new ServerAddress(parts[0], Integer.valueOf(parts[1]));
+ }
+ catch (final Exception e) {
+ throw new RuntimeException("Port number is not a valid number", e);
+ }
+ }
+ else {
+ throw new RuntimeException("Invalid host format. The format should be \"server:port\"");
+ }
+ }
+ else {
+ try {
+ address = new ServerAddress(host);
+ }
+ catch (final UnknownHostException e) {
+ throw new RuntimeException("Unknown host name specified", e);
+ }
+ }
+ addresses.add(address);
+ }
+ }
+
+ final MongoOptions options = new MongoOptions();
+ final String autoConnectRetry = p.getProperty(PREFIX + "autoConnectRetry");
+ if (autoConnectRetry != null && autoConnectRetry.length() > 0) {
+ options.autoConnectRetry = Boolean.valueOf(autoConnectRetry.trim());
+ }
+ final String connectionsPerHost = p.getProperty(PREFIX + "connectionsPerHost");
+ if (connectionsPerHost != null && connectionsPerHost.length() > 0) {
+ options.connectionsPerHost = Integer.valueOf(connectionsPerHost.trim());
+ }
+ final String connectTimeout = p.getProperty(PREFIX + "connectTimeout");
+ if (connectTimeout != null && connectTimeout.length() > 0) {
+ options.connectTimeout = Integer.valueOf(connectTimeout.trim());
+ }
+ final String maxWaitTime = p.getProperty(PREFIX + "maxWaitTime");
+ if (maxWaitTime != null && maxWaitTime.length() > 0) {
+ options.maxWaitTime = Integer.valueOf(maxWaitTime.trim());
+ }
+ final String socketTimeout = p.getProperty(PREFIX + "socketTimeout");
+ if (socketTimeout != null && socketTimeout.length() > 0) {
+ options.socketTimeout = Integer.valueOf(socketTimeout.trim());
+ }
+ final String threadsAllowedToBlockForConnectionMultiplier = p.getProperty(PREFIX + "threadsAllowedToBlockForConnectionMultiplier");
+ if (threadsAllowedToBlockForConnectionMultiplier != null && threadsAllowedToBlockForConnectionMultiplier.length() > 0) {
+ options.threadsAllowedToBlockForConnectionMultiplier = Integer.valueOf(threadsAllowedToBlockForConnectionMultiplier.trim());
+ }
+ if (p.containsKey(PREFIX + "databaseName")) {
+ this.databaseName = p.getProperty(PREFIX + "databaseName");
+ }
+ if (p.containsKey(PREFIX + "userName")) {
+ this.userName = p.getProperty(PREFIX + "userName");
+ }
+ if (p.containsKey(PREFIX + "password")) {
+ this.password = p.getProperty(PREFIX + "password");
+ }
+
+ if (this.databaseName == null || this.databaseName.length() == 0) {
+ throw new RuntimeException("Database name is required");
+ }
+ if (addresses.size() == 0) {
+ throw new RuntimeException("At least one valid server address is required");
+ }
+
+ // Now create the MongoDB
+ this.mongo = new Mongo(addresses, options);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#insert(java.lang.Object)
+ */
+ @Override
+ public void insert(final Object obj) {
+ final Class> clazz = obj.getClass();
+ final DBCollection collection = getDatabase().getCollection(ClassInfo.getClassInfo(clazz).tableName);
+ final DBObject entity = new BasicDBObject();
+
+ ClassInfo info = ClassInfo.getClassInfo(clazz);
+ Field idField = info.getIdField();
+ Object idVal = Util.readField(obj, idField);
+ if (idVal != null) {
+ throw new SienaException("Same key object already exists : " + obj);
+ }
+
+ fillEntity(obj, entity);
+ collection.insert(entity);
+ setId(ClassInfo.getIdField(clazz), obj, getId(entity));
+ }
+
+ /**
+ * Handles a query including setting the next offset on a query.
+ *
+ * @param
+ * @param query
+ * is the query to execute
+ * @param offset
+ * is the offset requested
+ * @param cursor
+ * is the MongoDB cursor
+ * @return
+ */
+ @SuppressWarnings("unchecked")
+ private List map(final Query query, final int offset, final DBCursor cursor) {
+ final Class> clazz = query.getQueriedClass();
+ final List result = (List) mapEntities(cursor, clazz);
+ query.setNextOffset(offset + result.size());
+ return result;
+ }
+
+ /**
+ * Convert the stored entities into models.
+ *
+ * @param
+ * @param cursor
+ * is the MongoDB cursor that resulted from an executed query
+ * @param clazz
+ * is the model class to convert to
+ * @return a list of model objects
+ */
+ protected List mapEntities(final DBCursor cursor, final Class clazz) {
+ final List list = new LinkedList();
+ final Field id = ClassInfo.getIdField(clazz);
+ while (cursor.hasNext()) {
+ final DBObject entity = cursor.next();
+ T obj;
+ try {
+ obj = clazz.newInstance();
+ fillModel(obj, entity);
+ list.add(obj);
+ setId(id, obj, getId(entity));
+ }
+ catch (final SienaException e) {
+ throw e;
+ }
+ catch (final Exception e) {
+ throw new SienaException(e);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Create a MongoDB query using the Siena query.
+ *
+ * @param
+ * @param query
+ * is the Siena query
+ * @param keysOnly
+ * determines whether or not just keys should be returned (lighter weight query)
+ * @return a MongoDB cursor
+ */
+ protected DBCursor prepare(final Query query, final boolean keysOnly) {
+ final Class> clazz = query.getQueriedClass();
+ final DBCollection collection = getDatabase().getCollection(ClassInfo.getClassInfo(clazz).tableName);
+
+ final BasicDBObject queryObject = new BasicDBObject();
+ final List filters = query.getFilters();
+ for (final QueryFilter filter : filters) {
+ QueryFilterSimple simpleFilter = (QueryFilterSimple) filter;
+ //final Field f = filter.field;
+ final Field f = simpleFilter.field;
+ final String propertyName = ClassInfo.getColumnNames(f)[0];
+ //Object value = filter.value;
+ Object value = simpleFilter.value;
+ //final String op = operators.get(filter.operator);
+ final String op = operators.get(simpleFilter.operator);
+
+ if (value != null && ClassInfo.isModel(value.getClass())) {
+ Object id = getId(value);
+ if (op != null) {
+ id = new BasicDBObject(op, id);
+ }
+ queryObject.put(propertyName, id);
+ }
+ else {
+ if (ClassInfo.isId(f)) {
+ value = new ObjectId(value.toString());
+ if (op != null) {
+ value = new BasicDBObject(op, value);
+ }
+ queryObject.put(ID_FIELD, value);
+ }
+ else {
+ if (op != null) {
+ value = new BasicDBObject(op, value);
+ }
+ queryObject.put(propertyName, value);
+ }
+ }
+ }
+
+ final BasicDBObject sort = new BasicDBObject();
+ final List orders = query.getOrders();
+ for (final QueryOrder order : orders) {
+ final Field f = order.field;
+ if (ClassInfo.isId(f)) {
+ sort.put(ID_FIELD, 1);
+ }
+ else {
+ sort.put(ClassInfo.getColumnNames(f)[0], order.ascending ? 1 : -1);
+ }
+ }
+
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace("Query using: " + queryObject);
+ }
+
+ DBCursor cursor = null;
+ if (keysOnly) {
+ cursor = collection.find(queryObject, new BasicDBObject(ID_FIELD, 1));
+ }
+ else {
+ cursor = collection.find(queryObject);
+ }
+ if (sort.isEmpty() == false) {
+ cursor = cursor.sort(sort);
+ }
+ return cursor;
+ }
+
+ /**
+ * Get the value of a field property from a model object.
+ *
+ * @param object
+ * is the model object
+ * @param field
+ * is the field to read
+ * @return
+ */
+ private Object readField(final Object object, final Field field) {
+ field.setAccessible(true);
+ try {
+ return field.get(object);
+ }
+ catch (final Exception e) {
+ throw new SienaException(e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#rollbackTransaction()
+ */
+ @Override
+ public void rollbackTransaction() {
+ }
+
+ /**
+ * Set the field value on a model object.
+ *
+ * @param object
+ * is the model instance
+ * @param f
+ * is the field on the model
+ * @param value
+ * is the value to set on the model
+ * @throws IllegalArgumentException
+ * @throws IllegalAccessException
+ */
+ private void setFromObject(final Object object, final Field f, final Object value) throws IllegalArgumentException, IllegalAccessException {
+ Util.setFromObject(object, f, value);
+ }
+
+ /**
+ * Set the ID from an entity onto the appropriate ID field on the model.
+ *
+ * @param f
+ * is the field on the model
+ * @param obj
+ * is the model instance
+ * @param id
+ * is the id to set
+ */
+ private void setId(final Field f, final Object obj, final Object id) {
+ try {
+ Object value = id;
+ if (f.getType() == String.class) {
+ value = value.toString();
+ }
+ f.setAccessible(true);
+ f.set(obj, value);
+ }
+ catch (final Exception e) {
+ throw new SienaException(e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#supportedOperators()
+ */
+ @Override
+ public String[] supportedOperators() {
+ return supportedOperators;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#update(java.lang.Object)
+ */
+ @Override
+ public void update(final Object obj) {
+ try {
+ final Class> clazz = obj.getClass();
+ final DBCollection collection = getDatabase().getCollection(ClassInfo.getClassInfo(clazz).tableName);
+ final DBObject entity = new BasicDBObject();
+ fillEntity(obj, entity);
+ final BasicDBObject queryObject = new BasicDBObject();
+ queryObject.put(ID_FIELD, new ObjectId(getId(obj).toString()));
+ collection.update(queryObject, entity);
+ }
+ catch (final SienaException e) {
+ throw e;
+ }
+ catch (final Exception e) {
+ throw new SienaException(e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see siena.PersistenceManager#save(java.lang.Object)
+ */
+ @Override
+ public void save(Object obj) {
+ Class clazz = obj.getClass();
+ ClassInfo info = ClassInfo.getClassInfo(clazz);
+ Field idField = info.getIdField();
+ Object idVal = Util.readField(obj, idField);
+ if (idVal == null) {
+ insert(obj);
+ } else {
+ update(obj);
+ }
+ }
+
+ @Override
+ public int save(Object... paramArrayOfObject) {
+ return save(Arrays.asList(paramArrayOfObject));
+ }
+
+ @Override
+ public int save(Iterable> paramIterable) {
+ int result = 0;
+ for (Iterator> it=paramIterable.iterator(); it.hasNext();) {
+ try {
+ save(it.next());
+ result++;
+ } catch (Exception e) {
+ LOGGER.error(e);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public int insert(Object... paramArrayOfObject) {
+ return insert(Arrays.asList(paramArrayOfObject));
+ }
+
+ @Override
+ public int insert(Iterable> paramIterable) {
+ int result = 0;
+ for (Iterator> it=paramIterable.iterator(); it.hasNext();) {
+ try {
+ insert(it.next());
+ result++;
+ } catch (Exception e) {
+ LOGGER.warn(e);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public int delete(Object... paramArrayOfObject) {
+ return delete(Arrays.asList(paramArrayOfObject));
+ }
+
+ @Override
+ public int delete(Iterable> paramIterable) {
+ int result = 0;
+ for (Iterator> it=paramIterable.iterator(); it.hasNext();) {
+ try {
+ delete(it.next());
+ result++;
+ } catch (Exception e) {
+ LOGGER.warn(e);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public int deleteByKeys(Class paramClass, Object... paramArrayOfObject) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int deleteByKeys(Class paramClass, Iterable> paramIterable) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int get(Object... paramArrayOfObject) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int get(Iterable paramIterable) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public T getByKey(Class paramClass, Object paramObject) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List getByKeys(Class paramClass, Object... paramArrayOfObject) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List getByKeys(Class paramClass, Iterable> paramIterable) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int update(Object... paramArrayOfObject) {
+ return update(Arrays.asList(paramArrayOfObject));
+ }
+
+ @Override
+ public int update(Iterable paramIterable) {
+ int result = 0;
+ for (Iterator> it=paramIterable.iterator(); it.hasNext();) {
+ try {
+ update(it.next());
+ result++;
+ } catch (Exception e) {
+ LOGGER.warn(e);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public void beginTransaction() {
+ //do nothing
+ }
+
+ @Override
+ public int update(Query paramQuery, Map paramMap) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Iterable iter(Query paramQuery) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Iterable iter(Query paramQuery, int paramInt) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Iterable iter(Query paramQuery, int paramInt, Object paramObject) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void paginate(Query paramQuery) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void nextPage(Query paramQuery) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void previousPage(Query paramQuery) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public PersistenceManagerAsync async() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/source/src/test/java/siena/mongodb/MongoDBPersistenceManagerTest.java b/source/src/test/java/siena/mongodb/MongoDBPersistenceManagerTest.java
new file mode 100644
index 0000000..3665b73
--- /dev/null
+++ b/source/src/test/java/siena/mongodb/MongoDBPersistenceManagerTest.java
@@ -0,0 +1,347 @@
+package siena.mongodb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Properties;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import siena.PersistenceManager;
+import siena.PersistenceManagerFactory;
+import siena.mongodb.model.SienaMongoDbTestModel;
+
+/**
+ * Test class for MongoDBPersistenceManager
+ *
+ * Before you run test cases, be sure run the mongod process in your localhost(127.0.0.1)
+ * and db name "test" doesn't have any important data.
+ *
+ * @author T.hagikura
+ *
+ */
+public class MongoDBPersistenceManagerTest {
+
+ private static PersistenceManager pm;
+
+ public static PersistenceManager createPersistenceManager(final List> classes) throws Exception {
+ final Properties p = new Properties();
+ p.setProperty("siena.mongodb.hostnames", "127.0.0.1");
+ p.setProperty("siena.mongodb.databaseName", "test");
+
+ final MongoDBPersistenceManager pm = new MongoDBPersistenceManager();
+ pm.init(p);
+
+ return pm;
+ }
+
+ @Test
+ public void testInsertAndDelete() {
+ SienaMongoDbTestModel model = createTestInstance();
+ pm.insert(model);
+ List models = SienaMongoDbTestModel.all().fetch();
+
+ assertEquals(1, models.size());
+ SienaMongoDbTestModel registered = models.get(0);
+ assertTrue(model.equals(registered));
+
+ registered.delete();
+ models = SienaMongoDbTestModel.all().fetch();
+
+ assertEquals(0, models.size());
+ }
+
+ @Test
+ public void testSaveAndDelete() {
+ SienaMongoDbTestModel model = createTestInstance();
+ pm.save(model);
+ List models = SienaMongoDbTestModel.all().fetch();
+ assertEquals(1, models.size());
+
+ SienaMongoDbTestModel registered = models.get(0);
+ assertTrue(model.equals(registered));
+
+ registered.stringField = "updatedBySave";
+ pm.save(registered); // will be updated because same _id object is already inserted.
+
+ models = SienaMongoDbTestModel.all().fetch();
+ assertEquals(1, models.size());
+
+ SienaMongoDbTestModel afterSave = models.get(0);
+ assertTrue(registered.equals(afterSave));
+
+ afterSave.delete();
+ models = SienaMongoDbTestModel.all().fetch();
+
+ assertEquals(0, models.size());
+ }
+
+ @Test
+ public void testUpdate() {
+ SienaMongoDbTestModel model = createTestInstance();
+ pm.insert(model);
+ List models = SienaMongoDbTestModel.all().fetch();
+
+ assertEquals(1, models.size());
+ SienaMongoDbTestModel registered = models.get(0);
+ assertTrue(model.equals(registered));
+
+ Integer changedInt = model.integerField + 10;
+ String changedStr = "changed";
+ registered.integerField = changedInt;
+ registered.stringField = changedStr;
+
+ registered.update();
+
+ models = SienaMongoDbTestModel.all().fetch();
+ assertEquals(1, models.size());
+ SienaMongoDbTestModel updatedModel = models.get(0);
+ assertTrue(registered.equals(updatedModel));
+
+ updatedModel.delete();
+ models = SienaMongoDbTestModel.all().fetch();
+ assertEquals(0, models.size());
+ }
+
+ @Test
+ public void testInsertIterable() {
+ Collection modelsToInsert = new ArrayList();
+ SienaMongoDbTestModel model = createTestInstance();
+ SienaMongoDbTestModel model2 = createTestInstance();
+ modelsToInsert.add(model);
+ modelsToInsert.add(model2);
+ int insertCount = pm.insert(modelsToInsert);
+
+ assertEquals(2, insertCount);
+ List models = SienaMongoDbTestModel.all().fetch();
+ assertEquals(2, models.size());
+
+ int sameModelInsertCount = pm.insert(modelsToInsert); // can not be inserted because same _id objects exists
+ assertEquals(0, sameModelInsertCount);
+ models = SienaMongoDbTestModel.all().fetch();
+ assertEquals(2, models.size());
+
+ deleteExistingModel();
+ }
+
+ @Test
+ public void testDeleteIterable() {
+ Collection modelsToInsert = new ArrayList();
+ SienaMongoDbTestModel model = createTestInstance();
+ SienaMongoDbTestModel model2 = createTestInstance();
+ modelsToInsert.add(model);
+ modelsToInsert.add(model2);
+ int insertCount = pm.insert(modelsToInsert);
+ assertEquals(2, insertCount);
+ List models = SienaMongoDbTestModel.all().fetch();
+ assertEquals(2, models.size());
+
+ int deleteCount = pm.delete(modelsToInsert);
+ assertEquals(2, deleteCount);
+ models = SienaMongoDbTestModel.all().fetch();
+ assertEquals(0, models.size());
+
+ deleteExistingModel();
+ }
+
+ @Test
+ public void testUpdateIterable() {
+ Collection modelsToInsert = new ArrayList();
+ SienaMongoDbTestModel model = createTestInstance();
+ SienaMongoDbTestModel model2 = createTestInstance();
+ modelsToInsert.add(model);
+ modelsToInsert.add(model2);
+ int insertCount = pm.insert(modelsToInsert);
+ assertEquals(2, insertCount);
+ List models = SienaMongoDbTestModel.all().fetch();
+ assertEquals(2, models.size());
+
+ model.stringField = "updateField1";
+ model2.stringField = "updateField2";
+ int updateCount = pm.update(modelsToInsert);
+ assertEquals(2, updateCount);
+ models = SienaMongoDbTestModel.all().order("stringField").fetch();
+ assertEquals(2, models.size());
+ SienaMongoDbTestModel registered1 = models.get(0);
+ SienaMongoDbTestModel registered2 = models.get(1);
+ assertTrue(model.equals(registered1));
+ assertTrue(model2.equals(registered2));
+
+ deleteExistingModel();
+ }
+
+ @Test
+ public void testStringFilter() {
+ SienaMongoDbTestModel model = createTestInstance();
+ pm.insert(model);
+ List models = SienaMongoDbTestModel.all().filter("stringField", "dummyString").fetch();
+ assertEquals(0, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("stringField", TEST_STRING).fetch();
+ assertEquals(1, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("stringField !=", TEST_STRING).fetch();
+ assertEquals(0, models.size());
+
+ deleteExistingModel();
+ }
+
+ @Test
+ public void testIntegerFilter() {
+ SienaMongoDbTestModel model = createTestInstance();
+ pm.insert(model);
+ List models = SienaMongoDbTestModel.all().filter("integerField", 0).fetch();
+ assertEquals(0, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("integerField", TEST_INTEGER).fetch();
+ assertEquals(1, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("integerField !=", TEST_INTEGER).fetch();
+ assertEquals(0, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("integerField >", TEST_INTEGER).fetch();
+ assertEquals(0, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("integerField <", TEST_INTEGER).fetch();
+ assertEquals(0, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("integerField >", TEST_INTEGER - 1).fetch();
+ assertEquals(1, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("integerField <", TEST_INTEGER + 1).fetch();
+ assertEquals(1, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("integerField >=", TEST_INTEGER).fetch();
+ assertEquals(1, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("integerField <=", TEST_INTEGER).fetch();
+ assertEquals(1, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("integerField >=", TEST_INTEGER + 1).fetch();
+ assertEquals(0, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("integerField <=", TEST_INTEGER - 1).fetch();
+ assertEquals(0, models.size());
+
+ deleteExistingModel();
+ }
+
+ @Test
+ public void testDateFilter() {
+ SienaMongoDbTestModel model = createTestInstance();
+ pm.insert(model);
+ List models = SienaMongoDbTestModel.all().filter("dateField", new Date()).fetch();
+ assertEquals(0, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("dateField", TEST_DATE).fetch();
+ assertEquals(1, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("dateField !=", TEST_DATE).fetch();
+ assertEquals(0, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("dateField >", TEST_DATE).fetch();
+ assertEquals(0, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("dateField <", TEST_DATE).fetch();
+ assertEquals(0, models.size());
+
+ Calendar nextDateCal = new GregorianCalendar();
+ nextDateCal.setTime(TEST_DATE);
+ nextDateCal.add(Calendar.DATE, 1);
+ Date nextDate = nextDateCal.getTime();
+
+ models = SienaMongoDbTestModel.all().filter("dateField <", nextDate).fetch();
+ assertEquals(1, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("dateField >=", TEST_DATE).fetch();
+ assertEquals(1, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("dateField <=", TEST_DATE).fetch();
+ assertEquals(1, models.size());
+
+ models = SienaMongoDbTestModel.all().filter("dateField >=", nextDate).fetch();
+ assertEquals(0, models.size());
+
+ deleteExistingModel();
+ }
+
+ @Test
+ public void testOrder() {
+ Collection modelsToInsert = new ArrayList();
+ SienaMongoDbTestModel model = createTestInstance();
+ SienaMongoDbTestModel model2 = createTestInstance();
+ model.stringField = "updateField1";
+ model2.stringField = "updateField2";
+ modelsToInsert.add(model);
+ modelsToInsert.add(model2);
+ int insertCount = pm.insert(modelsToInsert);
+ assertEquals(2, insertCount);
+ List models = SienaMongoDbTestModel.all().fetch();
+ assertEquals(2, models.size());
+
+ // get models in ascending order by stringField
+ models = SienaMongoDbTestModel.all().order("stringField").fetch();
+ assertEquals(2, models.size());
+ SienaMongoDbTestModel registered1 = models.get(0);
+ SienaMongoDbTestModel registered2 = models.get(1);
+ assertTrue(model.equals(registered1));
+ assertTrue(model2.equals(registered2));
+
+ // get models in descending order by stringField
+ models = SienaMongoDbTestModel.all().order("-stringField").fetch();
+ assertEquals(2, models.size());
+ registered1 = models.get(0);
+ registered2 = models.get(1);
+ assertTrue(model2.equals(registered1));
+ assertTrue(model.equals(registered2));
+
+ deleteExistingModel();
+ }
+
+ @BeforeClass
+ public static void setUpOnce() throws Exception {
+ pm = installPersistenceManager();
+ SienaMongoDbTestModel.deleteAll();
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ SienaMongoDbTestModel.deleteAll();
+ }
+
+ private static final Date TEST_DATE = new Date();
+ private static final String TEST_STRING = "testStr";
+ private static final Integer TEST_INTEGER = Integer.valueOf(10);
+ private static final Long TEST_LONG = Long.valueOf(20L);
+ private static final Double TEST_DOUBLE = Double.valueOf(20.02);
+
+ private static void deleteExistingModel() {
+ SienaMongoDbTestModel.deleteAll();
+ List models = SienaMongoDbTestModel.all().fetch();
+ assertEquals(0, models.size());
+ }
+
+ private static SienaMongoDbTestModel createTestInstance() {
+ SienaMongoDbTestModel model = new SienaMongoDbTestModel();
+ model.dateField = TEST_DATE;
+ model.doubleField = TEST_DOUBLE;
+ model.stringField = TEST_STRING;
+ model.integerField = TEST_INTEGER;
+ model.longField = TEST_LONG;
+ return model;
+ }
+
+ private static PersistenceManager installPersistenceManager() throws Exception {
+ PersistenceManager pm = createPersistenceManager(null);
+ PersistenceManagerFactory.install(pm, SienaMongoDbTestModel.class);
+ return pm;
+ }
+}
diff --git a/source/src/test/java/siena/mongodb/model/SienaMongoDbTestModel.java b/source/src/test/java/siena/mongodb/model/SienaMongoDbTestModel.java
new file mode 100644
index 0000000..d9db026
--- /dev/null
+++ b/source/src/test/java/siena/mongodb/model/SienaMongoDbTestModel.java
@@ -0,0 +1,91 @@
+package siena.mongodb.model;
+
+import java.util.Date;
+import java.util.List;
+
+import siena.Id;
+import siena.Model;
+import siena.Query;
+
+/**
+ * Only test Model class for MongoDBPersistenceManagerTest
+ *
+ * @author T.hagikura
+ *
+ */
+public final class SienaMongoDbTestModel extends Model {
+ @Id // in Siena-MongoDb, @Id annotation column must be Object not Long, since generated _id from java-mongo-driver contains alphabetical characters.
+ public Object id;
+ public String stringField;
+ public Integer integerField;
+ public Long longField;
+ public Double doubleField;
+ public Date dateField;
+
+ public static Query all() {
+ return Model.all(SienaMongoDbTestModel.class);
+ }
+
+ public static void deleteAll() {
+ List models = all().fetch();
+ for (SienaMongoDbTestModel model : models) {
+ model.delete();
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((dateField == null) ? 0 : dateField.hashCode());
+ result = prime * result + ((doubleField == null) ? 0 : doubleField.hashCode());
+ result = prime * result + ((integerField == null) ? 0 : integerField.hashCode());
+ result = prime * result + ((longField == null) ? 0 : longField.hashCode());
+ result = prime * result + ((stringField == null) ? 0 : stringField.hashCode());
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "SienaMongoDbTestModel [id=" + id + ", stringField=" + stringField + ", integerField=" + integerField
+ + ", longField=" + longField + ", doubleField=" + doubleField + ", dateField=" + dateField + "]";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SienaMongoDbTestModel other = (SienaMongoDbTestModel) obj;
+ if (dateField == null) {
+ if (other.dateField != null)
+ return false;
+ } else if (!dateField.equals(other.dateField))
+ return false;
+ if (doubleField == null) {
+ if (other.doubleField != null)
+ return false;
+ } else if (!doubleField.equals(other.doubleField))
+ return false;
+ if (integerField == null) {
+ if (other.integerField != null)
+ return false;
+ } else if (!integerField.equals(other.integerField))
+ return false;
+ if (longField == null) {
+ if (other.longField != null)
+ return false;
+ } else if (!longField.equals(other.longField))
+ return false;
+ if (stringField == null) {
+ if (other.stringField != null)
+ return false;
+ } else if (!stringField.equals(other.stringField))
+ return false;
+ return true;
+ }
+
+}