All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.code.morphia.mapping.Mapper Maven / Gradle / Ivy

The newest version!
/**
 * 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.google.code.morphia.mapping;


import java.io.IOException;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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 org.bson.BSONEncoder;
import org.bson.BasicBSONEncoder;
import com.google.code.morphia.EntityInterceptor;
import com.google.code.morphia.Key;
import com.google.code.morphia.annotations.Converters;
import com.google.code.morphia.annotations.Embedded;
import com.google.code.morphia.annotations.Entity;
import com.google.code.morphia.annotations.Id;
import com.google.code.morphia.annotations.NotSaved;
import com.google.code.morphia.annotations.PostLoad;
import com.google.code.morphia.annotations.PreLoad;
import com.google.code.morphia.annotations.PrePersist;
import com.google.code.morphia.annotations.PreSave;
import com.google.code.morphia.annotations.Property;
import com.google.code.morphia.annotations.Reference;
import com.google.code.morphia.annotations.Serialized;
import com.google.code.morphia.converters.DefaultConverters;
import com.google.code.morphia.converters.TypeConverter;
import com.google.code.morphia.logging.Logr;
import com.google.code.morphia.logging.MorphiaLoggerFactory;
import com.google.code.morphia.mapping.cache.DefaultEntityCache;
import com.google.code.morphia.mapping.cache.EntityCache;
import com.google.code.morphia.mapping.lazy.DatastoreProvider;
import com.google.code.morphia.mapping.lazy.DefaultDatastoreProvider;
import com.google.code.morphia.mapping.lazy.LazyFeatureDependencies;
import com.google.code.morphia.mapping.lazy.LazyProxyFactory;
import com.google.code.morphia.mapping.lazy.proxy.ProxiedEntityReference;
import com.google.code.morphia.mapping.lazy.proxy.ProxyHelper;
import com.google.code.morphia.query.FilterOperator;
import com.google.code.morphia.query.ValidationException;
import com.google.code.morphia.utils.ReflectionUtils;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.DBRef;


/**
 * 

This is the heart of Morphia and takes care of mapping from/to POJOs/DBObjects

This class is thread-safe and keeps various * "cached" data which should speed up processing.

* * @author Olafur Gauti Gudmundsson * @author Scott Hernandez */ @SuppressWarnings({"unchecked", "rawtypes"}) public class Mapper { private static final Logr log = MorphiaLoggerFactory.get(Mapper.class); /** * The @{@link Id} field name that is stored with mongodb. */ public static final String ID_KEY = "_id"; /** * Special name that can never be used. Used as default for some fields to indicate default state. */ public static final String IGNORED_FIELDNAME = "."; /** * Special field used by morphia to support various possibly loading issues; will be replaced when discriminators are implemented to * support polymorphism */ public static final String CLASS_NAME_FIELDNAME = "className"; /** * Set of classes that registered by this mapper */ private final Map mappedClasses = new ConcurrentHashMap(); private final ConcurrentHashMap> mappedClassesByCollection = new ConcurrentHashMap>(); //EntityInterceptors; these are called before EntityListeners and lifecycle methods on an Entity, for all Entities private final List interceptors = new LinkedList(); //A general cache of instances of classes; used by MappedClass for EntityListener(s) final Map instanceCache = new ConcurrentHashMap(); private MapperOptions opts = new MapperOptions(); // TODO: make these configurable final LazyProxyFactory proxyFactory = LazyFeatureDependencies.createDefaultProxyFactory(); final DatastoreProvider datastoreProvider = new DefaultDatastoreProvider(); final DefaultConverters converters = new DefaultConverters(); public Mapper() { converters.setMapper(this); } public Mapper(final MapperOptions opts) { this(); this.opts = opts; } /** *

Adds an {@link EntityInterceptor}

*/ public void addInterceptor(final EntityInterceptor ei) { interceptors.add(ei); } /** *

Gets list of {@link EntityInterceptor}s

*/ public Collection getInterceptors() { return interceptors; } public MapperOptions getOptions() { return opts; } public void setOptions(final MapperOptions options) { opts = options; } public boolean isMapped(final Class c) { return mappedClasses.containsKey(c.getName()); } /** * Creates a MappedClass and validates it. */ public MappedClass addMappedClass(final Class c) { return addMappedClass(new MappedClass(c, this), true); } /** * Validates MappedClass and adds to internal cache. */ public MappedClass addMappedClass(final MappedClass mc) { return addMappedClass(mc, true); } /** * Add MappedClass to internal cache, possibly validating first. */ private MappedClass addMappedClass(final MappedClass mc, final boolean validate) { if (validate) { mc.validate(); } final Converters c = (Converters) mc.getAnnotation(Converters.class); if (c != null) { for (final Class clazz : c.value()) { if (!converters.isRegistered(clazz)) { converters.addConverter(clazz); } } } mappedClasses.put(mc.getClazz().getName(), mc); Set mcs = mappedClassesByCollection.get(mc.getCollectionName()); if (mcs == null) { mcs = new CopyOnWriteArraySet(); final Set temp = mappedClassesByCollection.putIfAbsent(mc.getCollectionName(), mcs); if (temp != null) { mcs = temp; } } mcs.add(mc); return mc; } /** * Returns collection of MappedClasses */ public Collection getMappedClasses() { return new ArrayList(mappedClasses.values()); } /** * Returns map of MappedClasses by class name */ public Map getMCMap() { return Collections.unmodifiableMap(mappedClasses); } /** *

Gets the {@link MappedClass} for the object (type). If it isn't mapped, create a new class and cache it (without validating).

*/ 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(final Object object) { if (object == null) { throw new IllegalArgumentException(); } final MappedClass mc = getMappedClass(object); return mc.getCollectionName(); } /** *

Updates the @{@link Id} fields.

* * @param entity The object to update * @param dbObj Value to update with; null means skip */ public void updateKeyInfo(final Object entity, final DBObject dbObj, final EntityCache cache) { final MappedClass mc = getMappedClass(entity); // update id field, if there. if ((mc.getIdField() != null) && (dbObj != null) && (dbObj.get(ID_KEY) != null)) { try { final MappedField mf = mc.getMappedIdField(); final Object oldIdValue = mc.getIdField().get(entity); readMappedField(dbObj, mf, entity, cache); final 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 instanceof RuntimeException) { 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, final EntityCache cache) { if (dbObject == null) { final Throwable t = new Throwable(); log.error("Somebody passed in a null dbObject; bad client!", t); return null; } Object entity; entity = opts.objectFactory.createInstance(entityClass, dbObject); entity = fromDb(dbObject, entity, cache); return entity; } /** *

Converts a java object to a mongo-compatible object (possibly a DBObject for complex mappings). Very similar to {@link * Mapper#toDBObject}

Used (mainly) by query/update operations

*/ Object toMongoObject(final Object javaObj, final boolean includeClassName) { if (javaObj == null) { return null; } Class origClass = javaObj.getClass(); if (origClass.isAnonymousClass() && origClass.getSuperclass().isEnum()) { origClass = origClass.getSuperclass(); } final Object newObj = converters.encode(origClass, javaObj); if (newObj == null) { log.warning("converted " + javaObj + " to null"); return newObj; } final Class type = newObj.getClass(); final 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; } else { //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 is Long subType = (type.isArray()) ? type.getComponentType() : ReflectionUtils.getParameterizedClass(type, (isMap) ? 1 : 0); } if (isSingleValue && !ReflectionUtils.isPropertyType(type)) { final DBObject dbObj = toDBObject(newObj); if (!includeClassName) { dbObj.removeField(CLASS_NAME_FIELDNAME); } return dbObj; } else if (newObj instanceof DBObject) { return newObj; } else if (isMap) { if (ReflectionUtils.isPropertyType(subType)) { return toDBObject(newObj); } else { final HashMap m = new HashMap(); for (final Map.Entry e : (Iterable) ((Map) newObj).entrySet()) { m.put(e.getKey(), toMongoObject(e.getValue(), includeClassName)); } return m; } //Set/List but needs elements converted } else if (!isSingleValue && !ReflectionUtils.isPropertyType(subType)) { final List values = new BasicDBList(); if (type.isArray()) { for (final Object obj : (Object[]) newObj) { values.add(toMongoObject(obj, includeClassName)); } } else { for (final Object obj : (Iterable) newObj) { values.add(toMongoObject(obj, includeClassName)); } } return values; } else { return newObj; } } } /** *

Converts a java object to a mongo-compatible object (possibly a DBObject for complex mappings). Very similar to {@link * Mapper#toDBObject}

Used (mainly) by query/update operations

*/ public Object toMongoObject(final MappedField mf, final MappedClass mc, final Object value) { 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 (isAssignable(mf, value) || isEntity(mc)) { try { if (value instanceof Iterable) { MappedClass mapped = getMappedClass(mf.getSubClass()); if(mapped != null && Key.class.isAssignableFrom(mapped.getClazz())) { mappedValue = getDBRefs((Iterable) value); } else { mappedValue = value; } } else { final Key key = (value instanceof Key) ? (Key) value : getKey(value); if (key == null) { mappedValue = toMongoObject(value, false); } else { mappedValue = keyToRef(key); if (mappedValue == value) { throw new ValidationException("cannot map to @Reference/Key/DBRef field: " + value); } } } } catch (Exception e) { log.error("Error converting value(" + value + ") to reference.", e); mappedValue = toMongoObject(value, false); } } else if (mf != null && mf.hasAnnotation(Serialized.class)) { //serialized try { mappedValue = Serializer.serialize(value, !mf.getAnnotation(Serialized.class).disableCompression()); } catch (IOException e) { throw new RuntimeException(e); } } else if (value instanceof DBObject) { //pass-through mappedValue = value; } else { mappedValue = toMongoObject(value, EmbeddedMapper.shouldSaveClassName(value, mappedValue, mf)); if (mappedValue instanceof BasicDBList) { final BasicDBList list = (BasicDBList) mappedValue; if (list.size() != 0) { if (!EmbeddedMapper.shouldSaveClassName(extractFirstElement(value), list.get(0), mf)) { for (Object o : list) { if (o instanceof DBObject) { ((DBObject) o).removeField(CLASS_NAME_FIELDNAME); } } } } } else if (mappedValue instanceof DBObject && !EmbeddedMapper.shouldSaveClassName(value, mappedValue, mf)) { ((DBObject) mappedValue).removeField(CLASS_NAME_FIELDNAME); } } return mappedValue; } private Object extractFirstElement(final Object value) { return value.getClass().isArray() ? Array.get(value, 0) : ((Iterable) value).iterator().next(); } private Object getDBRefs(final Iterable value) { final Object mappedValue; final List refs = new ArrayList(); for (final Object o : (Iterable) value) { final Key key = (o instanceof Key) ? (Key) o : getKey(o); refs.add(keyToRef(key)); } mappedValue = refs; return mappedValue; } private boolean isAssignable(final MappedField mf, final Object value) { if (mf == null) { return false; } return (mf.hasAnnotation(Reference.class) || mf.getType().isAssignableFrom(Key.class) || mf.getType().isAssignableFrom(DBRef.class) || isMultiValued(mf, value)); } private boolean isMultiValued(final MappedField mf, final Object value) { final Class subClass = mf.getSubClass(); return value instanceof Iterable && mf.isMultipleValues() && (subClass.isAssignableFrom(Key.class) || subClass.isAssignableFrom( DBRef.class)); } private boolean isEntity(final MappedClass mc) { return (mc != null && mc.getEntityAnnotation() != null); } public Object getId(final Object entity) { Object unwrapped = entity; if (unwrapped == null) { return null; } unwrapped = ProxyHelper.unwrap(unwrapped); // String keyClassName = entity.getClass().getName(); final MappedClass mc = getMappedClass(unwrapped.getClass()); // // if (getMappedClasses().containsKey(keyClassName)) // mc = getMappedClasses().get(keyClassName); // else // mc = new MappedClass(entity.getClass(), getMapper()); try { return mc.getIdField().get(unwrapped); } catch (Exception e) { return null; } } public Key getKey(final T entity) { T unwrapped = entity; if (unwrapped instanceof ProxiedEntityReference) { final ProxiedEntityReference proxy = (ProxiedEntityReference) unwrapped; return (Key) proxy.__getKey(); } unwrapped = ProxyHelper.unwrap(unwrapped); if (unwrapped instanceof Key) { return (Key) unwrapped; } final Object id = getId(unwrapped); return id == null ? null : new Key((Class) unwrapped.getClass(), id); } /** *

Converts an entity (POJO) to a DBObject; A special field will be added to keep track of the class: {@link * Mapper#CLASS_NAME_FIELDNAME}

* * @param entity The POJO */ public DBObject toDBObject(final Object entity) { return toDBObject(entity, null); } /** *

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 Mapper#CLASS_NAME_FIELDNAME}

* * @param entity The POJO * @param involvedObjects A Map of (already converted) POJOs */ public DBObject toDBObject(final Object entity, final Map involvedObjects) { return toDBObject(entity, involvedObjects, true); } DBObject toDBObject(final Object entity, final Map involvedObjects, final boolean lifecycle) { DBObject dbObject = new BasicDBObject(); final MappedClass mc = getMappedClass(entity); if (mc.getEntityAnnotation() == null || !mc.getEntityAnnotation().noClassnameStored()) { dbObject.put(CLASS_NAME_FIELDNAME, entity.getClass().getName()); } if (lifecycle) { dbObject = mc.callLifecycleMethods(PrePersist.class, entity, dbObject, this); } for (final MappedField mf : mc.getPersistenceFields()) { 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; } Object fromDb(DBObject dbObject, final Object entity, final 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) { final Key key = new Key(entity.getClass(), dbObject.get(ID_KEY)); final Object cachedInstance = cache.getEntity(key); if (cachedInstance != null) { return cachedInstance; } else { cache.putEntity(key, entity); // to avoid stackOverflow in recursive refs } } final MappedClass mc = getMappedClass(entity); dbObject = mc.callLifecycleMethods(PreLoad.class, entity, dbObject, this); for (final MappedField mf : mc.getPersistenceFields()) { readMappedField(dbObject, mf, entity, cache); } if (dbObject.containsField(ID_KEY) && getMappedClass(entity).getIdField() != null) { final Key key = new Key(entity.getClass(), dbObject.get(ID_KEY)); cache.putEntity(key, entity); } mc.callLifecycleMethods(PostLoad.class, entity, dbObject, this); return entity; } private void readMappedField(final DBObject dbObject, final MappedField mf, final Object entity, final 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); } } private void writeMappedField(final DBObject dbObject, final MappedField mf, final Object entity, final Map involvedObjects) { Class annType = null; //skip not saved fields. if (mf.hasAnnotation(NotSaved.class)) { return; } // get the annotation from the field. for (final Class 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 { log.debug("No annotation was found, using default mapper " + opts.defaultMapper + " for " + 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 new DefaultEntityCache();// TODO choose impl } public Key refToKey(final DBRef ref) { return ref == null ? null : new Key(ref.getRef(), ref.getId()); } public DBRef keyToRef(final 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(), key.getId()); } public String updateKind(final 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(); } Key createKey(final Class clazz, final Serializable id) { return new Key(clazz, id); } Key createKey(final Class clazz, final 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. final BSONEncoder enc = new BasicBSONEncoder(); return new Key(clazz, enc.encode(toDBObject(id))); } /** * Validate the path, and value type, returning the mapped field for the field at the path */ public static MappedField validate(final Class clazz, final Mapper mapper, final StringBuffer origProp, final FilterOperator op, final Object val, final boolean validateNames, final boolean validateTypes) { //TODO: cache validations (in static?). MappedField mf = null; final String prop = origProp.toString(); boolean hasTranslations = false; if (validateNames) { final String[] parts = prop.split("\\."); if (clazz == null) { return null; } MappedClass mc = mapper.getMappedClass(clazz); for (int i = 0; ; ) { final 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 = mapper.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()) { final Throwable t = new Throwable(); final 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). */ private static StackTraceElement getFirstClientLine(final Throwable t) { for (final StackTraceElement ste : t.getStackTrace()) { if (!ste.getClassName().startsWith("com.google.code.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 */ private static boolean canQueryPast(final MappedField mf) { return !(mf.hasAnnotation(Reference.class) || mf.hasAnnotation(Serialized.class)); } public static boolean isCompatibleForOperator(final Class type, final FilterOperator op, final 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.MOD) && value.getClass().isArray()) { return ReflectionUtils.isIntegerType(Array.get(value, 0) .getClass()); } 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(final String kind) { final Set 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(); } }