Kontakt

As already posted here I’ll be giving a Tapestry talk at Herbstcampus in Nürnberg and two Tapestry talks at WebAppDays conference in Düsseldorf.

The talk at Herbstcampus is a 70 minute Tapestry introduction and takes place on September 14th.

The both talks at WebAppDays are scheduled on September 27th. The first talk will be a JSF 2.0 vs. Tapestry 5 comparison. It is the same talk I was giving at Jazoon, but this time I’ll be speaking together with Andy Bosch, a man behind JSF Forum. The second talk is a Tapestry only presentation and starts directly after the comparison.

Want to learn Tapestry? Come to Herbstcampus and/or WebAppDays.

In this post I want to show you some new features introduced in Tapestry 5.2. You’ll learn how to access request parameters in a easy way.

As you probably know Tapestry provides a very nice mechanism to pass some data from one page to another. This mechanism is called page activation context. Before a page is rendered Tapestry extracts the context values from the URL, converts the string values to appropriate type and activates the page. Usually a URL to a page looks like the following, whereby 12345 is the page activation context.

/viewuser/12345/

Note that the page activation context is encoded into the request path.

Mapping Fields to Request Parameters

Sometimes you might prefer request parameters over page activation context to pass data to a page. An example is a migration of an existing legacy application to Tapestry. It’s quite common to do this in stages, moving some functionality into Tapestry and leaving other parts, initially, in the other system.  Imagine you want to migrate a single page of your application which is accessed via a URL like:

/viewuser?userId=12345

You can use the @ActivationRequestParameter annotation to map a field of a page (not a component) to a request parameter. When a page is activated, the mapped fields will receive their values from request parameters before onActivate() event handler method is invoked. The annotation value is used to specify the name of the request parameter, which is mapped to the field. If no such value is provided, then the name of the field is taken as the name of the request parameter. For the following page Tapestry will extract the value from the userId request parameter, transform the string value to a User instance and assign the value to the user property.

public class ViewUser {

   @Property
   @ActivationRequestParameter("userId")
   private User user;
}

The assigned value can be accessed in the page template in a usual way:

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">
    <body>
        User: ${user.name}
        <br/>
        <t:pagelink page="viewuser">refresh</t:pagelink>
    </body>
</html>

The @ActivationRequestParameter annotation may be considered as an alternative to @Persist annotation. This annotation tells Tapestry to persist a field value from one request to the next. The main difference to the @Persist annotation is that the value is not stored into a session, onto the client, etc. The value is rescued as a request parameter. When Tapestry generates a component event or page render link for a page, it will identify all fields annotated with @ActivationRequestParameter and add their values as request parameters to the link. Open the ViewUser page in your browser with a valid user id as request parameter and roll your mouse over the refresh link. You will see in the statusbar of your browser that the user id has been appended to the URL of the link automatically.

Accessing Request Parameters in Handler Methods

Alternatively you can use the @RequestParameter annotation to access the request parameters. The main difference is that @RequestParameter annotation may be placed only on method parameters as shown in the following example. The annotation doesn’t persist the request parameter between two requests.

public class ViewUser {
   @Property
   private User user;

   void onActivate(@RequestParameter("userId") User user) {
      this.user = user;
   }
}

The @RequestParameter annotation may be placed on handler methods of both pages and components. The main use case for the annotations are probably methods handling ajax request.

Tapestry team is pleased to announce Tapestry 5.2.0 Alpha release. Read the news in Howard’s announcement and on Tapestry’s site. We have fixed a lot of bugs and added some cool new stuff. Release notes can be found here. In this post I’ll give you a preview of some new features.

Improvements to Live Class Reloading

With Tapestry you can see the changes to your pages and components immediately. You just change you class, reload the page and see the result. No redeploy, no restart required! In Tapestry 5.2 the Live Class Reloading feature has been enhanced to reload your service implementation classes. That means Tapestry will boost the developer’s productivity to a higher level than before. Read here for more details.

New Components

Two components have been added:

  • Error: Same as Errors but presents validation errors of a single field
  • Trigger: Triggers an arbitrary event during rendering. This is often useful to add JavaScript to a page or a component (via calls to the RenderSupport environmental).

New Mixins

Two mixins have been added:

  • RenderClientId: Forces a client element to render its client id by ensuring that ClientElement.getClientId() is called.
  • RenderNotification: This mixin triggers event notifications to identify when it enters the BeginRender and AfterRender render phases. The MarkupWriter is passed as the event context. The most common use of this is to handle the afterRender event to generate client-side JavaScript for content just rendered via a Block (this is a common Ajax use case related to partial page rendering).

JSR-303 Bean Validation Integration

An integration library for JSR-303 Bean Validation API has been added. Now you can annotate your pages, component and entities with JSR-303 annotations. Tapestry will perform the validation according to the specified validation constraints. Read here and here.

New Annotation for Contribute Methods

The @Contribute annotation has been introduced as an alternative to the naming convention for contribute methods. The annotation allows your contribute methods to be arbitrary named. You don’t have to follow the naming convention for contribute methods anymore. Just name your contribute method as you like and annotate it with @Contribute (plus some marker annotations).

New Annotations to Access Request Parameters

New annotations have been added to make the access of request parameters easier:

  • @RequestParameter: may be placed on parameters of event handler methods. Before invoking the handler method Tapestry will extract the parameter values from the request, transform the string values to the type of the method parameter automatically and pass the transformed value to the method.
  • @ActivationRequestParameter: may be used to map a field of a page (not a component) to a request parameter. When Tapestry generate component event or page render links for the page, it will identify all fields annotated with @ActivationRequestParameter and add their values as request parameters to the link. When a page is activated, the mapped fields will receive their values from request parameters before onActivate event handler method is invoked.

New Page Lifecycle Event

The pageReset event has been added to the page lifecycle. A page is reset when the page is accessed from another page; component event links and page render links that target the same page do not cause a reset, but linking from one page to another will cause a reset on the destination page when it is rendered. The reset lifecycle event occurs after page activation, allowing the page to reset its internal state to the match its activation context. Note that on pages that have such reset logic will have an additional query parameter added to the page render URL in some cases.

New Events for Link Decoration

During generation of component event or page render links Tapestry will fire two events to involve you as page/component author into the link generation process. The new events are:

  • decoratePageRenderLink: triggered during page render link generation. The first context parameter is the Link object, the second is the PageRenderRequestParameters from which the Link was created. The event is triggered on the actively rendering page. You can provide an event handler method for that event if you want to modify the link targeting the page.
  • decoreateComponentEventLink: triggered during component event link generation. The first context parameter is the Link object, the second is the ComponentEventRequestParameters from which the Link was created. The event is triggered on the actively rendering page, not necessarily the page containing the component. You can provide an event handler method for that event if you want to modify the link triggering a component event.

URL Rewriting API Deprecated

The URL Rewriting API has been deprecated in favour of LinkTransformer service. That service is a facade around ComponentEventLinkTransformer and PageRenderLinkTransformer services. You can contribute implementations of these services to transform component event and page render requests.

Template Parser Changed

In the past Tapestry used StAX XML parser for parsing templates. This caused some incompatibilities with Google App Engine and in OSGi environments. Now Tapestry uses a standard SAX parser.

Page Pool Deprecated

The page pool has been deprecated. Now Tapestry maintains for every page only one instance which is shared between threads. The values of the mutable fields are stored in a per-thread Map of keys and values so that a single page instance can be used across multiple threads without any synchronization issues. As of 5.2 the page pool is switched off but you can configure Tapestry to use it. Read here for more details.

JMX Support and Remote Management of the Pool Settings

If you configure Tapestry to use the page pool, you can change the settings of the pool remotely. Tapestry provides now a JMX support library which can be used to expose objects as MBeans. This library is used to register an MBean for managing pool settings. Read here for more details.

Upgrade to Spring 3

Tapestry’s Spring integration library depends now on Spring 3.

Upgrade to Hibernate 3.5.4

Tapestry’s Hibernate integration library depends now on Hibernate 3.5.4.

As you probably know Tapestry’s localization support provides localized component templates. A page or a component may have several locale-specific templates. When rendering a component Tapestry will search for a localized version of each component template and use the closest match. For example you could have MyPage_fr.tml for French users, MyPage_de.tml for German users and MyPage.tml for all other users.

In my Tapestry workshops I’m often asked how to extend the template lookup mechanism. Some Tapestry based applications have a use case to provide different flavors of a single page template. Imagine you want your app do be used on mobile phones. In this case you need to provide an alternative rendering for your pages. It is almost impossible to  create a template which looks good on standard web browser and mobile devices. Thus you need to provide different templates for a single page.

Extending Template Lookup Mechanism

This is the point at which Tapestry’s extensibility comes into play. As you know you can easily override almost every part of Tapestry by replacing a particular service. Now let me show you how to extend Tapestry’s template lookup mechanism in Tapestry 5.2. The discussed mechanism was also available in prior versions but it was a part of the internal API. You can use the  internal API only at your own risk because we can change in the future releases. In 5.2 this mechanism became a public API. Now it is safe to extend it without to fear any upgrade problems. Let’s see it.

The key part of Tapestry’s template lookup mechanism is the ComponentTemplateLocator service which is used to locate both page and component templates. The service implementation is a chain-of-command and can be configured by ComponentTemplateLocator instances. Tapestry contributes two implementations of ComponentTemplateLocator to the chain:

  • Default: Searches for a template on the classpath
  • Page: Searches for page templates in the application context (root folder of your WAR file)

 Now let’s contribute a command which will locate a page template based on a classifier. Such a classifier can be a string identifying the flavor of the template to render the page. For example if you want to render the page MyPage on iPhone you may provide a classifier like iphone to retrieve the template MyPage_iphone.tml. If the client is on the Palm phone, the classifier palm can be used to retrieve the template MyPage_palm.tml. If the classifier is not available, the default template MyPage.tml will be rendered.

Let’s create a per-thread service named TemplateClassifierSource, which is responsible for identifying the client and providing a corresponding classifier. The implementation details are not interesting.

public interface TemplateClassifierSource {
    String getClassifier();
}

Our implementation of ComponentTemplateLocator will use the TemplateClassifierSource service to identify the proper template for the page. In the following example we first retrieve the page class name and resolve the page name from it. Then we calculate the name of the template resource and load the resource from context root. We also try to find a localized version of the template. Thus we can provide templates like MyPage_iphone_de.tml. If no template was found we return null. In this case the next command in the ComponentTemplateLocator chain will try to locate the template.

public class ClassifierPageTemplateLocator
            implements ComponentTemplateLocator {
   @Inject
   @ContextProvider
   private AssetFactory contextAssetFactory;

   @Inject
   private ComponentClassResolver resolver;

   @Inject
   private TemplateClassifierSource classifierSource;

   public Resource locateTemplate(ComponentModel model,
                                  Locale locale) {
      String className = model.getComponentClassName();

      if (!className.contains(".pages.")) {
         return null;
      }

      String logicalName = this.resolver
            .resolvePageClassNameToPageName(className);

      String path = String.format("%s_%s.%s",
            logicalName,
            this.classifierSource.getClassifier(),
            TapestryConstants.TEMPLATE_EXTENSION);

      Resource contextRoot =
            this.contextAssetFactory.getRootResource();

      Resource resource = contextRoot.forFile(path);

      if (resource != null) {
         return resource.forLocale(locale);
      }

      return null;
   }
}

Contributing ComponentTemplateLocator

Next we contribute our ComponentTemplateLocator before the locator with id Page.

public class AppModule {

   public static void bind(ServiceBinder binder) {
      binder.bind(TemplateClassifierSource.class,
                  TemplateClassifierSourceImpl.class);
   }

   public static void contributeComponentTemplateLocator(
      OrderedConfiguration<ComponentTemplateLocator> config) {

      config.addInstance("Classifier",
                         ClassifierPageTemplateLocator.class,
                         "before:Page");
   }
}

Comparing To JSF Renderers

If you are familiar with JavaServer Faces technology, you will recognize that the described mechanism is comparable to JSF Component Renderers. Both Tapestry and JSF allow you to render a component in different ways. The big difference is the fact that JSF Renderers don’t have templates. You code the rendering of the component inside your Java classes. Isn’t it the reason why we don’t use Servlets anymore? However, in Tapestry you define the look and feel of a component in a template. Another disadvantage of JSF Renderers is that they are limited to components. There is no way in JSF to achieve the same for a Facelet page.

Summary

In this article you learned how to extend Tapestry’s template lookup mechanism. Note that we covered only the lookup of page templates from application context root. You can enhance the mechanism to locate component templates on the classpath.

The described mechanism is very flexible. Instead of coding a classifier into the template name, one could store the specific templates into a subfolder and use the classifier as folder name.

It’s unbelievable. Today I came across a bug in Hibernate which is known since 2005 and still is not fixed. Hibernate does not support UNION in HQL. I experienced this bug couple of years ago and can’t believe it is still not fixed. If you browse the Hibernate JIRA you will find out that this bug is the most popular one with 104 votes. I can imagine that resolving this bug is not trivial but aren’t Hibernate guys paid by JBoss for coding Hibernate?

It looks like September is going to be a successful Tapestry month. First of all because we are planing to release Tapestry 5.2 in September. Besides that I’ll be speaking on Tapestry three times. As already blogged I’ll be giving a Tapestry talk at Herbstcampus on September 14th. In addition I’ll be speaking on Tapestry at WebAppDays, a conference on web development. The conference takes place in Düsseldorf, Germany between 27th and 28th September. The program looks very promising. There will be talks on Dojo, Spring Roo, Grails, GlassFish,  CouchDB and many more. Check out the preliminary program here.

At WebAppDays I’ll be giving two talks on Tapestry. The first one will be an introduction to Tapestry, the second one will be a talk similar to JavaServer Faces 2.0 vs. Tapestry 5 talk I was giving at Jazoon 2010. This time the comparison will be more fun because the JSF part of the talk will be presented by Andy Bosch, a JSF guru in Germany.

I’m looking forward to visit WebAppDays.

Today I committed some changes for Tapestry 5.2 that allow you to access annotations placed on service implementation classes. As you probably know Tapestry creates proxies for services. The proxy implements the service interface and delegates method invocations to the actual service instance. When you inject a service into your page, component or another service you get a proxy which creates an instance of the service implementation class on demand. The first time a method is invoked on the proxy, the actual service is instantiated and initialized.

Until today Tapestry’s proxies did not expose annotations of service implementation classes. If you have placed annotations on a service implementation class you was not able to read them reflectively. The reason was the way how proxy classes were fabricated. Tapestry just skipped copying annotations from implementation class to proxy. This caused some problems when integrating third party libraries into Tapestry applications. For example imagine you want to expose your service as a web service and place the @WebService on the service implementation class. Because the annotation was not copied to the service proxy class, your service was not identified as web service.

import javax.jws.WebService;
import javax.jws.WebMethod;

@WebService
public class CalculatorImpl implements Calculator {  

   @WebMethod
   public int add( int x, int y )  {
      return x + y;
   }
}

As of version 5.2 the annotations from service implementation class will be placed on the proxy class if this implementation is bound to its interface inside a bind method.

public class AppModule {  

   public static void bind(ServiceBinder binder)  {
      binder.bind(Calculator.class, CalculatorImpl.class);
   }
}

In case of build methods annotations are still lost. For build methods there is no way to determine the implementation class at proxy creation time. A proxy invokes the build method to instantiate the actual service on demand. At this time you know the implementation class but it’s just too late. The proxy class already have been fabricated.

public class AppModule {  

   public static Calculator buildCalculator()  {
      return new CalculatorImpl();
   }
}

I was invited to present Tapestry 5 at Herbstcampus in Nürnberg, Germany. Herbstcampus is a conference for software developers, architects, and project managers with a technology focus in .NET and Java. My talk Fliegede Teppiche – Moderne Webanwendungen mit Tapestry 5 (Flying carpets – Modern web applications with Tapestry 5) takes place on September 14th, 2010. If you are living in Bavaria, this is a chance to learn Tapestry in a 70 minutes session. I’ll give an overview over Tapestry concepts and new features in the upcoming release 5.2. As always I will show a lot of demos.

In the article Advanced Service Builders in Tapestry IoC: Strategy Pattern I described how to implement the Strategy pattern with Tapestry. In this post I’ll cover the Gang of Four Chain of Responsibility Pattern. I’ll start with a simple example that demonstrates how to implement the pattern in Tapestry. At the end of the article, I’ll show you how the Chain of Responsibility Pattern is used inside Tapestry.

The use case

Suppose you need to support several authentication mechanisms in your application. Let’s say you need to support authentication based on both HTTP Basic authentication header (RFC 1945) and HTTP Digest authentication header (RFC 2617). Probably you would specify a service interface like AuthenticationService:

public interface AuthenticationService {
   boolean isAuthenticated();
}

You would also provide two implementations of the service. The implementation details are not interesting yet.

  • HttpBasicAuthenticationService: authentication based on HTTP Basic authentication header
  • HttpDigestAuthenticationService: authentication based on HTTP Digest authentication header

Both implementations can be bound to their interface in the application module. Note that you need to provide the service id to disambiguate both implementations of the AuthenticationService interface.

public class AppModule {

   public static void bind(ServiceBinder binder) {

      binder.bind(AuthenticationService.class,
                  HttpBasicAuthenticationService.class)
            .withId("HttpBasic");
      binder.bind(AuthenticationService.class,
                  HttpDigestAuthenticationService.class)
            .withId("HttpDigest");
   }
}

Naive Approach

Now let’s first have a look at a naive usage of the AuthenticationService service. Let’s implement a simple ComponentRequestFilter which is responsible for recognizing whether an incoming request is authenticated or not.  In the following example we need to inject both implementations of AuthenticationService service using the @InjectService annotation. The value of the annotation is used to identify the service to be injected. When a component event request is handled or a page is rendered we check if the request is authenticated by invoking both services HttpBasic and HttpDigest one after another. If the request is unauthenticated, the filter will set the WWW-Authenticate header and send a 401 HTTP error indicating the request requires HTTP authentication.

public class AuthenticationFilter implements ComponentRequestFilter {

   @InjectService("HttpBasic")
   private AuthenticationService basicAuthService;

   @InjectService("HttpDigest")
   private AuthenticationService digestAuthService;

   @Inject
   private Response response;

   public void handleComponentEvent(
         ComponentEventRequestParameters parameters,
         ComponentRequestHandler handler) throws IOException {

      if (isAuthenticated()) {
         handler.handleComponentEvent(parameters);
      }
   }

   public void handlePageRender(
         PageRenderRequestParameters parameters,
         ComponentRequestHandler handler) throws IOException {

      if (isAuthenticated()) {
         handler.handlePageRender(parameters);
      }

   }

   private boolean isAuthenticated() throws IOException {

      if (this.basicAuthService.isAuthenticated()) {
         return true;
      }

      if (this.digestAuthService.isAuthenticated()) {
         return true;
      }

      this.response.setHeader("WWW-Authenticate",
            "Basic realm=\"Tapestry\"");

      this.response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
            "Request unauthorized");

      return false;
   }
}

Injecting both services HttpBasic and HttpDigest and invoking them one after another is not really nice. If you decide to provide a further authentication mechanism in the future, you’ll have to change the AuthenticationFilter class. Imagine you decided to support the authentication based on a Remember-Me-Token. In this case you need to provide a new implementation of AuthenticationService interface which will be injected into AuthenticationFilter using the service id. Now you have three different instances of AuthenticationService that are invoked one after another.

Chain of Responsibility

Now let me show you how to improve our example. We’ll implement the AuthenticationService as a Chain of Responsibility. Instead of providing different implementations of AuthenticationService interface with dfferent service ids, we use the ChainBuilder service to build a chain of AuthenticationService inside a build method.

The ChainBuilder service is used to assemble a Chain Of Command implementation of an interface (the command interface) from an ordered list of objects implementing that interface (the commands). The commands of the chain are contributed to the service configuration.

public class AppModule {

   public static void contributeAuthenticationService(
       OrderedConfiguration<AuthenticationService> config) {

      config.addInstance("HttpBasic",
              HttpBasicAuthenticationService.class);
      config.addInstance("HttpDigest",
              HttpDigestAuthenticationService.class);
   }

   public static AuthenticationService buildAuthenticationService(
         List<AuthenticationService> configuration,
         ChainBuilder chainBuilder) {

      return chainBuilder.build(AuthenticationService.class,
                                configuration);
   }
}

The result is that there is a single instance of AuthenticationService service inside the Registry. You don’t need to disambiguate different instances of the service by providing unique ids and don’t have to care which instance to inject. You just inject the AuthenticationService service and use it. Note that we are not using the @InjectService annotation anymore. Now it is fine to use the @Inject annotation. Tapestry will call the Chain of Command behind the scenes for you. Now AuthenticationFilter can be simplified.

public class AuthenticationFilter implements ComponentRequestFilter {

   @Inject
   private AuthenticationService authenticationService;

   @Inject
   private Response response;

   public void handleComponentEvent(
         ComponentEventRequestParameters parameters,
         ComponentRequestHandler handler) throws IOException {

      if (isAuthenticated()) {
         handler.handleComponentEvent(parameters);
      }
   }

   public void handlePageRender(
         PageRenderRequestParameters parameters,
         ComponentRequestHandler handler) throws IOException {

      if (isAuthenticated()) {
         handler.handlePageRender(parameters);
      }

   }

   private boolean isAuthenticated() throws IOException {

      if (this.authenticationService.isAuthenticated()) {
         return true;
      }

      this.response.setHeader("WWW-Authenticate",
            "Basic realm=\"Tapestry\"");

      this.response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
            "Request unauthorized");

      return false;
   }
}

Now that you know how to create a Chain of Command with Tapestry, let me explain you how Tapestry works down the list of commands.

ChainBuilder Service

As you already learned, the ChainBuilder service creates a Chain Of Command based on a command interface and commands. Behind the scenes Tapestry generates an implementation of the command interface at runtime. For each method in the interface, the chain implementation will call the corresponding method on each command object in turn.  There are several situations when a chain is terminated:

  • If the return type of the interface method is boolean and a command in the chain returned true.
  • If the return type of the interface method is numeric and a command in the chain returned a non-zero value.
  • If the return type of the interface method is other than boolean or numeric and a command in the chain returned a non-null value.
  • If a command in the chain throws an Exception.

Note that in case of void methods only throwing an Exception can terminate the chain.

Now let me explain how chains can be terminated. In the following example you can see HttpBasicAuthenticationService service. It first checks if the Authorization header is set. If the header is not available, then false is returned. In this case the next command in the chain is invoked. If the header is present the command executions continues. If the credentials are available and valid, then true is returned. This terminsates the chain.

public class HttpBasicAuthenticationService implements AuthenticationService {
   @Inject
   private Request request;

   public boolean isAuthenticated() {
      String header = this.request.getHeader("Authorization");

      if (header == null || !header.startsWith("Basic ")) {
         return false;
      }

      String[] tokens = getCredentials(header);

      if (tokens.length != 2) {
         return false;
      }

      return tokens[0].equals("igor")
                  && tokens[1].equals("secret");

   }

   public String[] getCredentials(String header) {
      ...
   }
}

Real Life Example

Now that you know how to implement the Chain Of Command Pattern let me show you how this pattern is used inside Tapestry. As you know Tapestry performs the transformation of the page and component classes at runtime. This bytecode transformation is performed by the ComponentClassTransformWorker service which is implemented as a Chain of Command. The commands of the chain transform a component or page class in turn. For example there is an internal implementation of the  ComponentClassTransformWorker interface which is responsible for transforming a component or a page class in that way that you as page author can inject services into that component or page via @Inject annotation. Another command in the chain is responsible for identifying parameters via the @Parameter annotation on component fields. Almost for every annotation, used in a component or a page, there is a specialized command which is a part of the class transformation chain.

Here is see an extract from TapestryModule:

public class TapestryModule {

    public void contributeComponentClassTransformWorker(
        OrderedConfiguration<ComponentClassTransformWorker>
                     configuration){

        configuration.addInstance("Inject",
                                  InjectWorker.class);
        configuration.addInstance("InjectService",
                                  InjectServiceWorker.class);
        configuration.addInstance("InjectPage",
                                  InjectPageWorker.class);

        ...

        configuration.add("OnEvent", new OnEventWorker());

        ...
        configuration.addInstance("Parameter",
                                  ParameterWorker.class,
                                  "after:Inject*");
   }

   public ComponentClassTransformWorker buildComponentClassTransformWorker(
         List<ComponentClassTransformWorker> configuration,
         ChainBuilder chainBuilder) {

      return chainBuilder.build(
                           ComponentClassTransformWorker.class,
                           configuration);
   }
}

Summary

In this post I described how to implement Chain of Responsibility Pattern using Tapestry IoC. Based on the authentication example you learned how to decouple a sender of a request and its receiver by giving more than one service a chance to handle the request.

In the next post of this series I’ll cover further advance techniques for building services. You’ll learn how to use Shadow services.

In web applications it’s often necessary to execute extra logic at application startup. When your application is starting, you might want to schedule a task for one-time or repeated execution. A typical use case is the initialization of the database. Imagine you deliver a demo version of your application to a customer in order to convince him that you did a better job than your competitors. When the customer drops the WAR file into the servlet container, the application should be usable immediately. In this case you might want to create some demo data (e.g. demo accounts with default credentials) at start time.

In Tapestry you can execute some logic at Registry startup by making contributions to the RegistryStartup service configuration. The values contributed are Runnable objects. Here is an example:

public class AppModule {

  public static void contributeRegistryStartup(
         OrderedConfiguration<Runnable> configuration,
         DatabaseStartup dbStartup) {

      configuration.add("DbStartup", new Runnable() {
          dbStartup.init();
      }, "before:*");
  }
}

Startup Methods in Tapestry 5.2

In Tapestry 5.2 the way of providing extra logic to be executed at Registry startup has been simplified. Instead of making contributions to the RegistryStartup service configuration you can provide startup methods inside your modules. As you can see in the following example you just annotate an arbitrary module method with @Startup annotation and that’s it. Tapestry will detect these methods and invoke them after the registry has been started.

public class AppModule {

  @Startup
  public static void initApplication(DatabaseStartup dbStartup) {
      dbStartup.init();
  }
}

Note that RegistryStartup service configuration is ordered. Providing the order constraint it’s possible to control in what order the contributions are executed. In contrast the order of startup methods is determined by the alphabetical order of the method names. You should not rely on that. The alphabetical ordering of module methods is a part of the internal API and may be changed in the upcoming releases.

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