Package com.github.jmkgreen.morphia.mapping

Source Code of com.github.jmkgreen.morphia.mapping.MappedClass$ClassMethodPair

/**
*
*/
package com.github.jmkgreen.morphia.mapping;

import com.github.jmkgreen.morphia.EntityInterceptor;
import com.github.jmkgreen.morphia.annotations.*;
import com.github.jmkgreen.morphia.logging.Logr;
import com.github.jmkgreen.morphia.logging.MorphiaLoggerFactory;
import com.github.jmkgreen.morphia.mapping.validation.MappingValidator;
import com.github.jmkgreen.morphia.utils.ReflectionUtils;
import com.mongodb.DBObject;
import com.mongodb.ReadPreference;
import com.mongodb.WriteConcern;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Represents a mapped class between the MongoDB DBObject and the java POJO.
* <p/>
* This class will validate classes to make sure they meet the requirement for persistence.
*
* @author Scott Hernandez
*/
@SuppressWarnings("unchecked")
public class MappedClass {
    private static final Logr log = MorphiaLoggerFactory.get(MappedClass.class);

    private static class ClassMethodPair {
        Class<?> clazz;
        Method method;

        public ClassMethodPair(Class<?> c, Method m) {
            clazz = c;
            method = m;
        }
    }

    /**
     * special fields representing the Key of the object
     */
    private Field idField;

    /**
     * special annotations representing the type the object
     */
    private Entity entityAn;
    private Embedded embeddedAn;
    //    private Polymorphic polymorphicAn;

    /**
     * Annotations we are interested in looking for.
     */
    public static List<Class<? extends Annotation>> interestingAnnotations = new ArrayList<Class<? extends Annotation>>(Arrays.asList(
            Embedded.class,
            Entity.class,
            Polymorphic.class,
            EntityListeners.class,
            Version.class,
            Converters.class,
            Indexes.class));
    /**
     * Annotations interesting for life-cycle events
     */
    private static Class<? extends Annotation>[] lifecycleAnnotations = new Class[]{
            PrePersist.class,
            PreSave.class,
            PostPersist.class,
            PreLoad.class,
            PostLoad.class};

    /**
     * Annotations we were interested in, and found.
     */
    private Map<Class<? extends Annotation>, ArrayList<Annotation>> foundAnnotations = new HashMap<Class<? extends Annotation>, ArrayList<Annotation>>();

    /**
     * Methods which are life-cycle events
     */
    private Map<Class<? extends Annotation>, List<ClassMethodPair>> lifecycleMethods = new HashMap<Class<? extends Annotation>, List<ClassMethodPair>>();

    /**
     * a list of the fields to map
     */
    private List<MappedField> mappedFields = new ArrayList<MappedField>();

    /**
     * the type we are mapping to/from
     */
    private Class<?> clazz;
    Mapper mapr;

    /**
     * constructor
     */
    public MappedClass(Class<?> clazz, Mapper mapr) {
        this.mapr = mapr;
        this.clazz = clazz;

        if (log.isTraceEnabled())
            log.trace("Creating MappedClass for " + clazz);

        basicValidate();
        discover();

        if (log.isDebugEnabled())
            log.debug("MappedClass done: " + toString());
    }

    /**
     * @return the idField
     */
    public Field getIdField() {
        return idField;
    }

    /**
     * @return the entityAn
     */
    public Entity getEntityAnnotation() {
        return entityAn;
    }

    /**
     * @return the embeddedAn
     */
    public Embedded getEmbeddedAnnotation() {
        return embeddedAn;
    }

    /**
     * @return the relevantAnnotations
     */
    public Map<Class<? extends Annotation>, ArrayList<Annotation>> getRelevantAnnotations() {
        return foundAnnotations;
    }

    /**
     * Returns the first found Annotation, or null.
     *
     * @param clazz The Annotation to find.
     * @return First found Annotation or null of none found.
     */
    public Annotation getFirstAnnotation(Class<? extends Annotation> clazz) {
        ArrayList<Annotation> found = foundAnnotations.get(clazz);
        if (found == null || found.isEmpty()) {
            return null;
        }
        return found.get(0);
    }

    /**
     * Returns the last found Annotation instance or null.
     *
     * @param clazz The Annotation to find.
     * @return the instance if it was found, if more than one was found, the last one added
     */
    public Annotation getAnnotation(Class<? extends Annotation> clazz) {
        ArrayList<Annotation> found = foundAnnotations.get(clazz);
        return (found != null && found.size() > 0) ? found.get(found.size() - 1) : null;
    }

    /**
     * Returns all found Annotations for given Class (hierarchically).
     *
     * @return The ArrayList if found, else null
     */
    public ArrayList<Annotation> getAnnotations(Class<? extends Annotation> clazz) {
        ArrayList<Annotation> found = foundAnnotations.get(clazz);
        return found;
    }

    public List<MappedField> getMappedFields() {
        return mappedFields;
    }

    /**
     * @return the mappedFields
     * @deprecated Use {@link #getMappedFields()} instead.
     */
    public List<MappedField> getPersistenceFields() {
        return mappedFields;
    }

    /**
     * @return the collName
     */
    public String getCollectionName() {
        return (entityAn == null || entityAn.value().equals(Mapper.IGNORED_FIELDNAME)) ? clazz.getSimpleName() : entityAn.value();
    }

    /**
     * @return the clazz
     */
    public Class<?> getClazz() {
        return clazz;
    }

    /**
     * @return the Mapper this class is bound to
     */
    public Mapper getMapper() {
        return mapr;
    }

    public MappedField getMappedIdField() {
        return getFieldsAnnotatedWith(Id.class, javax.persistence.Id.class).get(0);
    }

    /**
     * Returns any Mongo ReadPreference stated on this class.
     * @return
     */
    public ReadPreference getReadPreference() {
        Entity entity = (Entity) getFirstAnnotation(Entity.class);
        if (entity == null) {
            return null;
        }

        if  (entity.queryNonPrimary()) {
            return ReadPreference.secondaryPreferred();
        }
        return null;
    }

    public CappedAt getCappedAt() {
        Entity entity = (Entity) getFirstAnnotation(Entity.class);
        if (entity == null) {
            return null;
        }

        return entity.cap();
    }

    /**
     * Returns a WriteConcern for this mapped class - by default it is SAFE.
     *
     * @return
     */
    public WriteConcern getWriteConcern() {
        WriteConcern wc = WriteConcern.SAFE;

        Entity entity = (Entity) getFirstAnnotation(Entity.class);
        if (entity == null) {
            return wc;
        }

        if (entity.concern() != null && !entity.concern().isEmpty()) {
            wc = WriteConcern.valueOf(entity.concern());
        }

        return wc;
    }

    /**
     * Should a class name be stored with entities?
     *
     * @return
     */
    public boolean isClassNameStored() {
        Entity entity = (Entity) getFirstAnnotation(Entity.class);
        if (entity == null) {
            return false;
        }

        return !entity.noClassnameStored();
    }

    /**
     * Do any of the fields of this class have {@link com.github.jmkgreen.morphia.annotations.Version}?
     * @return
     */
    public boolean hasVersioning() {
        return !getFieldsAnnotatedWith(Version.class).isEmpty();
    }

    /*
      * Update mappings based on fields/annotations.
      * TODO: Remove this and make these fields dynamic or auto-set some other way
      */
    public void update() {
        embeddedAn = (Embedded) getAnnotation(Embedded.class);
        entityAn = (Entity) getFirstAnnotation(Entity.class);
        // polymorphicAn = (Polymorphic) getAnnotation(Polymorphic.class);
        List<MappedField> fields = getFieldsAnnotatedWith(Id.class, javax.persistence.Id.class);
        if (fields != null && fields.size() > 0)
            idField = fields.get(0).field;


    }

    public List<ClassMethodPair> getLifecycleMethods(Class<Annotation> clazz) {
        return lifecycleMethods.get(clazz);
    }

    protected void basicValidate() {
        boolean isStatic = Modifier.isStatic(clazz.getModifiers());
        if (!isStatic && clazz.isMemberClass())
            throw new MappingException("Cannot use non-static inner class: " + clazz + ". Please make static.");
    }

    /**
     * Discovers interesting (that we care about) things about the class.
     */
    protected void discover() {
        for (Class<? extends Annotation> c : interestingAnnotations) {
            addAnnotation(c);
        }

        List<Class<?>> lifecycleClasses = new ArrayList<Class<?>>();
        lifecycleClasses.add(clazz);

        EntityListeners entityLisAnn = (EntityListeners) getAnnotation(EntityListeners.class);
        if (entityLisAnn != null && entityLisAnn.value() != null && entityLisAnn.value().length != 0)
            for (Class<?> c : entityLisAnn.value())
                lifecycleClasses.add(c);

        for (Class<?> cls : lifecycleClasses) {
            for (Method m : ReflectionUtils.getDeclaredAndInheritedMethods(cls)) {
                for (Class<? extends Annotation> c : lifecycleAnnotations) {
                    if (m.isAnnotationPresent(c)) {
                        addLifecycleEventMethod(c, m, cls.equals(clazz) ? null : cls);
                    }
                }
            }
        }

        update();

        for (Field field : ReflectionUtils.getDeclaredAndInheritedFields(clazz, true)) {
            field.setAccessible(true);
            int fieldMods = field.getModifiers();
            if (field.isAnnotationPresent(Transient.class))
                continue;
            else if (field.isSynthetic() && (fieldMods & Modifier.TRANSIENT) == Modifier.TRANSIENT)
                continue;
            else if (mapr.getOptions().actLikeSerializer && ((fieldMods & Modifier.TRANSIENT) == Modifier.TRANSIENT))
                continue;
            else if (mapr.getOptions().ignoreFinals && ((fieldMods & Modifier.FINAL) == Modifier.FINAL))
                continue;
            else if (field.isAnnotationPresent(Id.class) || field.isAnnotationPresent(javax.persistence.Id.class)) {
                MappedField mf = new MappedField(field, clazz);
                mappedFields.add(mf);
                update();
            } else if (field.isAnnotationPresent(Property.class) ||
                    field.isAnnotationPresent(Reference.class) ||
                    field.isAnnotationPresent(Embedded.class) ||
                    field.isAnnotationPresent(Serialized.class) ||
                    isSupportedType(field.getType()) ||
                    ReflectionUtils.implementsInterface(field.getType(), Serializable.class)) {
                mappedFields.add(new MappedField(field, clazz));
            } else {
                if (mapr.getOptions().defaultMapper != null)
                    mappedFields.add(new MappedField(field, clazz));
                else if (log.isWarningEnabled())
                    log.warning("Ignoring (will not persist) field: " + clazz.getName() + "." + field.getName() + " [type:" + field.getType().getName() + "]");
            }
        }
    }

    private void addLifecycleEventMethod(Class<? extends Annotation> lceClazz, Method m, Class<?> clazz) {
        ClassMethodPair cm = new ClassMethodPair(clazz, m);
        if (lifecycleMethods.containsKey(lceClazz))
            lifecycleMethods.get(lceClazz).add(cm);
        else {
            ArrayList<ClassMethodPair> methods = new ArrayList<ClassMethodPair>();
            methods.add(cm);
            lifecycleMethods.put(lceClazz, methods);
        }
    }

    /**
     * Adds the given Annotation to the internal list for the given Class.
     *
     * @param clazz
     * @param ann
     */
    public void addAnnotation(Class<? extends Annotation> clazz, Annotation ann) {
        if (ann == null || clazz == null) return;

        if (!this.foundAnnotations.containsKey(clazz)) {
            ArrayList<Annotation> list = new ArrayList<Annotation>();
            foundAnnotations.put(clazz, list);
        }

        foundAnnotations.get(clazz).add(ann);
    }

    /**
     * Adds the annotation, if it exists on the field.
     *
     * @param clazz
     */
    public void addAnnotation(Class<? extends Annotation> clazz) {
        ArrayList<? extends Annotation> anns = ReflectionUtils.getAnnotations(getClazz(), clazz);
        for (Annotation ann : anns) {
            addAnnotation(clazz, ann);
        }
    }

    @Override
    public String toString() {
        return "MappedClass - kind:" + this.getCollectionName() + " for " + this.getClazz().getName() + " fields:" + mappedFields;
    }

    /**
     * Returns fields annotated with the clazz
     */
    public List<MappedField> getFieldsAnnotatedWith(Class<? extends Annotation>... classes) {
        List<MappedField> results = new ArrayList<MappedField>();
        for (MappedField mf : mappedFields) {
            for (Class<? extends Annotation> clazz : classes) {
                if (mf.foundAnnotations.containsKey(clazz))
                    results.add(mf);
            }
        }
        return results;
    }

    /**
     * Returns the MappedField by the name that it will stored in mongodb as
     */
    public MappedField getMappedField(String storedName) {
        for (MappedField mf : mappedFields)
            for (String n : mf.getLoadNames())
                if (storedName.equals(n))
                    return mf;

        return null;
    }

    /**
     * Check java field name that will stored in mongodb
     */
    public boolean containsJavaFieldName(String name) {
        return getMappedField(name) != null;
    }

    /**
     * Returns MappedField for a given java field name on the this MappedClass
     */
    public MappedField getMappedFieldByJavaField(String name) {
        for (MappedField mf : mappedFields)
            if (name.equals(mf.getJavaFieldName())) return mf;

        return null;
    }

    /**
     * Checks to see if it a Map/Set/List or a property supported by the MangoDB java driver
     */
    public static boolean isSupportedType(Class<?> clazz) {
        if (ReflectionUtils.isPropertyType(clazz)) return true;
        if (clazz.isArray() || Map.class.isAssignableFrom(clazz) || Iterable.class.isAssignableFrom(clazz)) {
            Class<?> subType = null;
            if (clazz.isArray()) subType = clazz.getComponentType();
            else subType = ReflectionUtils.getParameterizedClass(clazz);

            //get component type, String.class from List<String>
            if (subType != null && subType != Object.class && !ReflectionUtils.isPropertyType(subType))
                return false;

            //either no componentType or it is an allowed type
            return true;
        }
        return false;
    }

    @SuppressWarnings("deprecation")
    public void validate() {
        new MappingValidator().validate(this);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Class<?>) return equals((Class<?>) obj);
        else if (obj instanceof MappedClass) return equals((MappedClass) obj);
        else return false;
    }

    public boolean equals(MappedClass clazz) {
        return this.getClazz().equals(clazz.getClazz());
    }

    public boolean equals(Class<?> clazz) {
        return this.getClazz().equals(clazz);
    }

    @Override
    public int hashCode() {
        return this.getClazz().hashCode();
    }

    /**
     * Call the lifecycle methods on the
     */
    public DBObject callLifecycleMethods(Class<? extends Annotation> event, Object entity, DBObject dbObj, Mapper mapr) {
        List<ClassMethodPair> methodPairs = getLifecycleMethods((Class<Annotation>) event);
        DBObject retDbObj = dbObj;
        try {
            Object tempObj = null;
            if (methodPairs != null) {
                HashMap<Class<?>, Object> toCall = new HashMap<Class<?>, Object>((int) (methodPairs.size() * 1.3));
                for (ClassMethodPair cm : methodPairs)
                    toCall.put(cm.clazz, null);
                for (Class<?> c : toCall.keySet())
                    if (c != null)
                        toCall.put(c, getOrCreateInstance(c));

                for (ClassMethodPair cm : methodPairs) {
                    Method method = cm.method;
                    Class<?> type = cm.clazz;

                    Object inst = toCall.get(type);
                    method.setAccessible(true);

                    if (log.isDebugEnabled())
                        log.debug("Calling lifecycle method(@" + event.getSimpleName() + " " + method + ") on " + inst + "");

                    if (inst == null)
                        if (method.getParameterTypes().length == 0)
                            tempObj = method.invoke(entity);
                        else
                            tempObj = method.invoke(entity, retDbObj);
                    else if (method.getParameterTypes().length == 0)
                        tempObj = method.invoke(inst);
                    else if (method.getParameterTypes().length == 1)
                        tempObj = method.invoke(inst, entity);
                    else
                        tempObj = method.invoke(inst, entity, retDbObj);

                    if (tempObj != null)
                        retDbObj = (DBObject) tempObj;
                }
            }

            callGlobalInterceptors(event, entity, dbObj, mapr, mapr.getInterceptors());
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }

        return retDbObj;
    }

    private Object getOrCreateInstance(Class<?> clazz) {
        if (mapr.isCached(clazz))
            return mapr.getCachedClass(clazz);

        Object o = mapr.getOptions().objectFactory.createInstance(clazz);
        try {
          mapr.cacheClass(clazz, o);
        } catch (ConcurrentModificationException duplicate) {
            if (log.isErrorEnabled())
                log.error("Race-condition",duplicate);
        }
        return o;

    }

    private void callGlobalInterceptors(Class<? extends Annotation> event, Object entity, DBObject dbObj, Mapper mapr,
                                        Collection<EntityInterceptor> interceptors) {
        for (EntityInterceptor ei : interceptors) {
            if (log.isDebugEnabled())
                log.debug("Calling interceptor method " + event.getSimpleName() + " on " + ei);

            if (event.equals(PreLoad.class)) ei.preLoad(entity, dbObj, mapr);
            else if (event.equals(PostLoad.class)) ei.postLoad(entity, dbObj, mapr);
            else if (event.equals(PrePersist.class)) ei.prePersist(entity, dbObj, mapr);
            else if (event.equals(PreSave.class)) ei.preSave(entity, dbObj, mapr);
            else if (event.equals(PostPersist.class)) ei.postPersist(entity, dbObj, mapr);
        }
    }


}
TOP

Related Classes of com.github.jmkgreen.morphia.mapping.MappedClass$ClassMethodPair

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.