Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: restore

Scrollbar

Tapestry IOC Configuration is the configuration of both the IOC services provided by Tapestry and those you write yourself. Both are configured in the same way: using Java, not XML.

Div
stylefloat:right
titleRelated Articles
classaui-label
Content by Label
showLabelsfalse
showSpacefalse
titleRelated Articles
cqllabel = "configuration" and space = currentSpace()

One of the key concepts in Tapestry IoC is distributed configuration. The distributed part refers to the fact that any module may configure a service. Distributed configuration is the key feature of Tapestry IoC that supports extensibility and modularity.

...

Let's say you've written a bunch of different services, each of which does something specific for a particular type of file (identified by the file's extension), and each implements the same interface, which we'll call FileServicer. And now let's say you need a central service that selects the one of your FileServicer implementations based on a given file extension. You start by providing a service builder method:

Code Block
java
java
  public static FileServiceDispatcher buildFileServicerDispatcher(Map<String,FileServicer> contributions)
  {
    return new FileServiceDispatcherImpl(contributions);
  } 

In order to provide a value for the contribution parameter, Tapestry will collect contributions from service contribution methods. It will ensure that the keys and values match the generic types shown (String for the key, FileServicer for the value). The map will be assembled and passed into the service builder method, and from there, into the FileServiceDispatcherImpl constructor.

So where do the values come from? Your service contributor methods, methods whose names start with "contribute":

Code Block
java
java
  public static void contributeFileServicerDispatcher(MappedConfiguration<String,FileServicer> configuration)
  {
    configuration.add("txt", new TextFileServicer());
    configuration.add("pdf", new PDFFileServicer());
  }  

Or, instead of instantiating those services ourselves, we could inject them:

Code Block
java
java
  public static void contributeFileServicerDispatcher(MappedConfiguration<String,FileServicer> configuration,
  
    @InjectService("TextFileServicer") FileServicer textFileServicer,
    @InjectService("PDFFileServicer") FileServicer pdfFileServicer)
  {
    configuration.add("txt", textFileServicer);
    configuration.add("pdf", pdfFileServicer);
  }  

The extensibility comes from the fact that multiple modules may all contribute to the same service configuration:

Code Block
java
java
  public static void contributeFileServicerDispatcher(MappedConfiguration<String,FileServicer> configuration)
  {
    configuration.add("doc", new WordFileServicer());
    configuration.add("ppt", new PowerPointFileServicer());
  }  

Now the FileServicerDispatcher builder method gets a Map with at least four entries in it.

...

The following example is an annotation-based alternative for the contribution method above.

Code Block
java
java
@Contribute(FileServiceDispatcher.class)
public static void arbitraryMethodName(MappedConfiguration<String,FileServicer> configuration)
{
    configuration.add("doc", new WordFileServicer());
    configuration.add("ppt", new PowerPointFileServicer());
}  

If you have several implementations of a service interface, you have to disambiguate the services. For this purpose the marker annotations should be placed on the contributor method.

Code Block
java
java
@Contribute(FileServiceDispatcher.class)
@Red @Blue
public static void arbitraryMethodName(MappedConfiguration<String,FileServicer> configuration)
{
    configuration.add("doc", new WordFileServicer());
    configuration.add("ppt", new PowerPointFileServicer());
}

In this example, the method will only be invoked when constructing a service configuration where the service itself has both the Red and Blue marker annotations. Tapestry knows which annotations are marker annotations, and which marker annotations apply to the service, via the @Marker annotation on the service implementation.

...

Note that it is possible for the same contribution method to be invoked to contribute to the configuration of multiple different services.

Code Block
java
java
  @Contribute(FileServiceDispatcher.class)
  @Local
  public static void arbitraryMethodName(MappedConfiguration<String,FileServicer> configuration)
  {
    configuration.add("doc", new WordFileServicer());
    configuration.add("ppt", new PowerPointFileServicer());
  }  

Configuration Types

There are three different styles of configurations (with matching contributions):

...

For example, here's a kind of Startup service that needs some Runnable objects. It doesn't care what order the Runnable objects are executed in.

Code Block
java
java
  public static Runnable buildStartup(final Collection<Runnable> configuration)
  {
    return new Runnable()
    {
      public void run()
      {
        for (Runnable contribution : configuration)
          contribution.run();
      }
    };
  }  

Here we don't even need a separate class for the implementation, we use an inner class for the implementation. The point is, the configuration is provided to the builder method, which passes it along to the implementation of the service.

On the contribution side, a service contribution method sees a Configuration object:

Code Block
java
java
  public static void contributeStartup(Configuration<Runnable> configuration)
  {
    configuration.add(new JMSStartup());
    configuration.add(new FileSystemStartup());
  }    

The Configuration interface defines just a single method: add(). This is very intentional: the only thing you can do is add new items. If we passed in a Collection, you might be tempted to check it for values, or remove them ... but that flies in the face of the fact that the order of execution of these service contribution methods is entirely unknown.

...

Tapestry supports only this simple form of parameterized types. Java generics supports a wider form, "wildcards", that Tapestry doesn't understand.

Anchor
Ordered_List
Ordered_List

Ordered List

Ordered lists are much more common. With an ordered list, the contributions are sorted into a proper order before being provided to the service builder method.

Again, the order in which service contribution methods are invoked is unknown. Therefore, the order in which objects are added to the configuration is not known. Instead, we enforce an order on the items after all the contributions have been added. As with service decorators, we set the order by giving each contributed object a unique id, and identifying (by id) which items must preceded it in the list, and which must follow.

So, if we changed our Startup service to require a specific order for startup:

Code Block
java
java
  public static Runnable buildStartup(final List<Runnable> configuration)
  {
    return new Runnable()
    {
      public void run()
      {
        for (Runnable contribution : configuration)
          contribution.run();
      }
    };
  }  

Notice that the service builder method is shielded from the details of how the items are ordered. It doesn't have to know about IDs and pre- and post-requisites. By using a parameter type of List, we've triggered Tapestry to collect all the ordering information.

For our service contribution methods, we must provide a parameter of type OrderedConfiguration:

Code Block
java
java
  public static void contributeStartup(OrderedConfiguration<Runnable> configuration)
  {
    configuration.add("JMS", new JMSStartup());
    configuration.add("FileSystem", new FileSystemStartup(), "after:CacheSetup");
  }    

Often, you don't care about ordering; the first form of the add method is used then. The ordering algorithm will find a spot for the object (here the JMSStartup instance) based on the constraints of other contributed objects.

For the "FileSystem" contribution, a constraint has been specified, indicating that FileSystem should be ordered after some other contribution named "CacheSetup". Any number of such ordering constraints may be specified (the add() method accepts a variable number of arguments).

...

No annotation is needed for these cases.

Anchor
overrides
overrides

Configuration Overrides

Since
since5.1
 
The OrderedConfiguration and MappedConfiguration interfaces now support overrides. An override is a replacement for a normally contributed object. An override must match a contributed object, and each contributed object may be overridden at most once.

...

In Tapestry 5.0, services that wanted to support this kind of override behavior had to implement it on an ad-hoc basis, such as ApplicationDefaults overriding FactoryDefaults. In many cases, that is still useful.

 

Scrollbar