/** * Copyright 2011 CaneData.org * * 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 org.canedata.provider.mongodb.entity; import java.io.Serializable; import java.lang.reflect.MalformedParameterizedTypeException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Set; import com.mongodb.*; import com.mongodb.util.JSON; import org.canedata.cache.Cache; import org.canedata.cache.Cacheable; import org.canedata.core.field.AbstractWritableField; import org.canedata.core.intent.Step; import org.canedata.core.intent.Tracer; import org.canedata.core.logging.LoggerFactory; import org.canedata.core.intent.Limiter; import org.canedata.core.util.StringUtils; import org.canedata.entity.Batch; import org.canedata.entity.Command; import org.canedata.entity.Entity; import org.canedata.entity.Joint; import org.canedata.exception.AnalyzeBehaviourException; import org.canedata.exception.DataAccessException; import org.canedata.exception.EntityNotFoundException; import org.canedata.expression.Expression; import org.canedata.expression.ExpressionBuilder; import org.canedata.field.Field; import org.canedata.field.Fields; import org.canedata.field.WritableField; import org.canedata.logging.Logger; import org.canedata.provider.mongodb.MongoResource; import org.canedata.provider.mongodb.expr.MongoExpression; import org.canedata.provider.mongodb.expr.MongoExpressionBuilder; import org.canedata.provider.mongodb.expr.MongoExpressionFactory; import org.canedata.provider.mongodb.field.MongoFields; import org.canedata.provider.mongodb.field.MongoWritableField; import org.canedata.provider.mongodb.intent.MongoIntent; import org.canedata.provider.mongodb.intent.MongoStep; import org.canedata.ta.Transaction; import org.canedata.ta.TransactionHolder; /** * @author Sun Yat-ton * @version 1.00.000 2011-7-29 */ public abstract class MongoEntity extends Cacheable.Adapter implements Entity { protected static final Logger logger = LoggerFactory .getLogger(MongoEntity.class); public final static String internalCmds = "\\$(inc|set|unset|push|pushAll|addToSet|pop|pull|pullAll|rename|bit)"; protected boolean hasClosed = false; protected static final String CHARSET = "UTF-8"; protected abstract MongoIntent getIntent(); abstract DBCollection getCollection(); abstract MongoResource getResource(); abstract MongoEntityFactory getFactory(); abstract Cache getCache(); public String getKey() { return getFactory().getName().concat(":").concat(getSchema()) .concat(":").concat(getName()); } public Entity put(String key, Object value) { logger.debug("Put value {0} to {1}.", value, key); getIntent().step(MongoStep.PUT, key, value); return this; } public Entity putAll(Map<String, Object> params) { if (null == params || params.isEmpty()) return this; for (Entry<String, Object> e : params.entrySet()) { put(e.getKey(), e.getValue()); } return this; } public WritableField field(final String field) { if (StringUtils.isBlank(field)) throw new IllegalArgumentException("You must specify a field name."); return new MongoWritableField() { String label = field; public String getLabel() { return label; } public Field label(String label) { this.label = label; return this; } public String label() { return label; } public String typeName() { return value == null ? null : value.getClass().getName(); } public String getName() { return field; } @Override protected AbstractWritableField put(String field, Object value) { getIntent().step(MongoStep.PUT, field, value); return this; } @Override protected MongoEntity getEntity() { return MongoEntity.this; } @SuppressWarnings("unchecked") @Override protected MongoIntent getIntent() { return MongoEntity.this.getIntent(); } }; } /** * This method is only for multi-column unique index, and using Mongo * default primary key value. */ public Fields create(Map<String, Object> keys) { putAll(keys); return create(); } public Fields create(Serializable... keys) { try { validateState(); // generate key Object key = null; if (keys != null && keys.length > 0) { key = keys[0]; } if (logger.isDebug()) logger.debug( "Creating entity, Database is {0}, Collection is {1}, key is {2}.", getSchema(), getName(), key); final BasicDBObject doc = new BasicDBObject(); final BasicDBObject options = new BasicDBObject(); getIntent().playback(new Tracer() { public Tracer trace(Step step) throws AnalyzeBehaviourException { switch (step.step()) { case MongoStep.PUT: if (logger.isDebug()) logger.debug( "Analyzing behivor, step is {0}, purpose is {1}, scalar is {2}.", step.step(), step.getPurpose(), Arrays.toString(step.getScalar())); doc.put(step.getPurpose(), step.getScalar() == null ? null : step .getScalar()[0]); break; case MongoStep.OPTION: options.append(step.getPurpose(), step.getScalar()[0]); break; default: logger.warn( "Step {0} does not apply to activities create, this step will be ignored.", step.step()); } return this; } }); if (key != null) doc.put("_id", key); //process options if(!options.isEmpty()) prepareOptions(options); WriteResult rlt = getCollection().insert(doc, getCollection().getWriteConcern()); if (!StringUtils.isBlank(rlt.getError())) throw new DataAccessException(rlt.getError()); MongoFields fields = new MongoFields(MongoEntity.this, MongoEntity.this.getIntent(), doc); if(logger.isDebug()) logger.debug( "Created entity, Database is {0}, Collection is {1}, key is {2}.", getSchema(), getName(), key); return fields; } catch (AnalyzeBehaviourException e) { if(logger.isDebug()) logger.debug(e, "Analyzing behaviour failure, cause by: {0}.", e.getMessage()); throw new DataAccessException(e); } finally { getIntent().reset(); } } public Fields createOrUpdate(Serializable... keys) { try { validateState(); // generate key Object key = null; if (keys != null && keys.length > 0) { key = keys[0]; } if (logger.isDebug()) logger.debug( "Creating or Updating entity, Database is {0}, Collection is {1}, key is {2}.", getSchema(), getName(), key); final BasicDBObject doc = new BasicDBObject(); final BasicDBObject options = new BasicDBObject(); getIntent().playback(new Tracer() { public Tracer trace(Step step) throws AnalyzeBehaviourException { switch (step.step()) { case MongoStep.PUT: if (logger.isDebug()) logger.debug( "Analyzing behivor, step is {0}, purpose is {1}, scalar is {2}.", step.step(), step.getPurpose(), Arrays.toString(step.getScalar())); doc.put(step.getPurpose(), step.getScalar() == null ? null : step .getScalar()[0]); break; case MongoStep.OPTION: options.append(step.getPurpose(), step.getScalar()[0]); break; default: logger.warn( "Step {0} does not apply to activities create, this step will be ignored.", step.step()); } return this; } }); if (key != null) doc.put("_id", key); if(!options.isEmpty()) prepareOptions(options); WriteResult rlt = getCollection().save(doc, getCollection().getWriteConcern()); if (!StringUtils.isBlank(rlt.getError())) throw new DataAccessException(rlt.getError()); MongoFields fields = new MongoFields(MongoEntity.this, MongoEntity.this.getIntent(), doc); // cache if (null != getCache()) { if(logger.isDebug()) logger.debug("Invalid fields to cache, cache key is {0} ...", fields.getKey()); getCache().remove(key); } if(logger.isDebug()) logger.debug( "Created or Updated entity, Database is {0}, Collection is {1}, key is {2}.", getSchema(), getName(), key); return fields; } catch (AnalyzeBehaviourException e) { if(logger.isDebug()) logger.debug(e, "Analyzing behaviour failure, cause by: {0}.", e.getMessage()); throw new DataAccessException(e); } finally { getIntent().reset(); } } public Fields createOrUpdate(Map<String, Object> keys) { putAll(keys); return createOrUpdate(); } public Entity projection(String... projection) { getIntent().step(MongoStep.PROJECTION, null, (Object[]) projection); return this; } public Entity select(String... projection) { return projection(projection); } public Fields restore(Serializable... keys) { if(logger.isDebug()) logger.debug( "Restoring entity, Database is {0}, collection is {1}, key is {2}", getSchema(), getName(), Arrays.toString(keys)); try { validateState(); if (keys == null || keys.length == 0) throw new IllegalArgumentException( "Keys must be contain one element."); final BasicDBObject projection = new BasicDBObject(); final BasicDBObject options = new BasicDBObject(); getIntent().playback(new Tracer() { public Tracer trace(Step step) throws AnalyzeBehaviourException { switch (step.step()) { case MongoStep.PROJECTION: for (Object field : step.getScalar()) { String f = (String) field; projection.put(f, 1); } break; case MongoStep.OPTION: options.append(step.getPurpose(), step.getScalar()[0]); break; default: logger.warn( "Step {0} does not apply to activities restore, this step will be ignored.", step.step()); } return this; } }); BasicDBObject bdbo = new BasicDBObject(); bdbo.put("_id", keys[0]); // cache if (null != getCache()) { String cacheKey = getKey().concat("#").concat( keys[0].toString()); MongoFields cachedFs = null; if (getCache().isAlive(cacheKey)) { if(logger.isDebug()) logger.debug( "Restoring entity from cache, by cache key is {0} ...", cacheKey); cachedFs = (MongoFields) getCache().restore(cacheKey); } else { if(!options.isEmpty()) prepareOptions(options); BasicDBObject dbo = (BasicDBObject) getCollection() .findOne(bdbo); if (null == dbo) return null; cachedFs = new MongoFields(this, getIntent(), dbo); getCache().cache(cachedFs); if(logger.isDebug()) logger.debug( "Restored entity and put to cache, cache key is {0}.", cachedFs.getKey().toString()); } return cachedFs.clone().project(projection.keySet()); } else {// no cache if(!options.isEmpty()) prepareOptions(options); DBObject dbo = getCollection().findOne(bdbo, projection); if(logger.isDebug()) logger.debug("Restored entity, key is {0}, target is {1}.", keys[0].toString(), dbo); if (null == dbo) return null; return new MongoFields(this, getIntent(), (BasicDBObject) dbo); } } catch (NoSuchElementException nsee) { throw new EntityNotFoundException(this.getKey(), keys[0].toString()); } catch (AnalyzeBehaviourException e) { if(logger.isDebug()) logger.debug(e, "Analyzing behaviour failure, cause by: {0}.", e.getMessage()); throw new RuntimeException(e); } finally { getIntent().reset(); } } public ExpressionBuilder getExpressionBuilder() { return new MongoExpressionBuilder(this); } public ExpressionBuilder expr() { return getExpressionBuilder(); } public ExpressionBuilder filter() { return expr(); } public Entity filter(Expression expr) { if (null == expr) return this; getIntent().step(MongoStep.FILTER, "filter", expr); return this; } public Entity order(String... orderingTerm) { if (null == orderingTerm) return this; getIntent().step(MongoStep.ORDER, null, (Object[]) orderingTerm); return this; } public Entity orderDESC(String... orderingTerm) { if (null == orderingTerm) return this; getIntent().step(MongoStep.ORDER, "desc", (Object[]) orderingTerm); return this; } public Entity limit(int count) { getIntent().step(MongoStep.LIMIT, "limita", count); return this; } public Entity limit(int offset, int count) { getIntent().step(MongoStep.LIMIT, "limitb", offset, count); return this; } /** * can use key and name of columns. first param is key, others is columns. * When you check whether the columns exists, if one of column does not * exist, than return false. */ public boolean exists(Serializable... keys) { if (keys == null || keys.length == 0) throw new IllegalArgumentException("You must specify the key!"); if(logger.isDebug()) logger.debug( "Existing entity, Database is {0}, Collection is {1}, key is {0}", getSchema(), getName(), keys[0]); getIntent().reset(); // cache if (null != getCache()) { String cacheKey = getKey().concat("#").concat(keys[0].toString()); if (getCache().isAlive(cacheKey)) { if(logger.isDebug()) logger.debug( "Restoring entity from cache, by cache key is {0} ...", cacheKey); MongoFields cachedFs = (MongoFields) getCache().restore( cacheKey); if (keys.length < 2) return null != cachedFs; else { Set<String> ks = cachedFs.getTarget().keySet(); return ks.containsAll(Arrays.asList(keys)); } } } validateState(); BasicDBObject query = new BasicDBObject(); query.put("_id", keys[0]); for (int i = 1; i < keys.length; i++) { query.append((String) keys[i], new BasicDBObject().append("$exists", true)); } return getCollection().count(query) > 0; } public Fields first() { List<Fields> rlt = list(0, 1); if (rlt == null || rlt.isEmpty()) return null; return rlt.get(0); } public Fields last() { long c = opt(Options.RETAIN, true).count().longValue(); logger.debug("Lasting entity, total of {0} entities.", c); return list((int) c - 1, 1).get(0); } public List<Fields> list() { return list(-1, -1); } public List<Fields> list(int count) { return list(0, count); } public List<Fields> list(int offset, int count) { if(logger.isDebug()) logger.debug( "Listing entities, Database is {0}, Collection is {1}, offset is {2}, count is {3}.", getSchema(), getName(), offset, count); List<Fields> rlt = new ArrayList<Fields>(); BasicDBObject options = new BasicDBObject(); DBCursor cursor = null; try { validateState(); MongoExpressionFactory expFactory = new MongoExpressionFactory.Impl(); BasicDBObject projection = new BasicDBObject(); Limiter limiter = new Limiter.Default(); BasicDBObject sorter = new BasicDBObject(); IntentParser.parse(getIntent(), expFactory, null, projection, limiter, sorter, options); if(!options.isEmpty()) prepareOptions(options); if (null != getCache()) {// cache cursor = getCollection().find(expFactory.toQuery(), new BasicDBObject().append("_id", 1)); } else {// no cache // projection if (projection.isEmpty()) cursor = getCollection().find(expFactory.toQuery()); else cursor = getCollection().find(expFactory.toQuery(), projection); } // sort if (!sorter.isEmpty()) cursor.sort(sorter); if (offset > 0) limiter.offset(offset); if (count > 0) limiter.count(count); if (limiter.offset() > 0) cursor.skip(limiter.offset()); if (limiter.count() > 0) cursor.limit(limiter.count()); if (null != getCache()) { Map<Object, MongoFields> missedCacheHits = new HashMap<Object, MongoFields>(); while (cursor.hasNext()) { BasicDBObject dbo = (BasicDBObject) cursor.next(); Object key = dbo.get("_id"); String cacheKey = getKey().concat("#").concat( key.toString()); MongoFields ele = null; if (getCache().isAlive(cacheKey)) {// load from cache MongoFields mf = (MongoFields) getCache().restore( cacheKey); if(null != mf) ele = mf.clone();// pooling } if(null != ele && !projection.isEmpty()) ele.project(projection.keySet()); if(null == ele){ ele = new MongoFields(this, getIntent()); missedCacheHits.put(key, ele); } rlt.add(ele); } // load missed cache hits. if (!missedCacheHits.isEmpty()) { loadForMissedCacheHits(missedCacheHits, projection.keySet()); missedCacheHits.clear(); } if(logger.isDebug()) logger.debug("Listed entities hit cache ..."); } else { while (cursor.hasNext()) { BasicDBObject dbo = (BasicDBObject) cursor.next(); rlt.add(new MongoFields(this, getIntent(), dbo)); } if(logger.isDebug()) logger.debug("Listed entities ..."); } return rlt; } catch (AnalyzeBehaviourException abe) { if(logger.isDebug()) logger.debug(abe, "Analyzing behaviour failure, cause by: {0}.", abe.getMessage()); throw new RuntimeException(abe); } finally { if (!options.getBoolean(Options.RETAIN)) getIntent().reset(); if (cursor != null) cursor.close(); } } public List<Fields> find(Expression expr) { this.filter(expr); return list(); } public List<Fields> find(Expression expr, int offset, int count) { this.filter(expr); return list(offset, count); } public Fields findOne(Expression expr) { this.filter(expr); return first(); } /** * Finds the first document in the query and updates it. * @see com.mongodb.DBCollection#findAndModify(com.mongodb.DBObject, com.mongodb.DBObject, com.mongodb.DBObject, boolean, com.mongodb.DBObject, boolean, boolean) * @param expr * @return */ public Fields findAndUpdate(Expression expr) { if(logger.isDebug()) logger.debug( "Finding and updating entity, Database is {0}, Collection is {1} ...", getSchema(), getName()); BasicDBObject options = new BasicDBObject(); try { validateState(); final BasicDBObject fields = new BasicDBObject(); MongoExpressionFactory expFactory = new MongoExpressionFactory.Impl(); BasicDBObject projection = new BasicDBObject(); Limiter limiter = new Limiter.Default(); BasicDBObject sorter = new BasicDBObject(); IntentParser.parse(getIntent(), expFactory, fields, projection, limiter, sorter, options); BasicDBObject query = expFactory.parse((MongoExpression) expr); if(logger.isDebug()) logger.debug( "Finding and updating entity, Database is {0}, Collection is {1}, expression is {2}, " + "Projections is {3}, Update is {4}, Sorter is {5}, Options is {6} ...", getSchema(), getName(), query.toString(), projection.toString(), fields.toString(), sorter.toString(), JSON.serialize(options)); DBObject rlt = getCollection().findAndModify(query, projection, sorter, options.getBoolean(Options.FIND_AND_REMOVE, false), fields, options.getBoolean(Options.RETURN_NEW, false), options.getBoolean(Options.UPSERT, false)); if (rlt == null || rlt.keySet().isEmpty()) return null; // alive cache if (null != getCache()) { invalidateCache(query); } return new MongoFields(this, getIntent(), (BasicDBObject) rlt).project(projection.keySet()); } catch (AnalyzeBehaviourException abe) { if(logger.isDebug()) logger.debug(abe, "Analyzing behaviour failure, cause by: {0}.", abe.getMessage()); throw new RuntimeException(abe); } finally { if (!options.getBoolean(Options.RETAIN, false)) getIntent().reset(); } } private void loadForMissedCacheHits(Map<Object, MongoFields> missed, Set<String> proj) { Set<Object> ids = missed.keySet(); if (logger.isDebug()) logger.debug("Loading data for missed cache hits, _id is {0}.", Arrays.toString(ids.toArray())); BasicDBObject query = new BasicDBObject(); query.append("_id", new BasicDBObject().append("$in", ids.toArray())); DBCursor cursor = null; try { cursor = getCollection().find(query); while (cursor.hasNext()) { BasicDBObject dbo = (BasicDBObject) cursor.next(); Object id = dbo.get("_id"); if(logger.isDebug()) logger.debug("Loaded data for missed cache hits, _id is {0}.", id.toString()); MongoFields mf = missed.get(id).putTarget(dbo); getCache().cache(mf.clone()); if (!proj.isEmpty()) mf.project(proj); } } finally { if (cursor != null) cursor.close(); } } public Number count() { return count(null); } /** * @param projection will be ignored in mongodb. */ public Number count(String projection) { if(logger.isDebug()) logger.debug( "Listing entities, Database is {0}, Collection is {1}, offset is {2}, count is {3}.", getSchema(), getName(), 0, 1); BasicDBObject options = new BasicDBObject(); try { validateState(); MongoExpressionFactory expFactory = new MongoExpressionFactory.Impl(); IntentParser.parse(getIntent(), expFactory, null, null, null, null, options); BasicDBObject query = expFactory.toQuery(); if (query == null || query.isEmpty()) return getCollection().count(); else return getCollection().count(query); } catch (AnalyzeBehaviourException abe) { if(logger.isDebug()) logger.debug(abe, "Analyzing behaviour failure, cause by: {0}.", abe.getMessage()); throw new RuntimeException(abe); } finally { if (!options.getBoolean(Options.RETAIN)) getIntent().reset(); } } public List<Fields> distinct(String projection) { if(logger.isDebug()) logger.debug( "Distincting entities, Database is {0}, Collection is {1}, column is {2} ...", getSchema(), getName(), projection); List<Fields> rlt = new ArrayList<Fields>(); BasicDBObject options = new BasicDBObject(); try { validateState(); MongoExpressionFactory expFactory = new MongoExpressionFactory.Impl(); IntentParser.parse(getIntent(), expFactory, null, null, null, null, options); BasicDBObject query = expFactory.toQuery(); List r = getCollection().distinct(projection, query); for (Object o : r) { BasicDBObject dbo = new BasicDBObject(); dbo.put(projection, o); rlt.add(new MongoFields(this, getIntent(), projection, o)); } } catch (AnalyzeBehaviourException abe) { if(logger.isDebug()) logger.debug(abe, "Analyzing behaviour failure, cause by: {0}.", abe.getMessage()); throw new RuntimeException(abe); } finally { if (!options.getBoolean(Options.RETAIN)) getIntent().reset(); } return rlt; } public List<Fields> distinct(String projection, Expression exp) { filter(exp); return distinct(projection); } /** * @deprecated UnsupportedOperation */ public Entity join(Entity target) { throw new UnsupportedOperationException("Unsupported operation <join>."); } /** * @deprecated UnsupportedOperation */ public Entity joinOn(Entity target, Joint type, String on) { throw new UnsupportedOperationException( "Unsupported operation <joinOn>."); } /** * @deprecated UnsupportedOperation */ public Entity joinUsing(Entity target, Joint type, String... using) { throw new UnsupportedOperationException( "Unsupported operation <joinUsing>."); } /** * @deprecated UnsupportedOperation */ public Entity join(String table) { throw new UnsupportedOperationException("Unsupported operation <join>."); } /** * @deprecated UnsupportedOperation */ public Entity joinOn(String table, Joint type, String on) { throw new UnsupportedOperationException( "Unsupported operation <joinOn>."); } /** * @deprecated UnsupportedOperation */ public Entity joinUsing(String table, Joint type, String... using) { throw new UnsupportedOperationException( "Unsupported operation <joinUsing>."); } /** * @deprecated UnsupportedOperation */ public Entity group(String... on) { throw new UnsupportedOperationException( "Unsupported operation <group>."); } /** * @deprecated UnsupportedOperation */ public Entity having(String selection, Object... args) { throw new UnsupportedOperationException( "Unsupported operation <having>."); } /** * @deprecated UnsupportedOperation */ public Entity union(Entity target) { throw new UnsupportedOperationException( "Unsupported operation <union>."); } /** * @deprecated UnsupportedOperation */ public Entity union(Entity target, String alias) { throw new UnsupportedOperationException( "Unsupported operation <union>."); } /** * @deprecated UnsupportedOperation */ public Entity unionAll(Entity target) { throw new UnsupportedOperationException( "Unsupported operation <unionAll>."); } /** * @deprecated UnsupportedOperation */ public Entity unionAll(Entity target, String alias) { throw new UnsupportedOperationException( "Unsupported operation <joinUsing>."); } /** * @deprecated UnsupportedOperation */ public Entity except(Entity target) { throw new UnsupportedOperationException( "Unsupported operation <except>."); } /** * @deprecated UnsupportedOperation */ public Entity except(Entity target, String alias) { throw new UnsupportedOperationException( "Unsupported operation <except>."); } /** * @deprecated UnsupportedOperation */ public Entity exceptAll(Entity target) { throw new UnsupportedOperationException( "Unsupported operation <exceptAll>."); } /** * @deprecated UnsupportedOperation */ public Entity exceptAll(Entity target, String alias) { throw new UnsupportedOperationException( "Unsupported operation <exceptAll>."); } /** * @deprecated UnsupportedOperation */ public Entity intersect(Entity target) { throw new UnsupportedOperationException( "Unsupported operation <intersect>."); } public Entity intersect(Entity target, String alias) { throw new UnsupportedOperationException( "Unsupported operation <intersect>."); } /** * @deprecated UnsupportedOperation */ public Entity intersectAll(Entity target) { throw new UnsupportedOperationException( "Unsupported operation <intersectAll>."); } /** * @deprecated UnsupportedOperation */ public Entity intersectAll(Entity target, String alias) { throw new UnsupportedOperationException( "Unsupported operation <intersectAll>."); } /** * @deprecated UnsupportedOperation */ public Number max(String projection) { throw new UnsupportedOperationException("Unsupported operation <max>."); } /** * @deprecated UnsupportedOperation */ public Number min(String projection) { throw new UnsupportedOperationException("Unsupported operation <min>."); } /** * @deprecated UnsupportedOperation */ public Number sum(String projection) { throw new UnsupportedOperationException("Unsupported operation <sum>."); } /** * @deprecated UnsupportedOperation */ public Number avg(String projection) { throw new UnsupportedOperationException("Unsupported operation <avg>."); } /** * @deprecated UnsupportedOperation */ public String concat(String delimiter, String... projections) { throw new UnsupportedOperationException( "Unsupported operation <concat>."); } public int update(Serializable... keys) { if(logger.isDebug()) logger.debug( "Updating entitiy, Database is {0}, Collection is {1}, keys is {2}.", getSchema(), getName(), Arrays.toString(keys)); try { if (keys == null || keys.length == 0) return 0; validateState(); final BasicDBObject fields = new BasicDBObject(); final BasicDBObject othersFields = new BasicDBObject(); final BasicDBObject options = new BasicDBObject(); getIntent().playback(new Tracer() { public Tracer trace(Step step) throws AnalyzeBehaviourException { switch (step.step()) { case MongoStep.PUT: if(logger.isDebug()) logger.debug( "Analyzing behivor PUT, step is {0}, purpose is {1}, scalar is {2}.", step.step(), step.getPurpose(), Arrays.toString(step.getScalar())); if (StringUtils.isBlank(step.getPurpose())) break; Object val = (step.getScalar() == null || step .getScalar().length == 0) ? null : step .getScalar()[0]; if (step.getPurpose().matches(internalCmds)) othersFields.append(step.getPurpose(), val); else fields.append(step.getPurpose(), val); break; case MongoStep.OPTION: options.append(step.getPurpose(), step.getScalar()[0]); break; default: logger.warn( "Step {0} does not apply to activities create, this step will be ignored.", step.step()); } return this; } }); BasicDBObject fs = new BasicDBObject(); if (!fields.isEmpty()) fs.put("$set", fields); if (!othersFields.isEmpty()) fs.putAll(othersFields.toMap()); if (fs.isEmpty()) return 0; WriteResult wr = getCollection().update( new BasicDBObject().append("_id", keys[0]), fs, options.getBoolean(Options.UPSERT, false), false, getCollection().getWriteConcern()); if (!StringUtils.isBlank(wr.getError())) throw new DataAccessException(wr.getError()); // invalidate cache if (null != getCache()) { String cacheKey = getKey().concat("#").concat( keys[0].toString()); getCache().remove(cacheKey); if (logger.isDebug()) logger.debug("Invalidated cache key is {0}.", keys[0].toString()); } return wr.getN(); } catch (AnalyzeBehaviourException abe) { if(logger.isDebug()) logger.debug(abe, "Analyzing behaviour failure, cause by: {0}.", abe.getMessage()); throw new RuntimeException(abe); } finally { getIntent().reset(); } } public int updateRange(Expression expr) { if(logger.isDebug()) logger.debug( "Updating entities, Database is {0}, Collection is {1} ...", getSchema(), getName()); try { validateState(); final BasicDBObject fields = new BasicDBObject(); final BasicDBObject othersFields = new BasicDBObject(); final BasicDBObject options = new BasicDBObject(); getIntent().playback(new Tracer() { public Tracer trace(Step step) throws AnalyzeBehaviourException { switch (step.step()) { case MongoStep.PUT: if (StringUtils.isBlank(step.getPurpose())) break; Object val = (step.getScalar() == null || step .getScalar().length == 0) ? null : step .getScalar()[0]; if (step.getPurpose().matches(internalCmds)) othersFields.append(step.getPurpose(), val); else fields.append(step.getPurpose(), val); break; case MongoStep.OPTION: options.append(step.getPurpose(), step.getScalar()[0]); break; default: logger.warn( "Step {0} does not apply to activities create, this step will be ignored.", step.step()); } return this; } }); final MongoExpressionFactory expFactory = new MongoExpressionFactory.Impl(); BasicDBObject query = expFactory.parse((MongoExpression) expr); if(logger.isDebug()) logger.debug( "Updating entities, Database is {0}, Collection is {1}, expression is {2} ...", getSchema(), getName(), query.toString()); BasicDBObject fs = new BasicDBObject(); if (!fields.isEmpty()) fs.append("$set", fields); if (!othersFields.isEmpty()) fs.putAll(othersFields.toMap()); if (fs.isEmpty()) return 0; WriteResult wr = getCollection().update(query, fs, options.getBoolean(Options.UPSERT, false), true, getCollection().getWriteConcern()); if (!StringUtils.isBlank(wr.getError())) throw new DataAccessException(wr.getError()); // invalidate cache if (null != getCache()) { invalidateCache(query); } return wr.getN(); } catch (AnalyzeBehaviourException abe) { if(logger.isDebug()) logger.debug(abe, "Analyzing behaviour failure, cause by: {0}.", abe.getMessage()); throw new RuntimeException(abe); } finally { getIntent().reset(); } } public int delete(Serializable... keys) { if (keys == null || keys.length == 0) { logger.warn("System does not know what data you want to update, " + "you must specify the data row identity."); return 0; } if(logger.isDebug()) logger.debug( "Deleting entitiy, Database is {0}, Collection is {1}, keys is {2}.", getSchema(), getName(), Arrays.toString(keys)); try { validateState(); WriteResult wr = getCollection().remove( new BasicDBObject().append("_id", keys[0]), getCollection().getWriteConcern()); if (!StringUtils.isBlank(wr.getError())) throw new DataAccessException(wr.getError()); // invalidate cache if (null != getCache()) { String cacheKey = getKey().concat("#").concat( keys[0].toString()); getCache().remove(cacheKey); if (logger.isDebug()) logger.debug("Invalidated cache key is {0}.", keys[0].toString()); } return wr.getN(); } finally { getIntent().reset(); } } public int deleteRange(Expression expr) { if(logger.isDebug()) logger.debug( "Deleting entities, Database is {0}, Collection is {1} ...", getSchema(), getName()); try { validateState(); final MongoExpressionFactory expFactory = new MongoExpressionFactory.Impl(); BasicDBObject query = expFactory.parse((MongoExpression) expr); if(logger.isDebug()) logger.debug( "Deleting entities, Database is {0}, Collection is {1}, expression is {2} ...", getSchema(), getName(), query.toString()); // invalidate cache if (null != getCache()) { invalidateCache(query); } WriteResult wr = getCollection().remove(query, getCollection().getWriteConcern()); if (!StringUtils.isBlank(wr.getError())) throw new DataAccessException(wr.getError()); return wr.getN(); } catch (AnalyzeBehaviourException abe) { if(logger.isDebug()) logger.debug(abe, "Analyzing behaviour failure, cause by: {0}.", abe.getMessage()); throw new RuntimeException(abe); } finally { getIntent().reset(); } } /** * @deprecated UnsupportedOperation */ public Batch batch() { throw new UnsupportedOperationException(); } /** * @deprecated UnsupportedOperation */ public Transaction openTransaction() { throw new UnsupportedOperationException(); } /** * @deprecated UnsupportedOperation */ public Entity transaction() { throw new UnsupportedOperationException(); } /** * @deprecated UnsupportedOperation */ public Entity transaction(TransactionHolder holder) { throw new UnsupportedOperationException(); } /** * @deprecated UnsupportedOperation */ public Entity rollback() { throw new UnsupportedOperationException(); } /** * @deprecated UnsupportedOperation */ public Entity commit() { throw new UnsupportedOperationException(); } /** * @deprecated UnsupportedOperation */ public Entity end(boolean expr) { throw new UnsupportedOperationException(); } public <D> D execute(Command cmd, Object... args) { if (logger.isDebug()) logger.debug("Executing command {0}, args is {2} ...", cmd.describe(), Arrays.toString(args)); return cmd.execute(getFactory(), getResource(), this, args); } /** * @see #execute(Command, Object...) */ public <D> D call(Command cmd, Object... args) { return execute(cmd, args); } /** * @see org.canedata.provider.mongodb.entity.Options * @see org.canedata.entity.Entity#opt(java.lang.String, * java.lang.Object[]) */ public Entity opt(String key, Object... values) { getIntent().step(MongoStep.OPTION, key, values); return this; } public Entity relate(String name) { return getFactory().get(getResource(), name); } public Entity relate(String schema, String name) { return getFactory().get(getResource(), schema, name); } public Entity revive() { hasClosed = false; getIntent().reset(); getCollection(); return this; } public boolean hasClosed() { return hasClosed; } protected void validateState() { if (hasClosed()) throw new IllegalStateException( "Entity have been closed, can use the revive method to reactivate it."); } private void invalidateCache(BasicDBObject query) { DBCursor cursor = null; try { cursor = getCollection().find(query, new BasicDBObject().append("_id", 1)); while (cursor.hasNext()) { String key = cursor.next().get("_id").toString(); String cacheKey = getKey().concat("#").concat(key); getCache().remove(cacheKey); if (logger.isDebug()) logger.debug("Invalidated cache key is {0}.", cacheKey); } } finally { if (cursor != null) cursor.close(); } } private void prepareOptions(BasicDBObject options){ for(String o : options.keySet()){ if(Options.MONGO_OPTION.equals(o)){ getCollection().addOption(options.getInt(o)); continue; } if(Options.RESET_MONGO_OPTIONS.equals(o)){ getCollection().resetOptions(); continue; } if(Options.READ_PREFERENCE.equals(o)){ if(!(options.get(o) instanceof ReadPreference)) throw new MalformedParameterizedTypeException(); getCollection().setReadPreference((ReadPreference)options.get(o)); break; } if(Options.WRITE_CONCERN.equals(o)){ if(!(options.get(o) instanceof WriteConcern)) throw new MalformedParameterizedTypeException(); getCollection().setWriteConcern((WriteConcern)options.get(o)); break; } } } }