Kontakt

17
Jan

Last week I made some changes to Tapestry IoC which provide the support for the JSR-330. Beginning with version 5.3 it will be possible to use JSR-330 annotations for injection. Let’s see the overlappings between JSR-330 and Tapestry IoC before we dive in into the details.

JSR-330
javax.inject
Tapestry
org.apache.tapestry5.ioc.annotations
@Inject @Inject
@Inject
@Named
@InjectService
@Scope @Scope
@Qualifier Tapestry marker annotations don’t need any qualifier annotations
@Singleton By default all Tapestry services are singletons

In the table above you can see that most of annotations are interchangeable. However, there are few differences in semantics.

Field Injection

Let’s start with field injection. In Tapestry the injection into fields is triggered by @Inject or @InjectService annotations. When @Inject annotation is present on a field, Tapestry tries to resolve the object to inject by the type of the field. If several implementations of the same service interface are available in the registry, you have to disambiguate which implementation you want to be injected. This can be done by placing the @InjectService annotation on the injection point.

package com.tapestry5inaction.services.impl;

import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.InjectService;

...

public class AuthenticationFilter implements ComponentRequestFilter {

   @InjectService("HttpBasic")
   private AuthenticationService basicAuthService;

   @InjectService("HttpDigest")
   private AuthenticationService digestAuthService;

   @Inject
   private Response response;

   ...

}

Now let’s see the JSR-330 equivalent of the same service. As you can see the @Inject annotations are interchangeable. The difference is how to get a service by its unique id. For this purpose JSR-330 provides the @Named annotation which accompanies the @Inject annotation.

package com.tapestry5inaction.services.impl;

import javax.inject.Inject;
import javax.inject.Named;

...

public class AuthenticationFilter implements ComponentRequestFilter {

   @Inject @Named("HttpBasic")
   private AuthenticationService basicAuthService;

   @Inject @Named("HttpDigest")
   private AuthenticationService digestAuthService;

   @Inject
   private Response response;

   ...

}

Constructor Injection

For constructor injection the @Inject annotations are interchangeable. You can use either JSR-330 or Tapestry annotation to mark a constructor for injection. Note that at most one constructor per class may be marked as injection point.

However, the semantics of constructor injection are different in JSR-330 and Tapestry IoC. In JSR-330 a constructor is injectable only if the @Inject annotation is present.

public class Car {

   public Car() { ... }

   @Inject
   public Car(Engine engine) { ... }
}

In Tapestry the @Inject annotation for constructors is optional. All available constructors are candidates for injection: the constructor with the most parameters will be invoked.

public class Car {

   public Car() { ... }

   public Car(Engine engine) { ... }

}

When several constructors are available and you don’t want the constructor with most  parameters to be injectable, you need to place the @Inject annotation.

public class Car {

   public Car() { ... }

   @Inject
   public Car(Engine engine) { ... }

   public Car(Engine engine, Logger logger) { ... }

}

Injection Into Pages and Components

Inside Tapestry components, injection occurs exclusively on fields. So far the injection was triggered by the @Inject or @InjectService annotations. As of version 5.3 the injection points can also be marked with JSR-330 annotations. The following example demonstrates that.

public class Index {

   @Inject
   private Request request;

   @javax.inject.Inject
   private ComponentResources resources;

   @javax.inject.Inject
   @Named("FrenchGreeter")
   private Greeter greeter;

   @javax.inject.Inject
   @Symbol(SymbolConstants.PRODUCTION_MODE)
   private boolean productionMode;

   void onActivate() { ... }

}

Method Injection

Injectable methods is a next slight difference. In JSR-330 a method is injectable if the @Inject annotation is present. In Tapestry the @Inject annotation is optional. An ordinary setter method is a candidate to perform injection.

public class Car {

   private Engine engine;

   public void setEngine(Engine engine) {
      this.engine = engine;
   }

}

When building a Car instance, Tapestry IoC will try to resolve a service of type Engine. If available, Tapestry will perform injection by invoking the setter method.

Besides that, module methods are injectable. Again, there is no need to mark the methods with @Inject annotation as Tapestry explicitly knows which module methods to invoke. In the following example you can see how to use @Named annotation to inject a service by id into a contribute method.

public class TapestryModule {

   @Contribute(BindingSource.class)
   public static void provideBindings(
         MappedConfiguration<String, BindingFactory> cfg,

         @Named("PropBindingFactory")
         BindingFactory propBindingFactory, 

         @Named("MessageBindingFactory")
         BindingFactory messageBindingFactor ) {

      cfg.add(BindingConstants.PROP,
               propBindingFactory);
      cfg.add(BindingConstants.MESSAGE,
               messageBindingFactory);

   }

   ...
}

Marker Annotations

Both JSR-330 and Tapestry IoC allow you to disambiguate services by marker or qualifier annotations, as shown in the following example.

public class Index {

   @Inject
   @French
   private Greeter greeter;

}

Again, there is a slight difference. In JSR-330 a qualifier annotation like @French in the example above needs to be annotated by the @Qualifier annotation.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@javax.inject.Qualifier
public @interface French {
}

In Tapestry any annotation can be a marker annotation. You don’t need to place something like the @Qualifier annotation on your marker annotation.

Scopes

Now let’s talks about service scopes.

By default, a JSR-330 injector creates an instance, uses the instance for one injection, and then forgets it. By placing the @Scope annotation you can tell the injector to retain the instance for possible reuse in a later injection. If you want a service to be a singleton, you need to use the @Singleton annotation.

In Tapestry, it is exactly the other way around. By default a service is a singleton. Once an instance is created, it is reused for injection. Another available scope is perthread, which exists primarily to help multi-threaded servlet applications. If a service has perthread scope, it is recreated for every incoming request.

Summary

As you have seen the most annotations in JSR-330 and Tapestry IoC are interchangeable, but there are few differences in semantics. These differences might be a blocker to pass the TCK. There are also some other challenges which make it difficult to provide a 100% portability: for example the TCK expects that a service interface can be downcasted to a service implementation (honestly I’m wondering why the TCK expects such a behavior). This just doesn’t work in Tapestry as the implementation is a proxy which delegates to the real implementation instance.

Right now you can use JSR-330 annotations in Tapestry IoC with respect to Tapestry semantics. In the next weeks I’ll be working on the portability issues. Stay tuned.

24
Nov

As you probably know currently I’m writing a “Tapestry 5 in Action” book to be published by Manning Publications Co.

Yesterday I had a chat with my contact at Manning. We discussed the further steps which needs to be done before MEAP. Among other things we need 5-10 technical reviewers on a voluntary basis. If you want to participate on the project, just drop me a line. Unless I know you from the Tapestry’s community, please describe your experience with Tapestry. Note that not only experts but also Tapestry newbies are needed. So, feel free to contact me. We will choose the best combination of beginners, advanced users and experts.

21
Nov

This week I was attending Matt Raible’s talk Comparing JVM Web Frameworks at Devoxx 2010. Matt came up with a matrix containing grades for a chosen set of features in various frameworks. The top 5 frameworks in the matrix has been presented in details. Unfortunately Tapestry got 13.5 points, only 1 point behind Wicket/Struts 2 (both 14.5 points).

I have no problems with being behind any framework if the comparison is made accurate. But in this case I feel like I need to defend Tapestry because I disagree with some of the grades given by Matt. The problem is that Matt is very famous and a lot of Java developers believe him blindly. Matt, you should be aware of it and be very careful by making any statements.

I talked to Matt after his presentation and promised him to send some links which will hopefully improve Tapestry’s grades. But Matt’s comparison caused a lot of discussions over the world, so I decided to write a public answer.

Here are some few grades I disagree with:

Developer productivity

I cannot understand why Tapestry didn’t get 1 point for developer productivity. C’mon Matt, did you ever hear about Tapestry’s Live Class Reloading feature which allows you to change your code and see the results immediately? You have mentioned that JRebel can boost developer productivity but forgot to mention that Tapestry has a similar functionality which is built-in.

Do you really believe that GWT, which requires an additional Java-to-JavaScript compile step, offers a better developer productivity? Sorry, but even GWT users I know don’t believe it.

Is Spring MVC really more productive than Tapestry? Is it because JSPs are better to read and to debug than Tapestry’ HTML templates? Or is it XML-based configuration that outweighs Tapestry’s annotation and convention-over-configuration approach?

Developer perception

Uh? Zero? Is it really that bad? Matt, your last mail on Tapestry’s user list was on 11th November 2009. One year is a long time. You should read the comments of new users who are enjoying learning Tapestry.

Indeed, the Tapestry team have been very bad in marketing but it will change. The new web site has been launched yesterday. The documentation has been improved. A German book on Tapestry 5 has been published. Currently I’m working on a translation of this book for Manning Publications Co. I’m pretty sure the “Tapestry 5 in Action” will make Tapestry more successful.

I’m talking on Tapestry very often and get a lot of feedback from Java developers. My impression is that the developer perception is better than zero. Much better.

Project Health

The first stable release of Tapestry 5, namely version 5.0.18, was released on 12th December, 2008. Since that time we successfully released 5.1.0.5 and 5.2.4 without to break any backward compatibility. Two major releases with various innovations and without any backward compatibility issues in two years. Isn’t it a healthy project? Shouldn’t you all forget the Tapestry’s bad reputation of backward compatibility? It is history now.

Scalability

One of Tapesry’s goals is scalability. Tapestry was built with scalability in mind and that’s why has a lean session usage. Tapestry claims to be scalable and you should read it in Tapestry’s documentation. Again, why not the best grade for this feature? Why do Struts 2 or GWT scale better? Did you try to scale a Tapestry application? I guess not.

Testing

C’mon Matt. Are you kidding? Tapestry is built for testability and provides a very nice testing functionality. I guess this coverage report tells everything.

Moral of the story

There are even more points which are better in Tapestry than they appear to be. If only the mentioned points have been rated accurately, Tapestry would easily make it to the top 5 frameworks in Matt’s presentation.

The moral of the story is: even though Matt Raible is a very respected person, you should not blindly rely on everything he tells you. Some frameworks are much better than some people speak about them. Give them a try and decide if they fit your needs.

18
Nov

In the last post I described the enhancements on PageTester which will improve the testability of your Tapestry applications. In this post I would like to show you a further improvement which will make your life easier when writing tests. In the following example you can see a simple example on how to render a page named SimplePage. The markup of the page contains a link that you want to click inside the test. So, the returned Document instance can be used to find the link by its client id. This can be done by passing the id of the link to th getElementById() method. The result of the method is an Element, if found, which represents the link.

public class SimplePageTest {

   private PageTester tester;

   @Test
   public void click() {
      Document doc = tester.renderPage("SimplePage");

      Element link = doc.getElementById("myLink");

      doc = tester.clickLink(link);

      assertTrue(doc.toString().contains(
         "Tapestry 5 Test"));
   }

   ...
}

Frequently it is easier to find an element inside a document by its name or any arbitrary attribute than by the client id, but unfortunately this was not possible with Tapestry till now. So last week I committed some changes which will be available in the upcoming 5.2.4 release.

In the following example you can see an example how to find an element which has class attribute set to value fancy.

public class SimplePageTest {

   private PageTester tester;

   @Test
   public void click() {
      Document doc = tester.renderPage("SimplePage");

      Element root = doc.getRootElement();

      Element link = root.getElementByAttributeValue(
                          "class", "fancy");

      doc = tester.clickLink(link);

      assertTrue(doc.toString().contains(
            "Tapestry 5 Test"));
   }

   ...
}

If you want more flexibility when finding your elements, you may provide a Predicate which is responsible to accept or decline an Element. This predicate will be used when Tapestry iterrates over all elements inside a Document. The first accepted Element is returned. Note that widht-first search is performed.

public class SimplePageTest {

   private PageTester tester;

   @Test
   public void click() {
      Document doc = tester.renderPage("SimplePage");

      Element root = doc.getRootElement();

      Element link = root.getElement(
         new Predicate<Element>(){
            public boolean accept(Element e) {
                return "a".equals(e.getName());
            }
        });

     doc = tester.clickLink(link);

      assertTrue(doc.toString().contains(
            "Tapestry 5 Test"));
   }

   ...
}

Hope this helps you to improve the tests of your Tapestry applications.

16
Nov

Tapestry provides a very nice functionality which allows you to test your applications without any servlet container. The PageTester class is used to run a Tapestry application inside a test and render certain pages of the application. As shown in the following example, you can initialize a PageTester instance by providing:

  • the root package of the application. This is the same value you would specify using the tapestry.app-package context parameter inside web.xml.
  • Application name. This is the same value you would specify as the filter name inside web.xml.

The initialized PageTester instance may be used to render a page by its name. The return value of the renderPage() method is an instance of Document. A Document represents the root node of the DOM of the rendered page and may be used to perform asserts.

public class SimplePageTest {
   private PageTester tester;

   @BeforeClass
   public void setup() {
      tester = new PageTester(
            "org.example.testapp", "app");
    }

   @Test
   public void simple_test() {
      Document document = tester.renderPage("SimplePage");

      String markup = document.toString();

      assertTrue(markup.contains("Tapestry 5 Test"));
   }
}

This functionality allows you to test your pages with minimal effort but has a limitation: there is now way to test pages which don’t have a template. For example it is not possible to test the following page as there is no markup to assert against. So, PageTester fails to create an instance of Document and your tests fails.

public class SimplePage {

   Object onActivate() {
      return new HttpError(
         HttpServletResponse.SC_MOVED_TEMPORARILY,
         "Oups! Resource moved. Try again later.");
   }
}

Last week I committed some improvements on PageTester which provide you the full access to the response generated when the page was rendered. Instead of renderPage() method you can use the renderPageAndReturnResponse() method which returns an instance of TestableResponse. A TestableResponse is a testable extension of Response to perform asserts against. In the following example you can see how to assert the HTTP error sent by the page above.

public class SimplePageTest {

   @Test
   public void http_error) {
      TestableResponse response = tester
                .renderPageAndReturnResponse("SimplePage");

      assertEquals(
         response.getStatus(),
         HttpServletResponse.SC_MOVED_TEMPORARILY);

      assertEquals(
         response.getErrorMessage(),
         "Oups! Resource moved. Try again later.");
   }
}

You can also access the HTTP headers set during rendering the page or read the data written into ServletOutputStream.

public class SimplePageTest {

   @Test
   public void http_error() {
      TestableResponse response = tester
                .renderPageAndReturnResponse("AnotherPage");

      assertEquals(
         response.getHeader("my-header"),
         "foo");

      assertEquals(
         response.getOutput(),
         "<html><body>Bla bla</body></html>");
   }
}

For further details check out the classes PageTester and TestableResponse. There is more new stuff which will simplify testing of Tapestry applications.

Happy testing.

6
Nov

As posted earlier, I was giving a Tapestry talk at Enterprise Java User Group (eJUG) Austria on November 2nd. This presentation was quite different than most of my Tapestry talks as almost the half of the attendees (around 35 ) had already experience with Tapestry. Quite many attendees have already read my book. I’ve got some feedback from them and even some praise.

I also had a chance to meet some interesting people like Juergen Hoeller and Peter Niederwieser. Before the presentation me, Juergen Hoeller and Martin Ahrer had a lunch. We had a small talk on programming, open source and other stuff. I was quite happy that Juergen attended my Tapestry talk as I wanted to show him Tapestry IoC. Peter Niederwieser is the creator of the spock framework and Groovy committer. He is using Tapestry at his company and had already provided some interesting patches. I’m very happy to have met him. We talked a lot about his framework and I’m very enthusiastic about trying it out (there is a Tapestry integration). I also met Christian Köberl and Kristian Marinkovic, who are working for Porsche Informatic. I know these two guys from the Tapestry community, but never met them in person before.

Meeting a lot of Tapestry folks was a pleasure for me. I enjoyed it very much so that I start thinking about organizing a Tapestry meet-up in 2011. If you are reading this blog and like the idea, just drop me a line.

Many thanks to Martin Ahrer and Juergen Hoeller for inviting me to eJUG. I would love to come again.

28
Okt

If you are living in Austria and wish to learn Tapestry, I have a good news for you. I’ll be speaking on Tapestry at Enterprise Java User Group (eJUG) Austria on November 2nd. The talk is a 60 minutes introduction into web development with Tapestry. After a short overview I’ll show new features in the upcoming 5.2 release. Along the way I’ll show a lot of demos.

Hope to see you there!

24
Okt

If you are reading this blog frequently, you might know that starting from Tapestry 5.2.0 the @Contribute annotation can be used as an alternative for naming convention for contribution methods. The annotation may be placed on an arbitrary named method of a module to identify this method as a contribution method. The value of the annotation is the type of the service to contribute into. Additionally, marker annotations may be used to disambiguate several implementation of a service interface. Here is an example:

public class AppModule {

   @Contribute(SymbolProvider.class)
   @ApplicationDefaults
   public static void provideSymbols(
         MappedConfiguration<String, String> config) {

      config.add(SymbolConstants.PRODUCTION_MODE, "false");
   }
}

I prefer this new approach over naming conventions as this is less error-prone: no more errors caused by a typo inside the service id. I love this new feature that much that I started to hate the naming conventions for decorator and advisor methods. So, last week I checked in new feature allowing you to identify these methods by annotations. Let’s see some examples.

Currently an advice will match against a service with the id which is encoded into the advisor method name. This is quite error-prone as you can misspell the service id and wonder why your advice is not executed.

public class AppModule {

   public static void adviseMyService(
               MethodAdviceReceiver receiver) {

      receiver.adviseAllMethods(new MethodAdvice() {
         public void advise(Invocation invocation) {
            //advise here
            invocation.proceed();
         }
      });
   }
}

In Tapestry 5.2.2 it will be possible to match the service to advise by its interface, as shown in the following example. With this approach you don’t have to know the id of the service.

public class AppModule {

   @Advise(MyService.class)
   public static void foo(
               MethodAdviceReceiver receiver) {

      receiver.adviseAllMethods(new MethodAdvice() {
         public void advise(Invocation invocation) {
            //advise here
            invocation.proceed();
         }
      });
   }
}

If there are multiple implementations of a service interface inside the Registry, you need to disambiguate the implementation which you want to advise. For this purpose you need to place the marker annotation on your advisor method which is used to mark the service implementation.

public class AppModule {

   @Advise(MyService.class)
   @GreenMarker
   public static void foo(
               MethodAdviceReceiver receiver) {

      receiver.adviseAllMethods(new MethodAdvice() {
         public void advise(Invocation invocation) {
            //advise here
            invocation.proceed();
         }
      });
   }
}

When providing multiple advisor methods, the order might be very important. The @Order annotation allows you to explicitly set the order in which any single advice is executed. For this purpose you need to provide an advice id, which must be unique across all modules. The id attribute of the @Advise annotation is used to provide such an id. Note, that this attribute defaults to the simple name of the service interface.

public class AppModule {

   @Advise(serviceInterface=MyService.class, id="foo")
   public static void foo(
               MethodAdviceReceiver receiver) {

      receiver.adviseAllMethods(new MethodAdvice() {
         public void advise(Invocation invocation) {
            //advise here
            invocation.proceed();
         }
      });
   }

   @Advise(MyService.class)
   @Order("before:foo")
   public static void bar(
               MethodAdviceReceiver receiver) {

      receiver.adviseAllMethods(new MethodAdvice() {
         public void advise(Invocation invocation) {
            //advise here
            invocation.proceed();
         }
      });
   }
}

Now let’s have a look at decorator methods. The following example demonstrates how a service can be decorated in prior Tapestry versions. The service to decorate is matched by the id encoded into the decorator method name.

public class AppModule {

   public static MyService decorateMyService(
            MyService delegate,
            ServiceResources resources,
            Logger logger,
            LoggingDecorator decorator) {

      return decorator.build(MyService.class,
                             delegate,
                             resources.getServiceId(),
                             logger);
   }
}

As of version 5.2.2 a decorator method can be arbitrary named, if the @Decorate annotation is placed on it. As with @Advise annotation, marker annotations may be used to disambiguate the service implementation.

public class AppModule {

   @Decorate(MyService.class)
   public static MyService foo(
            MyService delegate,
            ServiceResources resources,
            Logger logger,
            LoggingDecorator decorator) {

      return decorator.build(MyService.class,
                             delegate,
                             resources.getServiceId(),
                             logger);
   }
}

Have fun with Tapestry and stay tuned.

21
Okt

Do you love Tapestry? Do you want to see Tapestry in action? Christophe Cordenier just announced a Tapestry 5 demo application. The application has been inspired from Seam hotel booking application and demonstrates what is possible with Tapestry.

The application has been written by Christophe Cordenier, Katia Aresti Gonzalez and Kalle Korhonen. Volunteers are wanted. If you want to contribute, just check out the project here and provide a patch. Don’t hesitate to ask in the Tapestry’ mailing list, if you have any questions regarding joining the team.

Thank you Christophe, Katia and Kalle. Amazing job.

15
Okt

Tapestry team is pleased to announce the Tapestry 5.2.1 Beta release. This release consists mostly of bug fixes on top of release 5.2.0. Now we are on the verge of making Tapestry 5.2 final. Once 5.2 release has been marked as generally available, we’ll concentrate on new features which will make Tapestry 5.3 even better.

Beside bug fixing the Tapestry team is working on the new appealing web site and improving documentation. The preview can be seen here.

Also a new book on Tapestry is approaching. There are no official dates yet, but I expect the book to be published in the first quarter of 2011.

Stay tuned and have fun with Tapestry.

Tapestry 5 Blog - Copyright © 2009 - Eclectic Theme by Your Inspiration Web - Powered by WordPress