Configure a Java SE application using the Obix Framework Developing easily configurable software is of paramount importance in today’s business environment. Software applications are no longer judged simply by the amount of business logic that they encapsulate; they are also judged by how easy they are to maintain. The ability to alter software behavior, via configuration, forms an important aspect of this maintenance cycle.While the Java language provides a number of features, such as property files and resource bundles, to aid configuration, these lack the features required for today’s dynamic business environments. Many Java standards, tools, and containers already utilize more advanced and custom XML configuration formats.The Obix Framework is an open source framework that provides the common means and formats for storing configuration data in XML, and for accessing this data via simple Java objects. It enables the modularization of configuration data by allowing configuration files to be imported and included in each other, and by organizing configuration information into “modules.” In addition, it supports “hot” configuration amendments—through auto-detection and auto-reload of changes to configuration data—and also provides support for the Java Naming and Directory Interface API (JNDI). Furthermore, it can be integrated into Java applications in numerous ways, including through Java Management Extensions (JMX) and Java Platform, Enterprise Edition listeners that do not require coding, as well as plain Java classes that can be invoked directly. Finally, the framework provides an easy-to-use plug-in API that lets developers extend it to perform initialization-related tasks. This API has been used by the Obix team to provide initialization utilities for other open source frameworks such as Apache’s log4j, Hibernate, and Commons DBCP (database connection pools).In this tutorial, I describe a hypothetical scenario that requires configurable software and for which we create skeletal applications using Obix. The first example provides the closest thing to a “Hello World”-style proof of concept, while the second and third extend this application to showcase less trivial aspects of configuration.Please note that all the code samples in this article are packaged as an archive, which can be downloaded via the link provided in Resources. Problem scenarioValuing financial assets such as stocks or options sometimes involves simulating the price of the asset thousands of times, and taking the average of these values—in the belief that the average provides a best guess as to the asset’s “true” future value. Such simulations typically require statistical input in the form of the current price of the asset(s), the average price over a given time span, as well as the deviation from the average.Let’s suppose we are creating an application for valuing such instruments. As such, this application will need to download the statistical inputs via a Web service, and the details—such as URL and authentication information—for connecting to this service are stored in a configuration document. Suffice to say, the number of simulations to be performed for a given valuation request should also be flexible and, as such, will be specified via configuration.Example 1: A basic configuration fileIn this example, we create a basic configuration file, example1-config.xml, for our application, which holds the details for connecting to the Web service that provides the statistical inputs to the valuation process. This configuration file will also store the number of simulations to be performed for any valuation request. This file (as well as the configuration files for the other examples) is in the config directory of the downloadable archive associated with this tutorial. The contents of the configuration file are listed as follows: <?xml version="1.0" encoding="UTF-8"?> <configuration> <entry entryKey="market.data.service.url"> <value> https://www.some-exchange.com/marketdata </value> </entry> <entry entryKey="market.data.service.uid"> <value>trading_app_dbo</value> </entry> <entry entryKey="market.data.service.password"> <value>nopassword</value> </entry> <entry entryKey="number.of.valuation.simulations"> <value>10000</value> </entry> </configuration> If we examine the file in more detail, notice that it starts with the root node <configuration>; this marks the beginning of an Obix configuration document. There are four <entry> nodes, each encapsulating a configuration entry. The first three hold the URL, user ID, and password for connecting to the inputs service; the final entry holds the number of simulations to be performed for each valuation request. Notice that each entry has a unique key, as specified by the entryKey attribute, and that the value in each entry is encapsulated by a <value> node.Next, we create the skeleton of our valuation application, and, more importantly, we demonstrate how the configuration document is read at runtime. The class of interest is called Example1.java and can be found in the src folder of the downloadable archive associated with this tutorial. The class definition is as follows: import org.obix.configuration.Configuration; import org.obix.configuration.ConfigurationAdapter; import org.obix.configuration.ConfigurationAdapterFactory;public class Example1 { public static void main(String[] args) { ConfigurationAdapterFactory adapterFactory = ConfigurationAdapterFactory.newAdapterFactory(); ConfigurationAdapter adapter = adapterFactory.create(null); adapter.adaptConfiguration(Configuration.getConfiguration(), "config/example1-config.xml"); printMarketDataInfo(); } private static void printMarketDataInfo() { Configuration globalConfig = Configuration.getConfiguration(); System.out.println("Data Service URL :tt" + globalConfig.getValue("market.data.service.url")); System.out.println("Data Service User-ID :tt" + globalConfig.getValue("market.data.service.uid")); System.out.println("Data Service Password :tt" + globalConfig.getValue("market.data.service.password")); System.out.println("Simulation Count :tt" + globalConfig.getValue("number.of.valuation.simulations")); } } To run this and subsequent examples, you need to download the Obix Framework binaries to a location accessible via your classpath. Your classpath must reference the Obix library, obix-framework.jar, which can be found in the lib folder of the framework’s root directory. You will also need the following third-party open source libraries: dom.jar, jaxen-full.jar, sax.jar, saxpath.jar, and xercesImpl.jar, which can be found in the lib/thirdParty folder of the framework’s root directory. Executing this class should produce the following result: Data Service URL : https://www.some-exchange.com/marketdata Data Service User-ID : trading_app_dbo Data Service Password : nopassword Simulation Count : 10000 To dissect this class, we start with the main method. The first line of this method creates an instance of the class org.obix.configuration.ConfigurationAdapterFactory, which is responsible for creating a configuration adapter (an instance of class org.obix.configuration.ConfigurationAdapter). The adapter, in turn, is responsible for actually reading a configuration document from a given location (specified as a file path or URL).The following code extract reads the contents of our configuration file into the global/static configuration instance by invoking the adapter method adaptConfiguration(), and by passing a reference to the global instance—as obtained from the call Configuration.getConfiguration()—and the path to our configuration file config/example1-config.xml: adapter.adaptConfiguration(Configuration.getConfiguration(), "config/example1-config.xml"); Note that it is possible to create a new configuration instance to store our configuration data, rather than use the static (global) instance, but for the sake of simplicity (and brevity), we use the static instance for this example.Next, we briefly examine the method printMarketDataInfo(), which simply reads the configuration entries (i.e., the <entry> XML nodes) and prints their values (i.e., their <value> child nodes). Notice that each entry’s value is obtained by calling the method getValue (...) on the associated Configuration instance, passing in the name/key of the entry—as specified for the entry node’s entryKey attribute. As an aside, note that an entry can have multiple values, which will be demonstrated later in this tutorial.Example 2: Modularizing configuration dataApplications of this nature will typically generate a report detailing a request’s results in some sort of format. Our hypothetical application is no different; it is capable of producing valuation reports in a variety of formats. In addition, the reporting formats used in a given application run are dictated by a configuration entry, and all generated reports are emailed to a list of recipients within our organization—where the recipients are also specified in the configuration set. Logically, reporting is a distinct piece of functionality—when compared to valuation—even though both are related; so it would be quite reasonable to encapsulate our “reporting” configuration data. This not only provides a cleaner separation of the configuration data, but also makes it simpler for a novice to visualize the delineation of functionality within the application.We encapsulate the reporting configuration for this example by creating a configuration module for reporting, which is a child of our root module. We modify the configuration file from the last example by appending the node shown below to its list of nodes; the resulting file is called example2-config.xml and can be found in the config directory of the source archive. <?xml version="1.0" encoding="UTF-8"?> <configuration> .................... .................... ................... <!--NEW NODE : a module holding reporting information--> <configuration-module moduleId="reporting.parameters"> <entry entryKey="reports.destination.email"> <value>risk_analysts@mybank.com</value> </entry> <entry entryKey="report_formats"> <value>spreadsheet</value> <value>text-file</value> <value>pdf</value> </entry> </configuration-module></configuration> Two things immediately stand out in this configuration file: the first, of course, is our module definition <configuration-module ....>, followed by the module’s second entry node <entry entryKey="report_formats">. We begin with the module definition. An Obix configuration document can contain any number of submodules. Barring two elements—not discussed in this tutorial—modules support the same node set as the root module. In other words, modules have entries and can contain other modules; hence, modules can effectively be used to replicate a tree structure. Recall that in the last example, I mentioned that a configuration entry can have multiple values. This functionality is demonstrated by the configuration entry for holding reporting formats, i.e., <entry entryKey="report_formats">. As you can see, this differs from other entries in that it has three values—specifying the three formats in which reports should be generated.We now examine the Java code for reading the entries in our reporting configuration module. We modify the Java source for the previous example by adding the following method; the modified source file (class) is renamed Example2.java, and can be found in the src folder of the archive associated with this tutorial: private static void printReportingConfig() { Configuration globalConfig = Configuration.getConfiguration(); Configuration reportingConig = globalConfig.getModule("reporting.parameters"); System.out.println("Reports Destination :tt" + reportingConig.getValue("reports.destination.email")); System.out.println("Reporting Formats :tt" + reportingConig.getValues("report_formats")); } On executing this class, it should produce the output: Data Service URL : https://www.some-exchange.com/marketdata Data Service User-ID : trading_app_dbo Data Service Password : nopassword Simulation Count : 10000Reporting Config Parameters= Reports Destination : risk_analysts@mybank.com Reporting Formats : [spreadsheet, text-file, pdf] Upon examining the additional method in detail, we notice that it first obtains a reference to the global Configuration instance; then it proceeds to acquire a reference to the configuration module holding the reporting configuration information. The method achieves these tasks by invoking the method getModule(...) on the parent module, passing in the ID of the module to be received. Note that this syntax is generic in the sense that obtaining the child of any module—even if not the root module—is achieved by invoking getModule(...) on the given module.Of the two println ( ...) statements, the second is the most interesting, as it demonstrates how to access all the values in a given configuration entry. While the method getValue(java.lang.Object) returns the default (i.e., the first) value in an entry, other values can be accessed either individually via the method getValue(java.lang.Object,int) or as a collection through the method getValues(...).The contents of configuration modules don’t necessarily have to reside in the same file within which the module is defined. It is possible to actually import an external configuration file as a module on its own. This is demonstrated in the next example. Example 3: Configuration import/includeAssume you have several components, which, to a large extent, have distinct configuration sets, but share some elements of configuration. An untidy means of configuring them would be through cut and paste, which not only leaves you with a nightmare—in terms of synchronization—but also opens the door to all sorts of runtime problems. Obix provides a simple solution by letting you link configuration documents. Whole configuration documents can be imported into each other, thus solving the problem of sharing. This also makes for neater configuration documents in the sense that large monolithic pieces of configuration can be externalized to make documents more readable.In our example scenario, our application will need various time-points (in years) of the interest-rate cycle to value certain types of instruments. We also assume that this data is shared with other applications, and, as such, is held in its own configuration file, example3-config-points.xml, which is available in the resources archive associated with this article. The contents of this file are as shown in the following snippet: <?xml version="1.0" encoding="UTF-8"?> <configuration> <entry entryKey="interest rate timepoints"> <value>1</value> <value>2</value> <value>5</value> <value>10</value> <value>15</value> <value>20</value> <value>25</value> <value>30</value> </entry> </configuration> To link this file into the configuration set for our fictitious application, we modify the configuration file from the previous example as follows; the resulting file is called example3-config.xml and is also available in the example source archive: <?xml version="1.0" encoding="UTF-8"?> <configuration> ....................... ....................... ....................... <!--import interest rate timepoints module--> <configuration-import moduleId="rate points" isRelativeLink="true"> example3-config-points.xml </configuration-import> </configuration> As is clear from this snippet, a module is imported via the <configuration-import> node, whose body content is the path to the document to be imported. This path is either treated as an absolute path (e.g., full system file path or absolute URL) or as a relative path, depending on the value of the attribute isRelativeLink. The referenced document is effectively imported into the configuration set as a self contained module, though accessible (via Java) in the same way as an inline module.We now examine the Java code for accessing the imported module. We modify the Java source code from the previous example by adding the method shown in the following listing. The resulting source file is called Example3.java and is also included in the article’s code archive. private static void printInterestRateTimepoints() { System.out.println("nPrinting Interest Rate Timepoints .... "); Configuration globalConfig = Configuration.getConfiguration(); Configuration interestRateConfig = globalConfig.getModule("rate points"); System.out.println("Rate-Curve Timepoints (Years) :tt" + interestRateConfig.getValues("interest rate timepoints")); } Executing this class should produce the following output: Data Service URL : https://www.some-exchange.com/marketdata Data Service User-ID : trading_app_dbo Data Service Password : nopassword Simulation Count : 10000Printing Reporting Config Parameters .... Reports Destination : risk_analysts@mybank.com Reporting Formats : [spreadsheet, text-file, pdf]Printing Interest Rate Timepoints .... Rate-Curve Timepoints (Years) : [1, 2, 5, 10, 15, 20, 25, 30] If you examine the code listing in more detail, you will notice that the imported module is accessed in the same way as the inline module defined in Example 2. Hence, the means by which a module is defined is of no relevance to the Java code; inline and imported modules are treated exactly the same.Alternatives to ObixObix is effectively a lightweight framework for system configuration that offers a great wealth of functionality when compared to the alternatives, such as Apache Jakarta Project’s Commons Configuration, JFig, SmartFrog, and the Spring Framework.Commons Configuration merely gives you generic Java interfaces for reading configuration. Obix, on the other hand, gives you XML formats for configuration and the means for accessing this data via Java. It also has features to enable you to import/include configuration files into one another and to create modular configuration sets. JFig and SmartFrog lack the same range of adaptors and management APIs that Obix provides—e.g., JMX managers. Also, Obix allows the user to define plug-ins that can be used for a whole host of tasks, such as validating the configuration data, initializing other frameworks, such as log4j, and, of course, performing other system initialization tasks. Obix is also transactional in the sense that configuration changes do not take effect until all data is successfully loaded; a failure at any point in time causes the framework to revert to the last known good version.Spring is an Inversion of Control (IoC) framework that forces developers to code in a certain fashion (with IoC in mind). Obix, on the other hand, is a specialist configuration framework and simply manages configuration data. It doesn’t rely or enforce any development methodology, nor does it intrude into application logic.Other usesThe fact that Obix is a specialist configuration framework also means that it offers more advanced features tailored specifically towards its core remit of configuration. In addition to the features discussed in this article, the framework also supports hot configuration changes, meaning that changes to configuration data can be automatically picked up by the framework, without the application having to be restarted. It also supports code-free integration, which removes the need for developers to manually instantiate and invoke the adapters or adapter factories; however, this is only available for enterprise environments such as servlet containers and environments where JMX is enabled. The framework can also be used to manage system initialization as it provides a well defined plug-in mechanism for developers to create custom start-up hooks that can be invoked when the application’s configuration set is loaded. This mechanism can be used by the framework’s developers to build initialization plug-ins for other open source frameworks, such as log4j, Hibernate, and Apache Commons DBCP.Obi Ezechukwu is a Java/Java EE consultant working in the UK financial industry. He specializes in designing and implementing real-time and computationally intensive credit risk and trading applications. He is also a guest researcher at Imperial College London, from where he holds a Ph.D., specializing in the field of computational finance. JavaSoftware DevelopmentProgramming Languages