This document explains the basic principles behind the design of the IberAgents platform.
A framework is as well defined as the components it contains. In IberAgents, there is a clear way to create a component, that only depends of what we expect it to do.
The framework is oriented towards data management using Java Beans and web communication, either by web services using SOAP or through a regular web application that displays web pages.
This document first explains the organization of the platform. Design of key features is discussed next: bean objects, configuration, component discovery, life cycle management, persistence and deployment. The conclusion contains some pointers to further information.
A node is an instance of the IberAgents platform. It is formally defined by three parameters: host, port and directory, which together specify the location at which it is listening for connections. So, the right address to access components, either by SOAP or with a web browser, is composed using the node specification. The main web access is done at
http://[host]:[port]/[directory]/platform
while SOAP access to service [service] should be directed to
http://[host]:[port]/[directory]/services/[service]
Nodes can communicate between them; a global registry is kept at one node, where the list of components.
There are several types of components specified in the platform. Each is defined by an interface, located in the package com.iberagents.
Component Hierarchy in IberAgents.
To define a component, the developer must first inherit from the appropriate interface from those described above.
The component is always accessible from other components on the same node. To locate it, a unique identifier is used which usually is just its class name. When there are alternative implementations of the same component (e.g. for the persistence layer), an interface is used as the identifier. The user guide shows such a setup in practice. Separating interface and implementation is not mandatory, but provides some benefits such as only publishing the methods in the interface.
Use of simplified Java Beans (called here bean objects) is spread throughout the IberAgents platform.
Any attribute (i.e. embedded value, be it a primitive class such as int or a java.lang.String value) that has get...() and set...() methods is considered to be a bean attribute. For example, an attribute like
private int value;
should be accessed through methods
public int getValue(); public void setValue(int value);
These methods are often called accessors in the literature.
Note: the original Java Bean specification from Sun allows for the use of is...() methods when the attribute is a boolean value. In IberAgents this is not allowed.
Note 2: the existence of the attribute itself is not verified; only the accessor methods are required. The value might therefore be stored in whatever form the developer sees fit, as the example below shows: even though accessors use int, the attribute itself is stored as a java.lang.String.
private String value;
public int getValue()
{
return Integer.parseInt(this.value);
}
public void setValue(int value)
{
this.value = Integer.toString(this.value);
}
A bean object is not allowed to include Collection classes, such as java.util.ArrayList. When an aggregation is necessary, a plain Java array should be used; it can contain either primitive classes or complex bean classes.
A bean object can contain other bean objects, following the same convention for accessor methods as for attributes. These other beans will in turn have other attributes, arrays and nested values.
The objective of this framework is to have straightforward serialization and deserialization of bean objects. A set of components provides converstion to xml files, SOAP messages, generic edition using web pages and persistence.
When elements can be added or removed from a group, an ItemList should be used. It contains a variable number of bean objects inside, sorted by a primary key; furthermore, theses lists can be modified directly via web.
An essential feature of IberAgents is that it keeps a centralized set of configuration parameters, that can be changed while the system is running.
Any component that implements the com.iberagents.Configurable interface admits a configuration object. When the relevant configuration file is read, and before the component starts to do its job, the interface method configure(com.iberagents.config.Configuration) is called, passing the com.iberagents.config.Configuration object. Any bean objects may be used. An example is shown in the user guide.
The component is responsible for storing its configuration, so that it can be accessed whenever it's needed. Usually an attribute is used for this purpose.
This architectural pattern refers to a certain way of component interaction. Instead of having the component make a call to get the desired data or behaviour, it is the framework itself the one that calls the component to pass the required object.
It is sometimes considered a bad practice to mix IoC with component-initiated calls. We believe that this is indeed the case, but only when both are mixed in the context of the same functionality. Therefore, in IberAgents all embedded configurations are passed to the component from the framework; while component discovery is initiated by the component that requires the behaviour.
An admin component (com.iberagents.config.Editor) is responsible for letting an administrator change configuration values. As soon as they are modified using the web interface, the configuration object inside the relevant component is updated. The next time it is used, the refreshed version will be used.
Some configuration parameters are required by more than one component. In this case, the optimal solution is to create a class that implements the interface com.iberagents.ConfigurationComponent. Afterwards, the usual component discovery mechanism (see below) can be used to obtain this object.
The IberAgents platform has a set of global components; it also supports independent, specialized applications (see below). Each set of configurations is embedded into a com.iberagents.config.Container object, and they are manipulated independently.
An application developer needs only to worry about the configuration of her components. They will be accessible only within that application.
Most components require the help of other components to perform their job. An easy mechanism for discovering other components is an important requirement for IberAgents.
As part of the global configuration, a set of components is started. Through modifying this set, the administrator can decide which components are started.
It may even be the case that there are two different component classes that implement the same interface. Only the component specified in the configuration file will be started.
Even though the task of discovery is performed through components, the developer must use a set of static methods. They are in com.iberagents.Finder, a class that only has static methods. (Obviously, it cannot be a component itself, since there would be no way to discover it!)
The developer must ensure that the component is present before calling it; otherwise, an RuntimeException will be thrown.
The com.iberagents.registry.LocalFinder component is responsible for locating components in the local node. The developer has to use one of the following methods:
Component Finder.findLocal(Class); Component Finder.findLocal(String);
Both can be used to locate any kind of component on the local node; the first one accepts a class object (the class of the component or the interface it implements), while the second one accepts a text string. The caller must cast the component to the right class before using it.
An example of use is provided in the user guide.
The component com.iberagents.registry.RemoteFinder is used internally to locate components on a different node, called services. It queries the com.iberagents.registry.GlobalRegistry on its native node, and returns a proxy for the requested service.
Communication with that service is performed through SOAP, but this is completely transparent to the developer; she will only have to make a regular component call.
Service Finder.find(Class); Service Finder.findRemote(Class, Node);
In the first call, the service in the local node will be returned if present; otherwise, the service on the first node that appears in the global registry. When there is more than one node containing the desired service, a round-robin algorithm is used (i.e., nodes are returned in turns).
When using the second call, the developer must know before-hand the node where the service resides.
A service is just a regular component that must implement an additional method ping(), returning its state as a boolean value.
In some occassions, the developer will want to call all nodes, and receive all answers at the same time. In this case a com.iberagents.bind.Collector is required. The following method is used.
Collector Finder.createCollector(Component, Class);
This collector object is then used to create a proxy to the service, which can be accessed as a regular service. The collector will asynchronously call all nodes that contain the service, and aggregate the responses. A call to its getAnswers() method returns a hash map with (node, object) pairs corresponding to each response.
The method getAnswers() blocks until responses from all nodes have been collected, or the timeout specified in com.iberagents.bind.send.DelivererConfiguration has been reached.
Each type of component has its own life cycle, which may depend on its interface. They are detailed below.
All components are created when the platform starts, right after reading the configuration file. An empty constructor is used to create the unique instance of the component.
After that, if the component implements the interface Configurable, a configuration object is passed using the method configure() (but only if one is present in the configuration file).
The life cycle of a restartable component (when it is not an agent) is as follows.
Observe that the startup can assume that the configuration object is present.
The agent interface is the richest in life cycle methods. The following sequence is performed; agent state is shown like this.
The method delay() returns the delay between rounds in milliseconds. This is only a hint to the task manager, since it is possible that act()ing takes longer than this delay. Also, if the agent object is notify()ed using the standard Java semaphore calls, the thread wakes up.
As shown above, a Restartable component acts differently depending on whether it is an agent. The purpose of this difference is to allow agents to run all of their code in a separate thread, avoiding synchronization problems.
Application developers have easy access to a persistence layer, via the component com.iberagents.persistence.Persistence. There are methods to store(), read() and delete() objects, where the return value (true or false) means success or failure of the operation.
IberAgents can store serialized objects either into a database or to individual files. The latter does not require any configuration (aside from creating a directory); while the former has maximum performance when anything beyond retrieving by primary key is required.
When using a database as back-end, each bean object is stored into its own table; and nested beans are stored in separate tables. An internal scheme of primary and foreign keys is used to retrieve all the proper objects and compose them as necessary.
Persistence has been tested using MySQL, PostgreSQL and Oracle; a slightly different configuration is required for each one.
Aside from storing, reading and deleting individual objects, a couple of methods provide flexible functionalities:
They are based on the com.iberagents.persistence.Query class. It is created for a particular class, and as constraints are added, more requirements will be placed on objects to retrieve or delete.
Queries can be easily extended to accept other constraints, should they be found necessary.
IberAgents supports several applications on the same node, roughly equivalent to web applications in J2EE. It features:
Each aspect is treated in a subsection below.
The extension directory (normally ./ext) is scanned every few seconds; the value is configurable. When a new or modified jar file is found, all classes inside it are loaded, along with all XSL files and the configuration file.
Modified application files substitute any applications previously loaded under the same file name.
A new class loader (inheriting directly from java.lang.ClassLoader) is instantiated for every application. They are separated from each other, so classes from another application cannot be seen.
A caching scheme is used so that class loading is as fast as possible. Unlike regular class loading, it is not possible to have some classes in a jar not loaded.
Any component found in the jar is automatically registered under the first interface it implements (and therefore instantiated and configured), so it is not necessary to configure what components to load anywhere. However, user and admin components are not registered until a user accesses them.
Note: if a class found in a jar file was already defined in the framework (i.e. it was in the classpath already) it is not loaded.
When an application is loaded, the deployer agent (com.iberagents.deploy.Deployer) looks for a file named configuration.xml in the top directory of the jar file. It then decompresses it into the extension directory, where any runtime changes are stored. It is assigned a name that matches the application: configuration from ibertest.jar will be stored into ibertest.xml. (If a previous uncompressed configuration file is found, then it is used instead of the one in the jar.)
Therefore, each application contains its default configuration. To retrieve it instead of the local one, simply delete the uncompressed configuration file.
IberAgents has been designed with simplicity as its main concern, since its goal is to be the basis for a number of projects. Please contact us if you have any comments.
Online documentation for supported features is available, along with a user guide for developers. Use as an agent platform is also documented. The status of the framework can be useful to decide whether IberAgents is suitable for use in a given project.
Updated: Dec 3 2004.
© 2002-2004 Ibermática.
Webmaster