|
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.
-
enableLogging()
[LogEnabled ]
-
contextualize()
[Contextualizable ]
-
compose()
[Composable ]
-
configure()
[Configurable ]
or
parameterize()
[Parameterizable ]
-
initialize()
[Initializable ]
-
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.
-
suspend()
[Suspendable ]
-
recontextualize()
[Recontextualizable ]
-
recompose()
[Recomposable ]
-
reconfigure()
[Reconfigurable ]
-
resume()
[Suspendable ]
|
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 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.
|
|
|
|
|