org.canedata.provider.mongodb.entity.MongoEntity Maven / Gradle / Ivy
The newest version!
/**
* 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 java.util.function.BiFunction;
import com.mongodb.*;
import com.mongodb.util.JSON;
import org.bson.BSONObject;
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 params) {
if (null == params || params.isEmpty())
return this;
for (Entry 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 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()) {
String cacheKey = getKey().concat("#").concat(
keys[0].toString());
if(logger.isDebug())
logger.debug("Invalid fields to cache, cache key is {0} ...",
cacheKey);
getCache().remove(cacheKey);
}
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 keys) {
putAll(keys);
return createOrUpdate();
}
public Entity projection(String... projection) {
getIntent().step(MongoStep.PROJECTION, null, (Object[]) projection);
return this;
}
public Entity projection(BasicDBObject projections) {
getIntent().step(MongoStep.PROJECTION, null, projections);
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:
if(step.getScalar()[0] instanceof BasicDBObject){
projection.putAll((BSONObject)step.getScalar()[0]);
options.put("disable_cache", true);
break;
}
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() && !options.getBoolean("disable_cache", false) && options.getBoolean(Options.CACHEABLE, true)) {
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 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 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 list() {
return list(-1, -1);
}
public List list(int count) {
return list(0, count);
}
public List 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 rlt = new ArrayList();
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);
boolean useCache = null != getCache() && !options.getBoolean("disable_cache", false) && options.getBoolean(Options.CACHEABLE, true);
if(!options.isEmpty())
prepareOptions(options);
if (useCache) {// 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 (useCache) {
Map