/**
* Copyright (C) 2010 Olafur Gauti Gudmundsson 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 com.github.jmkgreen.morphia.mapping;
import java.io.IOException;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.regex.Pattern;
import com.github.jmkgreen.morphia.mapping.cache.*;
import org.bson.BSONEncoder;
import org.bson.BasicBSONEncoder;
import com.github.jmkgreen.morphia.EntityInterceptor;
import com.github.jmkgreen.morphia.Key;
import com.github.jmkgreen.morphia.annotations.Converters;
import com.github.jmkgreen.morphia.annotations.Embedded;
import com.github.jmkgreen.morphia.annotations.Entity;
import com.github.jmkgreen.morphia.annotations.Id;
import com.github.jmkgreen.morphia.annotations.NotSaved;
import com.github.jmkgreen.morphia.annotations.PostLoad;
import com.github.jmkgreen.morphia.annotations.PreLoad;
import com.github.jmkgreen.morphia.annotations.PrePersist;
import com.github.jmkgreen.morphia.annotations.PreSave;
import com.github.jmkgreen.morphia.annotations.Property;
import com.github.jmkgreen.morphia.annotations.Reference;
import com.github.jmkgreen.morphia.annotations.Serialized;
import com.github.jmkgreen.morphia.converters.DefaultConverters;
import com.github.jmkgreen.morphia.converters.TypeConverter;
import com.github.jmkgreen.morphia.logging.Logr;
import com.github.jmkgreen.morphia.logging.MorphiaLoggerFactory;
import com.github.jmkgreen.morphia.mapping.lazy.DatastoreProvider;
import com.github.jmkgreen.morphia.mapping.lazy.LazyProxyFactory;
import com.github.jmkgreen.morphia.mapping.lazy.proxy.ProxiedEntityReference;
import com.github.jmkgreen.morphia.mapping.lazy.proxy.ProxyHelper;
import com.github.jmkgreen.morphia.query.FilterOperator;
import com.github.jmkgreen.morphia.query.ValidationException;
import com.github.jmkgreen.morphia.utils.ReflectionUtils;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.DBRef;
/**
* <p>This is the heart of Morphia and takes care of mapping from/to POJOs/DBObjects<p>
* <p>This class is thread-safe and keeps various "cached" data which should speed up processing.</p>
*
* @author Olafur Gauti Gudmundsson
* @author Scott Hernandez
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class DefaultMapper implements Mapper {
protected EntityCacheFactory entityCacheFactory = new NoOpEntityCacheFactory();
protected static final Logr log = MorphiaLoggerFactory.get(DefaultMapper.class);
/**
* Set of classes that registered by this mapper
*/
protected final Map<String, MappedClass> mappedClasses = new ConcurrentHashMap<String, MappedClass>();
protected final ConcurrentHashMap<String, Set<MappedClass>> mappedClassesByCollection = new ConcurrentHashMap<String, Set<MappedClass>>();
//EntityInterceptors; these are called before EntityListeners and lifecycle methods on an Entity, for all Entities
protected final List<EntityInterceptor> interceptors = new LinkedList<EntityInterceptor>();
//A general cache of instances of classes; used by MappedClass for EntityListener(s)
final Map<Class, Object> instanceCache = new ConcurrentHashMap();
protected MapperOptions opts;
private LazyProxyFactory proxyFactory;
private DatastoreProvider datastoreProvider;
DefaultConverters converters;
public DefaultMapper() {
this(new MapperOptions());
}
public DefaultMapper(MapperOptions opts) {
this.opts = opts;
this.setProxyFactory(opts.proxyFactory);
this.setDatastoreProvider(opts.datastoreProvider);
this.converters = opts.converters;
converters.setMapper(this);
}
/**
* Enable overriding of EntityCacheFactory.
* @param factory
* @return
* @since 1.2.4
*/
public Mapper setEntityCacheFactory(EntityCacheFactory factory) {
this.entityCacheFactory = factory;
return this;
}
/**
* <p>
* Adds an {@link EntityInterceptor}. Each instance is called
* </p>
*/
public void addInterceptor(final EntityInterceptor ei) {
interceptors.add(ei);
}
/**
* <p>
* Gets list of {@link EntityInterceptor}s
* </p>
*/
public Collection<EntityInterceptor> getInterceptors() {
return interceptors;
}
public MapperOptions getOptions() {
return this.opts;
}
public void setOptions(MapperOptions options) {
this.opts = options;
}
public boolean isMapped(final Class c) {
return mappedClasses.containsKey(c.getName());
}
/**
* Creates a MappedClass and validates it.
*/
public MappedClass addMappedClass(Class c) {
MappedClass mc = new MappedClass(c, this);
return addMappedClass(mc, true);
}
/**
* Validates MappedClass and adds to internal cache.
*/
public MappedClass addMappedClass(MappedClass mc) {
return addMappedClass(mc, true);
}
/**
* Add MappedClass to internal cache, possibly validating first.
*/
protected MappedClass addMappedClass(MappedClass mc, boolean validate) {
if (validate)
mc.validate();
Converters c = (Converters) mc.getAnnotation(Converters.class);
if (c != null)
for (Class<? extends TypeConverter> clazz : c.value())
if (!converters.isRegistered(clazz))
converters.addConverter(clazz);
mappedClasses.put(mc.getClazz().getName(), mc);
Set<MappedClass> mcs = mappedClassesByCollection.get(mc.getCollectionName());
if (mcs == null) {
mcs = new CopyOnWriteArraySet<MappedClass>();
Set<MappedClass> temp = mappedClassesByCollection.putIfAbsent(mc.getCollectionName(), mcs);
if (temp != null) mcs = temp;
}
mcs.add(mc);
return mc;
}
/**
* Returns collection of MappedClasses
*/
public Collection<MappedClass> getMappedClasses() {
return new ArrayList<MappedClass>(mappedClasses.values());
}
/**
* Returns map of MappedClasses by class name
*/
public Map<String, MappedClass> getMCMap() {
return Collections.unmodifiableMap(mappedClasses);
}
/**
* <p>
* Gets the {@link MappedClass} for the object (type). If it isn't mapped,
* create a new class and cache it (without validating).
* </p>
*/
public MappedClass getMappedClass(final Object obj) {
if (obj == null)
return null;
Class type = (obj instanceof Class) ? (Class) obj : obj.getClass();
if (ProxyHelper.isProxy(obj))
type = ProxyHelper.getReferentClass(obj);
MappedClass mc = mappedClasses.get(type.getName());
if (mc == null) {
mc = new MappedClass(type, this);
// no validation
addMappedClass(mc, false);
}
return mc;
}
public String getCollectionName(Object object) {
if (object == null) throw new IllegalArgumentException();
MappedClass mc = getMappedClass(object);
return mc.getCollectionName();
}
/**
* <p>
* Updates the @{@link Id} fields.
* </p>
*
* @param entity The object to update
* @param dbObj Value to update with; null means skip
*/
public void updateKeyInfo(final Object entity, final DBObject dbObj, EntityCache cache) {
MappedClass mc = getMappedClass(entity);
// update id field, if there.
if ((mc.getIdField() != null) && (dbObj != null) && (dbObj.get(ID_KEY) != null)) {
try {
MappedField mf = mc.getMappedIdField();
Object oldIdValue = mc.getIdField().get(entity);
readMappedField(dbObj, mf, entity, cache);
Object dbIdValue = mc.getIdField().get(entity);
if (oldIdValue != null) {
// The entity already had an id set. Check to make sure it
// hasn't changed. That would be unexpected, and could
// indicate a bad state.
if (!dbIdValue.equals(oldIdValue)) {
mf.setFieldValue(entity, oldIdValue);//put the value back...
throw new RuntimeException("@Id mismatch: " + oldIdValue + " != " + dbIdValue + " for "
+ entity.getClass().getName());
}
} else
mc.getIdField().set(entity, dbIdValue);
} catch (Exception e) {
if (e.getClass().equals(RuntimeException.class)) {
throw (RuntimeException) e;
}
throw new RuntimeException("Error setting @Id field after save/insert.", e);
}
}
}
/**
* Converts a DBObject back to a type-safe java object (POJO)
*
* @param entityClass The type to return, or use; can be overridden by the @see Mapper.CLASS_NAME_FIELDNAME in the DBObject
*/
public Object fromDBObject(final Class entityClass, final DBObject dbObject, EntityCache cache) {
if (dbObject == null) {
Throwable t = new Throwable();
log.error("Somebody passed in a null dbObject; bad client!", t);
return null;
}
Object entity = null;
entity = opts.objectFactory.createInstance(entityClass, dbObject);
entity = fromDBObject(dbObject, entity, cache);
return entity;
}
/**
* <p>
* Converts a java object to a mongo-compatible object (possibly a DBObject
* for complex mappings). Very similar to {@link DefaultMapper#toDBObject}
* </p>
* <p>
* Used (mainly) by query/update operations
* </p>
*/
public Object toMongoObject(MappedField mf, MappedClass mc, Object value) {
if (value == null) {
return null;
}
Object mappedValue = value;
//convert the value to Key (DBRef) if the field is @Reference or type is Key/DBRef, or if the destination class is an @Entity
if (isAnAssignableField(mf, value) || isAnEntity(mc)) {
try {
if (value instanceof Iterable) {
return getDbRefs((Iterable) value);
}
if (value.getClass().isAssignableFrom(Boolean.class)) {
return toMongoObject(value, false);
}
if (value.getClass().isAssignableFrom(Integer.class)) {
return toMongoObject(value, false);
}
Key<?> k = (value instanceof Key) ? (Key<?>) value : getKey(value);
mappedValue = keyToRef(k);
if (mappedValue == value) {
throw new ValidationException("cannot map to @Reference/Key<T>/DBRef field: " + value);
}
return mappedValue;
} catch (Exception e) {
log.error("Error converting value(" + value + ") to reference.", e);
return toMongoObject(value, false);
}
}
//serialized
if (mf != null && mf.hasAnnotation(Serialized.class)) {
try {
return Serializer.serialize(value, !mf.getAnnotation(Serialized.class).disableCompression());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//pass-through
if (value instanceof DBObject) {
return value;
}
mappedValue = toMongoObject(value, EmbeddedMapper.shouldSaveClassName(value, mappedValue, mf));
if (mappedValue instanceof DBObject && !EmbeddedMapper.shouldSaveClassName(value, mappedValue, mf)) {
((DBObject) mappedValue).removeField(CLASS_NAME_FIELDNAME);
}
return mappedValue;
}
/**
* <p>
* Converts a java object to a mongo-compatible object (possibly a DBObject
* for complex mappings). Very similar to {@link DefaultMapper#toDBObject}
* </p>
* <p>
* Used (mainly) by query/update operations
* </p>
*/
public Object toMongoObject(Object javaObj, boolean includeClassName) {
if (javaObj == null)
return null;
Class origClass = javaObj.getClass();
if (origClass.isAnonymousClass() && origClass.getSuperclass().isEnum())
origClass = origClass.getSuperclass();
Object newObj = converters.encode(origClass, javaObj);
if (newObj == null) {
log.warning("converted " + javaObj + " to null");
return newObj;
}
Class type = newObj.getClass();
boolean bSameType = origClass.equals(type);
//TODO: think about this logic a bit more.
//Even if the converter changed it, should it still be processed?
if (!bSameType && !(Map.class.isAssignableFrom(type) || Iterable.class.isAssignableFrom(type)))
return newObj;
//The converter ran, and produced another type, or it is a list/map
boolean isSingleValue = true;
boolean isMap = false;
Class subType = null;
if (type.isArray() || Map.class.isAssignableFrom(type) || Iterable.class.isAssignableFrom(type)) {
isSingleValue = false;
isMap = ReflectionUtils.implementsInterface(type, Map.class);
// subtype of Long[], List<Long> is Long
subType = (type.isArray()) ? type.getComponentType() : ReflectionUtils.getParameterizedClass(type, (isMap) ? 1 : 0);
}
if (isSingleValue && !ReflectionUtils.isPropertyType(type)) {
DBObject dbObj = toDBObject(newObj);
if (!includeClassName)
dbObj.removeField(CLASS_NAME_FIELDNAME);
return dbObj;
}
if (newObj instanceof DBObject) {
return newObj;
}
if (isMap) {
if (ReflectionUtils.isPropertyType(subType))
return toDBObject(newObj);
HashMap m = new HashMap();
for (Map.Entry e : (Iterable<Map.Entry>) ((Map) newObj).entrySet())
m.put(e.getKey(), toMongoObject(e.getValue(), includeClassName));
return m;
//Set/List but needs elements converted
}
if (!isSingleValue && !ReflectionUtils.isPropertyType(subType)) {
ArrayList<Object> vals = new ArrayList<Object>();
if (type.isArray())
for (Object obj : (Object[]) newObj)
vals.add(toMongoObject(obj, includeClassName));
else
for (Object obj : (Iterable) newObj)
vals.add(toMongoObject(obj, includeClassName));
return vals;
}
return newObj;
}
private boolean isAnAssignableField(MappedField mf, Object value) {
if (mf == null) {
return false;
}
return (mf.hasAnnotation(Reference.class) ||
mf.getType().isAssignableFrom(Key.class) ||
mf.getType().isAssignableFrom(DBRef.class) ||
//Collection/Array/???
isValueIterableAndTargetCompatible(mf, value)
);
}
private boolean isValueIterableAndTargetCompatible(MappedField mf, Object value) {
return (value instanceof Iterable && mf.isMultipleValues() && (
mf.getSubClass().isAssignableFrom(Key.class) ||
mf.getSubClass().isAssignableFrom(DBRef.class))
);
}
private boolean isAnEntity(MappedClass mc) {
return (mc != null && mc.getEntityAnnotation() != null);
}
private ArrayList<DBRef> getDbRefs(Iterable value) {
ArrayList<DBRef> refs = new ArrayList<DBRef>();
for (Object o : value) {
Key<?> k = (o instanceof Key) ? (Key<?>) o : getKey(o);
refs.add(keyToRef(k));
}
return refs;
}
public Object getId(Object entity) {
if (entity == null) return null;
entity = ProxyHelper.unwrap(entity);
// String keyClassName = entity.getClass().getName();
MappedClass mc = getMappedClass(entity.getClass());
//
// if (getMappedClasses().containsKey(keyClassName))
// mc = getMappedClasses().get(keyClassName);
// else
// mc = new MappedClass(entity.getClass(), getMapper());
try {
return mc.getIdField().get(entity);
} catch (Exception e) {
return null;
}
}
public <T> Key<T> getKey(T entity) {
if (entity == null)
throw new MappingException("getKey has been passed a null entity");
if (entity instanceof ProxiedEntityReference) {
return getKey((ProxiedEntityReference) entity);
}
entity = ProxyHelper.unwrap(entity);
if (entity instanceof Key)
return (Key<T>) entity;
Object id = getId(entity);
if (id == null)
throw new MappingException("Could not get id for " + entity.getClass().getName());
return new Key<T>((Class<T>) entity.getClass(), id);
}
private <T> Key<T> getKey(ProxiedEntityReference entity) {
return (Key<T>) entity.__getKey();
}
/**
* <p>
* Converts an entity (POJO) to a DBObject; A special field will be added to keep track of the class: {@link Mapper#CLASS_NAME_FIELDNAME}
* </p>
*
* @param entity The POJO
*/
public DBObject toDBObject(Object entity) {
return toDBObject(entity, null);
}
/**
* <p> Converts an entity (POJO) to a DBObject (for use with low-level driver); A special field will be added to keep track of the class: {@link DefaultMapper#CLASS_NAME_FIELDNAME} </p>
*
* @param entity The POJO
* @param involvedObjects A Map of (already converted) POJOs
*/
public DBObject toDBObject(Object entity, Map<Object, DBObject> involvedObjects) {
return toDBObject(entity, involvedObjects, true);
}
DBObject toDBObject(Object entity, Map<Object, DBObject> involvedObjects, boolean lifecycle) {
DBObject dbObject = new BasicDBObject();
MappedClass mc = getMappedClass(entity);
if (mc.getEntityAnnotation() == null || mc.isClassNameStored())
dbObject.put(CLASS_NAME_FIELDNAME, entity.getClass().getName());
if (lifecycle)
dbObject = (DBObject) mc.callLifecycleMethods(PrePersist.class, entity, dbObject, this);
for (MappedField mf : mc.getMappedFields()) {
try {
writeMappedField(dbObject, mf, entity, involvedObjects);
} catch (Exception e) {
throw new MappingException("Error mapping field:" + mf.getFullName(), e);
}
}
if (involvedObjects != null)
involvedObjects.put(entity, dbObject);
if (lifecycle)
mc.callLifecycleMethods(PreSave.class, entity, dbObject, this);
return dbObject;
}
public Object fromDBObject(DBObject dbObject, Object entity, EntityCache cache) {
//hack to bypass things and just read the value.
if (entity instanceof MappedField) {
readMappedField(dbObject, (MappedField) entity, entity, cache);
return entity;
}
// check the history key (a key is the namespace + id)
if (dbObject.containsField(ID_KEY) && getMappedClass(entity).getIdField() != null
&& getMappedClass(entity).getEntityAnnotation() != null) {
Key key = new Key(entity.getClass(), dbObject.get(ID_KEY));
Object cachedInstance = cache.getEntity(key);
if (cachedInstance != null)
return cachedInstance;
else
cache.putEntity(key, entity); // to avoid stackOverflow in recursive refs
}
MappedClass mc = getMappedClass(entity);
dbObject = (DBObject) mc.callLifecycleMethods(PreLoad.class, entity, dbObject, this);
try {
for (MappedField mf : mc.getMappedFields()) {
readMappedField(dbObject, mf, entity, cache);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
if (dbObject.containsField(ID_KEY) && getMappedClass(entity).getIdField() != null) {
Key key = new Key(entity.getClass(), dbObject.get(ID_KEY));
cache.putEntity(key, entity);
}
mc.callLifecycleMethods(PostLoad.class, entity, dbObject, this);
return entity;
}
protected void readMappedField(DBObject dbObject, MappedField mf, Object entity, EntityCache cache) {
if (mf.hasAnnotation(Property.class) || mf.hasAnnotation(Serialized.class)
|| mf.isTypeMongoCompatible() || converters.hasSimpleValueConverter(mf))
opts.valueMapper.fromDBObject(dbObject, mf, entity, cache, this);
else if (mf.hasAnnotation(Embedded.class))
opts.embeddedMapper.fromDBObject(dbObject, mf, entity, cache, this);
else if (mf.hasAnnotation(Reference.class))
opts.referenceMapper.fromDBObject(dbObject, mf, entity, cache, this);
else {
opts.defaultMapper.fromDBObject(dbObject, mf, entity, cache, this);
}
}
protected void writeMappedField(DBObject dbObject, MappedField mf, Object entity, Map<Object, DBObject> involvedObjects) {
Class<? extends Annotation> annType = null;
//skip not saved fields.
if (mf.hasAnnotation(NotSaved.class))
return;
// get the annotation from the field.
for (Class<? extends Annotation> testType : new Class[]{Property.class,
Embedded.class,
Serialized.class,
Reference.class}) {
if (mf.hasAnnotation(testType)) {
annType = testType;
break;
}
}
if (Property.class.equals(annType) || Serialized.class.equals(annType) || mf.isTypeMongoCompatible() ||
(converters.hasSimpleValueConverter(mf) || (converters.hasSimpleValueConverter(mf.getFieldValue(entity)))))
opts.valueMapper.toDBObject(entity, mf, dbObject, involvedObjects, this);
else if (Reference.class.equals(annType))
opts.referenceMapper.toDBObject(entity, mf, dbObject, involvedObjects, this);
else if (Embedded.class.equals(annType)) {
opts.embeddedMapper.toDBObject(entity, mf, dbObject, involvedObjects, this);
} else {
if (log.isDebugEnabled()) {
log.debug("No annotation was found, using default mapper {} for {}", opts.defaultMapper, mf);
}
opts.defaultMapper.toDBObject(entity, mf, dbObject, involvedObjects, this);
}
}
// TODO might be better to expose via some "options" object?
public DefaultConverters getConverters() {
return converters;
}
public EntityCache createEntityCache() {
return entityCacheFactory.create();
}
public <T> Key<T> refToKey(DBRef ref) {
if (ref == null) return null;
return new Key<T>(ref.getRef(), ref.getId());
}
public DBRef keyToRef(Key key) {
if (key == null) return null;
if (key.getKindClass() == null && key.getKind() == null)
throw new IllegalStateException("How can it be missing both?");
if (key.getKind() == null)
key.setKind(getCollectionName(key.getKindClass()));
return new DBRef(null, key.getKind(), toMongoObject(key.getId(), false));
}
public String updateKind(Key key) {
if (key.getKind() == null && key.getKindClass() == null)
throw new IllegalStateException("Key is invalid! " + toString());
else if (key.getKind() == null)
key.setKind(getMappedClass(key.getKindClass()).getCollectionName());
return key.getKind();
}
public <T> Key<T> createKey(Class<T> clazz, Serializable id) {
return new Key<T>(clazz, id);
}
public <T> Key<T> createKey(Class<T> clazz, Object id) {
if (id instanceof Serializable)
return createKey(clazz, (Serializable) id);
//TODO: cache the encoders, maybe use the pool version of the buffer that the driver does.
BSONEncoder enc = new BasicBSONEncoder();
return new Key<T>(clazz, enc.encode(toDBObject(id)));
}
/**
* Validate the path, and value type, returning the {@link MappedField} for the field at the path
*/
public static MappedField validate(Class clazz, Mapper mapr, StringBuffer origProp, FilterOperator op, Object val, boolean validateNames, boolean validateTypes) {
//TODO: cache validations (in static?).
MappedField mf = null;
String prop = origProp.toString();
boolean hasTranslations = false;
if (validateNames) {
String[] parts = prop.split("\\.");
if (clazz == null) return null;
MappedClass mc = mapr.getMappedClass(clazz);
for (int i = 0; ; ) {
String part = parts[i];
mf = mc.getMappedField(part);
//translate from java field name to stored field name
if (mf == null) {
mf = mc.getMappedFieldByJavaField(part);
if (mf == null)
throw new ValidationException("The field '" + part + "' could not be found in '" + clazz.getName() +
"' while validating - " + prop +
"; if you wish to continue please disable validation.");
hasTranslations = true;
parts[i] = mf.getNameToStore();
}
i++;
if (mf.isMap()) {
//skip the map key validation, and move to the next part
i++;
}
//catch people trying to search/update into @Reference/@Serialized fields
if (i < parts.length && !canQueryPast(mf))
throw new ValidationException("Can not use dot-notation past '" + part + "' could not be found in '" + clazz.getName() + "' while validating - " + prop);
if (i >= parts.length) break;
//get the next MappedClass for the next field validation
mc = mapr.getMappedClass((mf.isSingleValue()) ? mf.getType() : mf.getSubClass());
}
//record new property string if there has been a translation to any part
if (hasTranslations) {
origProp.setLength(0); // clear existing content
origProp.append(parts[0]);
for (int i = 1; i < parts.length; i++) {
origProp.append('.');
origProp.append(parts[i]);
}
}
if (validateTypes)
if ((mf.isSingleValue() && !isCompatibleForOperator(mf.getType(), op, val)) ||
((mf.isMultipleValues() && !(isCompatibleForOperator(mf.getSubClass(), op, val) || isCompatibleForOperator(mf.getType(), op, val))))) {
if (log.isWarningEnabled()) {
Throwable t = new Throwable();
StackTraceElement ste = getFirstClientLine(t);
log.warning("The type(s) for the query/update may be inconsistent; using an instance of type '"
+ val.getClass().getName() + "' for the field '" + mf.getDeclaringClass().getName() + "." + mf.getJavaFieldName()
+ "' which is declared as '" + mf.getType().getName() + (ste == null ? "'" : "'\r\n --@--" + ste));
if (log.isDebugEnabled())
log.debug("Location of warning:\r\n", t);
}
}
}
return mf;
}
/**
* Return the first {@link StackTraceElement} not in our code (package).
*/
public static StackTraceElement getFirstClientLine(Throwable t) {
for (StackTraceElement ste : t.getStackTrace())
if (!ste.getClassName().startsWith("com.github.jmkgreen.morphia") &&
!ste.getClassName().startsWith("sun.reflect") &&
!ste.getClassName().startsWith("org.junit") &&
!ste.getClassName().startsWith("org.eclipse") &&
!ste.getClassName().startsWith("java.lang"))
return ste;
return null;
}
/**
* Returns if the MappedField is a Reference or Serialized
*/
public static boolean canQueryPast(MappedField mf) {
return !(mf.hasAnnotation(Reference.class) || mf.hasAnnotation(Serialized.class));
}
public static boolean isCompatibleForOperator(Class<?> type, FilterOperator op, Object value) {
if (value == null || type == null)
return true;
else if (op.equals(FilterOperator.EXISTS) && (value instanceof Boolean))
return true;
else if (op.equals(FilterOperator.IN) && (value.getClass().isArray() || Iterable.class.isAssignableFrom(value.getClass()) || Map.class.isAssignableFrom(value.getClass())))
return true;
else if (op.equals(FilterOperator.NOT_IN) && (value.getClass().isArray() || Iterable.class.isAssignableFrom(value.getClass()) || Map.class.isAssignableFrom(value.getClass())))
return true;
else if (op.equals(FilterOperator.ALL) && (value.getClass().isArray() || Iterable.class.isAssignableFrom(value.getClass()) || Map.class.isAssignableFrom(value.getClass())))
return true;
else if (value instanceof Integer && (int.class.equals(type) || long.class.equals(type) || Long.class.equals(type)))
return true;
else if ((value instanceof Integer || value instanceof Long) && (double.class.equals(type) || Double.class.equals(type)))
return true;
else if (value instanceof Pattern && String.class.equals(type))
return true;
else if (value.getClass().getAnnotation(Entity.class) != null && Key.class.equals(type))
return true;
else if (value instanceof List<?>)
return true;
else if (!value.getClass().isAssignableFrom(type) &&
//hack to let Long match long, and so on
!value.getClass().getSimpleName().toLowerCase().equals(type.getSimpleName().toLowerCase())) {
return false;
}
return true;
}
public Class<?> getClassFromKind(String kind) {
Set<MappedClass> mcs = mappedClassesByCollection.get(kind);
if (mcs.isEmpty())
throw new MappingException("The collection '" + kind + "' is not mapped to a java class.");
if (mcs.size() > 1)
if (log.isInfoEnabled())
log.info("Found more than one class mapped to collection '" + kind + "'" + mcs);
return mcs.iterator().next().getClazz();
}
public boolean isCached(Class<?> clazz) {
return instanceCache.containsKey(clazz);
}
public void cacheClass(Class<?> clazz, Object instance) throws ConcurrentModificationException {
Object previousKey = instanceCache.put(clazz, instance);
if (previousKey != null) {
throw new ConcurrentModificationException("Duplicate class created "+clazz);
}
}
public Object getCachedClass(Class<?> clazz) {
return instanceCache.get(clazz);
}
public LazyProxyFactory getProxyFactory() {
return proxyFactory;
}
public void setProxyFactory(LazyProxyFactory proxyFactory) {
this.proxyFactory = proxyFactory;
}
public DatastoreProvider getDatastoreProvider() {
return datastoreProvider;
}
public void setDatastoreProvider(DatastoreProvider datastoreProvider) {
this.datastoreProvider = datastoreProvider;
}
}