/**
* 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;
}
}
}
}