Skip to content

Configuration

Dave Reynolds edited this page Apr 12, 2017 · 3 revisions

AppBase: Configuration

Appbase provides a simple, generic configuration mechanism that has proved useful on several projects.

Arguably it might be better to opt for some standard framework like Spring, Guice or OSGi but they all have a learning curve and impose some constraints on how you structure the application. Given the experience with OSGi (worked but the learning curve and tool chain issues were too much of a barrier) appbase opts for a very simple DIY mechanism inspired by Apache Shiro.

Apps

An appbase application can be divided into a set of bundles called apps, each represented by an instance of the App class.

It's not clear that this is really useful, all projects so far have essentially had a single app per war. However, the App class acts as a root for finding all configuration information and avoids some of the global singletons that kept slipping in to earlier versions of the code base.

Each app is defined by some configuration file which in turn is configured by the web.xml:

<context-param>
  <param-name>AppConfig.foo</param-name>
  <param-value>{webapp}/WEB-INF/app.conf</param-value>
</context-param>

which creates an app called "foo". The value of the context param is a file name or a comma-separated list of file names to check for configuration information. The first file found is used. This allows you to create a web.xml which checks some known list of external locations for configuration information but can still fall back on a configuration baked into the war.

In all file names the string "{webapp}" will be replaced by the local path of the containing webapp.

In addition the appbase configuration process needs to be kicked off when the webapp starts. This is enabled by the web.xml entry:

<listener>
  <listener-class>com.epimorphics.appbase.core.AppConfig</listener-class>
</listener>

The AppConfig class provides access to all the configured Apps.

Components

An app is made up of a set of components, where a component can be any java bean. The App object provides run time access to the set of components. It also provides access to an arbitrary set of configuration parameters. The configuration file specifies both the components and the app-wide configuration parameters.

The components that make up an app can be an instance of any java bean. It must have a null constructor and can have bean-style setter methods for any configuration parameters that are needed.

There is a reusable base class ComponentBase which allows components to be named and provides convenient access to the owning application. However, there is no requirement to use this.

Lifecycle

When the container has started then the app is constructed by parsing the configuration file, which in turn creates the app components and sets their configuration information.

Once all the components have been created the app goes through a startup cycle. This allows any post-configuration code to be run. It is possible to register a set of hooks to be run at startup of every app (AppConfig#addStartupHook). Any component which implements the Startup interface will have its startup method called.

When the container shuts down then each app goes through a shutdown cycle. Again it is possible to set global hooks to run for any app shutdown (AppConfig#addShutdownHook) and any component in the app which implements the Shutdown interace will have its shutdown method called. This is useful to flush out changes to databases and the like but obviously is limited to graceful shutdowns, not crashes.

Configuration file

The syntax of an app configuration file is pretty simple:

# This is a comment line

# This adds a jar (or class) file to load path for discovery of components
load extension.jar

# These line specify app-wide configuration values
app.p1  = this is a string
app.p2  = 42
app.p3  = true

# These lines create and configure some components
component1        = com.epimorphics.appbase.webapi.TrialBean
component1.prop1  = name 1

component2        = com.epimorphics.appbase.webapi.TrialBean
component2.prop1  = name 2

component3 = com.epimorphics.appbase.webapi.TrialBean
component3.xref = $component1, $component2

A line of the form:

# a comment

is a comment line and is ignored. There are no end-of-line comments, just whole-line comments.

A line of the form:

app.prop = value

sets a value for the configuration parameter prop on the app being defined. The string "app." that triggers this behaviour is fixed and independent of the actual name of the application.

The configuration properties are simple strings that are used to index a <String, Object> map.

The value can be a string, integer or boolean. Strings can include spaces and can optionally be surrounded by "". There are no multi-line strings.

A line of the form:

component        = com.epimorphics.appbase.webapi.TrialBean

Creates a component by instantiating the given class. If the class implements the Named interface then it will be given a runtime name matching the name in the config file - this can be helpful for debugging and runtime component discovery. If the class inherits from ComponentBase or similar then it will have a runtime reference to its containing app (getApp()).

A line of the form:

component.prop   = value

will set some property on the component by calling a corresponding bean setter. Normally the prop is a simple string in which case setProp will be called. However, the code uses the Apache beanutil library which can handle dotted-paths of properties including array and map references.

The values which can be set include the primitive values as above (string, integer, boolean) and a cross reference to another component ($foo) or to a java.util.List of components ($foo,$bar). The referenced components need to have been created higher up the configuration file (which is processed in order). Note that the type of the setter must match the value given for the configuration to work. In particular, you need different setters for assigning a single cross reference or assigning a list of cross references.

If the component property corresponds to a file location that it can be useful to expand the {webapp} placeholder with the actual webapp location. This can be done by AppConfig#expandFileLocation or ComponentBase#asFile.

A line of the form:

load file.jar

will add the given jar file to the class loader for the config file processing. So any subsequent component definition lines can refer to class names in that jar.

Handling web requests

The actual processing of requests to the webapp is handled by configuring filters in the web.xml. These filters stack up in the order defined in the web.xml file so it is possible to have a mix of Jersey REST endpoints, velocity rendered templates and plain files all sitting at the same pattern and serve the first one that matches.

To include velocity rendering then configure a single velocity filter:

<filter>
  <filter-name>VelocityFilter</filter-name>
  <filter-class>com.epimorphics.appbase.webapi.VelocityFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>VelocityFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

This filter will try each app to see if it defines a VelocityRender component and if so will ask that to try rendering the request. If no renderer can handle the request the filter passes on down the filter chain. To configure a renderer in your app put something like the following in the configuration file:

# Configure a basic velocity
velocity             = com.epimorphics.appbase.templates.VelocityRender
velocity.templates   = {webapp}/WEB-INF/templates
velocity.root        = /
velocity.production  = false

The templates parameter is a directory to be searched to locate the velocity templates, it can be external to the web app. Additional velocity parameters can be set through a velocity.properties file in the templates directory. If there is a file macros.vm in the templates directory it will be used to define cross-template velocity macros. See velocity documentation.

The root is a prefix on the request path at which the templates will appear so a request to /root/foo will be handled by template foo.vm if it exists.

The production parameter controls whether templates and macros are cached or dynamically read from the file system.

It is also possible to serve arbitrary Jersey REST endpoints by including Jersey in the pom.xml:

<dependency>
    <groupId>com.sun.jersey.contribs</groupId>
    <artifactId>jersey-multipart</artifactId>
    <version>1.17</version>
</dependency>
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.8.3</version>
</dependency>
<dependency>
  <groupId>com.sun.jersey</groupId>
  <artifactId>jersey-client</artifactId>
  <version>1.17</version>
  </dependency>
<dependency>
  <groupId>com.sun.jersey.contribs</groupId>
  <artifactId>jersey-apache-client</artifactId>
  <version>1.17</version>
  <scope>test</scope>
</dependency>

and the web.xml:

<filter>
  <filter-name>Jersey Web Application</filter-name>
  <filter-class>com.sun.jersey.spi.container.servlet.ServletContainer</filter-class>
  <init-param>
    <param-name>com.sun.jersey.config.property.packages</param-name>
    <!-- set the following to include packages containing your jersey endpoints -->
    <param-value>com.epimorphics.appbase.webapi</param-value>
  </init-param>
  <init-param>
    <param-name>com.sun.jersey.config.feature.FilterForwardOn404</param-name>
    <param-value>true</param-value>
  </init-param>
  <init-param>
       <param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
       <param-value>com.sun.jersey.api.container.filter.PostReplaceFilter</param-value>
   </init-param>
   <init-param>
     <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
     <param-value>true</param-value>
   </init-param>
   <init-param>
       <param-name>com.sun.jersey.config.feature.DisableWADL</param-name>
       <param-value>true</param-value>
   </init-param>
</filter>

<filter-mapping>
  <filter-name>Jersey Web Application</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

When providing API endpoints then we typically want CORS support. Appbase includes built-in support for that which derives from the Apache CORS filter. It can be configured by the following web.xml entry:

<filter>
  <filter-name>CORS</filter-name>
  <filter-class>com.epimorphics.appbase.webapi.CorsFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>CORS</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
Clone this wiki locally