Friday, February 17, 2012

Advanced injection of Maven properties into Java application

In the last post I have shown how to inject Maven project informations into Java application. This topic describes the injection for more useful properties. Advantage: no needs to define then any project specific Java constants and change constants with each release. Current project infos already exist in pom.xml. In the PrimeFaces Extensions project we have defined some profile dependent properties like these
...
<dependency>
    <groupId>org.primefaces</groupId>
    <artifactId>primefaces</artifactId>
    <version>${primefaces.core.version}</version>
</dependency> 
<dependency>
    <groupId>org.primefaces.extensions</groupId>
    <artifactId>primefaces-extensions</artifactId>
    <version>${pe.impl.version}</version>
</dependency>
...
<properties>
    <java.version.source>1.6</java.version.source>
    <java.version.target>1.6</java.version.target>
    <jetty.server.version>8.1.0.RC2</jetty.server.version>
    <pe.jsf.impl>mojarra</pe.jsf.impl>
    <pe.jsf.displayname>Mojarra</pe.jsf.displayname>
    <pe.jsf.group>com.sun.faces</pe.jsf.group>
    <pe.jsf.artifact>jsf</pe.jsf.artifact>
    <pe.jsf.version>2.1.6</pe.jsf.version>
    <pe.impl.version>0.3.0-SNAPSHOT</pe.impl.version>        
    <pe.webapp.filter>development</pe.webapp.filter>
    <pe.webapp.online>false</pe.webapp.online>
    <primefaces.core.version>3.1.1</primefaces.core.version>
    <primefaces.theme.version>1.0.3</primefaces.theme.version>
</properties>
And we also configured two Maven plugins which put more useful properties to Maven based applications.
<plugin>
    <groupId>com.google.code.maven-svn-revision-number-plugin</groupId>
    <artifactId>maven-svn-revision-number-plugin</artifactId>
    <version>1.7</version>
    <executions>
        <execution>
            <goals>
                <goal>revision</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <entries>
            <entry>
                <prefix>svn</prefix>
            </entry>
        </entries>
    </configuration>                    
</plugin>
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>buildnumber-maven-plugin</artifactId>
    <version>1.0</version>
    <executions>
        <execution>
            <id>generate-timestamp</id>
            <phase>validate</phase>
            <goals>
                <goal>create-timestamp</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <format>{0,date,yyyy-MM-dd HH:mm}</format>
        <items>
            <item>timestamp</item>
        </items>
    </configuration>
</plugin>
Let's start now. Property file under src/main/resources is called now pe-showcase.properties and has again only one line (surprise).
 
application.properties=${project.properties}
 
This line gets resolved at build time as follows:
application.properties={pe.jsf.impl=mojarra, pe.jsf.group=com.sun.faces, timestamp=1329486964505, svn.committedRevision=928, svn.revision=928, primefaces.theme.version=1.0.3, pe.impl.version=0.3.0-SNAPSHOT, pe.jsf.displayname=Mojarra, jetty.server.version=8.1.0.RC2, svn.committedDate=2012-02-17 01:48:44 +0100 (Fri, 17 Feb 2012), pe.jsf.version=2.1.6, primefaces.core.version=3.1.1, ...}
The task is now to parse this resolved line in a Java application. A good entry point for parsing in JSF is an application scoped bean noted with eager=true.
@ApplicationScoped
@ManagedBean(eager = true)
public class TechnicalInfo {

    private static final Logger LOGGER = Logger.getLogger(TechnicalInfo.class.getName());

    private String primeFaces;
    private String primeFacesExt;
    private String jsfImpl;
    private String server;
    private String revision;
    private String buildTime;
    private boolean online = false;
    private boolean mojarra = true;

    @PostConstruct
    protected void initialize() {
        ResourceBundle rb;
        try {
            rb = ResourceBundle.getBundle("pe-showcase");

            String strAppProps = rb.getString("application.properties");
            int lastBrace = strAppProps.indexOf("}");
            strAppProps = strAppProps.substring(1, lastBrace);

            Map<String, String> appProperties = new HashMap<String, String>();
            String[] appProps = strAppProps.split("[\\s,]+");
            for (String appProp : appProps) {
                String[] keyValue = appProp.split("=");
                if (keyValue != null && keyValue.length > 1) {
                    appProperties.put(keyValue[0], keyValue[1]);
                }
            }

            primeFaces = "PrimeFaces: " + appProperties.get("primefaces.core.version");
            primeFacesExt = "PrimeFaces Extensions: " + appProperties.get("pe.impl.version");
            jsfImpl = "JSF-Impl.: " + appProperties.get("pe.jsf.displayname") + " " + appProperties.get("pe.jsf.version");
            server = "Server: Jetty " + appProperties.get("jetty.server.version");
            revision = "SVN-Revision: " + appProperties.get("svn.revision");

            DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm");
            Calendar calendar = Calendar.getInstance();
            calendar.setTimeInMillis(Long.valueOf(appProperties.get("timestamp")));
            buildTime = "Build time: " + formatter.format(calendar.getTime());

            online = Boolean.valueOf(appProperties.get("pe.webapp.online"));
            mojarra = appProperties.get("pe.jsf.impl").contains("mojarra");
        } catch (MissingResourceException e) {
            LOGGER.warning("Resource bundle 'pe-showcase' was not found");
        }
    }

    ... getter ...

}
Access in JSF facelets:
<h:panelGrid columns="7" style="float: left;">
    <h:panelGroup styleClass="ui-icon ui-icon-info"/>
    <h:outputText value="#{technicalInfo.primeFaces},"/>
    <h:outputText value="#{technicalInfo.primeFacesExt},"/>
    <h:outputText value="#{technicalInfo.jsfImpl},"/>
    <h:outputText value="#{technicalInfo.server},"/>
    <h:outputText value="#{technicalInfo.revision},"/>
    <h:outputText value="#{technicalInfo.buildTime}"/>
</h:panelGrid>
And voilà. We have done this! Click on image to enlarge it.

Wednesday, February 15, 2012

Inject Maven project informations into Java application

Do you have a Maven project and would like to inject project informations from pom.xml into your application to avoid duplications? This is possible with resource filtering. This small guide will help you to achieve that. More funny advanced examples are coming soon in the next post. I have applied this approach for PrimeFaces Extensions project, so that all examples are related to this project.

Step 1. Create a property file under src/main/resources. In my case it's called primefaces-extensions.properties and consists of only one line.
 
application.version=${project.version}
 
Placeholder ${...} gets resolved at project build time. Maven also exposes other project properties like ${project.artifactId}, ${project.name}, etc. Please refer Maven properties guide. Not only Maven self exposes such properties. Various Maven plugins as buildnumber-maven-plugin and maven-svn-revision-number-plugin expose useful properties too.

Step 2. To get placeholders replaced you need to enable filtering in your pom.xml.
<build>
    <resources>
        <resource>src/main/resources</resource>
        <filtering>true</filtering>
    </resources>
    ...
</build>

Step 3. Access in Java is simple. I have created a singleton class VersionProvider for that (because I only have one placeholder for project version). Project version is cached after the first access.
public final class VersionProvider {

    private static final Logger LOGGER = Logger.getLogger(VersionProvider.class.getName());

    private static final VersionProvider INSTANCE = new VersionProvider();
    private String version;

    private VersionProvider() {
        ResourceBundle rb;
        try {
            rb = ResourceBundle.getBundle("primefaces-extensions");
            version = rb.getString("application.version");
        } catch (MissingResourceException e) {
            LOGGER.warning("Resource bundle 'primefaces-extensions' was not found or error while reading current version.");
        }
    }

    public static String getVersion() {
        return INSTANCE.version;
    }
}

Step 4. Using in any places is simple as well. Just call VersionProvider.getVersion(). For instance in the following JSF listener class
public class PostConstructApplicationEventListener implements SystemEventListener {

    private static final Logger LOGGER = Logger.getLogger(PostConstructApplicationEventListener.class.getName());

    @Override
    public boolean isListenerForSource(final Object source) {
        return true;
    }

    @Override
    public void processEvent(final SystemEvent event) {
        if (StringUtils.isNotBlank(VersionProvider.getVersion())) {
            LOGGER.log(Level.INFO, "Running on PrimeFaces Extensions {0}", VersionProvider.getVersion());
        }
    }
}
or in JSF resource handler
public class PrimeFacesExtensionsResource extends ResourceWrapper {

    private Resource wrapped;
    private String version;

    public PrimeFacesExtensionsResource(final Resource resource) {
        super();
        wrapped = resource;

        // get current version
        if (StringUtils.isNotBlank(VersionProvider.getVersion())) {
            version = "&v=" + VersionProvider.getVersion();
        } else {
            version = UUID.randomUUID().toString();
        }
    }

    @Override
    public String getRequestPath() {
        return super.getRequestPath() + version;
    }

    ...
}
With resource filtering you don't need to care about available current project infos in Java programs.