@Override public void onStop() { if (isEnabled) { MorphiaLoggerFactory.reset(); morphia = null; ds = null; gridfs = null; mongo.close(); } }
/** * 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> persistenceFields = new ArrayList<MappedField>(); /** the type we are mapping to/from */ private Class<?> clazz; DefaultMapper mapr; /** constructor */ public MappedClass(Class<?> clazz, DefaultMapper 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()); } 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."); } /* * 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); if (fields != null && fields.size() > 0) idField = fields.get(0).field; } /** 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)) { MappedField mf = new MappedField(field, clazz); persistenceFields.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)) { persistenceFields.add(new MappedField(field, clazz)); } else { if (mapr.getOptions().defaultMapper != null) persistenceFields.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); } public List<ClassMethodPair> getLifecycleMethods(Class<Annotation> clazz) { return lifecycleMethods.get(clazz); } /** * Adds the annotation, if it exists on the field. * * @param clazz */ private 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:" + persistenceFields; } /** Returns fields annotated with the clazz */ public List<MappedField> getFieldsAnnotatedWith(Class<? extends Annotation> clazz) { List<MappedField> results = new ArrayList<MappedField>(); for (MappedField mf : persistenceFields) { 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 : persistenceFields) 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 : persistenceFields) 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); } /** 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.instanceCache.containsKey(clazz)) return mapr.instanceCache.get(clazz); Object o = mapr.getOptions().objectFactory.createInstance(clazz); Object nullO = mapr.instanceCache.put(clazz, o); if (nullO != null) if (log.isErrorEnabled()) log.error("Race-condition, created duplicate class: " + clazz); 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); } } /** @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 releventAnnotations */ public Map<Class<? extends Annotation>, ArrayList<Annotation>> getReleventAnnotations() { 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; } /** @return the persistenceFields */ public List<MappedField> getPersistenceFields() { return persistenceFields; } /** @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).get(0); } }
@Override public void onStart() { if (!isEnabled) { return; } // Register SLF4JLogrImplFactory as Logger // @see http://nesbot.com/2011/11/28/play-2-morphia-logging-error MorphiaLoggerFactory.reset(); MorphiaLoggerFactory.registerLogger(SLF4JLogrImplFactory.class); try { Configuration morphiaConf = Configuration.root().getConfig(ConfigKey.PREFIX); if (morphiaConf == null) { throw Configuration.root() .reportError(ConfigKey.PREFIX, "Missing Morphia configuration", null); } MorphiaLogger.debug(morphiaConf); String dbName = morphiaConf.getString(ConfigKey.DB_NAME.getKey()); if (StringUtils.isBlank(dbName)) { throw morphiaConf.reportError( ConfigKey.DB_NAME.getKey(), "Missing Morphia configuration", null); } // Connect to MongoDB String seeds = morphiaConf.getString(ConfigKey.DB_SEEDS.getKey()); if (StringUtils.isNotBlank(seeds)) { mongo = connect(seeds); } else { mongo = connect( morphiaConf.getString(ConfigKey.DB_HOST.getKey()), morphiaConf.getString(ConfigKey.DB_PORT.getKey())); } morphia = new Morphia(); // To prevent problem during hot-reload if (application.isDev()) { morphia.getMapper().getOptions().objectFactory = new PlayCreator(); } // Configure validator new ValidationExtension(morphia); // Check if credentials parameters are present String username = morphiaConf.getString(ConfigKey.DB_USERNAME.getKey()); String password = morphiaConf.getString(ConfigKey.DB_PASSWORD.getKey()); if (StringUtils.isNotBlank(username) ^ StringUtils.isNotBlank(password)) { throw morphiaConf.reportError( ConfigKey.DB_NAME.getKey(), "Missing username or password", null); } // Create datastore if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) { ds = morphia.createDatastore(mongo, dbName, username, password.toCharArray()); } else { ds = morphia.createDatastore(mongo, dbName); } MorphiaLogger.debug("Datastore [%s] created", dbName); // Create GridFS String uploadCollection = morphiaConf.getString(ConfigKey.COLLECTION_UPLOADS.getKey()); if (StringUtils.isBlank(dbName)) { uploadCollection = "uploads"; MorphiaLogger.warn( "Missing Morphia configuration key [%s]. Use default value instead [%s]", ConfigKey.COLLECTION_UPLOADS, "uploads"); } gridfs = new GridFS(ds.getDB(), uploadCollection); MorphiaLogger.debug("GridFS created", ""); MorphiaLogger.debug("Add Interceptor...", ""); morphia .getMapper() .addInterceptor( new AbstractEntityInterceptor() { @Override public void postLoad(final Object ent, final DBObject dbObj, final Mapper mapr) { if (ent instanceof Model) { Model m = (Model) ent; m._post_Load(); } } }); MorphiaLogger.debug("Classes mapping...", ""); mapClasses(); MorphiaLogger.debug("End of initializing Morphia", ""); } catch (MongoException e) { MorphiaLogger.error(e, "Problem connecting MongoDB"); throw new RuntimeException(e); } catch (ClassNotFoundException e) { MorphiaLogger.error(e, "Problem mapping class"); throw new RuntimeException(e); } }