package net.csdn.mongo;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import net.csdn.common.collections.WowCollections;
import net.csdn.common.exception.AutoGeneration;
import net.csdn.common.logging.CSLogger;
import net.csdn.common.logging.Loggers;
import net.csdn.common.reflect.WowMethod;
import net.csdn.mongo.annotations.*;
import net.csdn.mongo.association.*;
import net.csdn.mongo.commands.Delete;
import net.csdn.mongo.commands.Insert;
import net.csdn.mongo.commands.Save;
import net.csdn.mongo.commands.Update;
import net.csdn.mongo.embedded.AssociationEmbedded;
import net.csdn.mongo.embedded.BelongsToAssociationEmbedded;
import net.csdn.mongo.embedded.HasManyAssociationEmbedded;
import net.csdn.mongo.embedded.HasOneAssociationEmbedded;
import net.csdn.mongo.validate.ValidateParse;
import net.csdn.mongo.validate.ValidateResult;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static net.csdn.common.collections.WowCollections.*;
import static net.csdn.common.logging.support.MessageFormat.format;
import static net.csdn.common.reflect.ReflectHelper.staticField;
import static net.csdn.common.reflect.ReflectHelper.staticMethod;
/**
* User: WilliamZhu
* Date: 12-10-16
* Time: 下午8:11
* <p/>
* The +Document+ class is the Parent Class of All MongoDB model,
* this means any MongoDB model class should extends this class.
* <p/>
* Once your class extends Document,you get all ODM(Object Data Mapping )
* and +net.csdn.mongo.Criteria+ (A convenient DSL) power.
* <p/>
* Example setup:
* <p/>
* ```java
* public class Person extends Document{
* static{
* storeIn("persons")
* }
* }
* ```
*/
public class Document {
private static CSLogger logger = Loggers.getLogger(Document.class);
//instance attributes
@Transient
protected Map attributes = map();
@Transient
protected boolean new_record = true;
@Transient
protected Map<String, Association> associations = map();
@Transient
protected Map<String, AssociationEmbedded> associationsEmbedded = map();
//for embedded association
@Transient
public Document _parent;
@Transient
public String associationEmbeddedName;
public <T> T attr(String key, Class<T> clzz) {
return (T) attributes.get((String) staticMethod(this.getClass(), "translateFromAlias", key));
}
public Document attr(String key, Object obj) {
attributes.put((String) staticMethod(this.getClass(), "translateFromAlias", key), obj);
return this;
}
public static String translateFromAlias(String key) {
if (parent$_alias_names != null && parent$_alias_names.containsKey(key)) {
return parent$_alias_names.get(key);
}
return key;
}
public static Map translateKeyForParams(Map params) {
Map newParams = map();
for (Object key : params.keySet()) {
String keyStr = (String) key;
newParams.put(translateFromAlias(keyStr), params.get(key));
}
params.clear();
params.putAll(newParams);
return params;
}
public static String translateToAlias(String key) {
if (parent$_alias_names != null) {
for (Map.Entry<String, String> entry : parent$_alias_names.entrySet()) {
if (key.equals(entry.getValue())) {
return entry.getKey();
}
}
}
return key;
}
/*
* +Class Methods+ will be copied into subclass when system startup.
* you should access them using class methods instead of directly accessing;
*
* Example:
*
* ```java
* Person.collection();
* ```
* instead of
*
* ```java
* Person.parent$_collection;//this is a wrong way to access class attributes
* ```
*
*/
protected static Map parent$_primaryKey;
protected static boolean parent$_embedded;
protected static List<String> parent$_fields;
protected static DBCollection parent$_collection;
protected static String parent$_collectionName;
protected static Map<String, Association> parent$_associations;
protected static Map<String, AssociationEmbedded> parent$_associations_embedded;
protected static Map<String, String> parent$_alias_names;
public static MongoMongo mongoMongo;
/*
* Warning: all methods modified by +static+ keyword , you should call them in subclass.
*/
public static String collectionName() {
return parent$_collectionName;
}
public static boolean embedded() {
return parent$_embedded;
}
public static boolean embedded(boolean isEmbedded) {
parent$_embedded = isEmbedded;
return parent$_embedded;
}
public static DBCollection collection() {
return parent$_collection;
}
public static Map<String, Association> associationsMetaData() {
return parent$_associations;
}
public static Map<String, AssociationEmbedded> associationsEmbeddedMetaData() {
return parent$_associations_embedded;
}
public static List fields() {
return parent$_fields;
}
public boolean merge(Map item) {
attributes.putAll(item);
copyAllAttributesToPojoFields();
return true;
}
/*
# setting the collection name to store in.
#
# Example:
#
# <tt>Person.store_in(populdation)</tt>
#
# Warning: you should call this Class Method in subclass.
*/
protected static DBCollection storeIn(String name) {
parent$_collectionName = name;
parent$_collection = mongoMongo.database().getCollection(name);
return parent$_collection;
}
protected static Map parent$_validate_info;
protected static void validate(String fieldName, Map validate) {
if (parent$_validate_info == null) {
parent$_validate_info = map();
}
parent$_validate_info = map(fieldName, validate);
}
protected static Map validate_info() {
return parent$_validate_info;
}
/*
index({ ssn: 1 }, { unique: true, name: "ssn_index" })
*/
protected static void index(Map keys, Map indexOptions) {
parent$_collection.ensureIndex(translateMapToDBObject(keys), translateMapToDBObject(indexOptions));
}
public static DBObject translateMapToDBObject(Map map) {
DBObject query = new BasicDBObject();
query.putAll(map);
return query;
}
protected static void alias(String originalName, String aliasName) {
if (parent$_alias_names == null) {
parent$_alias_names = map(aliasName, originalName);
} else {
parent$_alias_names.put(aliasName, originalName);
}
}
public static <T extends Document> T create(Map map) {
throw new AutoGeneration();
}
public static <T extends Document> T create9(Map map) {
throw new AutoGeneration();
}
public boolean save() {
if (valid()) {
return Save.execute(this);
}
return false;
}
public boolean save(boolean validate) {
if (validate && valid()) {
return Save.execute(this);
}
return false;
}
public boolean insert() {
if (valid()) {
return Insert.execute(this, false);
}
return false;
}
public boolean insert(boolean validate) {
if (validate && valid()) {
return Insert.execute(this, false);
}
return false;
}
public boolean update() {
if (valid()) {
return Update.execute(this, false);
}
return false;
}
public boolean update(boolean validate) {
if (validate && valid()) {
return Update.execute(this, false);
}
return false;
}
public final List<ValidateResult> validateResults = list();
public final static List validateParses = list();
public boolean valid() {
runCallbacks(Callbacks.Callback.before_validation);
if (validateResults.size() > 0) return false;
for (Object validateParse : validateParses) {
((ValidateParse) validateParse).parse(this, this.validateResults);
}
runCallbacks(Callbacks.Callback.after_validation);
return validateResults.size() == 0;
}
public void remove() {
Delete.execute(this);
}
public void remove(Document child) {
String name = child.associationEmbeddedName;
AssociationEmbedded association = this.associationEmbedded().get(name);
if (association instanceof HasManyAssociationEmbedded) {
List<Map> children = (List) this.attributes().get(name);
Map shouldRemove = null;
for (Map wow : children) {
if (child.id().equals(wow.get("_id"))) {
shouldRemove = wow;
break;
}
}
if (shouldRemove != null) {
children.remove(shouldRemove);
}
} else {
this.attributes().remove(name);
}
this.associationEmbedded().get(name).remove(child);
this.save();
}
public void copyPojoFieldsToAllAttributes() {
try {
List<PropertyDescriptor> fromPd = Arrays.asList(Introspector.getBeanInfo(this.getClass())
.getPropertyDescriptors());
for (PropertyDescriptor pd : fromPd) {
if (pd.getName().equals("class")) continue;
this.attributes.put((String) staticMethod(this.getClass(), "translateFromAlias", pd.getName()), pd.getReadMethod().invoke(this));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void copyAllAttributesToPojoFields() {
DBObject newAttributes = new BasicDBObject();
newAttributes.putAll(this.attributes());
Map<String, String> tempMap = (Map<String, String>) staticField(this.getClass(), "parent$_alias_names");
try {
if (tempMap != null && tempMap.size() > 0) {
for (Map.Entry<String, String> entry : tempMap.entrySet()) {
Object temp = this.attributes().get(entry.getValue());
if (temp != null)
newAttributes.put(entry.getKey(), temp);
}
}
BeanUtils.copyProperties(this, newAttributes);
} catch (Exception e) {
e.printStackTrace();
}
}
public Object id() {
return attributes.get("_id");
}
public Object id(Object id) {
attributes.put("_id", id);
return id;
}
public Document fields(String... names) {
for (String name : names) {
fields().add(name);
}
return this;
}
public Map<String, Association> associations() {
return associations;
}
public Map<String, AssociationEmbedded> associationEmbedded() {
return associationsEmbedded;
}
public Map reload() {
attributes.putAll(collection().findOne(map("_id", attributes.get("_id"))).toMap());
return attributes;
}
public Map attributes() {
return attributes;
}
//embedded Association methods
public static HasManyAssociationEmbedded hasManyEmbedded(String name, Options options) {
HasManyAssociationEmbedded association = new HasManyAssociationEmbedded(name, options);
if (parent$_associations_embedded == null) parent$_associations_embedded = map();
parent$_associations_embedded.put(name, association);
return association;
}
public static BelongsToAssociationEmbedded belongsToEmbedded(String name, Options options) {
BelongsToAssociationEmbedded association = new BelongsToAssociationEmbedded(name, options);
if (parent$_associations_embedded == null) parent$_associations_embedded = map();
parent$_associations_embedded.put(name, association);
return association;
}
public static HasOneAssociationEmbedded hasOneEmbedded(String name, Options options) {
HasOneAssociationEmbedded association = new HasOneAssociationEmbedded(name, options);
if (parent$_associations_embedded == null) parent$_associations_embedded = map();
parent$_associations_embedded.put(name, association);
return association;
}
//Association methods
public static HasManyAssociation hasMany(String name, Options options) {
HasManyAssociation association = new HasManyAssociation(name, options);
if (parent$_associations == null) parent$_associations = map();
parent$_associations.put(name, association);
return association;
}
public static HasOneAssociation hasOne(String name, Options options) {
HasOneAssociation association = new HasOneAssociation(name, options);
if (parent$_associations == null) parent$_associations = map();
parent$_associations.put(name, association);
return association;
}
public static BelongsToAssociation belongsTo(String name, Options options) {
BelongsToAssociation association = new BelongsToAssociation(name, options);
if (parent$_associations == null) parent$_associations = map();
parent$_associations.put(name, association);
return association;
}
public String toString() {
String attrs = join(iterate_map(attributes, new WowCollections.MapIterator<String, Object>() {
@Override
public Object iterate(String key, Object value) {
if (value instanceof String) {
value = StringUtils.substring((String) value, 0, 50);
}
return format("{}: {}", key, value);
}
}), ",");
return "#<" + this.getClass().getSimpleName() + " _id: " + id() + ", " + attrs + ">";
}
/*
# Return the root +Document+ in the object graph. If the current +Document+
# is the root object in the graph it will return self.
def _root
object = self
while (object._parent) do object = object._parent; end
object || self
end
*/
public Document _root() {
Document doc = this;
while (doc._parent != null) {
doc = doc._parent;
}
return doc == null ? this : doc;
}
public boolean newRecord(boolean saved) {
return new_record = saved;
}
//bind Criteria
public static Criteria where(Map conditions) {
//return new Criteria(Document.class).where(conditions);
throw new AutoGeneration();
}
public static Criteria select(List fieldNames) {
throw new AutoGeneration();
}
public static Criteria order(Map orderBy) {
throw new AutoGeneration();
}
public static Criteria skip(int skip) {
throw new AutoGeneration();
}
public static Criteria limit(int limit) {
throw new AutoGeneration();
}
public static int count() {
throw new AutoGeneration();
}
public static Criteria in(Map in) {
throw new AutoGeneration();
}
public static Criteria not(Map not) {
throw new AutoGeneration();
}
public static Criteria notIn(Map notIn) {
throw new AutoGeneration();
}
public static <T> T findById(Object id) {
throw new AutoGeneration();
}
public static <T> List<T> find(List list) {
throw new AutoGeneration();
}
public static <T> List<T> findAll() {
throw new AutoGeneration();
}
protected Map<Callbacks.Callback, List<WowMethod>> callbacks = map();
protected void collectCallback() {
Method[] methods = this.getClass().getDeclaredMethods();
if (callbacks.size() > 0) return;
for (Method method : methods) {
WowMethod wowMethod = new WowMethod(this, method);
if (method.isAnnotationPresent(BeforeSave.class)) {
putAndAdd(Callbacks.Callback.before_save, wowMethod);
}
if (method.isAnnotationPresent(BeforeCreate.class)) {
putAndAdd(Callbacks.Callback.before_create, wowMethod);
}
if (method.isAnnotationPresent(BeforeDestroy.class)) {
putAndAdd(Callbacks.Callback.before_destroy, wowMethod);
}
if (method.isAnnotationPresent(BeforeUpdate.class)) {
putAndAdd(Callbacks.Callback.before_update, wowMethod);
}
if (method.isAnnotationPresent(BeforeValidation.class)) {
putAndAdd(Callbacks.Callback.before_validation, wowMethod);
}
if (method.isAnnotationPresent(AfterCreate.class)) {
putAndAdd(Callbacks.Callback.after_create, wowMethod);
}
if (method.isAnnotationPresent(AfterDestory.class)) {
putAndAdd(Callbacks.Callback.after_destroy, wowMethod);
}
if (method.isAnnotationPresent(AfterSave.class)) {
putAndAdd(Callbacks.Callback.after_save, wowMethod);
}
if (method.isAnnotationPresent(AfterUpdate.class)) {
putAndAdd(Callbacks.Callback.after_update, wowMethod);
}
if (method.isAnnotationPresent(AfterValidation.class)) {
putAndAdd(Callbacks.Callback.after_validation, wowMethod);
}
}
}
public void runCallbacks(Callbacks.Callback callback, Object... params) {
if (callbacks.size() == 0) {
collectCallback();
}
List<WowMethod> wowMethods = callbacks.get(callback);
if (wowMethods == null) return;
for (WowMethod wowMethod : wowMethods) {
wowMethod.invoke(params);
}
}
private void putAndAdd(Callbacks.Callback key, WowMethod item) {
if (callbacks.containsKey(key)) {
callbacks.get(key).add(item);
} else {
callbacks.put(key, list(item));
}
}
public static Criteria nativeQuery(String tableName) {
return new Criteria(tableName);
}
}