Adding annotations via bytecode enhancement

If you remember the JSON and XML plugin written in Chapter 5, which was called API plugin, there was a possible improvement at the end of the recipe: adding the XML annotations is cumbersome and a lot of work, so why not add them automatically, so every entity defined in your application does not need the usually required annotations for XML processing with JAXB.

The source code of the example is available at examples/chapter6/bytecode-enhancement-xml.

Getting ready

As usual, write a test first, which actually ensures that the annotations are really added to the model. In this case they should not have been added manually to the entity, but with the help of bytecode enhancement:

public class XmlEnhancerTest extends UnitTest {

    @Test
    public void testThingEntity() {
       XmlRootElement xmlRootElem = Thing.class.getAnnotation(XmlRootElement.class);
       assertNotNull(xmlRootElem);
       assertEquals("thing", xmlRootElem.name());
       
       XmlAccessorType anno = Thing.class.getAnnotation(XmlAccessorType.class);
       assertNotNull(anno);
       assertEquals(XmlAccessType.FIELD, anno.value());
    }
}

All this test does is to check for the XmlAccessorType and the XmlRootElement annotations inside the Thing entity. The Thing class looks like any normal entity in the following example:

@Entity
public class Thing extends Model {
   public String foo;
   public String bar;
   
   public String toString() {
      return "foo " + foo + " / bar " + bar;
   }
}

How to do it...

As most of the work has already been done in the recipe about JSON and XML, we will only line out the differences in this case and create our own module. So create a module, copy the play.plugins file, the ApiPlugin, and RenderXml class into the src/ folder, and create a new XmlEnhancer as shown in the following code:

public class XmlEnhancer extends Enhancer {

   @Override
   public void enhanceThisClass(ApplicationClass applicationClass) throws Exception {
        CtClass ctClass = makeClass(applicationClass);

        if (!ctClass.subtypeOf(classPool.get("play.db.jpa.JPABase"))) {
            return;
        }

        if (!hasAnnotation(ctClass, "javax.persistence.Entity")) {
            return;
        }

        ConstPool constpool = ctClass.getClassFile().getConstPool();
        AnnotationsAttribute attr = new 

AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
        
        if (!hasAnnotation(ctClass, "javax.xml.bind.annotation.XmlAccessorType")) {
           Annotation annot = new Annotation("javax.xml.bind.annotation.XmlAccessorType", constpool);
           EnumMemberValue enumValue = new EnumMemberValue(constpool);
           enumValue.setType("javax.xml.bind.annotation.XmlAccessType");
           enumValue.setValue("FIELD");
           annot.addMemberValue("value", enumValue);
           attr.addAnnotation(annot);
           ctClass.getClassFile().addAttribute(attr);
        }

        if (!hasAnnotation(ctClass, "javax.xml.bind.annotation.XmlRootElement")) {
           Annotation annot = new Annotation("javax.xml.bind.annotation.XmlRootElement", constpool);
           String entityName = ctClass.getName();
           String entity = entityName.substring(entityName.lastIndexOf('.') + 1).toLowerCase();
           annot.addMemberValue("name", new StringMemberValue(entity, constpool));
           attr.addAnnotation(annot);
           ctClass.getClassFile().addAttribute(attr);
        }

        applicationClass.enhancedByteCode = ctClass.toBytecode();
        ctClass.defrost();
   }
}

Finally, add loading of the enhancer to your plugin:

public class ApiPlugin extends PlayPlugin {
   ...
   private XmlEnhancer enhancer = new XmlEnhancer();
   ...

   public void enhance(ApplicationClass applicationClass) throws Exception {
       enhancer.enhanceThisClass(applicationClass);
    }
   ...
}

From now on whenever the application starts, all the models used are enhanced automatically with the two annotations.

How it works...

In order to check whether the module actually works, you can fire up the preceding unit test written.

As most of the code has already been written, the only part which needs a closer look is actually the XmlEnhancer. The XmlEnhancer first checks whether the application class is an entity, otherwise it returns without doing any enhancement.

The next step is to check for the @XmlAccessorType annotation, which must be set to field access. As the XmlAccessType is actually an enum, you have to create an EnumMemberValue object, which then gets added to the annotation.

The last step is to add the @XmlRootElement annotation, which marks the class for the JAXB marshaller to parse it. The name of the entity in lowercase is used as the root element name. If you want to change it, you can always use the annotation at the model and overwrite it. Here a StringMemberValue object is used, as the annotation takes a string as argument.

There's more...

Bytecode enhancement always helps to make your application easier, by possibly shortening error prone tasks such as adding annotations, or doing things which are not possible with standard Java, such as changing behavior of static methods. However, using bytecode enhancement means that you are doing something not visible at source code level. You should use this powerful feature only, if really needed, as it reduces the readability of your project. Do not forget to document these features clearly.

Javassist documentation

The Javassist documentation is actually not the easiest to read; however, it often helps because there are not too many examples floating around on the Internet. You can check most of it at http://www.javassist.org as well as some more links to introductions at http://www.jboss.org/javassist.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.76.138