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.
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.
Below you will find an example interface, followed by some best practices along with their reasoning.
package org.apache.bizserver.docs; public interface DocumentRepository { String ROLE = DocumentRepository.class.getName(); Document getDocument(Principal requestor, int refId); }
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.
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.
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
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
The entire Avalon Framework can be divided into seven main categories (as is the API): Activity, Service, 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.
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.
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
]
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.
suspend
[Suspendable
]
recontextualize
[Recontextualizable
]
recompose
[Recomposable
]
reconfigure
[Reconfigurable
]
resume
[Suspendable
]
In this section, we will cover all the sections alphabetically with the exception of the most important concern area: Component.
A Word About Containers |
---|
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 ServiceManager or ServiceSelector. 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. |
This is the core of Avalon Framework. Any interface defined in this concern area will throw ServiceException.
A Component that uses other Components needs to implement either this
interface or the old Composable interface. The new interface is the
preferred way of doing things. 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.
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
ServiceManager
.
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.
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.
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.
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.
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.
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
.
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.
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.
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.
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.
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.
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
.
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.
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.
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.
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.
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.
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"/>
This used to be the core of Avalon Framework. The Component interface and it friends have been deprecated in favor of the Service package, which is exactly the same, except that the service package uses java.lang.Object in place of the Component interface. Any interface defined in this concern area will throw ComponentException.
Before the service package was put in place, every Avalon Component had to implement the Component interface. We have removed this restriction in the service package. 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.
For maximum backward compatibility with existing applications, it can still be useful to implement the Component interface as older applications may depend on it being available.
Any Component must use default no parameter constructors. All
configurations are done with the
Configurable
or
Parameterizable
interfaces.
A Component that uses other Components needs to implement either this
interface or the new Serviceable interface. The new interface is the
preferred way of doing things. 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
.
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.
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.
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.
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.
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.
Java 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.