Tapestry Magic #1: Using Class Transformations

One of the most power tools in Tapestry5 is the Class transformation of Components and Pages. In plain language it means you can add or modify methods in components and pages at runtime. The magic is done by implementing ComponentClassTransformWorker which has just one method

public interface ComponentClassTransformWorker {
   public void transform(ClassTransformation transform, MutableComponentModel model);
}

To demonstrate what can be done using this magic, I will present it with the following example.

@DiscardOnPageReset

Whenever you use @Persist(PersistenceConstants.SESSION) which is the default configuration for @Persist, the values are stored in session and stay there till you call ComponentResources.discardPersistentFieldChanges() or till the Session expires. One of the way to get rid of these variables is to call ComponentResources.discardPersistentFieldChanges() in pageReset() method in a page. This will discard the values and on the next visit to the page(remember only after the next visit). So, the code will be something like

public class MyPage {
   @Inject
   private ComponentResources resources;

   void pageReset(){
      resources.discardPersistentFieldChanges();
   }
}

Repeating this in a number of pages can be avoided by using the power of ComponentClassTransformWorker. For that we have to create an annotation

package com.googlecode.tawus.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DiscardOnPageReset {

}

This annotation has to be placed on any page on which you want the persistent fields to be discarded on a fresh visit. Now we have to create a ComponentClassTransformWorker.

package com.googlecode.tawus.internal;

import java.lang.reflect.Modifier;

public class DiscardOnPageResetWorker implements ComponentClassTransformWorker {

   private final TransformMethodSignature PAGE_RESET_SIGNATURE = new TransformMethodSignature(
         Modifier.PUBLIC, "void", "pageReset", null, null);

   public void transform(ClassTransformation transformation,
         MutableComponentModel model) {
      DiscardOnPageReset annotation = transformation
            .getAnnotation(DiscardOnPageReset.class);
      if (annotation == null) {
         return;
      }

      transformation.getOrCreateMethod(PAGE_RESET_SIGNATURE).addAdvice(
            new ComponentMethodAdvice() {
               public void advise(ComponentMethodInvocation invocation) {
                  invocation.getComponentResources()
                        .discardPersistentFieldChanges();
                  invocation.proceed();
               }

            });

   }

}

We first check if the class is annotated with @DicardOnPageReset. If it is annotated, then get the method pageChanged(in case it is not present, create one and hence the name ‘getOrCreate’). Now we add advice to the method which is nothing more than a call to ComponentResources.discardPersistentFieldChanges(). Note we have first asked the method to do whatever it has to do on pageReset by calling invocation.proceed().

Once the worker is ready, we contribute it as

   @Contribute(ComponentClassTransformWorker.class)
   public static void contributeWorkers(OrderedConfiguration<ComponentClassTransformWorker> workers) {
      workers.addInstance("DiscardOnPageReset", DiscardOnPageResetWorker.class, "before:PageReset");
   }

To use it in a page


@DiscardOnPageReset
public class MyPage {

}

Tagged: , , , ,

8 thoughts on “Tapestry Magic #1: Using Class Transformations

  1. e0f4 April 16, 2011 at 6:48 PM Reply

    Great example!
    Maybe it misses a example of the annotation use (trivial but useful).
    Good work.

    e0f4

    • tawus April 16, 2011 at 7:54 PM Reply

      Thanks! I just updated with an example.

  2. Nik April 16, 2011 at 8:11 PM Reply

    Hi Taha,

    Thanks for sharing your experiences. I’m not much experienced with web app development, so was wondering what would be the proper use of your example, why would you want and on which pages to delete data stored inside a session? Thanks for the response

    • tawus April 16, 2011 at 8:39 PM Reply

      It will not delete all the data from the session just the one stored specifically for this page. Usually component-based frameworks have many components in a single page and each component has some associated persisted fields. Imagine every user navigating 10 such pages with 15/20 components each.
      Moreever, the intention is to show how classtransformations can be used to perform some tapestry5 magic

      • Nik April 16, 2011 at 9:54 PM

        Of course I know this example was just for demonstration purposes. But I was wondering if and when I need to watch out for those data stored inside a session. Thanks!

  3. Howard Lewis Ship April 16, 2011 at 11:59 PM Reply

    My only issue with this example is that ordering it “before:*” is a little drastic, and will lead to runtime warnings if other contributions attempt to do the same thing.

    Other than that, it’s very gratifying to see people grok the power of the class tranformation APIs;it’s another way in which to use composition rather than inheritance.

    • tawus April 17, 2011 at 5:49 AM Reply

      Thanks for your feedback. Changed “before:*” to “before:PageReset”

  4. Quu November 21, 2011 at 10:22 PM Reply

    One question:
    invocation.getComponentResources()

    Works without injecting them into a class?

Leave a reply to Quu Cancel reply