Framework and Foundations

Avalon Framework is the central piece to the entire Avalon project. If you understand the contracts and constructs defined in the framework, you can understand anything that uses it. Remember the principles and patterns we have already discussed so far. In this section, we will expound on how the Role concept works practically, the lifecycle of components, and how the interfaces work.

Defining the Service Interface

In Avalon, all components implements a Service, so we need to define the Service Interface and the associated semantic contract.

Creating the Service Interface

Below you will find an example interface, followed by some best practices along with their reasoning.


package org.apache.avalon.bizserver.docrepository;

/** Interface to store and retrieve arbitrary Documents in a persisted store.
 *
 * The DocumentRepository is responsible to store Documents, create a reference
 * identity and be able to retrieve the same document at any point later in 
 * time using the document identity returned in the <code>storeDocument()</code>
 * method call.
 */
public interface DocumentRepository
{
    /** Retrieves a Document from the persisted document store.
     *
     * @return Document of the previously stored Document or null if not
     * found.
     * @throw SecurityException if the Principal is not authorized to 
     * retrieve that document.
     */
    Document retrieveDocument(Principal requestor, int refId)
        throws SecurityException;
    
    /** Store a Document into the persisted store.
     *
     * @return A Unique Document Identity, to be used later to retrieve the
     * same document.
     * @throw SecurityException if the Principal is not allowed to store
     * documents in the persisted store.
     */
    int storeDocument( Principal requestor, Document doc )
        throws SecurityException;
}

      

Best Practices

  1. Do NOT extend the any life cycle interfaces in your service interface.
  2. Document as much semantic rules (implicit or explicit) as possible. This will limit the possibilities of misunderstanding between the people who define the Service Contract and the implementing developer. Even if that is today the same person, the Service may be re-implemented in the future by someone else. Also, additional clients shouldn't have to understand the implementation, and everything should be 100% clear from the service documentation.
  3. Do one thing and do it well. A Service should have the simplest interface possible, When your service interface extends several other interfaces, you muddy the contract for this Service. An old American acronym helps define this pattern: Keep It Simple, Stupid (KISS). It's not hard to outsmart yourself -- We do it all the time.
  4. Only specify the methods that are needed in the Service interface. It is important to realize that the Service interface is used by client code. Within the component, you probably need additional methods between the classes. These methods should be package protected and NOT exposed in the Service interface. This increase understandability and the possibility to exchange the component in the future.

Overview of Framework Interfaces

The entire Avalon Framework can be divided into 5 main categories (as is the API): Activity, Service, Configuration, Context, Logger. Each of those categories represents a unique concern area. It is common for a component to implement several interfaces to identify all the concern areas that the component is concerned about. This will allow the component's container to manage each component in a consistent manner.

Lifecycle for Avalon Interfaces

When a framework implements several interfaces to separate the concerns of the component, there is potential for confusion over the order of method calls. Avalon Framework realizes this, so we developed the contract for lifecycle ordering of events. If your component does not implement the associated Interface, then simply skip to the next that will be called. The lifecycle order makes it easy to setup and prepare the component for use, in each of the lifecycle methods.

The Lifecycle of a component is split into three phases: Initialization, Active Service, and Destruction. Because these phases are sequential, we will discuss them in that order. The Active Service phase is outside the concern of the container, and basically consists of other components calling the methods in the Service interface. In addition, the act of Construction and Finalization is implicit due to the Java language, so they will be skipped. The steps will list the method name, and the required interface. Within each phase, there will be a number of stages identified by method names. Those stages are executed if your component extends the associated interface specified in parenthesis.

Initialization

This list of stages occurs in this specific order, and occurs only once during the life of the component.

  • enableLogging [ LogEnabled ]
  • contextualize [ Contextualizable ]
  • compose [ Composeable ]
  • service [ Serviceable ]
  • configure [ Configurable ] or parameterize [ Parameterizable ]
  • initialize [ Initializable ]
  • start [ Startable ]

Destruction

This list of stages occurs in the order specified, and occurs only once during the life of the component.

  • stop [ Startable ]
  • dispose [ Disposable ]

Avalon Framework Contracts

In this section, we will cover all the sections alphabetically.

Activity

This group of interfaces refers to contracts for the life cycle of the component. Each method allows the generic java.lang.Exception to be thrown in any case of error.

Disposable

The Disposable interface is used by any component that wants a structured way of knowing it is no longer needed. Once a component is disposed of, it can no longer be used. In fact, it should be awaiting garbage collection. The interface only has one method dispose that has no parameters.

The contract surrounding this interface is that the dispose method is called once and the method is the last one called during the life of the component. Further implications include that the component will no longer be used, and all resources held by this component must be released. In a way, it is a replacement of finalize() , which has very complicated semantics, that doesn't apply to dispose() .

Initializable

The Initializable interface is used by any component that needs to perform initializations that take information from other initialization steps. It is more natural to place all the initialization in the intialize() method, and only gather the required pieces in the preceeding lifecycle methods. The interface only has one method initialize that has no parameters.

The contract surrounding this interface is that the initialize method is called once and the method is the last one called during the initialization sequence. Further implications include that the component is now live, and it can be used by other components in the system.

Startable

The Startable interface is used by any component that is constantly running for the duration of its life. The interface defines two methods: start and stop . Neither method has any parameters.

The contract surrounding this interface is that the start method is called once after the component is fully initialized, and the stop method is called once before the component is disposed of. Neither method will be called more than once, and start will always be called before stop . Furthermore, the method must return, it can not go into a endless loop. From this follows that the component should create a thread and call the Thread.start() method inside this method to ctart the continous processing. The stop() method should interrupt the thread in a safe manner. This should be done by variables and the Thread.interrupt() method, slightly depending on how the thread is operating. Implications of using this interface require that the start and stop methods be conducted safely (unlike the Thread.stop method) and not render the system unstable.

Configuration

This group of interfaces describes the concern area of configuration. If there are any problems, such as required Configuration elements that are missing, then the component may throw a ConfigurationException .

Configurable

Components that modify their exact behavior based on configurations must implement this interface to obtain an instance of the Configuration object. There is one method associated with this interface: configure with a Configuration object as the only parameter.

The contract surrounding this interface is that the configure method is called once during the life of the component. The Configuration object passed in must not be null .

Configuration

The Configuration object is a representation of a tree of configuration elements that have attributes. In a way, you can view the configuration object as an overly simplified DOM. There are too many methods to cover in this document, so please review the JavaDocs. You can get the Configuration object's value as a String , int , long , float , or boolean -- all with default values. You can do the same for attribute values. You may also get child Configuration objects.

There is a contract that says that if a Configuration object has a value that it should not have any children, and the corollary is also true -- if there are any children, there should be no value.

To simplify the code, the method calls getChild() and getChildren() will always return Configuration and never null.

You will notice that you may not get parent Configuration objects. This is by design. To reduce the complexity of the Configuration system, containers will most likely pass child configuration objects to child components. The child components should not have any access to parent configuration values. This approach might provide a little inconvenience, but the Avalon team opted for security by design in every instance where there was a tradeoff.

Context

The concept of the Context in Avalon arose from the need to provide a mechanism to pass simple objects (i.e. non-components) from a container to a component. This has since then grown into a very powerful mechanism in Avalon Merlin, where you can create your own Context entries, as well as extend the Context instance itself with your own implementation. See the Avalon Merlin documentation for details on how to define custom context entries.

Context

The Context interface defines only the method get . It has an Object for a parameter, and it returns an object based on that key. The Context is populated by the container, and passed to the child component who only has access to read the Context .

There is no set contract with the Context other than it should always be read-only by the child component. If you extend Avalon's Context , please respect that contract. It is part of the Inversion of Control pattern as well as security by design. In addition, it is a bad idea to pass a reference to the container in the Context for the same reason that the Context should be read-only .

Contextualizable

A component that wishes to receive the container's Context will implement this interface. It has one method named contextualize with the parameter being the container's Context object.

The contract surrounding this interface is that the contextualize method is called once during the life of a component, after LogEnabled but before any other initialization method.

Logger

Every system needs the ability to log events. Avalon Merlin uses a subsystem called Avalon Logging which is capable of logger subsystem plug-ins. Currently, it supports Apache Log4J and the original Avalon LogKit. Expect more systems to follow soon. Also, it is fairly easy to create a new Avalon Logging Plug-In, in case you need to access a custom logging system.

Logging is often done by statically addressing some factory method in the Logging subsystem. Avalon always tries to apply Inversion of Control, which means that the component should not try to figure out how to obtain a Logger instance, it should be given one by the container. The container can then be configured with the most suitable Logging system for the application, without any changes to the components.

LogEnabled

Every component that needs a Logger instance implements this interface. The interface has one method named enableLogging and passes Avalon Framework's Logger instance to the component.

The contract surrounding this method is that it is called only once during the component's lifecycle before any other initialization step.

Logger

The Logger interface is used to abstract away the differences in logging systems. It provides only a client API, and a fairly simple API that is.

Service

This is the core of Avalon Framework. Any interface defined in this concern area will throw ServiceException.

Serviceable

A component that uses other components (what we call a Dependency ) needs to implement this interface. The interface has only one method service with a ServiceManager passed in as the only parameter.

The contract surrounding this interface is that the service is called once and only once during the lifetime of this component.

ServiceManager

The ServiceManager is passed to the components that implements the Serviceable interface in the service method. The ServiceManager has a single method, lookup , which is used by the component to locate its dependencies. It does this by passing a key in the moethod call and the ServiceManager will hand back a component reference of the requested Service . I.e. The component requests a Service , but doesn't need to be concerned about which implementation, and safely cast the returned object to the Service Interface . The component must declare which dependencies it has, and can explicitly also declare the key that will be used in the lookup method call. If no key is defined, the Service Interface name must be used.