Jakarta main

Avalon main

About


Chapters


Printer Friendly


Framework and Foundations
Framework and Foundations

We will describe Avalon's contracts and interfaces so we have a foundation to actually build our Components.

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 Component's Role

In Avalon, all Components have a role. The reason is that you retrieve your Components by role. At this stage, the only concern area we are using is the signature of the role. If you recall in the second section, we defined a Component as "the combination of a work interface and the implementation of the interface". That work interface is your role.

Creating the Role's Interface

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


package org.apache.bizserver.docs;

public interface DocumentRepository extends Component
{
    String ROLE = DocumentRepository.class.getName();

    Document getDocument(Principal requestor, int refId);
}

      
Best Practices

  • Include a String called "ROLE" that has the role's official name. That name is the same as the fully qualified name for the work interface. This helps later on when we need to get an instance of the Component later.

  • Do extend the Component interface if possible. This makes it easier on you when it is time to release your Component. If you are not in control of the work interface, then you do not have this option. It is not the end of the world, as you can recast the instance to Component when it is time to release it.

  • Do one thing and do it well. A Component should have the simplest interface possible, When your work interface extends several other interfaces, you muddy the contract for this Component. An old American acronym helps define this pattern: Keep It Simple, Stupid (KISS). It's not hard to outsmart yourself—I've done it a number of times myself.

  • Only specify the methods you need. The client should have no knowledge of implementation details, and too many alternative methods only introduce unneeded complexity. In other words pick an approach and stick with it.

  • Don't let your Role's interface extend any lifecycle or lifestyle interfaces. By implementing any of those classes of interfaces, you are tying an implementation to the specification. This is a bad pattern and this will only lead to debugging and implementation problems later.


Choosing the Role's Name

In Avalon, every Role has a name. It is how you get references to other Components in the system. The Avalon team has outlined some idioms to follow for the naming of your role.

Naming Idioms

  • The fully qualified name of the work interface is usually the role name. The exceptions are listed after this general rule. Using this example, our theoretical Component's name would be "org.apache.bizserver.docs.DocumentRepository". This is the name that would be included in your interface's "ROLE" property.

  • If we obtain the reference to this Component through a Component Selector, we usually take the role name derived from the first rule and append the word "Selector" to the end. The result of this naming rule would be "org.apache.bizserver.docs.DocumentRepositorySelector". You can use the shorthand DocumentRepository.ROLE + "Selector".

  • If we have multiple Components that implement the same work interface, but are used for different purposes, we have separate roles. A Role is the Component's purpose in the system. Each role name will start with the original role name, but the purpose name of the role will be appended with a /${purpose}. By example we could have the following purposes for our DocumentRepository: PurchaseOrder and Bill. Our two roles would be expressed as DocumentRepository.ROLE + "/PurchaseOrder" and DocuementRepository.ROLE + "/Bill", respectively.





Overview of Framework Interfaces

The entire Avalon Framework can be divided into seven main categories (as is the API): Activity, Component, Configuration, Context, Logger, Parameters, Thread, and Miscellany. Each of those categories (except Miscellany) 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 worried 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, and 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 event that will be called. Because there is a correct way to create and prepare Components, you can set up your Components as you receive events.

The Lifecycle of a Component is split into three phases: Initialization, Active Service, and Destruction. Because these phases are sequential, we will discuss the events in order. 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.

  1. enableLogging() [LogEnabled]

  2. contextualize() [Contextualizable]

  3. compose() [Composable]

  4. configure() [Configurable] or parameterize() [Parameterizable]

  5. initialize() [Initializable]

  6. start() [Startable]


Active Service

This list of stages occurs in this specific order, but may occur multiple times during the life of the Component. Please note that should you choose to not implement the Suspendable interface, it is up to your Component to ensure proper functionality while executing any of the Re* stages.

  1. suspend() [Suspendable]

  2. recontextualize() [Recontextualizable]

  3. recompose() [Recomposable]

  4. reconfigure() [Reconfigurable]

  5. resume() [Suspendable]


Destruction

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

  1. stop() [Startable]

  2. dispose() [Disposable]



Avalon Framework Contracts

In this section, we will cover all the sections alphabetically with the exception of the most important concern area: Component.

When I use the word "container" or "contains" when describing Components, I have a very specific meaning. I am referring to child Components that the parent Component has instantiated and controls. I am not referring to Components obtained through a ComponentManager or ComponentSelector. Furthermore, some Avalon stages received by a container must be propagated to all of its children implementing the appropriate interface. The specific interfaces in question are Initializable, Startable, Suspendable, and Disposable. The reasoning for this contract is that these particular interfaces have specific execution contracts.

Component

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

Component

Every Avalon Component must implement the Component interface. The Component Manager and Component Selector only handle Components. There are no methods associated with this interface. It is only used as a marker interface.

Any Component must use default no parameter constructors. All configurations are done with the Configurable or Parameterizable interfaces.


Composable

A Component that uses other Components needs to implement this interface. The interface has only one method compose() with a ComponentManager passed in as the only parameter.

The contract surrounding this interface is that the compose() is called once and only once during the lifetime of this Component.

This interface along with any other interface that has methods specified uses the Inversion of Control pattern. It is called by the Component's container, and only the Components that this Component needs should be present in the ComponentManager.


Recomposable

On rare occasions, a Component will need a new ComponentManager with new Component role mappings. For those occasions, implement the recomposable interface. It has a separate method from Composable called recompose().

The contract surrounding the interface states that the recompose() method can be called any number of times, but never before the Component is fully initialized. When this method is called, the Component must update itself in a safe and consistent manner. Usually this means all processing that the Component is performing must stop before the update and resume after the update.



Activity

This group of interfaces refers to contracts for the life cycle of the Component. If there is an error during any method call with this group of interfaces, then you can throw a generic Exception.

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.


Initializable

The Initializable interface is used by any Component that needs to create Components or perform initializations that take information from other initialization steps. 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(). 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.


Suspendable

The Suspendable interface is used by any Component that is running for the duration of its life that permits itself to be suspended. While it is most commonly used in conjunction with the Startable interface, it is not required to do so. The interface defines two methods: suspend() and resume(). Neither method has any parameters.

The contract surrounding this interface is that suspend() and resume() may be called any number of times, but never before the Component is initialized and started or after the Component is stopped and disposed. Calls to suspend() when the system is already suspended should have no effect as well as calls to resume() when the system is already running.



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 you 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.

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.


Reconfigurable

Components that implement this interface behave very similar to Recomposable Components. It's only method is named reconfigure(). This design decision is used to minimize the learning curve of the Re* interfaces. Reconfigurable is to Configurable as Recomposable is to Composable.



Context

The concept of the Context in Avalon arose from the need to provide a mechanism to pass simple objects from a container to a Component. The exact protocol and binding names are purposely left undefined to provide the greatest flexibility to developers. The contracts surrounding the use of the Context object are left for you to define in your system, however the mechanism is the same.

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.


Recontextualizable

Components that implement this interface behave very similar to Recomposable Components. It's only method is named recontextualize(). This design decision is used to minimize the learning curve of the Re* interfaces. Recontextualizable is to Contextualizable as Recomposable is to Composable.


Resolvable

The Resolvable interface is used to mark objects that need to be resolved in some particular context. An example might be an object that is shared by multiple Context objects, and modifies its behavior based on a particular Context. The resolve() method is called by the Context before the object is returned.



Logger

Every system needs the ability to log events. Avalon uses its LogKit project internally. While LogKit does have ways of accessing a Logger instance statically, the Framework wishes to use the Inversion of Control pattern.

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 libraries. It provides only a client API. Avalon Framework provides three wrapper classes that implement this interface: LogKitLogger for LogKit, Log4jLogger for Log4J, and Jdk14Logger for JDK 1.4 logging.



Parameters

Avalon realizes that the Configuration object hierarchy can be heavy in many circumstances. Therefore, we came up with a Parameters object that captures the convenience of Configuration objects with a simple name and value pair.

Parameterizable

Any Component that wants to use Parameters instead of Configuration objects will implement this interface. Parameterizable has one method named parameterize() with the parameter being the Parameters object.

The contract is that this is called once during the lifecycle of the Component. This interface is not compatible with the Configurable interface.


Parameters

The Parameters object provides a mechanism to obtain a value based on a String name. There are convenience methods that allow you to use defaults if the value does not exist, as well as obtain the value in any of the same formats that are in the Configurable interface.

While there are similarities between the Parameters object and the java.util.Property object, there are some important semantic differences. First, Parameters are read-only. Second, Parameters are easily derived from Configuration objects. Lastly, the Parameters object is derived from XML fragments that look like this:


<parameter name="param-name" value="param-value"/>

          


Thread

The thread marker interfaces are used to signal to the container essential semantic information regarding the Component use. They mark a component implementation in regards to thread safety. It is a best practice to delay implementing these interfaces until the final Component implementation class. This avoids complications when an implementation is marked ThreadSafe, but a component that extends that implementation is not. The interfaces defined in this package comprise part of what I call the LifeStyle interfaces. There is one more LifeStyle interface that is part of the Excalibur package—so it is an extension to this core set—Poolable that is defined in Excalibur's pool implementations.

SingleThreaded

The contract with SingleThreaded Components is that the interface or the implementation precludes this Component being accessed by several threads simultaneously. Each thread needs its own instance of the Component. Alternatively, you may use Component pooling instead of creating a new instance for every request for the Component. In order to use pooling, you will need to implement Avalon Excalibur's Poolable interface instead of this one.


ThreadSafe

The contract with ThreadSafe Components is that both their interface and their implementation function correctly no matter how many threads access the Component simultaneously. While this is generally a lofty design goal, sometimes it is simply not possible due to the technologies you are using. A Component that implements this interface will generally only have one instance available in the system, and other Components will use that one instance.



Miscellany

The classes and interfaces in the root package for Avalon Framework incorporates Cascading Exceptions, and a couple of generic utilities. However, one class deserves mention beyond the others.

Version

JavaTM versioning techniques are entries in the manifest file in a jar. The problem is, when the jar is unpacked you lose the versioning information, and the versioning is in an easily modified text file. When you couple this with a higher learning curve, detecting Component or Interface versions is difficult.

The Avalon team came up with the Version object to allow you to have easily determined versions, and to compare versions. You may implement the Version object in your Components and your tests for the proper Component or minimum version level will be much easier.







Copyright ©1999-2002 by the Apache Software Foundation. All Rights Reserved.