Avalon for Apache Cocoon
This document tries to provide you with the basic knowledge of Avalon that is necessary to understand the source code of Apache Cocoon and the structure of the cocoon.xconf configuration file.
People trying to understand Avalon in depth, will probably not be much helped by this document. But, if you want to understand the internals of Cocoon, you have to have a basic grasp of Avalon.
The document also outlines the basic steps for configuring Avalon components within Cocoon.
Avalon has closed ... long live Avalon ?!?
The Apache Avalon project has closed in 2004. This message on the Avalon website has caused a lot of confusion to some people. "Avalon is dead" has often been heared, but this is misleading at least.
What has closed is the Apache Avalon project, not the concept of the Avalon Framework. On the former Avalon website there are links to a number of new projects that succeed the Apache Avalon project inside and outside of the Apache Foundation. One of them is the Apache Excalibur project which is hosting the Avalon framework as well as the Fortress container and some more.
Cocoon has been built using the Excalibur Component Manager (ECM), a direct predecessor of the Fortress container. Most of what is written in the Framework - Developing section of the Excalibur website applies directly to the Cocoon source code and makes a good tutorial style reading for potential Cocoon developers.
As of the time of writing this document (right after the release of Cocoon 2.1.7) there are discussions among the Cocoon developers about switching to a different container for future Cocoon versions, but as this is no trivial move and the Cocon 2.1.x line will have to be supported for quite some time, the ECM will continue to live as a part of Cocoon as well. Even a potential new container would most likely be based upon the same basic ideas of Separation of Concerns (SoC) and Inversion of Control (IoC).
For an introduction into the Avalon Framework, see the Excalibur homepage as well as the section about the Framework. If you are entirely new to this, you might be confused by a lot of different new terms on the Excalibur website such as Fortress, Containerkit, Components, etc.
In addition, it is not always clear what parts refer to a tangible piece of software and what parts talk about concepts.
For now, just keep in mind that
- the Avalon framework is a conceptual model for decomposition of a software into components (later called services).
- Cocoon is built on this concept.
- one implementation (among others) of this concept is the Excalibur Component Manager (ECM) which Cocoon uses.
There has been a lot of exchange between Cocoon and Excalibur (and the former Apache Avalon project) in that Cocoon has been using the Excalibur Component Manager (ECM) and has been donating Avalon Components (now often referred to as Avalon Services) that were originally developed as part of Cocoon to the Excalibur project because it was felt they were of general interest and should be available to other projects as well.
The Excalibur project decided to take the ECM further with the development of the Fortress container, while Cocoon stayed with ECM and made it's own local changes to ECM. Therefore the link today is primarily in the concept of the Avalon framework, not necessarily in any code. The Cocoon code is self-contained in that respect, i.e. you cannot run Cocoon in a newer version of ECM/Fortress as you could run Cocoon in the latest version of the Jakarta Tomcat Servlet container.
Components (Services) play a ROLE
A lot of attempts to explain the Avalon framework use the roleplay on a stage or in a movie to explain the concept of roles in the framework. This can be quite misleading, depending on what you assoiciate with a role in a movie.
The modern perception of a role in a movie comprises not only what the actor should do but also how exactly she is suppsed to do it. The author of the book and the director of the movie often control every single word, any gesture and every move of the actor. A role in a modern movie or screenplay is as programmed that it could easily be played by a robot or be simulated in a computer, which is already reality 21st century's Hollywood. This concept of a role is not what the creators of the Avalon framework had in mind, though.
The idea of a role in the Avalon framework relates more directly to the classic greek drama where there have been the roles of the protagonist and the antagonist for example and it was up to the actors to fill this basic idea of the role with live. A play was much different depending on who played a certain role in a specific performance on a given day on a given stage. But there has been a kind of contract between the roles in the paly that the actors had to stick to in order to keep the plot moving towards the desired outcome.
This is exactly what roles in Avalon are about; basically they are contracts, which is why they materialize as a Java Interface after all in Cocoon.
For any Cocoon play (aka pipeline) it takes at least two, in most cases three roles:
- A generator (mandatory)
- One or more transformers (optional)
- A serializer (mandatory)
Generators, transformers and serializers are Avalon components (services). Cocoon was designed with the idea in mind that it will provide most of the commonly needed services for each role, but to support application specific components (services) equally. This is used by applications which are "based on Cocoon" such as Apache Lenya or Daisy for example. These packages are basically Cocoon + specific matchers, generators, transformers, serizalizers, readers and actions.
The Cocoon configuration files
The Cocoon configuration is split into three layers logically:
- The Sitemap
- The cocoon.xconf file
- The cocoon.roles (and user.roles) file(s)
Many Cocoon implementors have never touched anything else but the sitemap. As Cocoon comes with good defaults for the cocoon.xconf and the cocoon.roles files, many tasks can be accomplished by just wiring together the predefined components from the default cocoon.xconf and cocoon.roles files.
One level deeper, there is the cocoon.xconf file, which contains configuration information for the individual Avalon services that Cocoon uses internally. The cocoon.xconf file serves two purposes:
- It determines the Avalon components (services) which will be instantiated upon the initial startup of Cocoon
- It contains runtime configuration information for each component. This is just a mechanism to avoid having to have dozends or hundreds of individual little .xconf files, but to have a Cocoon configuration in a central place and in once piece.
Sooner or later most Cocoon implementors get to know the cocoon.xconf file as they need to make changes such as switching of components that are unwanted in a specific environment (HSQLDB is a candidate for that for example) or changing the default behaviour of components.
But if you get into not only implementing but developing Cocoon (or code for a Cocoon based application) there is a deeper secret you should now and understand: the cocoon.roles file.
The cocoon.roles file is never touched by Cocoon implementors but only by developers. This is why you often don't even see it as it is usually hidden in the jar files and accessed through the classpath. Making changes to cocoon.roles means you have to build and deploy Cocoon from scratch.
The classes and interfaces
These Avalon classes and interfaces are extensively used by Cocoon:
A ServiceManager selects Components based on a role. The contract is that all the Components implement the differing roles and there is one Component per role. If you need to select one of many Components that implement the same role, then you need to use a ServiceSelector. Roles are the full interface name.
To understand roles better let's use the the analogy of a play. There are many different roles in a script. Any actor or actress can play any given part and you get broadly the same results (same phrases spoken, same movements made, etc.), but with each actor the exact nuances of the performance are different.
This is the deprecated version of the ServiceManager. In some places this component is still used, but over time all the usages of this class will be replaced with the ServiceManager.
A Serviceable is a class that needs to connect to software components using a "role" abstraction, thus not depending on particular implementations but on behavioral interfaces. This means, if you need to use other components in your implementation, you have to implement Serviceable to be able to get other components.
This is the deprecated version of Serviceable. In some places this component is still used, but over time all the usages of this class will be replaced with Serviceable.
This interface identifies classes that can be used as Components by a Composable.
A Component is the basic building block of Avalon. When a class implements this interface, it allows itself to be managed by a ComponentManager and used by an outside element called a Composable. The Composable must know what type of Component it is accessing, so it will re-cast the Component into the type it needs.
Components in Cocoon are e.g. those defined in cocoon.xconf.
Configuration is an interface encapsulating a configuration node used to retrieve configuration values. This is a "read only" interface preventing applications from modifying their own configurations. The contract surrounding the Configuration is that once it is created, information never changes. The Configuration is built by the ConfigurationBuilder.
Configurable is an interface describing a component which can be configured. This component gets a Configuration object as input.
A ConfigurationBuilder builds Configurations.
Most available Avalon components are configured in the cocoon.xconf.
Avalon now incorporates a couple of modifiers for a Component definition that allows you to control the number of Components in a pool. This is especially helpful in Cocoon where the defaults don't always work well.
The magic attribute is "pool-max". The defaults are:
- pool-max: 8
What this means is that the pool for the default component initially contains zero instances. If demand exceeds this then the pool will increase, one component at a time, up to 8 instances. Beyond that the pool turns into a factory. That is, new Component instances are created, but destroyed when they are returned. This is a performance issue - but it does manage the number of instances available at one time.