web-dev-qa-db-fra.com

Lire le propre manifeste de mon bocal

J'ai besoin de lire le fichier Manifest, qui a livré ma classe, mais quand j'utilise:

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

Je reçois le MANIFEST du premier .jar chargé dans le Java Runtime.
Mon application s'exécutera à partir d'une applet ou d'un webstart,
Je n'aurai donc pas accès à mon propre .jar fichier, je suppose.

Je veux en fait lire le Export-package attribut de la .jar qui a lancé Felix OSGi afin que je puisse exposer ces paquets à Felix. Des idées?

121
Houtman

Vous pouvez faire l'une des deux choses suivantes:

  1. Appelez getResources() et parcourez la collection d'URL renvoyée, en les lisant comme des manifestes jusqu'à ce que vous trouviez la vôtre:

    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. Vous pouvez essayer de vérifier si getClass().getClassLoader() est une instance de Java.net.URLClassLoader. La majorité des chargeurs de classe Sun sont, y compris AppletClassLoader. Vous pouvez alors le lancer et appeler findResource(), qui est connu - pour les applets du moins - pour renvoyer directement le manifeste nécessaire:

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

Vous pouvez d'abord trouver l'URL de votre classe. Si c'est un fichier JAR, vous chargez le manifeste à partir de là. Par exemple,

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");
115
ZZ Coder

Vous pouvez utiliser Manifests à partir de jcabi-manifestests et lire tout attribut dans l'un des fichiers MANIFEST.MF disponibles avec une seule ligne:

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

La seule dépendance dont vous avez besoin est:

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

Voir également ce billet de blog pour plus de détails: http://www.yegor256.com/2014/07/03/how-to-read-manifest-mf.html

21
yegor256

Je pense que le moyen le plus approprié d’obtenir le manifeste de tout bundle (y compris celui qui a chargé une classe donnée) est d’utiliser l’objet Bundle ou BundleContext.

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

Notez que l'objet Bundle fournit également getEntry(String path) pour rechercher les ressources contenues dans un bundle spécifique, plutôt que de rechercher le chemin d'accès aux classes complet de ce bundle.

En général, si vous voulez des informations spécifiques à un ensemble, ne vous fiez pas aux hypothèses sur les chargeurs de classes, utilisez simplement les API OSGi.

11
Anthony Juckel

Le moyen le plus simple consiste à utiliser la classe JarURLConnection:

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

Parce que dans certains cas, ...class.getProtectionDomain().getCodeSource().getLocation(); donne le chemin avec vfs:/, Ceci devrait donc être traité en plus.

8
ayurchuk

Le code suivant fonctionne avec plusieurs types d'archives (jar, war) et plusieurs types de chargeurs de classes (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);
    }
  }
8
muellair

Vous pouvez utiliser getProtectionDomain (). GetCodeSource () comme ceci:

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

J'admettrai d'emblée que cette réponse ne répond pas à la question initiale, à savoir qu'il est généralement possible d'accéder au Manifeste. Toutefois, si ce qui est réellement requis est de lire l’un des attributs du manifeste "standard", la solution suivante est beaucoup plus simple que celles indiquées ci-dessus. J'espère donc que le modérateur le permettra. Notez que cette solution est en Kotlin, pas en Java, mais je m'attendrais à ce qu'un port vers Java soit trivial. (Bien que j'avoue que je ne connais pas le Java équivalent de ".`package`".

Dans mon cas, je voulais lire l'attribut "Implementation-Version" et j'ai donc commencé par les solutions données ci-dessus pour obtenir le flux, puis lu pour obtenir la valeur. Pendant que cette solution fonctionnait, un collègue révisant mon code m'a montré un moyen plus facile de faire ce que je voulais. Notez que cette solution est en Kotlin, pas en Java.

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

Encore une fois, notez que cela ne répond pas à la question initiale, en particulier "Export-package" ne semble pas être l'un des attributs pris en charge. Cela dit, il existe un myPackage.name qui renvoie une valeur. Peut-être quelqu'un qui comprend cela plus que je ne peux dire si cela retourne la valeur demandée par l'affiche originale.

5
Steven W. Klassen

Pourquoi incluez-vous l'étape getClassLoader? Si vous dites "this.getClass (). GetResource ()", vous devriez obtenir des ressources relatives à la classe appelante. Je n’ai jamais utilisé ClassLoader.getResource (), bien qu’un rapide coup d’œil à la documentation Java) donne l’impression que vous obtiendrez la première ressource de ce nom trouvée dans n’importe quel chemin de classe actuel.

2
Jay
  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
        }
      }
    }
  }
1
Alex Konshin

J'ai cette solution étrange qui exécute des applications de guerre sur un serveur Jetty intégré, mais ces applications doivent également s'exécuter sur des serveurs Tomcat standard, et nous avons des propriétés spéciales dans le manfest.

Le problème était que, lorsque dans Tomcat, le manifeste pouvait être lu, mais lorsqu'il était dans la jetée, un manifeste aléatoire était ramassé (ce qui manquait les propriétés spéciales).

Sur la base de la réponse d'Alex Konshin, j'ai proposé la solution suivante (le flux d'entrée est ensuite utilisé dans une classe Manifest):

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

J'ai utilisé la solution de Anthony Juckel mais dans MANIFEST.MF, la clé doit commencer par une majuscule.

Donc, mon fichier MANIFEST.MF contient une clé du type:

Mykey: valeur

Ensuite, dans l'activateur ou une autre classe, vous pouvez utiliser le code d'Anthony pour lire le fichier MANIFEST.MF et la valeur dont vous avez besoin.

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