/** * Author: OMAROMAN * Date: 9/19/11 * Time: 10:25 AM */ package play.modules.chronostamp; import javassist.CannotCompileException; import javassist.CtClass; import javassist.CtField; import javassist.CtMethod; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.ConstPool; import javassist.bytecode.annotation.Annotation; import javassist.bytecode.annotation.EnumMemberValue; import net.parnassoft.playutilities.EnhancerUtility; import play.classloading.ApplicationClasses.ApplicationClass; import play.classloading.enhancers.Enhancer; import javax.persistence.PreUpdate; import javax.persistence.Temporal; import javax.persistence.TemporalType; public class ChronostampEnhancer extends Enhancer { private CtClass ctClass; private ConstPool constPool; @Override public void enhanceThisClass(ApplicationClass appClass) throws Exception { // Initialize member fields ctClass = makeClass(appClass); constPool = ctClass.getClassFile().getConstPool(); if (!isEnhanceableThisClass()) { return; // Do NOT enhance this class } createChronostampFields(); createMethodOnPreUpdate(); createGettersMethods(); // Done - Enhance Class. appClass.enhancedByteCode = ctClass.toBytecode(); ctClass.defrost(); } private boolean isEnhanceableThisClass() throws Exception { // Only enhance model classes. if (!EnhancerUtility.isAModel(classPool, ctClass)) { return false; } // Only enhance model classes with Entity annotation. if (!EnhancerUtility.isAnEntity(ctClass)) { return false; } // Skip enhance model classes if are annotated with @NoTracking if (isClassAnnotatedWithNoChronostamp()) { return false; } // Only enhance model classes without created_at and updated_at attributes if (EnhancerUtility.hasField(ctClass, "created_at") || EnhancerUtility.hasField(ctClass, "updated_at")) { return false; } return true; // this class is enhanceable } private boolean isClassAnnotatedWithNoChronostamp() throws Exception { return EnhancerUtility.hasAnnotation(ctClass, NoChronostamp.class.getName()); } private void createChronostampFields() throws CannotCompileException { EnumMemberValue enumValue; // ----- Add created_at property ----- CtField created_at = CtField.make("private java.util.Date created_at = new java.util.Date();", ctClass); ctClass.addField(created_at); // ----- Add updated_at property ----- CtField updated_at = CtField.make("private java.util.Date updated_at = new java.util.Date(created_at.getTime());", ctClass); ctClass.addField(updated_at); // ----- Add annotation @Temporal(TemporalType.TIMESTAMP) to created_at and updated_at fields ----- AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag); Annotation annotation = new Annotation(Temporal.class.getName(), constPool); enumValue = new EnumMemberValue(constPool); enumValue.setType(TemporalType.class.getName()); enumValue.setValue("TIMESTAMP"); annotation.addMemberValue("value", enumValue); attr.addAnnotation(annotation); created_at.getFieldInfo().addAttribute(attr); updated_at.getFieldInfo().addAttribute(attr); // // Annotate created_at & updated_at with @CRUD.Exclude attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag); annotation = new Annotation(play.modules.crud.annotations.Exclude.class.getName(), constPool); attr.addAnnotation(annotation); created_at.getFieldInfo().addAttribute(attr); updated_at.getFieldInfo().addAttribute(attr); } private void createMethodOnPreUpdate() throws Exception { // ----- Add onPreUpdate() method ----- // Check if there's a method annotated with @PreUpdate CtMethod methodWithPreUpdateAnnot = EnhancerUtility.getMethodAnnotatedWith(ctClass, PreUpdate.class.getName()); if (methodWithPreUpdateAnnot != null) { methodWithPreUpdateAnnot.insertBefore("updated_at = new java.util.Date();"); } else { // ----- Add onUpdate() method ----- String code = "public void onPreUpdate() { updated_at = new java.util.Date(); }"; final CtMethod onUpdate = CtMethod.make(code, ctClass); ctClass.addMethod(onUpdate); // ----- Add annotation @PreUpdate to onPreUpdate() method ----- Annotation annotation = new Annotation(PreUpdate.class.getName(), constPool); AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag); attr.addAnnotation(annotation); onUpdate.getMethodInfo().addAttribute(attr); } } private void createGettersMethods() throws CannotCompileException { // ----- GETTERS ----- String code; // ----- Add getCreated_at() method ----- code = "public java.util.Date getCreated_at() { return this.created_at; }"; final CtMethod getCreated_at = CtMethod.make(code, ctClass); ctClass.addMethod(getCreated_at); // ----- Add getCreatedAt() method ----- code = "public java.util.Date getCreatedAt() { return this.created_at; }"; final CtMethod getCreatedAt = CtMethod.make(code, ctClass); ctClass.addMethod(getCreatedAt); // ----- Add getUpdated_at() method ----- code = "public java.util.Date getUpdated_at() { return this.updated_at; }"; final CtMethod getUpdated_at = CtMethod.make(code, ctClass); ctClass.addMethod(getUpdated_at); // ----- Add getUpdatedAt() method ----- code = "public java.util.Date getUpdatedAt() { return this.updated_at; }"; final CtMethod getUpdatedAt = CtMethod.make(code, ctClass); ctClass.addMethod(getUpdatedAt); } // /** // * THIS METHOD WAS DONATED TO PLAY!, NOW IT CLASHES (v1.2.4), SO IT'S NO NEEDED ANY LONGER // * Test if a method has the provided annotation // * @param ctMethod the javassist method representation // * @param annotation fully qualified name of the annotation class eg."javax.persistence.Entity" // * @return true if field has the annotation // * @throws java.lang.ClassNotFoundException // */ // private boolean hasAnnotation(CtMethod ctMethod, String annotation) throws ClassNotFoundException { // for (Object object : ctMethod.getAvailableAnnotations()) { // java.lang.annotation.Annotation ann = (java.lang.annotation.Annotation) object; // if (ann.annotationType().getName().equals(annotation)) { // return true; // } // } // return false; // } }