Appendix B. The Expresso Component Architecture [Future Direction]

Table of Contents

Introduction
Goals
Architectural Overview
What makes a component then?
Architectural Tradeoffs
Component Containers and Services
Example
Component Lifecycle and Interfaces.
ExpressoComponent Interface
ExpressoComponent Lifecycle
Startup Configuration
Example expresso-services.xml
Component Creation
Define the component's interface.
Implement ExpressoComponent interface
Write the component's metadata
Implement Lifecycle interface if you want to receive configuration and lifecycle events.
System initialization
Required System Parameters
Logging: The odd guy out
Enter the System Factory
System Destruction
To Do Items
Future Expansion possibilities
Conclusion
Conclusion
Contributors

Introduction

Note

This chapter is a discussion of a future architectural concept for Expresso. If you are just learning Expresso, then you can skip this chapter without missing out on any of Expresso's features. If you wish to participate in the direction that Expresso develops in the future, then this chapter is for you! Please post your comments about the design and code to the Expresso forums or OpenSource mailing list.

When you read this, you may be thinking to yourself. "Why are we doing this? What is wrong with the current architecture." So before we get into the specific, let's discuss the reasons for going into this refactoring effort in the first place.

First off, Expresso has grown huge. We need a way to refactor the system so that a minimalist application can exist without 8 mb of object code. We also need to provide a way that people can understand Expresso piece by piece without feeling overwhelmed.

The second problem that has readily come apparent as I've come aboard is that Expresso has MANY interdependencies. There is absolutely no way to take one part of Expresso and replace it with something else, or worse yet, try to remove a piece all together. As the developers, we need a way to separate and divide the framework so that we can more easily change code without fear of breaking other areas in the framework.

Goals

Some basic goals are:

  1. Provide a clean way to componentize pieces of Expresso

  2. Provide a basic set of interfaces that allow people to plug in their own components in many areas of Expresso.

  3. Provide the ability to manage the components in a consistent way, including provide an ability to eventually interface well with Java Management Extensions.

  4. All meta-data about the component and its settings are externalized into xml files... component definitions if you will. However, this will be optional to give maximum flexibility to the component writer. Some people hate writing XML definitions, and this will provide them that capability. However, for Expresso proper, we will encourage the use of XML component definitions.

Architectural Overview

The basic architecture will be similar to JBoss in the sense that there will only be a 'microkernel' left as the core, and the rest of the framework will then become services and extras sitting on top of the microkernel. After some deliberation, I decided to not go with the concept that everything is an MBean like JBoss does. The reasons are at the time:

  • Would require yet another 3rd party library, to develop with such as MX4J or Sun's reference implementation.

  • Implementing our own high performance version of JMX would take significant time.

  • The author, at the time of this writing, is strongly suspicious of the possibility of opening up security holes by exposing everything to potential remote management through JMX. This may or may not turn out to be true, but as far as security goes, paranoia pays off.

Instead, we'll make our own component definition and metadata definitions, immediately provide a controller management interface, and in the near future provide a bridge to allow exposure of all the Expresso components to JMX management if the web site operator so chooses. However, using a standard controller, web site operators will still be able to have the full configuration power that a JMX user would have access to.

We'll accomplish this through describing the components through XML files. This way, we can later on provide transformation sheets for

  • Upgrade Paths: Allow easy and automatic upgrade by applying various stylesheets against the various xml documents.

  • JMX: Projects such as Apache Modeler take XML definitions and construct MBeans out of them. By using a platform neutral XML file that contains basically the same information, we can provide a stylesheet to perform on-the-fly transformation and interfacing with Apache Modeller to provide instant JMX capabilities.

What makes a component then?

Some things are easy to spot as a component. Expresso's Cache or DBConnectionPools are obvious candidates for componentization. Expresso's Schema class (and its derivatives) has traditionally been considered a component, but it seems like it would have different needs than a standard component. The proposed architecture actually unifies Schema as just another component with a special section in its metadata definition.

Architectural Tradeoffs

Of course, with any architecture there are a series of tradeoffs. It is the goal here to make the adjustments:

  • When judging component simplicity vs ComponentManager complexity, we will go with the side of component simplicity. The basic goal is to allow a component writer to make a component as simple as possible. By doing this, we will provide a baseline of interfaces, and provide futher interfaces to provide additional basic functionality if the component writer so chooses.

  • Simplicity vs Extensibility: The component manager interface should try to err on the side of extensibility even at the cost of its own implementation being a little more complex. This will allow folks to plug in the Expresso runtime kernel into a wider variety of applications. However, care should be made to not make things so complicated that people cannot tackle the learning curve. If folks are not understanding this architecture, then we need to find a compromise to make things more understandable.

  • Although the use of System properties to provide config information will by and large not be encouraged, it will be possible to pass int system properties where any command line parameter would normally exist.

  • Servlet vs. Generic Container: The Expresso Runtime will emphasize use over a wide variety of systems including AWT and command line programs. Although an equivalent to DefaultInit will exist in Servlet form, there will also be a command line initialization capability as well. If it turns out that there are issues that need to be resolved for the servlet environment to run properly, we will discuss these and modify this design.

  • Statics vs Non Statics. The runtime framework will discourage the use of static variables. The use of component containers and locators will take the place of static objects such as singletons.

Component Containers and Services

To get a reference to a component, the programmer will need to look up a reference to the component. This will be similar to the Internet's DNS system or a JNDI server. The concept is nearly identical to the Apache Avalon concept of a Service manager. However, one of the major concepts that the Expresso component system will encourage is the fact that each component container can serve as its own service locator in a hierarchical manner as well as contain subcomponents. Whenever a component container wants to find a service, it queries its parent component for the service it wants to locate, which then first checks itself for the requested service, and then checks the parent for the service. The request will then be delegated up the hierarchy of service locators.

The next concept behind this will turn the current Expresso configuration system on its ear. Each component contains various 'setup values'. Similar to the concept of the Setup table. Locating setup values will be identical to locating services. A hierarchical search takes place looking for the requested setup value. This is important in that it'll dictate a lot of the program organization: Any class that requires a setup value must be either itself a component, or explicitly contained by a component container. Yes, there will be a few global setup values, but by and large these will be discouraged. This will be in fact similar to the fact that EJBs only store their own setup values in a JNDI directory.

Example

Ok, so enough of the hand waving. Let's look at a real sample:

Here the box labelled Service Locator would be the analogous to a root level Domain Server. Everything eventually percolates to the root server. All boxes in the diagram are components that are also acting as containers and all circles are components that do not have subcomponents. If Schema1 wants to get an instance of cache, it would call getService("CryptoManager") and retrieve a handle to an instance of the system CryptoManager. However, the call to getService("Cache"), would be the same, only that it returns the Cache that is associated with the Context that Schema2 resides in. Which brings us to another point: Every Context is a component (and a component container) too! It is just a specialized component container that has its own custom services. You will see that any current component in Expresso that has getInstance("ContextName") will be replaced with myContainer.getService(), and the appropriate instance for the current Data context will be returned.

Component Lifecycle and Interfaces.

ExpressoComponent Interface

Before we begin, we'll show a diagram with the various interfaces and classes that participate in the component class creation.

We will address the ideas of these components one at a time.

ExpressoComponent

The basic building blocks here are the Containable interface and the Component interface. Containable denotes that an object can contain components. An ExpressoComponent interface denotes the most basic set of methods needed to allow a component to interact with the rest of the system. There are other interfaces available [as can be denoted by all the yellow interfaces in the UML diagram above], but they all add additional optional functionality. The interface for ExpressoComponent is as follows:

public interface ExpressoComponent {

    /**
     * Retrieve the metadata for this component.
     * @return Metatdata for the file.  This can then be used with
     * <code>getInputStream()</code> to read in and parse the data.
     */
    public ComponentMetadata getMetaData();

    /**
     * Retrieve the parent component [if any].  May return null.
     * @return ExpressoComponent or null
     */
    public Containable getParent();

    /**
     * Called by the parent upon initial Expresso component creation so that the
     * child component can find it's container.
     * @param parent the 'containing' component.
     */
    public void setParent(Containable parent);

    /**
     * Retrieve a property of the component.
     * @param propertyName the name of the property to retrieve
     * @return java.lang.Object  The type of the property depends on the type
     * defined in the metadata description file.
     */
    public Object getProperty(String propertyName);

Method Details

  • getMetaData() This method simply returns the component metadata class. This class is simply a JavaBean containing a description of manageable methods, configuration values, etc. If a component writer chooses to not implement the interface Describable to provide for XML-capable metadata, then it would be up to the component to create the component metadata class. However, this is a fairly trivial thing to do. If the component writer implements Describable then the framework will construct the ComponentMetatdata object for the component automatically.

  • getParent() Every component must nest within a container so it can be located, queried, etc. The basic implementation should contain a private Containable variable that is set for the component automatically when the component is created.

  • setParent() This method would be called automatically by the system factory as it builds and nests ExpressoComponents. Other than returning the container with the getParent(), the component basically does not have to do anything with this variable as far as framework management goes. The component itself would query its Container to other components and other setup properties, however.

  • getProperty() Each component has its own set of properties, and this allows the component to get its own setup values through this method. If the component writer does not implement any lifecycle events, then it will be responsible for its own configuration, although it could certainly provide read-only variables. However, this method's use will be limited if the component writer does not implement lifecycle events.

Containable

The Interface Containable denotes that this object can contain Expresso components. Each ExpressoComponent in fact, will have a Containable object as its parent. The interface definition for this class is:

public interface Containable {
    /**
     * Locates an Expresso Service for use by a client.
     * @param componentName the name of the service to locate.
     * @return ExpressoService.
     * @throws IllegalArgumentException if the service cannot be found.
     * @throws IllegalStateException if the service exists, but is not in a
     * 'runnable' state due to some configuration error or other unforeseen
     * issue.
     */
    public ExpressoComponent locateComponent(String componentName);

    /**
     * Query the container to see if a particular service name is installed
     * in the system
     * @param componentName the name of the component to query for.
     * @return true if the service is installed and running.
     */
    public boolean isComponentExists(String componentName);


    /**
     * To register the component for control by the Component Manager.  This will
     * in essense transfer the control of ther service to the Component Manager.
     * This will often be called by the Configuration Bootstrap system.
     * @param newComponent the component to install
     */
    public void addComponent(ExpressoComponent newComponent);

    /**
     * Removes a component from this container.
     * @param componentName The name of the component to remove.
     */
    public void removeComponent(String componentName);


    /**
     * Install a component into the system.  If newComponent implements <code>
     * installable</code> then it shall be installed.  After that, the component
     * is added.
     * @param newComponent An instance of the component to install.
     * @param log a Logger-like interface to a component tha records the process
     * of the installation including any errors, etc.
     */
    public void installComponent(ExpressoComponent newComponent, InstallLog log);


    /**
     * Uninstalls the component.  If the component implements <code>
     * installable</code> then it shall be uninstalled.  After that, it shall
     * be removed.
     * @param componentName the name of the component to uninstall
     * @param log a Logger-like interface to a component tha records the process
     * of the installation including any errors, etc.
     */
    public void uninstallComponent(String componentName, InstallLog log);

    /**
     * Retrieves a list of instances of all contained ExpressoComponents.  Use
     * this for iterating through the components of a current 'context'.  Do not
     * attempt to modify the map given.  Either add or remove a component through
     * the addComponent or removeComponent methods.
     * @return Read only map of the components.
     */
    public Map getChildComponents();

}
As you can see, it provides basic management capabilities for iterating container components, locating components, etc.

Describable

The Describable interface is for allowing a component's metadata to be described by an XML file. Its definition is fairly straightfoward:

public interface Describable {

    /**
     * Get the location of the metadata.
     * @return the URL for the metadata
     */
    public URL getMetadataLocation();

    /**
     * Sets the component metadata for component.  Usually the external
     * 'factory class' that parsed the metadata would then set a ComponentMetadata
     * object for this component.
     */
    public void setMetaData(ComponentMetadata metadata);


}

Method Details

  • getMetadataLocation() All the component has to do is return the resource location as a URL. This can be either through the Class.loadResource() method or for highly specialized applications, the component writer could conceivably retrieve the component metadata through a central web server. Although this method won't be used in Expresso itself, it is available to the component writer if so desired.

  • setMetaData() This method will be called by the system factory that is building the component. The system factory will parse the metadata xml file and then with the ComponentMetadata bean successfully constructed, will associate the component with the constructed metadata class.

Installable

The interface Installable represents any component that when it is first installed into the system needs to perform some specific initialization tasks. Examples of this are a Schema object that needs tables and security set up when it is first installed. The interface definition would look like so:

public interface Installable {

    /**
     * Called when the Service Manager installs this component
     * @param dataContext the DataContext (aka dbname) to install into
     */
    public boolean install(InstallLog log);

    /**
     * Called when the Service Manager uninstalls this component
     * @param dataContext the DataContext (aka dbname) to install into
     */
    public void uninstall(InstallLog uninstallLog);
}

Method details

  • install() This is the function that installs the actual component. It logs its progress through the InstallLog object. If an error occurs, then it logs the error in InstallLog and returns false, at which point the component container will call uninstall() to remove any components that were partially installed.

  • uninstall() This is the function that uninstalls the component, like install(), it will log its progress. The uninstaller() should be aware of the possibility of partially installed components. Therefore, for example, if an expected table is missing, don't quit, just log the fact that the table was missing, and continue uninstallation.

Note about InstallLog. The InstallLog interface is basically identical to log4j in that it has debug(), info(), warn(), and error(). However, InstallLog will probably NOT be a log4j log, rather the actual implementation will capture the output and print it to screen, such as DBCreate currently does. However, it provides a good way of abstracting the actual output to be able to write it to a console, AWT window, or HTML page.

ComponentLifecycle

Most components will want to implement this interface to be able to be configured by the ComponentManager as well as provide initialization and deinitialization services.

A brief note about nesting limits

Although a component could theoretically have unlimited levels of nesting, the current implementation of Apache Digester does not support recursive rules. As such, Expresso dynamically creates rules for Digester to parse nested components up to twenty levels deep. In most cases, this should not affect the designs of most web applications, but it is still a limitation to be noted if you are heavily leaning on nested components for your application design.

How many object instances?

This architecture is kind of a spinoff of the Singleton pattern, first defined by Erich Gamma, et al. The rule of thumb is this: Only one instance of a component will exist with respect to itself and its children components. What does this mean? Looking at the diagram, above, if I call locateComponent("Cache") from Schema1, I'll get the cache component for my data context/ While other data contexts may have other cache components, if I exist inside the context component, I'll only ever see one Cache component ever. So if a component is globally accessible, then there will indeed be only one instance. If the component differs per context (such as DBConnectionPool), then there will be one DBConnectionPool per data context.

ExpressoComponent Lifecycle

Unless the component that is going to be built has absolutely no manageable configuration values, does not need any configuration whatsoever, and is completely threadsafe with no static variables, then a component is going to need some sort of lifecycle management. The ComponentLifecycle interface will need to be implemented by the component. The Lifecycles, to be summed up, are:

  • init Called after the constructor, but before configuration. Allows the component to run any basic construction needs.

  • configure Called to configure the component.

  • reconfigure Called to re-configure the component with the servlet engine still running. The component may have to essentially shut down and restart, but it gives us great runtime flexibility to be able to reconfigure without an entire system restart.

  • destroy Called when the component should release all resources and consider itself dead. This event may or may not occur at program termination, but may be called for a context reload, or many other situations. [Unused Service passivation comes to mind]

The semantics of the tree structure are as follows: For init, configure, and reconfigure, a child component is guaranteed that the parent will have already completed its lifecycle call. For example, if the child depends on a parent setup value, the child is guaranteed that it will be set when it receives a configure event.

The only difference is the destroy() event, which is performed in reverse: Children are destroyed before the parent is destroyed.

Below is the full code for the interface:

 
public interface ComponentLifecycle {

    /**
     * Initialize the component, this is called before the component receives
     * any configuration information.
     */
    public void initialize();

    /**
     * Configure the service.  This is where any parameter settings will take
     * place.
     * @param newConfig a read only dynabean containing all the needed configuration.
     * @throws IllegalArgumentException if the configuration is improper.
     */
    public void configure(Configuration newConfig);

    /**
     * Reconfigures the service during runtime without having to restart the
     * container.
     * @param newConfig a read only dynabean containing all the needed configuration.
     * @throws IllegalArgumentException if the configuration is improper.
     */
    public void reconfigure(Configuration newConfig);


    /**
     * Called upon destruction of the service.  This may or may not have anything
     * to do with container shutdown or reloading.
     */
    public void destroy();
}

You might ask yourself what the Configuration object is. Well its actually pretty simple, it's an interface to an underlying Dynabean, but all the read methods have been removed. This helps enforce the contractual obligation that a component does not modify its own configuration.

In addition to basic lifecycle events, We'll also provide the 'startable' interface to indicate that the component or service can be started and stopped. stop() will be called before destroy() and start() will always be called after configure. Its full text is listed here

public interface Startable {

    /**
     * Called when the system wants the service to start. This, like any of the
     * other lifecycle events may
     */
    public void start();

    /**
     * Called when the system wants the service to stop.  Stop should attempt to
     * release all resources, so that a component can virtuall "cold start" when (if)
     * start is called again.
     */
    public void stop();
}

An example use of this is a Job Server. If it is stopped, then it will not give job handlers any new jobs to perform. When it is started again, it will begin handing out jobs again. On normal startup and shutdown, start() is called after configure(), and stop() is called before destroy(). However, through an administrative interface, start() and stop() can be called any number of times during a component's lifecycle.

Startup Configuration

Obviously all this kind of configuration is going to need some sort of boot strapping process. Whatever factory is combining all these components will have to find out about them, and construct the necessary components to make the running structure like the diagram listed above. This is where the expresso-services.xml file comes into play.

The system factory will have to either be fed an expresso-services.xml file location, which for servlet usage, can easily be sent in via the servlet parameters to the DefaultInit servlet. Or if no parameter is fed in, it will try to locate the expresso-services.xml file in the classpath through a loadResource() call. The second method will be best used for standalone applications rather than servlet environments.

The System Factory will then go through the expresso-services.xml file and use it to instantiate all Expresso components and subcomponents. The System factory would also load the component metadata and call first init() and then configure() for each of the components.

Example expresso-services.xml

Here we list an example expresso-services.xml file. This example will be MUCH larger in real life. For example, every item in the Setup table would be included in each context for each Schema. However, with proper GUI, this won't be a daunting task at all IMO.

<?xml version="1.0" encoding="UTF-8"?>
<expresso-services>
	<!-- 
	Globally Defineable components.  Put here anything that does NOT care about what data
	context it operates in.
	-->
	<component name="CryptoManager" class-name="com.jcorporate.expresso.core.security.CryptoManager">
		<property name="strongCrypto" value="y"/>
		<property name="cryptoKey" value="[random string of base64 encoded values could go here]"/>
		<property name="ecryptMode" value="AES"/>
	</component>
	<component name="SSLRedirector" class-name="com.jcorporate.expresso.core.controller.">
		<property name="httpPort" value="8080"/>
		<property name="sslPort" value="443"/>
	</component>
	<!-- Globally definable setup values -->
	<property name="dummy" value="whatever"/>
	<component name="default" class-name="com.jcorporate.expresso.core.components.DataContext">
		<property name="active" value="true|false"/>
		<property name="hasSetupTables" value="true"/>
		<property name="description" value="Default Database"/>
		<property name="dbConfig" value="hypersonic"/>
		<property name="useSSL" value="true|false"/>
		<property name="startJobHandler" value="true|false"/>
		<!-- Don't want to cache things?  Don't install this component! There will be officially one 
		CacheManager instance per Context component-->
		<component name="Cache" class-name="com.jcorporate.expresso.core.cache.CacheManager">
			<!-- Whatever settings we want here -->
		</component>
		<!-- The number of components will grow as we manage to componentize other components -->
		<component name="email" class-name="whatever the class-name is">
			<property name="mailDebug" value="true|false"/>
			<property name="smtpServer" value="mail.jcorporate.com"/>
			<property name="smtpPassword" value=""/>
		</component>
		<component name="Persistance" class-name="com.jcorporate.expresso.core.components.DefaultContainer">
			<!-- Notice that driver name, type mappings, connect format, etc are NOT listed here.  We'll
			Move these to a 'driver' directory in the Expresso code base, so once and for all, we start
			officially handling the 'non-variable stuff' and save people a lot of non-neceesary configuration
			options.  We find this by the dbConfig-->
			<component name="DBConnectionPool" class-name="com.jcorporate.expresso.core.db.DBConnectionPool">
				<property name="url" value="[URL to database]"/>
				<property name="login" value="[login name]"/>
				<property name="password" value="[password]"/>
				<property name="minConnections" value="3"/>
				<property name="maxConnections" value="24"/>
			</component>
			<!-- New component that takes the place of Schema class to do the type mapping to
			a database -->
			<component name="type-mapper" class-name="com.jcorporate.expresso.core.db.TypeMapper">
				<!-- Type mapper would use the Context level variable dbConfig to know what to load-->
			</component>
		</component>
		<!--
		All the properties will be located here, BUT all the basic metadata, like what controllers, 
		what dbobjects, etc will NOT be listed here.
		-->
		<component name="ExpressoSchema" class-name="com.jcorporate.expresso.core.dbobj.XMLSchema">
			<property name="metaData" value="/com/jcorporate/expresso/core/ExpressoSchema.xml"/>
			<property name="whatever" value="whatever"/>
		</component>
		<!-- And so on for each Schema... each Schema is a component -->
	</component>
	<!-- The following example would show how we could map a certain number of tables to be installed in another database while still using the default security context. In fact, the OtherDBTables would actually go bye bye eventually in the sense that this would do the same job for us.
-->
	<component name="eContent Database" class-name="com.jcorporate.expresso.core.components.DependentDatabase">
		<property name="hasSetupTables" value="false"/>
		<property name="securityContext" value="default"/>
		<component name="eContent" class-name="com.jcorporate.expresso.core.dbobj.XMLSchema">
			<property name="metaData" value="/com/jcorporate/econtent/ContentSchema.xml"/>
			<property name="whatever" value="whatever"/>
		</component>
		<!-- and so on -->
	</component>
</expresso-services>
If you notice, each component gives itself a service name, and an implementation class that it is associated with. After that, it defines the current settings of its own read/write properties. And finally, if it contains any nested components, it will do so after that.

Component Creation

TODO: Flesh this out.

Define the component's interface.

If you want your component to be interchangeable, then you will want to define an interface to your component first. For example the system cache should be a Cache interface rather than a static CacheManager class. The client that is using the cache component would then write:

	Cache systemCache = (Cache)mySchema.locateComponent("Cache");
If you keep your component's usage faithful to the interface, then you are well on your way to making sure your component can be interchangeable.

Implement ExpressoComponent interface

If you look at the ExpressoComponent interface, you will notice that the algorithms behind each of the methods are nearly identical. For example, locateComponent() is going to first check out its own internal Map (HashMap or otherwise) and then call super.locateComponent(). To alleviate coder fatigue, There will be an ExpressoComponentImpl class that will provide a default implementation of this interface. Classes that cannot derive from ExpressoComponentImpl are encouraged to aggregate ExpressoComponentImpl, and forward all standard component calls to the default implementation. For worst case scenario, the user will need to write about 10 lines of code, one line for each method in the interface to call the equivalent method in ExpressoComponentImpl.

Write the component's metadata

The component's metadata will include all setup values necessary to run, a definition of all subcomponents, any schema relations, version information, and descriptions. A minimal description would be as small as:

<component-metadata name="sample1" load-on-startup="false" class="com.sample.MySampleComponent">
	<description>My Sample Component</description>
	<version-info>
		<major-version>1</major-version>
	</version-info>
</component-metadata>
and that is about it. This can be seriously expanded into the full fledged schema component listed below:
<?xml version="1.0" encoding="UTF-8"?>
<component-metadata name="test">
	<description>Test Component</description>
	<version-info>
		<major-version>1</major-version>
		<minor-version>2</minor-version>
		<micro-version>3</micro-version>
	</version-info>
	<message-bundle>/com/jcorporate/expresso/core/Messages.properties</message-bundle>
	<property-list>
		<!-- value= default value. -->
		<property name="testProperty" value="true" type="java.lang.Boolean" description="A simple property" access="readwrite"/>
		<property name="testDropdown" value="ab" type="java.lang.String" description="A dropdown property" access="readwrite">
			<property-valid-value name="a" value="A value"/>
			<property-valid-value name="ab" value="AB value"/>
			<property-valid-value name="abc" value="ABC value"/>
			<property-valid-value name="abcd" value="ABCD value"/>
			<property-valid-value name="abcde" value="ABCDE value"/>
		</property>
<!-- Not supported yet 
		<array-property name="" type="" description="" access="">
			<array-property-value value=""/>
		</array-property>
		<mapped-property name="" description="" access="">
			<mapped-value key="" type="" value=""/>
		</mapped-property>
-->		
	</property-list>
	<method-list>
		<method name="getDataObjects" return-type="java.util.List" description="Retrieve all embedded DBObjects">
			<method-arg name="list" type="java.lang.Integer"/>
		</method>
	</method-list>
	<schema>
		<struts-config location=""/>
		<dbobject>
			<classname name="com.jcorporate.expresso.services.dbobj.MimeTypes"/>
		</dbobject>
		<controller>
			<classname name="com.jcorporate.expresso.services.controller.DBMaint"/>
		</controller>
		<job>
			<classname name="com.jcorporate.expresso.services.job.TestJob"/>
		</job>
		<report>
			<classname name="com.jcorporate.expresso.ext.report.TestReport"/>
		</report>
	</schema>	
	<component-metadata name="nested1">
		<description>Sample Nested Do Nothing Component</description>
		<version-info>
			<major-version>4</major-version>
			<minor-version>5</minor-version>
			<micro-version>6</micro-version>
		</version-info>		
	</component-metadata>
</component-metadata>

Note that the component has all the metadata necessary for an Administrative application to render a form to administer all config values for the component. java.lang.String properties would be text boxes, java.lang.Boolean properties would be checkboxes, etc. Furthermore, properties with property-valid-value sublistings would be rendered as a dropdown box. This allows for extensive component configuration validation before the changes are applied.

You'll notice another section called 'method-list'. This would list all methods that could be called be the administrative app. Examples could be: 'purge job queue' or 'remove old validation entries'. They would be rendered as buttons from the admin page.

Implement Lifecycle interface if you want to receive configuration and lifecycle events.

The Configuration Interface's rules are simple:

 Boolean booleanProperty = (Boolean)newConfig.get("myBooleanProperty");
Since the component knows about what it requires type-wise, as per its metadata, then it knows how to case the property that it is getting.

Init is called after the component's constructor, but before any configuration. The component writer will often put any code that would often go in the constructor into the init() method.

Reconfigure's logic is similar but it will tend to be more complex. The component will most likely stop everything that it is doing before reconfiguring itself. However, sophisticated reconfiguration will check which fields have changed (if any) and dynamically adjust itself to the new configuration without stopping. The choice is up to the programmer.

Destroy is simple: destroy all subcomponents first, and then release all the component's own resources.

System initialization

Required System Parameters

To bootstrap the Expresso component system, the component system will need two parameters:

  1. Location of the expresso-services.xml file. If no parameter is configured, then SystemFactory will first look for the system property "expresso.services". If it does not find that then it will attempt to locate in the classpath an expresso-services.xml file. This initialization tactic is identical to that used by Log4j and allows easier use of the component architecture in self-contained jar file where you don't want the user to have to enter a ton of command line options.

  2. Directory to use for logging. Like expresso-services, we will look for the expresso.logDir system property if this parameter is not provided. If this system parameter doesn't exist, we will go ahead anyway since it is possible for log4j configuration files to have absolute paths for logging that do not require a logging directory. This should give the developer the maximum flexibility in working with the logging configuration.

  3. Logging Configuration file. Like the others, you can use the log4j.configFile parameter to pass the location of the config file. If you pass a filename into the system, you gain the added ability that the ExpressoRuntime initializes log4j using DomConfigurator.ConfigureAndWatch(), so you can adjust log4j.xml config files at runtime without even doing a context reload!

As discussed above, these can be acquired in several ways::

  • Command Line: Pass in those two variables via java virtual machine properties

  • Command Line: Pass in those two variables via command line args. [A quick note here, it is planned to remove the /webAppDir= parameter because of several incompatibilities with a few application servers that rely on keeping the war file compressed.]

  • Servlet Environment: Pass in those two variables servlet initialization properties

  • Either Environment: Load the configuration via some properties file located by the classloader

Logging: The odd guy out

Logging is an interesting system because it has to be the VERY first thing initialized, at startup, and it has to be running while everything else is destroyed. So currently, I'm looking at the following methodology:

Initialize logging first... even before Digesting the config file. [Digester needs a working logging system] By retrieving the LogDirectory in the command line rather than in the config file, it allows us to set up appropriate logging this way.

Never deinitizlize it! The only time Logging should be de-initialized is when the LogManager detects a classloader change. [ie a servlet context reload]. At that point, it would re-initialize logging. But we will leave the deinitialization to the log4j package itself. This is the current plan after experience so far in dealing with classloading and log4j. It sill may change some as experience proves or disproves the author's theories. Log4j can always be moved outside the context classloader if ClassLoading is a problem.

Enter the System Factory

The entire job of the System Factory is to take the initialization values, and build a Global runtime container for the rest of Expresso. The following snippet shows how to build the global container from within the new init servlet:

 
/**
 * Initialize the Expresso runtime system.
 * @param sc The ServletConfig.  The Servlet config in web.xml needs to
 * have 3 parameters set:  loggingDirectory, expressoConfig, and loggingConfig.
 * @throws javax.servlet.ServletException upon instantiation error
 */
public void init(ServletConfig sc) throws javax.servlet.ServletException {
    super.init(sc);

    long startTime = System.currentTimeMillis();
    loggingDirectory = sc.getInitParameter("loggingDirectory");
    expressoServicesConfig = sc.getInitParameter("expressoConfig");
    loggingConfig = sc.getInitParameter("loggingConfig");
    root = SystemFactory.buildExpressoComponentSystem(expressoServicesConfig,
            loggingConfig,loggingDirectory);

    log = Logger.getLogger(RuntimeInitServlet.class);
    long endTime = System.currentTimeMillis();
    log.info("Completed initialization in " + (endTime-startTime)/1000 + " seconds");
}
As can be seen, the initialization can easily be ported to a command line environment, or anything else the Expresso developer can conceive.

System Destruction

Any server engine must also be able to shut itself down in a graceful manner. The global container implements the Lifecycle events and therefore, you can call the destroy() event to trigger a graceful shutdown of the Expresso runtime system. Below is the code for the init servlet's destroy method:

/**
 * Destroys the servlet and the Global Expresso runtime container.
 */
public void destroy() {
    if (globalContainer != null) {
        globalContainer.destroy();
    }
}

Graceful system destruction is also vital for context reloading since the entire runtime must be destroyed and then reloaded.

To Do Items

Other sections still must be done

  1. Error Handling: What are the defined Exceptions and how are they dealt with?

  2. Full Implementation: Currently classes that properly digest the expresso-services.xml and metadata xml are in place. Work is ongoing to finalize the Global Container implementation and System Factory.

  3. Backwards Compatibility: Since we need to keep ConfigManager, Setup, SchemaList around for another version, how are we to map these classes to the new version. The current train of thought is to map Setup to Schema-Level properties, ConfigManager to various level properties as known by the default system layout and SchemaList to walk the component hierarchy to locate Schemas. However, this is still in 'think tank' stage and has not yet been cemented.

  4. Either rearranging expresso.core OR adding expresso.kernel package to signify this as the 'crux' of the Expresso package with other things being secondary. [For example, persistence would actually be still a service]

  5. I want the new component manager GUI to be able to work with no expresso-services file installed at all! I want the GUI to be able to add contexts and components piece by piece. [Of course the default distribution will have some basic components available and preinstalled] This might lead to a pure struts Action class since DBObject configuration and persistance may or may not exist yet. This is similar to the fact that DBCreate currently is running as a Servlet since Expresso Security is not necessarily set up. If any sort of database capability is set up, then we will use it from that point forward.

  6. We need a full analysis of what 3rd party jars will be required for the Expresso runtime kernel to function properly. The goal of the analysis would be to reduce the number of jars required for a minimal command-line application. By minimal we also mean no servlet environment, and no configuration manager. Currently we're looking at:

    1. Oswego Concurrency Library: This allows us to have much higher performance component lookups that are thread safe.

    2. Commons Digester: Required for parsing the configuration files

    3. Commons Logging: Required by Digester

    4. JAXP compliant parser: Our goal is to be Xerces independent if so desired.

    5. Commons Beanutils: Needed for the introspection of the components and invocation of the configuration handlers. For a minimalist app, this MIGHT end up being optional.

    6. Log4j: Needed for logging, of course

    However, this list has not been fully realized yet since there are still implementation issues to overcome.

  7. J2EE App Server Integration: This is the ideal time to also check to make sure that the basic architecture will work with the rest if the current J2EE standards. For example, a version of the GlobalContainer could be created that runs under JNDI rather than its own internal hashtables. Instances of components could be retrieved from the JNDI server this way. Although there would be a performance penalty, this would be a good method to make sure we can expand into the EJB world. Before Expresso 5.1 is released, there should at least be a 'game plan' for how this framework could be run and used from within an EJB server.

  8. Remote Objects and Clustering: The final architecture should work well in a clustered and remote object environment. Although the architecture itself is not particularly concerned with clustering aspects, it should be determined that it would be used to access remote and clustered services.

Future Expansion possibilities

This model with metadata has many possibilities in the future. Examples are:

  • Adding the full DBObject metadata in the dbobject tag in the schema tag.

  • Defining regular expressions that could be used for standardized field and parameter validation. This would often be defined on a Schema-wide basis.

  • Full JMX integration. The use of Apache modeler would make dynamic MBeans relatively automatic. All the design team has to do is write a single bridge class to translate the digested XML file into MBean metadata.

  • Full database relations: Inside the schema tag, one could define how all the various dbobjects are interrelated. Foreign key relationships could be well defined here.

  • Component Event Listeners: We should be able to allow components to listen to events that occur with other components. This way, things like cached setup values can be reread. An example of this would be to have a component get notified whenever the Data Context level setup values get changed.

Conclusion

It is hoped that this article will give you a clear enough picture about where the development team is steering Expresso towards its 5.1 release. Feedback can be directed directly to the author at rimovm@centercomp.com or to the JCorporate opensource mailing list.

Conclusion

Contributors

The following persons have contributed their time to this chapter:

Note

Was this EDG documentation helpful? Do you wish to express your appreciation for the time expended over years developing the EDG doc? We now accept and appreciate monetary donations. Your support will keep the EDG doc alive and current. Please click the Donate button and enter ANY amount you think the EDG doc is worth. In appreciation of a $35+ donation, we'll give you a subscription service by emailing you notifications of doc updates; and donations $75+ will also receive an Expresso T-shirt. All online donation forms are SSL secured and payment can be made via Credit Card or your Paypal account. Thank you in advance.

Copyright © 2001-2004 Jcorporate Ltd. All rights reserved.