|
Jakarta main
Avalon main
Essentials
Guide
Reference
For Developers
|
Avalon Framework - Guide - COP in Avalon
Avalon Framework - Guide - COP in Avalon
|
by Berin Loritsch, Leo Simons
Components in Avalon |
At the core of the Avalon framework is the Component. We define it as "a
passive entity that performs a specific role". This is important to grasp
because it requires a specific way of thinking.
A passive API |
A passive entity must employ a passive API. A passive API is one that is
acted upon, versus one that acts itself. See the
Inversion of Control pattern
for an explanation.
|
A specific Role |
The concept of roles come from the theater. A play, musical,
or movie will have a certain number of roles that actors play.
Although there never seems to be a shortage of actors, there
are a finite number of roles. I am not going to make reference
to different types of roles at this point, but simply bring
the concept to light. The function or action of a role is
defined by it's script.
We are introducing this concept now because you need to have it
in mind when you are designing your system architecture. Think
of the different roles in your system, and you will have your
"cast" of components so to speak.
For each role, you need to specify it's script, or interface to
the rest of the system. To be honest the interface is not enough.
There are specific contracts that you must define and keep in mind
when you specify your interfaces. In other words, what users
of the Component must provide, and what the Component produces.
When the interface and contract are defined, you can work on your
implementation.
|
|
The Component |
John Donne wrote, "No man is an island." to communicate that we
are all interdependent. The same is true for the Component. That
is why there are different concerns regarding the Component. In
the section on roles we specified one of the concerns: the role.
The concerns directly supported by the Avalon Framework are:
configuration, external component use, management, and execution.
We used to have an marker interface Component. This has been deprecated
because requiring all components extend this interface makes
integrating Avalon with other component systems like
CORBA very cumbersome.
As you might of guessed, each one of these concerns has a separate
interface that describes that concern. We will delve deeper into
the interfaces and the reasoning behind them in other sections. It
is important to know the order of precedence for the concerns so
that you know the overall contracts of how they are put together.
-
Configurable: marks an object that can be configured.
-
Composable: marks an object that uses Components.
-
Initializable: marks an object that can be initialized.
-
Disposable: marks an object that can be disposed.
-
Stoppable: marks an object that can be started and stopped.
The contract surrounding this order means that the methods defined
by each of those interfaces are called in a specific order by the object
that created the Component. Each interface represents a narrow view
of the Component or object being controlled.
Notice that each interface is separate from Component, so you can use
them for simple objects.
|
The Composable |
In Avalon, Composable is defined as an active entity that controls
or uses Components. Its best analogy is that of a musical composer.
The musical composer chooses what instruments (Components) by their
role in the symphony (system) and tells them which notes to play.
The Avalon Composable follows the principles of Inversion of Control,
and is assigned a Component Manager. Within this section we will
discuss how to look up specific Components, and then how to prepare
the ComponentManager for the Composable.
The Composable has a specific contract that must be enforced for security
reasons. The ComponentManager must only be set once. That means that
the compose method must ignore all subsequent
requests to set the ComponentManager after it is successfully set.
|
Finding your Component |
The Component Manager |
For the majority of all cases, you will need to use the ComponentManager
get the instance of the Component you need. If you recall the discussion
on Component Roles in the Component documentation, you already have
a head start. In Avalon, Roles are defined by the work interface a
Component has. A work interface is different from any other interface
because it is the interface that defines the Component's Role. Composable
and Component are concern interfaces because they address specific
concerns about the Component.
The ComponentManager has one method to retrieve all of your Components.
The lookup method will look up the Component based on the
fully qualified name (FQN) of the work interface (Role). It is important
to realize that the ComponentManager returns Components, and therefore
you must recast the Component to the Role you need. See the following
example:
final MyComponent component = (MyComponent)manager.
lookup( "com.mycompany.myproject.MyComponent" );
|
It is important to note that Role is not the same thing as functional
equivalence. In other words, if you have a MailSpooler that is functionally
equivalent to a FileStore (they do the same thing), it does not mean that
they perform the same Role. The FileStore is used to store objects to
files, and the MailSpooler is used to temporarily store messages until
they are sent. Thus they are separate roles. Sometimes you need to
create a new interface name that does nothing more than allow access to
alternate roles who have the same role.
|
The Component Selector |
Sometimes you will have several Components that function in the same role.
For those cases, you will use the ComponentSelector to choose the exact
one you need. The best way to describe its proper use is the scenario
described here. You have several formatters that have the same Role:
to take an input document and format it according to the rules in the
individual Component implementations. One formatter may take a text file
and remove all tabs and replace them with four spaces. Another formatter
may reverse the formerly mentioned one. Yet another takes the text file
and formats it for a canvas object. For the Composable, it makes no difference
what the implementation does--just that it formats the text.
Using the processing chain example in the previous paragraph, we realize
the unsuitability of the ComponentManager for getting the right Component.
The Component addresses the concern of one Component per role. Fortunately,
the ComponentSelector is a Component. That means we use the ComponentManager
to lookup the ComponentSelector. The ComponentSelector is designed to choose
the specific Component out of many that perform the same
Role. The following code will help:
final ComponentSelector selector = (ComponentSelector)manager.
lookup( "org.mycompany.myproject.FormatterSelector" );
final Formatter formatter = (Formatter)selector.select( myURL );
|
The selector does not discriminate against lookup keys. In that respect it
acts much like a hashtable lookup. Keep in mind that the implementation of the
selector does not limit you to a hashtable lookup--you can dynamically
instantiate objects as well. It takes an object (a hint), and returns the
specific Component based on that hint.
|
When you are done with the Component |
Both the ComponentManager and the ComponentSelector require you to
release your Component when you are done with it. The method used
to do this is "release". One way of handling this is to use the
try/catch/finally construct. For your convenience, the following
code can help:
MyComponent component = null;
try
{
component = (MyComponent) manager.lookup("org.mycom.MyComponent");
component.myMethod();
}
catch (Exception e)
{
getLogger().debug("Error using MyComponent", e);
}
finally
{
if (component != null) manager.release(component);
}
|
The reason for this is so that smart Component Managers that
select Components from a pool can properly manage the resources.
|
|
Populating the ComponentManager |
It is the responsibility of the entity that creates the Composable to give it a
ComponentManager with all of the Roles populated. If you create your own
implementations of the ComponentManager and ComponentSelector then you have
the liberty of deciding how to populate them. Keep in mind that there are
default implementations included, and you should model their behavior as
much as possible.
DefaultComponentManager |
The DefaultComponentManager is nothing more than a Hashtable lookup of roles
and Components. It even gives you the method put to populate
the ComponentManager. One feature of the DefaultComponentManager is that
it can cascade. In other words, if the role is not found in this ComponentManager,
the default implementation will look in the parent ComponentManager.
For the paranoid developer, the Cascading feature of the ComponentManager
can be seen as a security hole as opposed to a usability enhancement. You
are free to create your own implementation that does not use the Cascading
feature--but you have to manually populate it with anything that would
have been in the parent ComponentManager that your child Composable needs.
Truth be told, there is very little risk due to the set-once contract for
ComponentManagers. The method is never exposed to hostile agents before
the ComponentManager is set.
|
DefaultComponentSelector |
The DefaultComponentSelector again is simply a Hashtable selection of Components
based on hints. It gives the method put to populate the ComponentSelector.
The ComponentSelector does not have the cascading feature of the ComponentManager,
nor should it. A ComponentSelector simply holds a number of Components that
implement the same role--there really is no need to cascade.
After the ComponentSelector is populated, you must put it in the ComponentManager.
Please use the role of the Component you are selecting, not the role of the selector
itself. An acceptable convention is to add the "Selector" name to the end of the
Role you are looking up. Just be consistent.
|
|
|