Danno / Dannotate Implementation Notes


Maven module roadmap

The Danno / Dannotate codebase is split into a number of interdependent Maven modules. The structure is intended to foster reuse, and to allow us to separate Danno and Dannotate functionality for the case where the system integrator wants to deploy them separately.

The modules are as follows:

  • The danno.webapp and dannotate.webapp modules contain the servlet configurations for the Danno and Dannotate sides. These modules also contain the stylesheet and web content for the demo sites, and in the case of Dannotate, the Javascript that implements the Dannotate client-side.
  • The danno.common.webapp module contains webapp-related files that are shared by the Danno and Dannotate webapps. This currently comprises the logging properties, the Spring wiring file that implements the site configuration mechanism and the associated default override and substitution properties.
  • The danno.servlet, danno.oai, danno.rss, danno.import and dannotate.servlet modules contain the Java code that implements the Danno and Dannotate services. This code comprises Spring MVC controller and view classes, and the business logic of annotation and reply CRUD.
  • The danno.core module defines Danno's RDF triplestore abstraction layer, and related base classes.
  • The danno.jena and danno.sesame modules provide the Jena and OpenRDF / Sesame implementations of the abstraction layer.
  • The danno.protocol module defines the Annotea schema and protocol constants, and some associated client-side libraries; e.g. to simplify client-side interaction with Annotea servers. (The latter are used by Dannotate, admin tools and test clients.)
  • The danno.test and danno.test.clients modules provide support classes for the JUnit tests and some Danno test / benchmark clients.
  • The danno.admin module provides some simple command-line tools for Danno administration.
  • The danno.spring modules provides project-specific extension classes; e.g. common Spring MVC base classes, Spring configuration enhancements and SpringSecurity enhancements.
  • Finally, the danno.demosite, dannotate.demosite and combined.demosite modules are templates "custom site" modules.

The project's parent POM file has three embedded properties:

  • The "danno-version" property is the master version number for all Danno artifacts.
  • The "spring-version" property gives the version of Spring to be used.
  • The "spring-security-version" property gives the version of SpringSecurity to be used.

The "danno-version" property is injected into the file "danno.common.webapp/src/main/resources/build.properties" using Maven resource filtering. Other build-time properties could potentially be injected there if there was a need to do this.

Spring extensions

The siteTailoring.xml file and other black magic

The siteTailoring.xml file is where the Spring beans that perform "simple property customizations" are configured. If you need to change the way that site-specific tailoring is implemented, this is file is the place to do it.

The file currently configures two beans to do apply the tailorings in "classes/*.properties" files to main Spring wirings:

  • The "poc" bean uses an enhanced version of the Spring "PropertyOverrideConfigurer" class to perform whole value replacement of bean property values using replacements defined in a custom site's "overrides.properties" file. (The enhancement is to enable the replacements to be performed on properties in the Properties objects generated by the properties factory beans; e.g. "dannoProps".)
  • The "ppc" bean uses the standard Spring "PropertyPlaceholderConfigurer" to do replacement of "${...}" placeholders in bean properties, and so on. The properties of this bean specify the "search path" finding the replacement value.

The "poc" and "ppc" beans do their stuff on the bean descriptors after they have been read from the Spring wiring files, and before the descriptors are used to instantiate beans. The "order" properties of the "poc" and "ppc" beans specify that property overrides are applied first followed by the replacements.

One of the limitations of Spring 2.x wiring files is that there is no direct way expressing conditional wiring; e.g. "if property xyz is 'pqr', create this bean, otherwise that one.". However, we did discover and use one trick that works with Spring 2.5 wiring. For example:

    <alias name="${tripleStore}TypeFactory" alias="typeFactory" />

This defines the id "typeFactory" to be an alias for a bean whose name depends on the "tripleStore" replacement property. If we then declare lazily initialized beans called "sdbTypeFactory", "rdbTypeFactory" and so on, we can switch between different "typeFactory" beans by changing one property substitution parameter.

It is also possible to do more complicated things like this:

    tripleStore=sdb
    sdb.default.db=test
    rdb.default.db=rtest
    tripleStore.default.db=${${tripleStore}.default.db}

However, for reasons that are not obvious, when you declare a bean "parent" using an alias, any placeholders in the alias values do not get substituted. This means that the alias switches won't work in conjunction with Spring property merging in a PropertyBeanFactory declaration. Fortunately, there is an (inelegant) alternative that works. A PropertyBeanFactory can be also be declared with an "array" of property objects, including references to property objects created elsewhere in the Spring wirings. The property object in the array are then merged by the PropertyBeanFactory.

The custom ContextLoaderListener

In the previous section, we mentioned that you can implement conditional wiring using an <alias> element with placeholders in the "name" attribute. Another approach that you would imagine would work is this:

    <import name="Danno-${tripleStore}.xml"/>

However, this only works with standard Spring wiring if the parameters to be substituted are defined in the Java "system properties". This is unfortunate:

  1. There are some kinds of conditional wiring that cannot be done using aliases; e.g. when the wiring relies on custom XML parsers and doesn't expose bean references.
  2. Putting configuration parameters into the system properties makes them global to all webapps.
  3. Getting the configuration parameters into the system properties entails modifying the Tomcat launch parameters.

As a workaround, we use a (hacky) custom ContextLoaderListener that inserts properties from a configurable set of properties file into the system properties before starting the Spring wiring process. This works, modulo that it does not address point "2" above. A better solution may be to make more extensive changes to the Spring configuration codebase to get the placeholder expander used for <import> elements to get parameters from other places. But we are hoping that the Spring developers will do this work for us, as the changes look to be rather intrusive.