J2EE developer with over 7 years of experience in designing and implementing enterprise j2ee solutions based on open source technologies like Tapestry, Hibernate, Spring. Current interests include Tapestry, Plastic, Spock, Scala. Taha is a DZone MVB and is not an employee of DZone and has posted 40 posts at DZone. You can read more from them at their website. View Full User Profile

Tapestry Mixins & ClassTransformations

  • submit to reddit

Tapestry‘s Class Transformation can save you a lot of time and can show you clearly why it is better than inheritance most of the time. This blog has many examples of it. I just finished a new one. Here is the scenario. I want a label to have a title attribute(simple tooltip) which is to be fetched from the message catalog of the corresponding component/page. This is not that difficult, just create a mixin. But what if you want all labels to have this feature, even the ones you have no control over like the labels in BeanEditors.

Lets go step by step. First the mixin

public class HelpText {

    private Component component;

    private Environment environment;

    void setupEnvironment(MarkupWriter writer) {

        final ValidationDecorator delegate = environment.peek(ValidationDecorator.class);
        Messages containerMessages = component.getComponentResources().getContainerMessages();

                new HelpTextValidationDecorator(delegate,  environment, containerMessages));



This mixin just pushes our own ValidationDecorator implementation onto the Environment. Also note that we are passing Messages of the component’s container so that any component/page containing the label is able to use its property file.

public class HelpTextValidationDecorator extends ValidationDecoratorWrapper {

    private ValidationDecorator delegate;

    private Messages messages;

    private Environment environment;

    public HelpTextValidationDecorator(ValidationDecorator delegate,
        Environment environment, Messages messages) {

        this.delegate = delegate;
        this.messages = messages;
        this.environment = environment;


    public void insideLabel(Field field, Element labelElement){
        delegate.insideLabel(field, labelElement);

        String key = field.getClientId() + "-help";
        if(messages.contains(key)) {
            labelElement.attribute("title", messages.get(key));

        ValidationDecorator currentDecorator = environment.peek(ValidationDecorator.class);
        if(currentDecorator == this){


This decorator delegates every method call to the original decorator except for insideLabel() call which is called just after creating the opening tag of label. We add an attribute named title to the label tag. The content of the title is obtained from the message catalog of the components container. Finally we remove the decorator from the Environment.

Now that the mixin is in place, we need a way to add this mixin to every label. Here comes the magic, Class transformation. We implement ComponentClassTransformWorker.

public class HelpTextMixinWorker implements ComponentClassTransformWorker {

    public void transform(ClassTransformation transformation, MutableComponentModel model) {

        if (Label.class.getName().equals(transformation.getClassName())) {





For Tapestry 5.3+, the code is a bit different

public class HelpTextMixinWorker implements ComponentClassTransformWorker2 {
   public void transform(final PlasticClass plasticClass,
        TransformationSupport support, MutableComponentModel model) {
        if (Label.class.getName().equals(plasticClass.getClassName())) {


Finally, remember to contribute this to the ComponentClassTransformWorker(ComponentClassTransformWorker2 in case of 5.3+) service.

public static void
        provideWorkers(OrderedConfiguration<ComponentClassTransformWorker> workers) {
    workers.addInstance("HelpTextMixinWorker", HelpTextMixinWorker.class);


From http://tawus.wordpress.com/2011/08/01/tapestry-mixins-classtransformations/
Published at DZone with permission of Taha Siddiqi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)