ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Using PASX

by Andrew Newton
07/10/2001

Have you ever written a program, only to look back upon it and find dissatisfaction with its configuration process? Have you ever used properties files, only to find that they are not expressive enough for you needs? Have you ever created a small configuration hack to solve some specific problem, only to later wish you had spent more time on a generic solution?

If you answered "yes," then you are like most Java programmers and, fortunately for you, there are tools to help solve these problems. If you answered "no," then perhaps a discussion on the limitations of properties files will convince you that there are better ways.

Properties files are a mainstay of the Java programming and execution environment. But once a programmer needs to get beyond the simplicity of name/value pairs offered by the Properties class, there is a need for more expressiveness. Often, Java programmers attempt to extend properties files by attaching extra semantics to the names or values (or both) of the properties themselves. And often times this is the slippery slope that starts off as a minor dip and ends up a rather large chasm.

To illustrate, try using properties to assign a list of values to a single name. Let's say you wanted to keep track of a set of nameservers. You might try something like this:

hosts_1=ns.foo.com
hosts_2=ns.bar.com
hosts_3=ns.acme.com

That was simple enough. By overriding the meaning of the name, you can easily write a program that understands that all property names beginning with "hosts_" are but a single element in the list "hosts."

Now let's make things a little trickier. Imagine that you have two separate instances of the same Bean class called InternetHost. Instance A is to be concerned with a list of web servers, while instance B is to be concerned with a list of name servers. To configure both instances from the same file, one solution might be as follows:

name_hosts_1=ns.foo.com
name_hosts_2=ns.bar.com
name_hosts_3=ns.acme.com
web_hosts_1=www.foo.com
web_hosts_2=www.bar.com
web_hosts_3=www.acme.com

Well, that works, but it is a kludge. If you are still not a believer then try cranking up the complexity just a little more: try making one of the elements in these lists into a list of its own, or try making the list of name/value pairs into a map of name/value pairs where the underscore ("_") character is legal. Your simple properties file has now become very complex.

Comment on this articleWhat are your open source tool solutions? Do you use PASX or an alternative to it?
Post your comments

Being an observant reader, you probably also noticed that the naming of the individual instances of InternetHost was gently side-stepped and not addressed in the last example. To truly assign the first three properties to instance A and the second three properties to instance B, you must somehow tell the instances which set they are to use in an instance-independent manner. If instance A were hard-coded to look for property names starting with name_, and instance B were hard-coded to look for property names starting with web_, then the two instances would not be of the same object class.

Finally, this last example also brings up another point. Just what do you call instance A? Is it simply "A"? Where do you go to find it? Is it a local instance or just a remote interface? Is there a globally static reference to it? If so, how do you get to instance B (or is that "B")?

The solution to these problems is to use a component configuration and naming framework. There are many tools to aid you in this task, and one of these tools is PASX. PASX is an open source tool for Java utilizing XML for configuration and JNDI for naming.

PASX uses XML for configuration because it is much more expressive than a simple list of properties. To illustrate how XML is better for this task, consider the first example, which would look something like this in the markup defined by PASX:

<List>
  <String>ns.foo.com</String>
  <String>ns.bar.com</String>
  <String>ns.acme.com</String>
</List>

While this is more verbose, it is much more elegant. And because some Lists can be ordered, the order in which the elements appear in the XML determines the order of the final data structure. With a properties file that ends up being a Properties object, the name must indicate the element index, because a Properties object is a simple derivation of Hashtable.

The second properties example would look like this in XML:

<List name="name-servers">
  <String>ns.foo.com</String>
  <String>ns.bar.com</String>
  <String>ns.acme.com</String>
</List>
<List name="web-servers">
  <String>www.foo.com</String>
  <String>www.bar.com</String>
  <String>www.acme.com</String>
</List>

And remember the challenge given? Make one of the elements in the list into a sub-list, and make one of the lists into a map structure where the name of each element may contain an underscore. Here is that example:

<List name="name-servers">
  <String>ns.foo.com<String>
  <List>
    <String>ns1.bar.com</String>
    <String>ns2.bar.com</String>
  </List>
  <String>ns.acme.com</String>
</List>
<Map name="web-servers">
  <String name="most_visited">www.foo.com<String>
  <String name="most_bytes">www.bar.com</String>
</Map>

In PASX, components (a class or tightly-integrated set of classes) are Java Beans that implement the PASXService interface. They are defined by the <Service> tag in XML, and this tag is used to name the individual instances of a component. The XML to give instance "A" the list of name servers and instance "B" the list of web servers would look like this:

<Service name="A" class="my.InternetHost">
	  <List name="hosts">
    <String>ns.foo.com</String>
    <String<ns.bar.com</String>
    <String<ns.acme.com</String>
  </List>
</Service>
<Service name="B" class="my.InternetHost">
  <List name="hosts">
    <String>web.foo.com</String>
    <String>web.bar.com</String>
    <String>web.acme.com</String>
  </List>
</Service>

PASX defines a set of standard XML tags for declaring properties of type List, Map, Integer, String, Boolean, etc. However, a PASXService class may also use XML tags of its own through the miracles of Namespaces and XML Schemas. The use of an XML Schema allows the component author to define his own tags and allows the parser to validate both the tags defined by PASX and the tags defined by the component author. The following example schema defines a <Server> tag, which must have hostName and portNumber attributes. The <Server> tag must appear as the child of the <Cluster> tag at least once, but as many times as are needed. And the <Cluster> tag must appear as the child of the <ServerFarm> tag at least once, but as many times as are needed.

<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2000/10/XMLSchema"
  xmlns:pce="http://pasx.org/PASX/CUSTOM-EXAMPLE"
  targetNamespace="http://pasx.org/PASX/CUSTOM-EXAMPLE"
  elementFormDefault="qualified" >

  <annotation>
    <documentation>
    A custom schema example to be using with PASX (PCE)
    </documentation>
  </annotation>

  <element name="Server">
    <complexType content="empty">
      <attribute
        name="hostName" 
        use="required" 
        type="string"/>
      <attribute
        name="portNumber"
        use="required" 
        type="positiveInteger"/>
    </complexType>
  </element>

  <element name="Cluster">
    <complexType>
      <sequence>
        <element
          ref="pce:Server"
          minOccurs="1" 
          maxOccurs="unbounded"/>
      </sequence>
      <attribute
        name="name" 
        use="required" 
        type="string"/>
    </complexType>
  </element>

  <element name="ServerFarm">
    <complexType>
      <sequence>
        <element
          ref="pce:Cluster" 
          minOccurs="1" 
          maxOccurs="unbounded"/>
      </sequence>
      <attribute
        name="name"
        use="required" 
        type="string"/>
    </complexType>
  </element>

  <element name="PCE">
    <complexType>
      <sequence>
        <element
          ref="pce:ServerFarm"
          minOccurs="1" 
          maxOccurs="unbounded"/>
      </sequence>
    </complexType>
  </element>

</schema>

The details about constructing an XML Schema document (XSD) are beyond the scope of this article. What is important to understand is that the author of a PASXService component can use a set of custom tags. What is even better is that the author need not write any validation code to ensure the XML is valid and not just well-formed; this is done by the parser. The following declaration of a <Service> tag uses the schema from above.

<Service 
  class="org.pasx.examples.CustomConfigExample"
  name="examples.customConfigExample" >

  <pce:PCE 
    xmlns="http://pasx.org/PASX/CUSTOM-EXAMPLE"
    xmlns:pce="http://pasx.org/PASX/CUSTOM-EXAMPLE"
    xsi:schemaLocation="http://pasx.org/PASX/CUSTOM-
EXAMPLE /org/pasx/examples/custom-example.xsd" >

    <ServerFarm name="farm0">
      <Cluster name="cluster0">
        <Server 
          hostName="app0.foo.com" 
          portNumber="8080" />
        <Server 
          hostName="app1.foo.com" 
          portNumber="8080" />
      </Cluster>
      <Cluster name="cluster1">
        <Server
          hostName="app2.foo.com"
          portNumber="8080" />
        <Server 
          hostName="app3.foo.com" 
          portNumber="8080" />
      </Cluster>
    </ServerFarm>
    <ServerFarm name="farm1">
      <Cluster name="cluster0">
        <Server 
          hostName="app4.foo.com" 
          portNumber="8080" />
        <Server 
          hostName="app5.foo.com" 
          portNumber="8080" />
      </Cluster>
      <Cluster name="cluster1">
        <Server 
          hostName="app6.foo.com" 
          portNumber="8080" />
        <Server 
          hostName="app7.foo.com" 
          portNumber="8080" />
      </Cluster>
    </ServerFarm>

  </pce:PCE>

</Service>

How a PASXService class uses the XML handed to it is really up to the author of the class. When it is time for the class to be configured, it is handed the XML element declaring it (<Service>) via the configure method. This method has the following signature:

public void configure(org.jdom.Element config,
                      Context context,
                      ServiceManager caller)

Notice that the Element parameter is a reference to a JDOM Element, not a DOM Element. JDOM is a more natural fit with Java than DOM, but if you need a DOM version, there are methods in the JDOM package to convert them. When using custom elements, the class author must use the JDOM API to traverse the parsed XML. However, because an XML Schema was employed, the class author only needs to concentrate on using the information in the JDOM data structures, and need not write any code to verify that the data structures are valid (i.e. the <Cluster> element only contains <Server> elements).

If the PASXService author chooses to stick with the XML tags defined by PASX and follows the Java Bean patterns for getting and setting Bean properties, then he or she may use a utility class called XMLBeanUtil. This class uses Bean introspection to match and assign properties of a Bean to the XML tags defined by PASX. This can make coding very easy. For example:

public void configure( Element config,
                       Context context, 
                       ServiceManager caller )
{
  xbu = new XMLBeanUtil( context );
  xbu.populate( config, this );
}

If a PASXService component is composed of a smaller, tightly integrated group of classes, the populate method may called multiple times. Consider this property:

<String name="lastName">Bushaw</String>

Given this XML fragment, the populate method could be used multiple times to give the lastName property to two different Beans:

Person father = new Person();
Person mother = new Person();
xbu.populate( config, father );
xbu.populate( config, mother );

When using an XML Schema and custom tags, one benefit is the validation of the expected configuration. Put another way, you can write an XML Schema so that if, for example, a <Host> tag is given, that the portNumber and ipAddress attributes must also be specified. The schema defining the PASX set of tags cannot do this, because it has no knowledge of the semantics needed by individual PASXService components. However, XMLBeanUtil can keep track of which Bean properties were set and which weren't, and it can be configured with a list of constraints about the properties. A PASXService class author may choose either method, and this choice is a matter of taste, need, and convenience.

XMLBeanUtil classifies Bean properties three ways: property, dependent, and compliment. The first classification is just a normal Bean property. The second type is a property that depends on another property (e.g. if you set a dependent property, you must also set the property on which it depends). The third type is a property that is a compliment of another property (e.g. if you do not set a compliment property you must set the property which compliments it). Finally, all three classifications of properties may be set as an aggregate if the Java type of the property is either List or Map. If designated as an aggregate and the property already has a value, the List or Map in the configuration is added to the current property. (If it wasn't designated as an aggregate, then the List or Map in the configuration would replace the property value).

The following configure method example demonstrates this use.

public void configure( Element config,
                       Context context,
                       ServiceManager caller )
{
  xbu = new XMLBeanUtil( context );
  xbu.addProperty( "hostName",
    "Host Name Of Mythical TCP Service", 
    true, false );
  xbu.addDependent( "portNumber",
    "Port Number Of Mythical TCP Service",
    "hostName", false );
  xbu.addCompliment( "serverFarm",
    "Back-end Server Farm", 
    "nameServers", true );
  xbu.addCompliment( "nameServers",
    "Name Servers To Resolve Against",
    "serverFarm", true );
  xbu.addProperty( "person", 
    "Person", true, false );
  xbu.addProperty( "binaryThing",
    "The binary input stream",
    true, false );
  xbu.addProperty( "props",
    "Example Protomatter Properties",
    true, false );
  xbu.addProperty( "xml",
    "An example JDOM XML Document",
    true, false ); 
  xbu.populate( config, this );
}

The description of the property passed to the addProperty, addDependent, and addCompliment methods is for use in error messages. The checkService method of the XMLBeanUtil class will check to see if the correct properties have been set. If they have not, then it will throw an exception and use the descriptions of the properties in the error text. For a complete example, consult the BeanServiceExample class.

Being the observant reader, you probably noticed that nowhere in any of these past examples was there any mention of JNDI. Even the name attribute of the <Service> tag said nothing about JNDI. So you are probably asking yourself how the naming part of PASX uses JNDI.

By default, PASX uses an in-memory flat JNDI service provider called PAS (which is from the underlying Protomatter package). This service provider is nothing more than a Hashtable with a JNDI interface. Through the use of URL naming, other JNDI service providers may be specified and even used together. Therefore, it would be possible to specify a URL from an RMI registry or an LDAP server (i.e. rmi://localhost/creditCardAuthorizer or ldap://foo.com/uid=littlek,dc=foo,dc=com).

The default JNDI action for using a service with JNDI is bind. However, using the optional action attribute, the actions of rebind or lookup may also be used. The bind action attempts to place the service into a JNDI directory, but will fail if the directory already contains an entry with the name specified. The rebind action is the same as the bind action except it will overwrite an existing entry if it already exists. With both the bind and rebind actions, PASX instantiates the PASXService object, but with the lookup action, the object is retrieved from the JNDI directory.

There are two other attributes on the <Service> tag that may be used: preRebind and postRebind. The preRebind attribute specifies a name, or URL, to be used to rebind the object into a JNDI directory before configuration has taken place. Likewise, the postRebind attribute does the same, except it occurs after the configuration step.

The combination of these features allows the configuration file to do some useful and powerful things. For instance, it is possible to deserialize an object from an LDAP server, configure it via XML, and then place it in-memory so it may be accessed by other objects. Consider an example where the expiration date for some business process is sitting in an LDAP server. The following markup pulls the expiration date from the LDAP server, configures it for the current time zone, and places it in memory:

<Service
  name="ldap://foo.com/cn=expirationDate,o=foo.com"
  class="my.UtilityDate"
  action="lookup"
  postRebind="pas:expirationDate">
  <Integer name="timeZone" value="-6"/>
</Service>

It is possible now for another service to reference this configured service using the <NamedService> tag. For example, consider a sales advertising service that needs to know when a sale has expired:

<Service
  name="pas:oriellySale"
  class="my.SalesAdBanner">
  <NamedService
    name="endDate"
    serviceName="pas:expirationDate"/>
</Service>

There is no magic here. The <NamedService> tag merely causes PASX to do a JNDI lookup, based on the serviceName attribute, and places the result in the Bean property named endDate. However, this manner of connecting one object to another at configuration time is very powerful. Once you get use to doing, it will probably change your class design style.

PASX contains an assortment of utilities for use with JNDI. These include state and object factories, for use in serializing and storing object state in directories, and two service providers, one of which is a tree-based, hierarchical namespace and the other which utilizes the Java classpath. Consult the JavaDoc for more information on them.

There is also an entire package of examples complete with source code, JavaDoc, and configuration files that accompanies PASX. These examples start with simple configuration examples and range all the way up to the mixed use of various JNDI directories. Just consult the org.pasx.examples JavaDoc.

Hopefully the advantages of the concepts presented here have convinced you that, at the very least, you need to consider your configuration needs and methodology the next time you sit down to write a program of any significant size. And while these concepts are a central theme, there are also other features in PASX which have not been presented here. PASX has many standard services and managers for accomplishing many tasks, and even a servlet framework from which to do management of services and managers.

Finally, there is the Potomac project. Potomac is a PASX-based collection of open source Java software. With Potomac, all you need to do is unzip or untar one file, and you are provided with all the necessary Java components for using PASX, such as Protomatter and JDOM, and some convenient startup scripts and configuration files for getting started.

Andrew Newton is an expert open source Java and XML developer.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.