Reading my own Jar's Manifest

天涯浪子 提交于 2019-11-26 00:32:41

问题


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?


回答1:


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());
          // 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
    }
    



回答2:


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());
Attributes attr = manifest.getMainAttributes();
String value = attr.getValue("Manifest-Version");



回答3:


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




回答4:


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.




回答5:


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);
    }
  }



回答6:


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. So I hope that the moderator will allow it. 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.




回答7:


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.




回答8:


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();
}



回答9:


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.




回答10:


  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
        }
      }
    }
  }



回答11:


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();



回答12:


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;
    }
}


来源:https://stackoverflow.com/questions/1272648/reading-my-own-jars-manifest

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!