Kontakt

5
Mrz

One of the most powerful concepts of Tapestry IoC is distributed configuration. Every service can have a configuration which can be created across several IoC modules of an application. When a service instance is created all modules are scanned for contribution methods. A contribution method is a module method whose name consists of the prefix contribute and a service id. The service id is used to identify the service to contribute into. There are three different styles of configurations:

  • Unordered Configuration of type java.util.Collection
  • Ordered Configuration of type java.util.List
  • Mapped Configuration of type java.util.Map

When creating a service instance the registry creates an empty configuration and passes it to all the available contribution methods for the service. In the following example you can see a contribution method for the service with the id ApplicationDefaults.

public class AppModule {

   public static void contributeApplicationDefaults(
         MappedConfiguration<String, String> config) {

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

This approach works fine because a service id is unique inside the service registry. But identifying services by string ids is often error prone. First if you want to contribute to a configuration of a service you need to know the id of the service. For this purpose you have to look into Tapestry sources or call the ServiceStatus page. Second if you refactor your application and rename an id of a service, your contribution won’t work anymore.

As of version 5.2 (I committed the code today) the annotation @Contribute 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. The following example is an alternative for the contribution method above. Note that service with id ApplicationDefaults is of type SymbolProvider.

public class AppModule {

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

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

What if you have several implementations of a service interface? For example Tapestry provides several instance of the service SymbolProvider. In this case you have to disambiguate the service instances with marker annotations. In Tapestry the services ApplicationDefaults and FactoryDefaults are marked with annotations @ApplicationDefaults and @FactoryDefaults. We need to modify our contribution method a little bit in order to identify the service to contribute into. For this purpose the @Marker annotation should be placed on the contributor method. The value of the annotation is the class of a marker annotation which was used to disambiguate the service instance. The following contribution method is meant for the instance of SymbolProvider service marked with the annotation @ApplicationDefaults.

public class AppModule {

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

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

Enjoy!

26
Feb

In this post I described the Tapestry/JSR-303 integration library. If you read this blog post you might remember a limitation: at that time the library didn’t support the client-side validation. The reason is that the JSR 303 doesn’t cover the client-side validation, so that most web frameworks supporting this JSR have to provide proprietary solutions.  Due to lack of time it took longer to add the missing feature but finally the client-side validation is supported.

Let’s have a look how to add client-side validation for JSR-303 constraints. Imagine you created the following constraint definition. The server-side implementation of the constraint is implemented by RangeValidator. I suppose you are familiar with JSR-303, so I don’t explain how to implement RangeValidator.

@Documented
@Constraint(validatedBy = RangeValidator.class)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
public @interface Range {
   long max() default Long.MAX_VALUE;

   long min() default Long.MIN_VALUE;

   String message() default "{com.acme.constraint.Range.message}"; 

   Class[] groups() default {}; 

   Class[] payload() default {};
}

To provide client-side validation of a constraint you have to add a JavaScript function to the built-in object Tapestry.Validator. The function should contain exactly three parameters:

  1. Field being validated
  2. Validation message
  3. JSON object with values from the constraint annotation

Here is an example:

Tapestry.Validator.range = function(field, message, spec) {
   field.addValidator(function(value) {
      if (value < spec.min || value > spec.max) {
         throw message;
      }
   });
};

Now you have to tell Tapestry to call the function Tapestry.Validator.range when client-side validation of @Range should be executed. This is accomplished by a contribution to the service ClientConstraintDescriptorSource. The configuration of this service is a collection of ClientConstraintDescriptor. Each ClientConstraintDescriptor represents a client-side validation constraint. The constructor of ClientConstraintDescriptor has three parameters:

  1. Class of the constraint annotation
  2. Name of the JavaScript function
  3. The last parameter is a varargs. It is used to pass the attribute names of the constraint annotation to be passed (along with their values) to the JavaScript function as an JSON object.

In the following example the annotation @Range is associated with the JavaScript function range. The attributes max and min and their values are passed to the function.

public class AppModule {

   public static void contributeClientConstraintDescriptorSource(
      Configuration<ClientConstraintDescriptor> config) {

      config.add(
         new ClientConstraintDescriptor(
            Range.class, "range", "min", "max"));
   }
}

That’s it! Enjoy.

17
Feb

JAXenter just published a new article on Tapestry 5 written by me. The article is in German and is a first part of an article series about Tapestry 5. If you read German and want to start with Tapestry, you should read it. Enjoy.

28
Jan

In the last weeks some cool projects were announced in the Tapestry community.  Especially the developers of Wookie and Tynamo have been very busy.

Christophe Cordernier, Robin Komiwes and Bruno Verachten announced Wookie – a smart tool for collaborative writing. Wookie is an open source project built with Tapestry. It is a very nice demonstration of what you can achieve with Tapestry. Check out the demo. It is very cool.

The Tynamo Team announced 3 new projects:

  1. Tapestry Conversations: Conversations (known from JBoss Seam) for Tapestry
  2. Tapestry RESTEasy:  Modul for building RESTful Java applications based on RESTEasy.
  3. Tapestry JPA:  Full JPA 2 integration for Tapestry.
19
Jan

In this article I’m going to describe the modularization features of Tapestry. You will learn how to create modular applications that can be customized without to change the code of the application. These customizable applications can be delivered in different editions to different customers with minimal effort. I intend to publish a series of articles on this topic. This post is the first article of the series and starts with a short introduction to OSGi and its modularity features.

Modularization with OSGi

In the last years there has been a hype around OSGi. Most of the attention that OSGi has been getting is related to its modularization features.  With OSGi you can cut your application in several bundles which can be installed, updated or removed at runtime. No shutdown of the whole system is needed. Furthermore the OSGi Service Platform allows you to specify the dependencies between bundles on the package level. A bundle can hide the private API by exporting only those packages that are meant to be visible to other bundles. Hiding internal packages allows bundle developers to change the implementation details without to fear any backward compatibility issues. One of the killer features of OSGi is that you can version not only your bundle but also the packages exported by your bundles (actually exporting packages without a version is a bad practice). This way you never run into the incompatibility problem between libraries in your application. In a traditional JEE container you have a problem if your application depends on two libraries that depend on two incompatible versions of a third library. In contrast the OSGi dependency resolution mechanism allows you to deploy several versions of the same bundle.

OSGi is a great technology but there is an impact of using it. For example you will find yourself at a lower level of productivity because you spend too much time debugging the class loader problems of your bundles. If you start developing OSGi-based applications, then ClassNotFoundException will be your best friend.

Another issue is the fact that the majority of java libraries out there are not currently shipped as OSGi bundles. Fortunately SpringSource launched the Enterprise Bundle Repository where you can find OSGi-ready versions of open source libraries but this repository is not up-to-date. Often you will need to transform the needed library into a bundle. There are also too much libraries that advertise to be OSGi-ready because their developers put a MANIFEST.MF file into the JAR. The most OSGi-based developers will concede that too much bundles are just bad. Some examples are the usage of  Require-Bundle manifest header or export of unversioned packages. Also unversioned imports or imports with fixed version make your life harder then it should be. Imagine you found a cool bundle and want to install it. Then you find out that the bundle has some dependencies. At first you think it is no problem: there is another bundle that exports the required packages. But then you realize that the new bundle imports the version 1.2.3 but 1.2.2 is exported. Damn, a version range would save your time.

Next annoying issue is that some cool libraries, that work in a JEE environment out of the box, are incompatible per se with OSGi because they load classes with Class.forName. Every OSGi-based developer, who had the pleasure to integrate Hibernate into OSGi, knows what I’m talking about.

Web applications in OSGi are problematic

If you ever built OSGi-based web application you surely came across HttpService. This service is described in the OSGi Compendium Specification and is intended to register Servlets and resources in the OSGi environment. The most annoying thing about HttpService is that it supports only the Servlet 2.1 specification. Filters and listeners are not available. This limitation is quite inconvenient because almost every Java web framework uses filters and listeners.

There exists several solutions to deal with the limitations of the HttpService. One of them is Pax Web. This project extends the HttpService with support of recent servlet APIs. Another solution is Spring DM that allows you to deploy a WAR file into an OSGi container. I’m not going to describe these two projects in this post. Instead I would like to describe how you can accomplish modularization of web applications without OSGi.

Do you really need OSGi?

Before you decide to build OSGi-based web applications you have to ask yourself the question: Do you have a requirement to add services to your applications at runtime without restarting the JVM? I guess the most web applications don’t have this requirement. Another question is: Do you want to build modular applications? In most cases the answer to this question is yes. You might have found a reason for using OSGi but wait. If your aim is to break your monolithic WAR files into smaller, more easily managed modules you don’t need OSGi at all. There exist much simple and probably more elegant solutions to accomplish it. Tapestry IoC is one of the them. As far as I know Tapestry is the only one Java web framework that provides this functionality out of the box.

Tapestry IoC vs. OSGi

Tapestry IoC and OSGi have a lot in common. Similar to OSGi bundles Tapestry IoC Modules are packaged into JAR files. The MANIFEST.MF file acts as deployment descriptor for OSGi bundles and for Tapestry IoC Modules. When setting up the IoC registry, Tapestry automatically locates modules packaged into JARs. The framework scans through all JARs in the classpath and searches for a manifest entry Tapestry-Module-Classes inside MANIFEST.MF of every single JAR. The value of the entry is a comma-separated list of fully qualified class names of module classes. This means that a single JAR can contain multiple modules.

Manifest-Version: 1.0
Tapestry-Module-Classes: org.example.lib.LibraryModule, org.example.internal.AnotherModule

The OSGi bundles are identified in the same way. Every OSGi bundle has a MANIFEST.MF containing Bundle Manifest Headers. The headers BundleSymbolicName and Bundle-Version are required. The combination of these two headers is the unique identifier of the bundle inside OSGi. A BundleActivator is used to perform the bundle-specific activities necessary to start or stop a bundle. This is the place where services are registered or unregistered. In Tapestry services are registered in IoC Modules.

Both OSGi and Tapestry IoC provide support for service-oriented architectures. OSGi Service Platform provides a lightweight publish, find, and bind service model for services inside the JVM with the OSGi Framework service registry. Tapestry IoC also provides a Registry which is used to access the available services. While in OSGi you need to perform a service lookup, Tapestry provides a powerful Inversion of Control container. This container allows you to assemble your application from many small, easily testable pieces. Using Tapestry IoC you don’t need to lookup the dependencies of your services. Tapestry will instantiate your classes and resolve all the dependencies of your services. More details about Tapestry IoC can be found here.

Inverting the purpose of Component Libraries

Tapestry is a component-oriented web framework and allows you to create components with ease. The components are reusable by their nature and can be packaged into component libraries. What is exciting about Tapestry is that component libraries can also be used to create modular web applications. For this purpose you can invert the purpose of a component library: Instead of creating reusable libraries that are used in several applications you can create small libraries that are used only in a single application of your customer.

For example if you deliver the same application to several customers, you often have a problem that a special requirement of a customer conflicts with a special requirement of another customer. If you include these special wishes into the application, it will become bloated sooner or later. What you need is a feature that allows you to extend your application without to change it. In Tapestry this can be accomplished by component libraries. If one of your customer wishes a special page in the delivered application, you can create a small library that contains only this special page. This library is delivered to the customer as an additional JAR fie.  You don’t have to integrate this special page into your application.

A component library consists not only of components but also pages, mixins and services. This means that you can break your monolithic WAR file into several JARs, each containing a subset of pages. This way you can assemble your application from different pieces and deliver it to different customers. Your WAR file doesn’t have to contain any pages. When you deliver your application to a customer, you just include several libraries (JARs) depending on the needs of the customer.

Another use case is an application that is available in different editions. Just imagine that the Community Edition contains pages that are located inside the WAR file. The Professional Edition contains some further modules with additional pages and services. The upgrade from the Community Edition to the Professional Edition is just the matter of dropping a new JAR file onto the classpath and restarting the application. Nice, isn’t?

Creating component libraries is very simple. Pleas read this guide for more details.

Modularization example

Now let’s have a look at the modularization features of Tapestry. Imagine you need to create a Menu component which creates a navigation menu dynamically. Depending on the edition of the installed application Menu component renders a different set of menu items. For example when your customer upgrades to the Professional edition, the menu component doesn’t have to be changed. It just renders more items provided by the additional JAR.

Creating dynamic menus

The template of the Menu component might look like the following one. The Loop component iterates over a collection of page names and creates a link to the page of current iteration.

<div class="menu" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">
   <ul>
      <li t:type="loop" source="pageNames"
          value="var:currentPage">
         <a t:type="pagelink" page="${var:currentPage}">
            ${var:currentPage}
         </a>
      </li>
   </ul>
</div>

Next we create the Java class of the component. The component retrieves the names of the available pages from the service ComponentClassResolver. This service is responsible for resolving page names and component types to fully qualified class names and can be injected into a page or component via the @Inject annotation. Just as with Tapestry applications, Tapestry component libraries have a unique base package. That’s why the service ComponentClassResolver is aware of all the pages available in all the modules of a Tapestry application.

@IncludeStylesheet("menu.css")
public class Menu {
   @Inject
   private ComponentClassResolver componentClassResolver;

   public List<String> getPageNames() {
      return this.componentClassResolver.getPageNames();
   }
}

Now create a simple Index page containing the Menu component.

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">
   <body>
      <t:menu/>
   </body>
</html>

Start the server and call the URL of your application. The result should look like in the following picture.

If you would add a new page to your application, then the Menu component would show a new entry in the navigation menu. The same applies for component libraries. If you just drop a new JAR onto the classpath and restart your application, Tapestry will find all the pages, components, mixins and services inside the JAR. There is no need to change the code of the application if you want to add a new entry into the Menu. For example if you include the Tapestry/Hibernate integration library into you application the built-in page Statistics will be automatically added into the menu. The result looks like:

Because Tapestry contains some built-in pages the code above has to be changed slightly. You probably want to exclude the built-in pages from your menu. For this purpose we need an annotation to mark the pages to be included in the menu.

Defining the Annotation

We will place the following annotation in page classes to mark a page as an element of the menu.

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

For example we want the Index page to be included into the menu.

@PluginPage
public class Index {
   ...
}

Excluding Pages

Now let’s have a look at the modified Menu component. We iterate over the names of the available pages and convert every page name into a fully qualified class name. The class name is used to check if the @PluginPage annotation is present. If the annotation is not present, we filter out the page name.

@IncludeStylesheet("menu.css")
public class Menu {
   @Inject
   private ComponentClassResolver componentClassResolver;

   public List<String> getPageNames() {
      List<String> result = new ArrayList<String>();

      List<String> pages =
                   this.componentClassResolver.getPageNames();

      for (final String pageName : pages) {
         String className = this.componentClassResolver
                          .resolvePageNameToClassName(pageName);

         Class clazz = loadClass(className);

         if (clazz.isAnnotationPresent(PluginPage.class)) {
            result.add(pageName);
         }
      }

      return result;
   }

   private Class loadClass(final String className) {
      try {
         return Thread.currentThread()
                    .getContextClassLoader().loadClass(className);
      } catch (final ClassNotFoundException e) {
         throw new RuntimeException(e);
      }
   }
}

The result looks like:

Creating customer libraries

Now let’s assume one of your customer wishes a special page to be included into the application you delivered. Because we don’t want to change the application at all we create a simple component library that contains the new page. The structure of the library should look like in the following picture:

The class LibraryModule is the IoC Modul that is used to configure the application. Inside this module we tell Tapestry where to search for our page and component classes. This is accomplished by making a contribution to the ComponentClassResolver configuration. As you remember, we used this service in the Menu component to find the available pages. In the following example we map the prefix mylib to the root package of our library, namely org.example.lib.

public class LibraryModule {
    public static void contributeComponentClassResolver(
             Configuration<LibraryMapping> configuration){
        configuration.add(new LibraryMapping(
                    "mylib", "org.example.lib"));
    }
}

As you remember, Tapestry scans the classpath to find all IoC Modules. That’s why we need to put the following manifest entry into the MANIFEST.MF file.

Manifest-Version: 1.0
Tapestry-Module-Classes: org.example.lib.LibraryModule

Now build the JAR file, drop it onto the classpath and restart the application. You will see the the following result.

Conclusion

In this article you learned how to create modular applications without OSGi. If you don’t have the requirement to add services to your applications at runtime without restarting the JVM then the described solution is sufficient for your application. You don’t need to migrate to OSGi. Of course OSGi solves some other problems that can’t be handled inside an IoC Container. An example is the version incompatibility of included libraries: OSGi is the only one technology that allows you to install the same library in different versions. At the end of the day you have to decide which technology is more suitable for your needs. Tapestry IoC is a simple, lightweight (and maybe limited) solution for modularization of web applications.

In the next article I’m going to describe some more Tapestry features that allow you to create modular web applications.  Stay tuned.

8
Jan

University of DortmundJanuary 12th I’ll have the pleasure to guest lecture to Computer Science students at the University of Dortmund about Tapestry 5. This guest lecture takes place within the scope of the course name Web technologies that is held weekly by Prof. Dr. Dietmar Jannach.

I have a 90 minutes slot to show the students an alternative approach to create web applications. This guest lecture gives me an opportunity to demonstrate Tapestry to junior staff that is not biased by the standards of the industry. According to experience Computer Science students are eager to learn cool technologies. They judge the technologies by their easiness, power and elegance, not by buzz words.

What I’m going to show? First of all the fundamental concepts of Tapestry and how to get started. Then I’m going to show the difference to JSF. Furtunately Prof. Dr. Dietmar Jannach covered Servlets, JSPs and JSF in his lecture and I can expect some previous knowledge about Java web technologies. As always in my talks I’m going to show a lot of demos. Additionally I’ll be speaking about the management and development processes of open source projects at Apache Software Foundation.

Speaking at University of Dortmund is a honor for me because I studied Computer Science at this university. I remember the auditorium in which the lecture is taking place quite well. Some years ago I attended some lectures in this auditorium. This time I’ll be speaking and don’t have to pass any exams anymore.

4
Jan

In the last days there has been a hype around JEE 6. One of the JSRs approved by the JCP is JSR-303 Bean Validation API. In this post I’m going to give you a preview of a nice Tapestry 5.2 feature, the JSR-303 integration library. I committed the code some weeks ago and you have to wait until Tapestry 5.2 is released if you want to use this library. You can also use the snapshots if you want to give it a try right now.

Tapestry Validation Mechanism

Tapestry provides a powerful validation mechanism which is described here. Among other things this mechanism allows you to annotate your domain model classes with the annotation @Validate. The annotation value is a comma separated list of validation constraints. Each constraint identifies a validator type and optionally a constraint value.

public class User {

    @Validate("required")
    private String username;

    @Validate("required,minlength=5")
    private String password;
}

The @Validate annotation is problematic if your domain model is used in non-Tapestry applications as well as in Tapestry applications. Your non-Tapestry application becomes dependent on Tapestry annotations.

JSR-303 Bean Validation API

The JSR 303 is a specification of the Java API for JavaBean validation in Java EE and Java SE. The JSR standardizes validation constraint declaration and provides a number of built-in constraints. Like in Tapestry the constraints are defined by constraint annotations. These annotations can be applied on properties of the JavaBeans.

public class User {

    @NotNull
    private String username;

    @NotNull @Size(min=5)
    private String password;
}

Tapestry and JSR-303 Bean Validation API

As of verison 5.2 Tapestry will provide an JSR-303 integration library that allows you to annotate your domain model with JSR-303 annotations.  The library is responsible for configuring and bootstrapping the javax.validation.Validator for you. In order to use this library you have to choose an implementation of the JSR-303 specification like Hibernate Validator 4.x. Once you have included an implementation of JSR-303 into your application the service BeanValidatorSource will create an instance of  javax.validation.ValidatorFactory which is used to create Validator instances.

The configuration of BeanValidatorSource is a list of BeanValidatorConfigurer. You can contribute a BeanValidatorConfigurer to the configuration of this service in order to participate on the configuration of javax.validation.Validator. Here is an example how to do it:

public class AppModule {

   public static void contributeBeanValidatorSource(
      OrderedConfiguration<BeanValidatorConfigurer> conf) {

      BeanValidatorConfigurer configurer
               = new BeanValidatorConfigurer() {
         public void configure(
             javax.validation.Configuration<?> aConf) {
            aConf.ignoreXmlConfiguration();
         }
      };
      conf.add("MyConfigurer", configurer);
   }
}

That’s it. If you are using the BeanEditForm component to create forms for your domain model objects you don’t have to change any of your pages. Just pass the object to be edited to the object parameter, BeanEditForm will detect the JSR 303 annotations and validate the user input accordingly.

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">
   <body>
      <t:beaneditform object="user"/>
   </body>
</html>

If you create your forms manually rather than generating them with BeanEditForm you might be interested in how to make the Form component interpret the JSR 303 annotations. Let’s have a look at a simple Login page. The properties userName and password of this page are bound to fields of the form. These properties are annotated with both Tapestry’s and JSR 303 annotation. Now to make Form component analyze the JSR 303 annotations you have to pass the object to be validated into the Forms’ parameter validate. In this examaple we pass the page instance itself.

public class Login {
   @NotNull
   @Property @Persist
   private String userName;

   @NotNull
   @Validate("minlength=10")
   @Property @Persist
   private String password;

   void onSuccess() {
      // Login the user here
   }
}
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">
   <body>
      <t:form validate="this">
         <t:errors/>

         <t:textfield t:id="userName"/>

         <t:textfield t:id="password"/>

         <input type="submit" value="Login"/>
      <t:form>
   </body>
</html>

Alternatively you can process the validation manually. For this purpose just remove validate=”this” from the template and inject the Validator into you page. Let’s see how it works.

Injecting Validator

The created javax.validation.ValidatorFactory is registered in the Tapestry Registry and can be injected into you pages, components or services to create Validator instances. You can also let Tapestry create a Validator object for you which can be injected into your page, component or service. In the following example we inject a Validator instance into the Login page and validate the use input in the onValidateForm() handler method. The violations of the constraints are then recorded in the form and presented to the user.

public class LoginPage {
   @NotNull
   @Property @Persist
   private String userName;

   @NotNull
   @Validate("minlength=10")
   @Property @Persist
   private String password;

   @Inject
   private Validator validator;

   @InjectComponent
   private Form form;

   void onValidateForm() {
      Set<ConstraintViolation<LoginPage>> errors
           = validator.validate(this, Default.class);

      for (ConstraintViolation<LoginPage> next : errors) {
         form.recordError(next.getMessage());
      }
   }

   void onSuccess() {
      // Login the user here
   }
}

Limitations

Unfortunately the JSR 303 doesn’t cover the client-side validation, so that most web frameworks supporting this JSR will come up with proprietary solutions. Tapestry’s integration library doesn’t provide any client-side validators for JSR 303 annotations so far. I’ll announce this missing feature in a separate post. Stay tuned.

3
Jan

I’m pleased to announce the Tapestry 5.0.19 Bug Fix release which contains a fix for TAP5-815, the second most popular Tapestry issue. If you are still a Tapestry 5.0.x user you should upgrade to 5.0.19 because we fixed a serious security bug in this release. The release also contains some performance improvements. Check out the release notes for further details.

The binaries are available for download here. In few days we are going to release Tapestry 5.1.0.7.

Release Notes – Tapestry 5 – Version 5.0.19

Bug

  • [TAP5-815] – Asset dispatcher allows any file inside the webapp visible and downloadable
  • [TAP5-419] – Logging of component class transformations has typo in prefix
  • [TAP5-449] – @CleanupRender Render phase methods not always invoked
  • [TAP5-894] – Fix PartialMarkupDocumentLinkerTest.stylesheet_link()
  • [TAP5-936] – Tapestry wiki link links to nothing for locales other then en

Improvement

  • [TAP5-417] – Tapestry Performance Improvements
  • [TAP5-762] – Upgrade Selenium dependencies to version 1.0.1
  • [TAP5-934] – Update javassist version + improve error reporting in OneShotLock

Task

  • [TAP5-819] – remove ide-specific files from all sub-modules and add them to svn:ignore
29
Dez

In this post Howard describes how you can handle security in your Tapestry app. The approach is based on Tapestry’s request processing mechanism that allows you to extend the framework by your own contributions. In his post Howard contributes a filter for the ComponentRequestHandler pipeline to prevent any access to particular pages unless the user is logged in. For more details read Howard’s post.

Based on Howard’s post I would like to show you how to differentiate the logging output of one client from another. In web applications multiple clients request the same pages, components and services simultaneously. This fact leads to tons of logging output that is unusable because you can’t see which logging output belongs to which user. A simple technique called Mapped Diagnostic Contexts (MDC) can help you to stamp each logging output. The idea is to put contextual information into your logger and is described here.

Mapped Diagnostic Context is a map which is managed on a per thread basis. In log4j or slf4j you can put key-value pairs into the context. These pairs will be used by the logging framework when the logging output is generated. Let’s have a look at a simple log4j configuration. The most interesting part is %X{username}. The %X conversion specifier tells log4j that the contextual information username should be put into the logging output. When generating output the logger will look for a value the context under this key. If available the value will apear in the output.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration>
   <appender name="console">
      <param name="Threshold" value="INFO"/>
      <layout>
         <param name="ConversionPattern"
            value="%d{dd.MM.yyyy HH:mm:ss,SSS} [%X{username}] # %-5p # %c # [%C{1}.%M] # %m%n"/>
      </layout>
   </appender>

   <root>
      <appender-ref ref="console"/>
   </root>

</log4j:configuration>

Now let’s put the name of the logged in user into the MDC. A ComponentRequestFilter is a part of the Tapestry’s request processing mechanism and that’s why a good place to provide the contextual information.  The following filter along with a bunch of other filters is responsible for handling the page requests or component events. Every time a page is rendered or a component event is triggered our MDCFilter will put the name of the logged in user into the context before delegating to the next handler in the pipeline. After the invocation of the next handler is finished the name of the user is removed from the context.

public class MDCFilter implements ComponentRequestFilter {

    static final String MDC_KEY = "username";
    private final ApplicationStateManager applicationStateManager;

    public MDCFilter(final ApplicationStateManager manager) {
        this.applicationStateManager = manager;
    }

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

        try {
            handler.handleComponentEvent(parameters);
        } finally {
            remove();
        }
    }

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

        try {
            handler.handlePageRender(parameters);
        } finally {
            remove();
        }

    }

    private void put() {
        User user = this.applicationStateManager
                            .getIfExists(User.class);

        if (user != null) {
            MDC.put(MDC_KEY, user.getUsername());
        }
    }

    private void remove() {
        MDC.remove(MDC_KEY);
    }

}

The last part of this is to plugin into the request processing mechanism of Tapestry. This is done by a contribution to the ComponentRequestHandler service’s configuration.

public class AppModule {
   public static void contributeComponentRequestHandler(
         OrderedConfiguration configuration) {
      configuration.addInstance("MDC", MDCFilter.class);
   }
}

If you are using Howard’s ComponentRequestFilter to check if the user is logged in you should place the MDCFilter after the RequiresLoginFilter. For this purpose we pass the order constraint after:RequiresLogin into the third parameter of the addInstance() method. This constraint is used to order the object relative to other contributed objects. This way MDCFilter will be executed after the RequiresLoginFilter.

public class AppModule {
   public static void contributeComponentRequestHandler(
         OrderedConfiguration configuration) {
      configuration.addInstance(
            "MDC", MDCFilter.class, "after:RequiresLogin"");
   }
}

That’s it. When a user is logged in you will see logging output like the following one. If the user is not there you will see just [] instead of [bart.simpson].

28.12.2009 22:55:41,066 [bart.simpson] # INFO  # foo.bar.pages.MyPage # [MyPage.onSuccess] # Bla bla bla

It is worth to mention that ComponentRequestHandler pipeline is not the only one place to plug into the request processing mechanism. You can also contribute a RequestFilter to the RequestHandler pipeline. Check out this diagramm for more details.

15
Dez

Ich habe eben im Blog von meinem Verlag Addison-Wesley einen Beitrag zu Tapestry gepostet. Es handelt sich um eine kurze Einführung in Tapestry 5.

Viel Spaß beim Lesen.

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