Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Fixed Related Articles

Wiki Markup
{scrollbar}

Tapestry IoC Configuration

Wiki Markup
{float:right|background=#eee}
{contentbylabel:title=Related Articles|showLabels=false|showSpace=false|space=@self|labels=configuration}
{float}

Tapestry Configuration of Tapestry IOC services – both those provided by Tapestry and those you write yourself – are configured is done 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);
  } 

...

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());
  }  

...

Naming conventions vs. Annotations

Since
since5.2
 
If you prefer annotations over naming conventions you can use the @Contribute annotation. As of version 5.2 this annotation that may be placed on a contributor method of a module instead of starting the methods name with "contribute". The value of the annotation is the type of the service to contribute into.

...

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());
}

...

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());
  }  

...

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();
      }
    };
  }  

...

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());
  }    

...

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();
      }
    };
  }  

...

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");
  }    

...

Null values, once ordered, are edited out (the List passed to the service builder method does not include any nulls). Again, they are allowed as placeholders, for the actual contributed objects to organize themselves around.

Since
since5.3
When using {{add()}} without any specific constraints, a default constraint is added: after the previously added element. These default constraints are only within a single contribution method, but makes it much easier to set the order of several related contributions. Note that the contributions will be ordered relative to each other and it's possible that contributions by some other module or method may be interspersed between them.

Mapped Configurations

As discussed in the earlier examples, mapped configurations are also supported. The keys passed in must be unique. When conflicts occur, Tapestry will log warnings (identifying the source of the conflict, in terms of invoked methods), and ignore the conflicting value.

...

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.

Wiki Markup
{scrollbar}