46
votes

I have a webapp which contains a manifest file, in which I write the current version of my application during an ant build task. The manifest file is created correctly, but when I try to read it in during runtime, I get some strange side-effects. My code for reading in the manifest is something like this:

    InputStream manifestStream = Thread.currentThread()
                                 .getContextClassLoader()
                                 .getResourceAsStream("META-INFFFF/MANIFEST.MF");
    try {
        Manifest manifest = new Manifest(manifestStream);
        Attributes attributes = manifest.getMainAttributes();
        String impVersion = attributes.getValue("Implementation-Version");
        mVersionString = impVersion;
    }
    catch(IOException ex) {
        logger.warn("Error while reading version: " + ex.getMessage());
    }

When I attach eclipse to tomcat, I see that the above code works, but it seems to get a different manifest file than the one I expected, which I can tell because the ant version and build timestamp are both different. Then, I put "META-INFFFF" in there, and the above code still works! This means that I'm reading some other manifest, not mine. I also tried

this.getClass().getClassLoader().getResourceAsStream(...)

But the result was the same. What's the proper way to read the manifest file from inside of a webapp running in tomcat?

Edit: Thanks for the suggestions so far. Also, I should note that I am running tomcat standalone; I launch it from the command line, and then attach to the running instance in Eclipse's debugger. That shouldn't make a difference, should it?

7
@PascalThivent's answer is the right way to it. Add some exception handling in case your MANIFEST does not exist (ie.:IDE) and it should be A1.srirachapills
This universal method helped me: [stackoverflow.com/a/29103019/3158918]user3158918

7 Answers

90
votes

Maybe your side-effects come from the fact that almost all jars include a MANIFEST.MF and you're not getting the right one. To read the MANIFEST.MF from the webapp, I would say:

ServletContext application = getServletConfig().getServletContext();
InputStream inputStream = application.getResourceAsStream("/META-INF/MANIFEST.MF");
Manifest manifest = new Manifest(inputStream);

Please note that running Tomcat from Eclipse is not the same as running Tomcat alone as Eclipse plays with the classloader.

11
votes

a bit late, but this works for me (web appl in Glassfish)

Properties prop = new Properties();
prop.load(getServletContext().getResourceAsStream("/META-INF/MANIFEST.MF"));
System.out.println("All attributes:" + prop.stringPropertyNames());
System.out.println(prop.getProperty("{whatever attribute you want}"));
3
votes

Try to use jcabi-manifests, that does all this loading work for you. For example:

String version = Manifests.read("My-Version");

loads My-Version attribute from one of available MANIFEST.MF files.

Important to mention that (more details are here) in most web containers current thread class loader is not the same as servlet context class loader. That's why you should append your servlet context to the register in runtime (more info):

Manifests.append(servletContext);

Also, check this out: http://www.yegor256.com/2014/07/03/how-to-read-manifest-mf.html

2
votes

The default way class loaders work is to defer to the parent before attempting to lookup their own resources. So if a parent class loader has any manifest available, that's what you'll get. In fact, app servers don't necessarily do this, to allow applications to override versions of libraries. Further, class loaders can have multiple jars and hence multiple manifests.

It may be able to get a resource URL of one of your uniquely named resource. Open a connection. Cast to JarURLConnection. Get the JarFile. Load the manifest from that. That may not work, particularly if Tomcat explodes the war.

[Update] Of course, the war file itself isn't on the classpath. The classpath will have something like WEB-INF/lib/(.jar|.zip) and WEB-INF/classes/. Getting a resource from the ServletContext should work.

Best solution: Do something different. :)

2
votes

The right manifest exists in application root at server. Find out the appication root, for instance by finding out classpath of your class:

String rootPath  = getClass().getProtectionDomain().getCodeSource().getLocation().getPath()

Then replace the path above with the founded path: Glassfish example:

/applications/<webProject>/META-INF/MANIFEST.MF

It work for me.

1
votes

Don't know about a "official" way to read it, but if the MANIFEST.MF can't be properly loaded as a resource, how about trying to derive its path from a "ServletContext.getRealPath()" on some web path defined in your app?

Writing the app version also to some else place (a property file in WEB-INF/classes) by ant during build is another solution that comes to my mind.

0
votes

This is what I do to print various versions to a logfile. I have hardcoded an expanded path but apps may use servletContext.getRealPath("/") to read a full path to webapp folder. May print just given libraries or everything from lib folder.

// print library versions (jersey-common.jar, jackson-core-2.6.1.jar)
try {
    List<String> jars  = Arrays.asList( "jersey-common", "jackson-core", "openjpa", "mylib" );
    StringBuilder verbuf = new StringBuilder();
    for(File file : new File("/opt/tomcat/webapps/myapp/WEB-INF/lib/").listFiles() ) {
        String name = file.getName();
        if (file.isDirectory() || !file.isFile() || !name.endsWith(".jar") ) continue;
        name = name.substring(0, name.length()-4);
        boolean found = jars.contains(name);
        if (!found) {
            int idx = name.lastIndexOf('-');
            if (idx>0)
                found = jars.contains( name.substring(0, idx) );
        }
        if (!found) continue;

        JarFile jarFile = new JarFile(file, false);
        try {
            String ver;
            Manifest mf = jarFile.getManifest();
            if (mf!=null) {
                ver = mf.getMainAttributes().getValue("Bundle-Version");
                if (ver==null || ver.isEmpty())
                    ver = mf.getMainAttributes().getValue("Implementation-Version");
            } else ver=null;
            if (verbuf.length()>0) verbuf.append(", ");
            verbuf.append(name + "=" + (ver!=null?ver:"") );                        
        } finally {
            jarFile.close();
        }
    }
    System.out.println( verbuf.toString() );
} catch(Exception ex) {
    ex.printStackTrace();
}