Kontakt

Archive for August, 2010

Context Values vs. Request Parameters

Montag, August 23rd, 2010

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 5.2 Preview

Mittwoch, August 11th, 2010

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.

Extending Template Lookup Mechanism

Freitag, August 6th, 2010

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.

Hibernate Doesn’t Support UNION in HQL: Bug Open since 2005

Freitag, August 6th, 2010

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?

More Tapestry Talks in September

Mittwoch, August 4th, 2010

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.

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