Getting source revision for a deployed app using Maven and a Servlet

With our modern version control systems, we could assume we know for every single release we make the exact source code revision that corresponds. But, in the hurry of a bug found by your dear clients (or your product owner), you can't find the corresponding tag in your VCS, it will make the whole patch process really difficult. You will have to find the exact revision, extract it and debug to patch (or backport an existing patch).
I will explain a little trick to make the find the revision thing a lot easier (I am assuming you're using maven to build your projects) : maven-buildnumber-plugin
Embed revision into artifacts
By using this plugin you can easily embbed revision informations into your artifacts, the configuration is, as always with maven, as easy as A-B-C, just add the following to your projects' pom :<profile>
<id>buildnumbering</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<version>1.0-beta-4</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>create</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<executions>
<execution>
<id>buildNumber</id>
<configuration>
<archive>
<manifestEntries>
<Implementation-Build>
${buildNumber}
</Implementation-Build>
</manifestEntries>
</archive>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-ear-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Implementation-Build>
${buildNumber}
</Implementation-Build>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</profile>
mvn clean install -Pbuildnumbering
Get revision number at runtime
Now that the revision is stored in our artifacts, we need a way to figure out, at runtime, which revision is deployed, the basic idea is to have a servlet get this info for us, and dump it on your web browser. For confidentiality purpose, it might be useful to put an authorization filter on top of this servlet, but it won't be covered in this post. The code for the servlet follows :/**
* Package and import are ommited on purpose
* User: cgatay Date: 17 oct. 2010 Time: 13:34:59
*/
public class VersionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
String appServerHome = getServletContext().getContext("/").getRealPath("/");
final int lastSlash = appServerHome.lastIndexOf(File.separator);
final int beforeLastSlash = appServerHome.lastIndexOf(File.separator, lastSlash-1);
appServerHome = appServerHome.substring(0, beforeLastSlash);
final File file = new File(appServerHome);
if (file.exists() && file.canRead() && file.isDirectory()) {
List<String> listFiles = new ArrayList<String>(Arrays.asList(file.list()));
listFiles.add(0, "");
for (String s : listFiles) {
File manifestFile = new File(appServerHome + File.separator + s, "META-INF/MANIFEST.MF");
if (manifestFile.exists()) {
dumpManifestInfo(resp, manifestFile, s);
}
}
}
resp.setStatus(200);
} catch (IOException e) {
e.printStackTrace();
resp.setStatus(500);
resp.getOutputStream().print("Cannot retrieve version data !");
}
}
private void dumpManifestInfo(HttpServletResponse resp, File manifestFile, String module) throws IOException {
Manifest mf = new Manifest();
mf.read(new FileInputStream(manifestFile));
Attributes atts = mf.getMainAttributes();
resp.getOutputStream().println(" ============================ ");
resp.getOutputStream().println("Module : " + (module.isEmpty() ? "EAR" : module));
resp.getOutputStream().println("Build: " + atts.getValue("Implementation-Build"));
}
}