Central Version reporting of deployed artifacts with JEE

While building our RESC.Info solution stack we found ourselves with the challenge to query the build versions of the components installed. After searching the web and consulting various communities we found out there is no ‘standard’ way of solving this.

What we actually wanted is a way to query the installed version of the various modules we deployed to the Java Container without any predefine knowledge which modules are actually installed. We did not want to rely on any platform/container specific solution to solve this.

The approach taken

Java comes standard with JMX, Java Management Extension, it would be preferable to utilize such a infrastructure instead of coming up with our own.
So we decided the technical approach to take would be JMX, now we need to define what we wanted to expose, we decided to stay very close our original goal, extract version information from the installed artifacts programmatically.

We identified 4 main attributes:

  • Name
  • Full name
  • Artifact
  • Version

Since we use Maven for our build process ideally this information would come from the build process itself, there are tons of references on the web on how to do this. We decided to use the Maven resource filtering to generate a properties files, in our pom.xml we have:

<resources>
  <resource>
    <directory>src/main/resources</directory>
    <filtering>true</filtering>
  </resource>
</resources>

in src/main/resources we placed a version.properties file which looks like:

version=${project.version}
name=${project.build.finalName}
fullname=${project.name}
artifact=${project.groupId}.${project.artifactId}

During the maven build process the variables are replaced by the actual data from the POM file which we then can reference from the version reporting code.

Drop in version reporting

On of the other requirements was to have ‘drop-in’ version reporting module. You don’t want to code version reporting over and over again since we would have the version in the same place all the time anyway. So we created a Maven artifact we can simply add as a dependency to our project. This leaves only the task create and modify some files in our project and from that moment on everything goes automatically.

Exposing the information

To expose information to JMX the easiest approach is to expose a MBean to the containers MBeanServer. This is a two step approach

  1. create a interface which name ends with MBean.
  2. create the actual implementation of the interface.

Since we are actually interested in some metadata of our artifact we will call our interface MetaDataMBean

package com.example;

/**
 * Interface for Metadata exposure
 */
public interface MetaDataMBean {
    public String getVersion();

    public String getName();

    public String getFullName();

    public String getArtifact(););
}

Next we will do the implementation of our MetaData class

package com.example;

import java.util.MissingResourceException;
import java.util.ResourceBundle;

/**
 *
 */
public class MetaData implements MetaDataMBean {
    /*
     */
    @Override
    public String getVersion() {
        try {
            return ResourceBundle.getBundle("version").getString("version");
        } catch (MissingResourceException e) {
            // the version.properties file is not present
            return null;
        }
    }

    /*
     */
    @Override
    public String getName() {
        try {
            return ResourceBundle.getBundle("version").getString("name");
        } catch (MissingResourceException e) {
            // the version.properties file is not present
            return null;
        }
    }

    /*
     */
    @Override
    public String getFullName() {
        try {
            return ResourceBundle.getBundle("version").getString("fullname");
        } catch (MissingResourceException e) {
            // the version.properties file is not present
            return null;
        }
    }

    /*
     */
    @Override
    public String getArtifact() {
        try {
            return ResourceBundle.getBundle("version").getString("artifact");
        } catch (MissingResourceException e) {
            // the version.properties file is not present
            return null;
        }
    }
}

As you can see not to much rocket science here, we simple return the strings we get from our version.properties file which is created during our build process.

Hooking up to JMX

In the web.xml of your application you can define a <listner> tag which will be called as soon as the application is deployed. the <listner> tag requires the name of a class within your war which subclasses javax.servlet.ServletContextListener. When your war is deployed the contextInitialized override you need to define gets called, this is the ideal location to do one time initializations like the version reporting.
We create a Class, LifeCycleListner which extends ServletContextListner, on initilization we create the MBean, and register it with the containers MBeanServer. When the context is destroyed we will deregister it.
As registration name and type we use the full package name and choose a type identifier. This identifier is the actual key by which later you can retrieve the version information of multiple artifacts with a JMX query. In our example we chose for the type name ‘EXVersion’ which is a arbitrary name, but it needs to be the same for all your projects !

The context listner looks like this:

package com.example;

import java.lang.management.ManagementFactory;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

/**
 *
 */
public class LifeCycleListner implements ServletContextListener {
    private MetaData metaData;
    private ObjectName beanName;

    /*
     */
    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        try {
            mbs.unregisterMBean(beanName);
        } catch (MBeanRegistrationException | InstanceNotFoundException e) {
            e.printStackTrace();
        }
    }

    /*
     */
    @Override
    public void contextInitialized(ServletContextEvent arg0) {
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

        try {
            this.beanName = new ObjectName(ResourceBundle.getBundle("version")
                    .getString("artifact") + ":type=EXVersion");
            this.metaData = new MetaData();
            mbs.registerMBean(this.metaData, this.beanName);
        } catch (MalformedObjectNameException | InstanceAlreadyExistsException
                | MBeanRegistrationException | NotCompliantMBeanException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (MissingResourceException e) {
            // the version.properties file is not present
            System.out.println("version.properties is not present!!");
        }
    }
}

In the context initialized we already use the version.properties file to get the artifact name. This is also a direct test to see if the version.properties file is present at all, if not we don’t even bother registering the MBean.
We save the MBean name to destroy it when the application gets undeployed.

To have this code called on application deployment we need to register a listner in web.xml:

 <listener>
    <listener-class>com.example.LifeCycleListner</listener-class>
</listener>

Packing it up

If you create a standard Maven project which you deploy to your local repo with the above files included you can use it as a dependency on your other projects.

In your project you need to do 3 things.

  1. add the version.properties template in src/main/resources ( sample above )
  2. add the extra lines to the POM.XML ( sample above )
  3. add extra lines to web.xml ( sample above )

Querying the information

So now we get to what it was all about, how do we extract the version information without any predefined knowledge of what is installed ? We use the simple JMX api to query for our EXVersion type, a code snippet looks like this:

MBeanServer thisServer = ManagementFactory.getPlatformMBeanServer();
        try {

            ObjectName query = new ObjectName("*:type=EXVersion");

            for (ObjectName name : thisServer.queryNames(query, null)) {
                out.println("module: " + thisServer.getAttribute(name, "Name")
                        + "tName: "
                        + thisServer.getAttribute(name, "FullName")
                        + "tVersion: "
                        + thisServer.getAttribute(name, "Version"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

This will print out a set of lines with information about ALL the deployed artifacts which expose the EXVersion type bean.

Rounding up

This is very simple approach of creating a centralized version reporting method without to much infrastructure. It is easy enough to simply include in all your projects with a minimum amount of configuration. It does have some limitations, e.g. 2 deployments of the same artifact on different contexts will not work.

For our internal use we did extend the MetaData class to have some extra properties, but those were very specific to our project.

Leave a Reply

Your email address will not be published. Required fields are marked *