Reading my own Jar's Manifest
Asked Answered
C

14

155

I need to read the Manifest file, which delivered my class, but when I use:

getClass().getClassLoader().getResources(...)

I get the MANIFEST from the first .jar loaded into the Java Runtime.
My app will be running from an applet or a webstart,
so I will not have access to my own .jar file, I guess.

I actually want to read the Export-package attribute from the .jar which started the Felix OSGi, so I can expose those packages to Felix. Any ideas?

Coonhound answered 13/8, 2009 at 15:17 Comment(1)
I think the FrameworkUtil.getBundle() answer below is the best. It answers what you actually want to do (get the bundle's exports) rather than what you asked (read the manifest).Berkowitz
R
137

You can do one of two things:

  1. Call getResources() and iterate through the returned collection of URLs, reading them as manifests until you find yours:

     Enumeration<URL> resources = getClass().getClassLoader()
       .getResources("META-INF/MANIFEST.MF");
     while (resources.hasMoreElements()) {
         try {
           Manifest manifest = new Manifest(resources.nextElement().openStream());
           // If the line above leads to <null> manifest Attributes try from JarInputStream:
           // Manifest manifest = resources.nextElement().openStream().getManifest();
    
           // check that this is your manifest and do what you need or get the next one
           ...
         } catch (IOException E) {
           // handle
         }
     }
    
  2. You can try checking whether getClass().getClassLoader() is an instance of java.net.URLClassLoader. Majority of Sun classloaders are, including AppletClassLoader. You can then cast it and call findResource() which has been known - for applets, at least - to return the needed manifest directly:

     URLClassLoader cl = (URLClassLoader) getClass().getClassLoader();
     try {
       URL url = cl.findResource("META-INF/MANIFEST.MF");
       Manifest manifest = new Manifest(url.openStream());
       // do stuff with it
       ...
     } catch (IOException E) {
       // handle
     }
    
Roveover answered 13/8, 2009 at 16:46 Comment(11)
Perfect! I never knew you could iterate through resources with the same name.Coonhound
How do you know the classloader is only aware of a single .jar file? (true in many cases I suppose) I would much rather use something associated directly with the class in question.Puffy
it's a good practice to make separate answers for each one, instead of including the 2 fixes in one answer. Separate answers can be voted independently.Eskew
just a note: I needed something similar but I'm inside a WAR on JBoss, so the second approach didn't work for me. I ended up with a variant of https://mcmap.net/q/103614/-reading-my-own-jar-39-s-manifestPollitt
@chris-dolan Gave the correct answer to this question (see comment above).Susquehanna
+1; BTW, I've just found that EAR/META-INF/... is not included into the runtime classpath (on Weblogic 10.3.5), so it cannot be inspected as a resource.Waves
Thanks to add the wiring around java.util.jar.Manifest. I didn't know about that class before I read that post. I probably would have dumbly / manually parsed the Manifest file myself...Afternoon
Manifest is giving me a rather useless empty map, so I still have to parse it manually. Still useful code to get the resource at first, though.Calix
The first option didn't work for me. I got the manifests of my 62 dependency jars, but not the one where the current class was defined...Banket
Option 2 appears to have stopped working as-of java 11 (maybe java 9).Alliaceous
For future also-stumblers: If new Manifest(resources.nextElement().openStream()) leads to <null> manifest Attributes try resources.nextElement().openStream().getManifest() from JarInputStream.Cara
E
126

You can find the URL for your class first. If it's a JAR, then you load the manifest from there. For example,

Class clazz = MyClass.class;
String className = clazz.getSimpleName() + ".class";
String classPath = clazz.getResource(className).toString();
if (!classPath.startsWith("jar")) {
  // Class not from JAR
  return;
}
String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + 
    "/META-INF/MANIFEST.MF";
Manifest manifest = new Manifest(new URL(manifestPath).openStream());
// If the line above leads to <null> manifest Attributes try instead from JarInputStream:
//Manifest manifest = new URL(manifestPath).openStream().getManifest();
Attributes attr = manifest.getMainAttributes();
String value = attr.getValue("Manifest-Version");
Englis answered 13/8, 2009 at 17:27 Comment(7)
I like this solution as it gets your own manifest directly rather than having to search for it.Sanctuary
can be improved a little bit by removing condition check classPath.replace("org/example/MyClass.class", "META-INF/MANIFEST.MF"Sanctuary
Who closes the stream?Teledu
This does not work in inner classes, because getSimpleName removes the outer class name. This will work for inner classes: clazz.getName().replace (".", "/") + ".class".Teledu
You need to close the stream, the manifest constructor does not.Scrawl
Note that functions attr.containsKey("Manifest-Version") and attr.get("Manifest-Version") will return null since those functions require arguments that are of the inner class Attribute.Name. Unfortunately the functions accept Object... so there's no compile-time error when giving it a String, but it will return a null value. So stick with Attribute.getValue() as show in the example above. Attributes.getValue() explicitly accepts a String, and then wraps it in a new Attributes.Name for you. (I know this is because Attributes implements Map, but it's just not safe API coding in my opinion.)Balladmonger
For future also-stumblers: If new Manifest(new URL(manifestPath).openStream()) leads to <null> manifest Attributes try new URL(manifestPath).openStream().getManifest() from JarInputStream.Cara
C
23

I will admit up front that this answer does not answer the original question, that of generally being able to access the Manifest. However if what is really required is to read one of a number of "standard" Manifest attributes, the following solution is much simpler than those posted above. Note that this solution is in Kotlin, not Java, but I would expect that a port to Java would be trivial. (Although I admit I don't know the Java equivalent of ".`package`".

In my case I wanted to read the attribute "Implementation-Version" so I started with the solutions given above to obtain the stream and then read it to obtain the value. While this solution worked, a coworker reviewing my code showed me an easier way to do what I wanted. Note that this solution is in Kotlin, not Java.

val myPackage = MyApplication::class.java.`package`
val implementationVersion = myPackage.implementationVersion

Once again note that this does not answer the original question, in particular "Export-package" does not seem to be one of the supported attributes. That said, there is a myPackage.name that returns a value. Perhaps someone who understands this more than I can comment on whether that returns the value the original poster is requesting.

Compunction answered 23/3, 2018 at 18:24 Comment(2)
Indeed, the java port is straightforward: String implementationVersion = MyApplication.class.getPackage().getImplementationVersion();Archduke
It fact this is what I was looking for. I'm also happy that Java has also an equivalent.Spahi
K
22

You can use Manifests from jcabi-manifests and read any attribute from any of available MANIFEST.MF files with just one line:

String value = Manifests.read("My-Attribute");

The only dependency you need is:

<dependency>
  <groupId>com.jcabi</groupId>
  <artifactId>jcabi-manifests</artifactId>
  <version>0.7.5</version>
</dependency>

Also, see this blog post for more details: http://www.yegor256.com/2014/07/03/how-to-read-manifest-mf.html

Kinsey answered 30/12, 2012 at 11:2 Comment(4)
Very nice libraries. Is there a way to control the log level?Eardrop
All jcabi libs log through SLF4J. You can dispatch log messages using any facility you wish, for example log4j or logbackKinsey
if using logback.xml the line you need to add is like <logger name="com.jcabi.manifests" level="OFF"/>Peadar
Multiple manifests from the same classloader overlap and overwrite each otherCumulate
M
15

The easiest way is to use JarURLConnection class :

String className = getClass().getSimpleName() + ".class";
String classPath = getClass().getResource(className).toString();
if (!classPath.startsWith("jar")) {
    return DEFAULT_PROPERTY_VALUE;
}

URL url = new URL(classPath);
JarURLConnection jarConnection = (JarURLConnection) url.openConnection();
Manifest manifest = jarConnection.getManifest();
Attributes attributes = manifest.getMainAttributes();
return attributes.getValue(PROPERTY_NAME);

Because in some cases ...class.getProtectionDomain().getCodeSource().getLocation(); gives path with vfs:/, so this should be handled additionally.

Mccaslin answered 17/2, 2016 at 11:47 Comment(3)
This is, by far, the easiest and cleanest way to do it.Marcelenemarcelia
@Marcelenemarcelia And, furthermore, it should be promoted more up the answer stack since code in the top two answers that returned <null> where it shouldn't took me quite a while. I left according comments there (and downvoted) but who reads (hidden, at first) comments...Cara
@GeroldBroser I upvoted your comments, but if you really care about visibility you can always edit both answers to add a //comment above the new Manifest() part showing the alternative.Marcelenemarcelia
K
12

The following code works with multiple types of archives (jar, war) and multiple types of classloaders (jar, url, vfs, ...)

  public static Manifest getManifest(Class<?> clz) {
    String resource = "/" + clz.getName().replace(".", "/") + ".class";
    String fullPath = clz.getResource(resource).toString();
    String archivePath = fullPath.substring(0, fullPath.length() - resource.length());
    if (archivePath.endsWith("\\WEB-INF\\classes") || archivePath.endsWith("/WEB-INF/classes")) {
      archivePath = archivePath.substring(0, archivePath.length() - "/WEB-INF/classes".length()); // Required for wars
    }

    try (InputStream input = new URL(archivePath + "/META-INF/MANIFEST.MF").openStream()) {
      return new Manifest(input);
    } catch (Exception e) {
      throw new RuntimeException("Loading MANIFEST for class " + clz + " failed!", e);
    }
  }
Kate answered 17/3, 2015 at 15:23 Comment(1)
can result of clz.getResource(resource).toString() have backslashes?Iberian
F
11

I believe the most appropriate way to get the manifest for any bundle (including the bundle which loaded a given class) is to use the Bundle or BundleContext object.

// If you have a BundleContext
Dictionary headers = bundleContext.getBundle().getHeaders();

// If you don't have a context, and are running in 4.2
Bundle bundle = FrameworkUtil.getBundle(this.getClass());
bundle.getHeaders();

Note that the Bundle object also provides getEntry(String path) to look up resources contained within a specific bundle, rather than searching that bundle's entire classpath.

In general, if you want bundle-specific information, do not rely upon assumptions about the classloaders, just use the OSGi APIs directly.

Fi answered 16/8, 2009 at 5:15 Comment(0)
P
7

A simpler way to do this is to use getPackage(). For example, to get Implementation-Version:

Application.class.getPackage().getImplementationVersion()
Pericycle answered 18/11, 2020 at 5:45 Comment(1)
This doesn't answer the original question specifically, but it is exactly what I was looking for.Spence
M
6

You can use getProtectionDomain().getCodeSource() like this :

URL url = Menu.class.getProtectionDomain().getCodeSource().getLocation();
File file = DataUtilities.urlToFile(url);
JarFile jar = null;
try {
    jar = new JarFile(file);
    Manifest manifest = jar.getManifest();
    Attributes attributes = manifest.getMainAttributes();
    return attributes.getValue("Built-By");
} finally {
    jar.close();
}
Mallett answered 29/3, 2013 at 9:12 Comment(2)
getCodeSource may return null. What are the criteria, that this will work? The documentation does not explain this.Teledu
Where is DataUtilities imported from? It doesn't seem to be in the JDK.Banket
S
1

Why are you including the getClassLoader step? If you say "this.getClass().getResource()" you should be getting resources relative to the calling class. I've never used ClassLoader.getResource(), though from a quick look at the Java Docs it sounds like that will get you the first resource of that name found in any current classpath.

Sanctuary answered 13/8, 2009 at 16:48 Comment(3)
If your class is named "com.mypackage.MyClass", calling class.getResource("myresource.txt") will try to load that resource from com/mypackage/myresource.txt. How exactly are you going to use this approach to get the manifest?Roveover
Okay, I have to backtrack. That's what comes of not testing. I was thinking that you could say this.getClass().getResource("../../META-INF/MANIFEST.MF") (However many ".."'s are necessary given your package name.) But while that works for class files in a directory to work your way up a directory tree, it apparently doesn't work for JARs. I don't see why not, but that's how it is. Nor does this.getClass().getResource("/META-INF/MANIFEST.MF") work -- that gets me the manifest for rt.jar. (To be continued ...)Sanctuary
What you can do is use getResource to find the path to your own class file, then strip off everything after the "!" to get the path to the jar, then append "/META-INF/MANIFEST.MF". Like Zhihong suggested, so I'm voting his up.Sanctuary
P
1
  public static Manifest getManifest( Class<?> cl ) {
    InputStream inputStream = null;
    try {
      URLClassLoader classLoader = (URLClassLoader)cl.getClassLoader();
      String classFilePath = cl.getName().replace('.','/')+".class";
      URL classUrl = classLoader.getResource(classFilePath);
      if ( classUrl==null ) return null;
      String classUri = classUrl.toString();
      if ( !classUri.startsWith("jar:") ) return null;
      int separatorIndex = classUri.lastIndexOf('!');
      if ( separatorIndex<=0 ) return null;
      String manifestUri = classUri.substring(0,separatorIndex+2)+"META-INF/MANIFEST.MF";
      URL url = new URL(manifestUri);
      inputStream = url.openStream();
      return new Manifest( inputStream );
    } catch ( Throwable e ) {
      // handle errors
      ...
      return null;
    } finally {
      if ( inputStream!=null ) {
        try {
          inputStream.close();
        } catch ( Throwable e ) {
          // ignore
        }
      }
    }
  }
Parapsychology answered 29/5, 2014 at 17:39 Comment(6)
This answer uses a very complex and error prone way of loading the Manifest. the much simpler solution is to use cl.getResourceAsStream("META-INF/MANIFEST.MF").Buitenzorg
Did you try it? What jar manifest it will get if you have multiple jars in classpath? It will take the first one that is not what you need. My code solves this problem and it really works.Parapsychology
I did not criticize the way how you us e the classloader for loading a specific resource. I was pointing out that all the code between classLoader.getResource(..) and url.openStream() is totally irrelevant and error prone as it tries to do the same as classLoader.getResourceAsStream(..) does.Buitenzorg
Nope. It is different. My code takes manifest from the specific jar where the class is located rather than from the the first jar in the classpath.Parapsychology
Your "jar specific loading code" is equivalent to the following two lines: ClassLoader classLoader = cl.getClassLoader(); return new Manifest(classLoader.getResourceAsStream("/META-INF/MANIFEST.MF"));Buitenzorg
indeed this solution can be refactored but it works. The two liners solution proposed by Robert ends-up with NullPointerException in spring boot applications.Emmie
C
1

This solution works for me :

Package pck=this.getClass().getPackage();
String msgVersion =  pck.getImplementationTitle()
+ " version:" + pck.getImplementationVersion()
+ " by " + pck.getImplementationVendor();       
Capsize answered 19/9, 2023 at 10:0 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Lalalalage
S
0

I have used the solution from Anthony Juckel but in the MANIFEST.MF the key have to start with uppercase.

So my MANIFEST.MF file contain a key like:

Mykey: value

Then in the activator or another class you can use the code from Anthony to read the MANIFEST.MF file and the the value that you need.

// If you have a BundleContext 
Dictionary headers = bundleContext.getBundle().getHeaders();

// If you don't have a context, and are running in 4.2 
Bundle bundle = `FrameworkUtil.getBundle(this.getClass()); 
bundle.getHeaders();
Seedbed answered 2/1, 2014 at 18:12 Comment(0)
S
0

I have this weird solution that runs war applications in a embedded Jetty server but these apps need also to run on standard Tomcat servers, and we have some special properties in the manfest.

The problem was that when in Tomcat, the manifest could be read, but when in jetty, a random manifest was picked up (which missed the special properties)

Based on Alex Konshin's answer, I came up with the following solution (the inputstream is then used in a Manifest class):

private static InputStream getWarManifestInputStreamFromClassJar(Class<?> cl ) {
    InputStream inputStream = null;
    try {
        URLClassLoader classLoader = (URLClassLoader)cl.getClassLoader();
        String classFilePath = cl.getName().replace('.','/')+".class";
        URL classUrl = classLoader.getResource(classFilePath);
        if ( classUrl==null ) return null;
        String classUri = classUrl.toString();
        if ( !classUri.startsWith("jar:") ) return null;
        int separatorIndex = classUri.lastIndexOf('!');
        if ( separatorIndex<=0 ) return null;
        String jarManifestUri = classUri.substring(0,separatorIndex+2);
        String containingWarManifestUri = jarManifestUri.substring(0,jarManifestUri.indexOf("WEB-INF")).replace("jar:file:/","file:///") + MANIFEST_FILE_PATH;
        URL url = new URL(containingWarManifestUri);
        inputStream = url.openStream();
        return inputStream;
    } catch ( Throwable e ) {
        // handle errors
        LOGGER.warn("No manifest file found in war file",e);
        return null;
    }
}
Snowslide answered 3/8, 2018 at 16:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.