From 3d549c90500da80c4bbac4bc911a306b0618c069 Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Thu, 27 Jul 2023 17:16:21 -0400 Subject: [PATCH 1/2] Replace static util class with JarDetails This class is able to be more optimized by using random access file classes depending on if the jar is embedded within another archive file (eg spring boot, war, ear, etc). --- .../runtimemetrics/java8/JarAnalyzer.java | 57 ++-- .../runtimemetrics/java8/JarAnalyzerUtil.java | 183 ----------- .../runtimemetrics/java8/JarDetails.java | 283 ++++++++++++++++++ 3 files changed, 305 insertions(+), 218 deletions(-) delete mode 100644 instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarAnalyzerUtil.java create mode 100644 instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarDetails.java diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarAnalyzer.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarAnalyzer.java index bba59e74c4e9..842d3767979c 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarAnalyzer.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarAnalyzer.java @@ -5,13 +5,8 @@ package io.opentelemetry.instrumentation.javaagent.runtimemetrics.java8; -import static io.opentelemetry.instrumentation.javaagent.runtimemetrics.java8.JarAnalyzerUtil.addPackageChecksum; -import static io.opentelemetry.instrumentation.javaagent.runtimemetrics.java8.JarAnalyzerUtil.addPackageDescription; -import static io.opentelemetry.instrumentation.javaagent.runtimemetrics.java8.JarAnalyzerUtil.addPackageNameAndVersion; -import static io.opentelemetry.instrumentation.javaagent.runtimemetrics.java8.JarAnalyzerUtil.addPackagePath; -import static io.opentelemetry.instrumentation.javaagent.runtimemetrics.java8.JarAnalyzerUtil.addPackageType; - import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.events.EventEmitter; @@ -46,6 +41,15 @@ final class JarAnalyzer implements ClassFileTransformer { private static final String WAR_EXTENSION = ".war"; private static final String EVENT_DOMAIN_PACKAGE = "package"; private static final String EVENT_NAME_INFO = "info"; + static final AttributeKey PACKAGE_NAME = AttributeKey.stringKey("package.name"); + static final AttributeKey PACKAGE_VERSION = AttributeKey.stringKey("package.version"); + static final AttributeKey PACKAGE_TYPE = AttributeKey.stringKey("package.type"); + static final AttributeKey PACKAGE_DESCRIPTION = + AttributeKey.stringKey("package.description"); + static final AttributeKey PACKAGE_CHECKSUM = AttributeKey.stringKey("package.checksum"); + static final AttributeKey PACKAGE_CHECKSUM_ALGORITHM = + AttributeKey.stringKey("package.checksum_algorithm"); + static final AttributeKey PACKAGE_PATH = AttributeKey.stringKey("package.path"); private final Set seenUris = new HashSet<>(); private final BlockingQueue toProcess = new LinkedBlockingDeque<>(); @@ -172,39 +176,22 @@ public void run() { * content. */ static void processUrl(EventEmitter eventEmitter, URL archiveUrl) { - AttributesBuilder builder = Attributes.builder(); - - try { - addPackageType(builder, archiveUrl); - } catch (Exception e) { - logger.log(Level.WARNING, "Error adding package type for archive URL: {0}" + archiveUrl, e); - } - - try { - addPackageChecksum(builder, archiveUrl); - } catch (Exception e) { - logger.log(Level.WARNING, "Error adding package checksum for archive URL: " + archiveUrl, e); - } - - try { - addPackagePath(builder, archiveUrl); - } catch (Exception e) { - logger.log(Level.WARNING, "Error adding package path archive URL: " + archiveUrl, e); - } - + JarDetails jarDetails; try { - addPackageDescription(builder, archiveUrl); + jarDetails = JarDetails.forUrl(archiveUrl); } catch (Exception e) { - logger.log( - Level.WARNING, "Error adding package description for archive URL: " + archiveUrl, e); + logger.log(Level.WARNING, "Error reading package for archive URL: {0}" + archiveUrl, e); + return; } + AttributesBuilder builder = Attributes.builder(); - try { - addPackageNameAndVersion(builder, archiveUrl); - } catch (Exception e) { - logger.log( - Level.WARNING, "Error adding package name and version for archive URL: " + archiveUrl, e); - } + builder.put(PACKAGE_PATH, jarDetails.packagePath()); + builder.put(PACKAGE_TYPE, jarDetails.packageType()); + builder.put(PACKAGE_NAME, jarDetails.packageName()); + builder.put(PACKAGE_VERSION, jarDetails.version()); + builder.put(PACKAGE_DESCRIPTION, jarDetails.packageDescription()); + builder.put(PACKAGE_CHECKSUM, jarDetails.computeSha1()); + builder.put(PACKAGE_CHECKSUM_ALGORITHM, "SHA1"); eventEmitter.emit(EVENT_NAME_INFO, builder.build()); } diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarAnalyzerUtil.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarAnalyzerUtil.java deleted file mode 100644 index 2e8bf814da48..000000000000 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarAnalyzerUtil.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.javaagent.runtimemetrics.java8; - -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.AttributesBuilder; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.security.DigestInputStream; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Properties; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.JarInputStream; -import java.util.jar.Manifest; - -final class JarAnalyzerUtil { - - static final AttributeKey PACKAGE_NAME = AttributeKey.stringKey("package.name"); - static final AttributeKey PACKAGE_VERSION = AttributeKey.stringKey("package.version"); - static final AttributeKey PACKAGE_TYPE = AttributeKey.stringKey("package.type"); - static final AttributeKey PACKAGE_DESCRIPTION = - AttributeKey.stringKey("package.description"); - static final AttributeKey PACKAGE_CHECKSUM = AttributeKey.stringKey("package.checksum"); - static final AttributeKey PACKAGE_CHECKSUM_ALGORITHM = - AttributeKey.stringKey("package.checksum_algorithm"); - static final AttributeKey PACKAGE_PATH = AttributeKey.stringKey("package.path"); - - private static final ThreadLocal MESSAGE_DIGEST_THREAD_LOCAL = - ThreadLocal.withInitial(JarAnalyzerUtil::createSha1MessageDigest); - - private static MessageDigest createSha1MessageDigest() { - try { - return MessageDigest.getInstance("SHA1"); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException( - "Unexpected error. Checksum algorithm SHA1 does not exist.", e); - } - } - - /** - * Set the attributes {@link #PACKAGE_TYPE} from the {@code archiveUrl}. - * - *

The {@link #PACKAGE_TYPE} is set extension of the archive, e.g. {@code jar}. - */ - static void addPackageType(AttributesBuilder builder, URL archiveUrl) throws Exception { - String path = archiveUrl.getFile(); - int extensionStart = path.lastIndexOf("."); - if (extensionStart > -1) { - builder.put(PACKAGE_TYPE, path.substring(extensionStart + 1)); - return; - } - throw new Exception("Cannot extract archive type from URL: " + archiveUrl); - } - - /** - * Set the attributes {@link #PACKAGE_CHECKSUM} from the {@code archiveUrl}. - * - *

The {@link #PACKAGE_CHECKSUM} is set to the SHA-1 checksum of the archive, e.g. {@code - * 30d16ec2aef6d8094c5e2dce1d95034ca8b6cb42}. - */ - static void addPackageChecksum(AttributesBuilder builder, URL archiveUrl) throws IOException { - builder.put(PACKAGE_CHECKSUM, computeSha1(archiveUrl)); - builder.put(PACKAGE_CHECKSUM_ALGORITHM, "SHA1"); - } - - private static String computeSha1(URL archiveUrl) throws IOException { - MessageDigest md = MESSAGE_DIGEST_THREAD_LOCAL.get(); - md.reset(); // Reset reused thread local message digest instead - - try (InputStream is = new DigestInputStream(archiveUrl.openStream(), md)) { - byte[] buffer = new byte[1024 * 8]; - // read in the stream in chunks while updating the digest - while (is.read(buffer) != -1) {} - - byte[] mdbytes = md.digest(); - - // convert to hex format - StringBuilder sb = new StringBuilder(40); - for (byte mdbyte : mdbytes) { - sb.append(Integer.toString((mdbyte & 0xff) + 0x100, 16).substring(1)); - } - - return sb.toString(); - } - } - - /** - * Set the attributes {@link #PACKAGE_PATH} from the {@code archiveUrl}. - * - *

The {@link #PACKAGE_PATH} is set to the archive file name, e.g. {@code - * jackson-datatype-jsr310-2.15.2.jar}. - */ - static void addPackagePath(AttributesBuilder builder, URL archiveUrl) throws Exception { - builder.put(PACKAGE_PATH, archiveFilename(archiveUrl)); - } - - private static String archiveFilename(URL archiveUrl) throws Exception { - String path = archiveUrl.getFile(); - int start = path.lastIndexOf(File.separator); - if (start > -1) { - return path.substring(start + 1); - } - throw new Exception("Cannot extract archive file name from archive URL: " + archiveUrl); - } - - /** - * Set the attributes {@link #PACKAGE_DESCRIPTION} from the {@code archiveUrl}. - * - *

The {@link #PACKAGE_DESCRIPTION} is set to manifest "{Implementation-Title} by - * {Implementation-Vendor}", e.g. {@code Jackson datatype: JSR310 by FasterXML}. - */ - static void addPackageDescription(AttributesBuilder builder, URL archiveUrl) throws IOException { - try (JarFile jarFile = new JarFile(archiveUrl.getFile())) { - Manifest manifest = jarFile.getManifest(); - if (manifest == null) { - return; - } - - java.util.jar.Attributes mainAttributes = manifest.getMainAttributes(); - String name = mainAttributes.getValue(java.util.jar.Attributes.Name.IMPLEMENTATION_TITLE); - String description = - mainAttributes.getValue(java.util.jar.Attributes.Name.IMPLEMENTATION_VENDOR); - - String packageDescription = name; - if (description != null && !description.isEmpty()) { - packageDescription += " by " + description; - } - builder.put(PACKAGE_DESCRIPTION, packageDescription); - } - } - - /** - * Set the attributes {@link #PACKAGE_NAME} and {@link #PACKAGE_VERSION} from the {@code - * archiveUrl}. - * - *

The {@link #PACKAGE_NAME} is set to the POM "{groupId}:{artifactId}", e.g. {@code Jackson - * datatype: JSR310 by FasterXML}. - * - *

The {@link #PACKAGE_VERSION} is set to the POM "{version}", e.g. {@code 2.15.2}. - */ - static void addPackageNameAndVersion(AttributesBuilder builder, URL archiveUrl) - throws IOException { - Properties pom = null; - try (InputStream inputStream = archiveUrl.openStream(); - JarInputStream jarInputStream = new JarInputStream(inputStream)) { - // Advance the jarInputStream to the pom.properties entry - for (JarEntry entry = jarInputStream.getNextJarEntry(); - entry != null; - entry = jarInputStream.getNextJarEntry()) { - if (entry.getName().startsWith("META-INF/maven") - && entry.getName().endsWith("pom.properties")) { - if (pom != null) { - // we've found multiple pom files. bail! - return; - } - pom = new Properties(); - pom.load(jarInputStream); - } - } - } - if (pom == null) { - return; - } - String groupId = pom.getProperty("groupId"); - String artifactId = pom.getProperty("artifactId"); - if (groupId != null && !groupId.isEmpty() && artifactId != null && !artifactId.isEmpty()) { - builder.put(PACKAGE_NAME, groupId + ":" + artifactId); - } - String version = pom.getProperty("version"); - if (version != null && !version.isEmpty()) { - builder.put(PACKAGE_VERSION, version); - } - } - - private JarAnalyzerUtil() {} -} diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarDetails.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarDetails.java new file mode 100644 index 000000000000..bd624cf4cab2 --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarDetails.java @@ -0,0 +1,283 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.javaagent.runtimemetrics.java8; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toMap; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.net.URL; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.Manifest; +import java.util.stream.Stream; +import javax.annotation.Nullable; + +/** + * For a given URL representing a Jar directly on the file system or embedded within another + * archive, this class provides methods which expose useful information about it. + */ +class JarDetails { + private static final Map EMBEDDED_FORMAT_TO_EXTENSION = + Stream.of("ear", "war", "jar") + .collect( + collectingAndThen( + toMap(ext -> ('.' + ext + "!/"), identity()), + Collections::unmodifiableMap)); + private static final ThreadLocal SHA1 = + ThreadLocal.withInitial( + () -> { + try { + return MessageDigest.getInstance("SHA1"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + }); + private static final ThreadLocal SHA512 = + ThreadLocal.withInitial( + () -> { + try { + return MessageDigest.getInstance("SHA-512"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + }); + + private final URL url; + protected final JarFile jarFile; + private final Properties pom; + private final Manifest manifest; + + private JarDetails(URL url, JarFile jarFile) { + this.url = url; + this.jarFile = jarFile; + this.pom = getPom(); + this.manifest = getManifest(); + } + + static JarDetails forUrl(URL url) throws IOException { + if (url.getProtocol().equals("jar")) { + String urlString = url.toExternalForm(); + String urlLower = urlString.toLowerCase(Locale.ROOT); + for (Map.Entry entry : EMBEDDED_FORMAT_TO_EXTENSION.entrySet()) { + int index = urlLower.indexOf(entry.getKey()); + if (index > 0) { + String targetEntry = urlString.substring(index + entry.getKey().length()); + JarFile jarFile = + new JarFile( + urlString.substring("jar:file:".length(), index + 1 + entry.getValue().length())); + JarEntry jarEntry = jarFile.getJarEntry(targetEntry); + return new Embedded(url, jarFile, jarEntry); + } + } + } + return new JarDetails(url, new JarFile(url.getFile())); + } + + /** Returns the archive file name, e.g. {@code jackson-datatype-jsr310-2.15.2.jar}. */ + String packagePath() { + String path = url.getFile(); + int start = path.lastIndexOf(File.separator); + if (start > -1) { + return path.substring(start + 1); + } + return null; + } + + /** Returns the extension of the archive, e.g. {@code jar}. */ + String packageType() { + String path = url.getFile(); + int extensionStart = path.lastIndexOf("."); + if (extensionStart > -1) { + return path.substring(extensionStart + 1); + } + return null; + } + + @Nullable + String packageName() { + if (pom == null) { + return null; + } + String groupId = pom.getProperty("groupId"); + String artifactId = pom.getProperty("artifactId"); + if (groupId != null && !groupId.isEmpty() && artifactId != null && !artifactId.isEmpty()) { + return groupId + ":" + artifactId; + } + return null; + } + + @Nullable + String version() { + if (pom == null) { + return null; + } + String version = pom.getProperty("version"); + if (version != null && !version.isEmpty()) { + return version; + } + return null; + } + + /** + * Returns the package description from the jar manifest "{Implementation-Title} by + * {Implementation-Vendor}", e.g. {@code Jackson datatype: JSR310 by FasterXML}. + */ + @Nullable + String packageDescription() { + if (manifest == null) { + return null; + } + + java.util.jar.Attributes mainAttributes = manifest.getMainAttributes(); + String name = mainAttributes.getValue(java.util.jar.Attributes.Name.IMPLEMENTATION_TITLE); + String description = + mainAttributes.getValue(java.util.jar.Attributes.Name.IMPLEMENTATION_VENDOR); + + String packageDescription = name; + if (description != null && !description.isEmpty()) { + packageDescription += " by " + description; + } + return packageDescription; + } + + @Nullable + String computeSha1() { + return computeDigest(SHA1.get()); + } + + @Nullable + String computeSha512() { + return computeDigest(SHA512.get()); + } + + private String computeDigest(MessageDigest md) { + try (InputStream inputStream = getInputStream()) { + DigestInputStream dis = new DigestInputStream(inputStream, md); + byte[] buffer = new byte[8192]; + while (dis.read(buffer) != -1) {} + byte[] digest = md.digest(); + return new BigInteger(1, digest).toString(16); + } catch (IOException e) { + return null; + } + } + + /** + * Open an input stream for the given url. If the url points to a jar within a jar, return an + * input stream starting at the embedded jar. + */ + protected InputStream getInputStream() throws IOException { + return url.openStream(); + } + + @Nullable + protected Manifest getManifest() { + try { + return jarFile.getManifest(); + } catch (IOException e) { + return null; + } + } + + /** + * Returns the values from pom.properties if this file is found. If multiple pom.properties files + * are found or there is an error reading the file, return null. + */ + @Nullable + protected Properties getPom() { + Properties pom = null; + try { + for (Enumeration entries = jarFile.entries(); entries.hasMoreElements(); ) { + JarEntry jarEntry = entries.nextElement(); + if (jarEntry.getName().startsWith("META-INF/maven") + && jarEntry.getName().endsWith("pom.properties")) { + if (pom != null) { + // we've found multiple pom files. bail! + return null; + } + Properties props = new Properties(); + props.load(jarFile.getInputStream(jarEntry)); + pom = props; + } + } + } catch (IOException e) { + return null; + } + return pom; + } + + private static class Embedded extends JarDetails { + + private final JarEntry jarEntry; + + private Embedded(URL url, JarFile jarFile, JarEntry jarEntry) { + super(url, jarFile); + this.jarEntry = jarEntry; + } + + /** + * Open an input stream for the given url. If the url points to a jar within a jar, return an + * input stream starting at the embedded jar. + */ + @Override + protected InputStream getInputStream() throws IOException { + return jarFile.getInputStream(jarEntry); + } + + @Override + protected Manifest getManifest() { + try (JarInputStream jarFile = new JarInputStream(getInputStream())) { + return jarFile.getManifest(); + } catch (IOException e) { + return null; + } + } + + /** + * Returns the values from pom.properties if this file is found. If multiple pom.properties + * files are found, return null. + */ + @Override + @Nullable + protected Properties getPom() { + Properties pom = null; + // Need to navigate inside the embedded jar which can't be done via random access. + try (JarInputStream jarFile = new JarInputStream(getInputStream())) { + for (JarEntry entry = jarFile.getNextJarEntry(); + entry != null; + entry = jarFile.getNextJarEntry()) { + if (entry.getName().startsWith("META-INF/maven") + && entry.getName().endsWith("pom.properties")) { + if (pom != null) { + // we've found multiple pom files. bail! + return null; + } + Properties props = new Properties(); + props.load(jarFile); + pom = props; + } + } + return pom; + } catch (IOException e) { + return null; + } + } + } +} From 100dc45cacc8a4bd7d8b7981d555ba5ae773796a Mon Sep 17 00:00:00 2001 From: Tyler Benson Date: Thu, 28 Sep 2023 14:06:14 -0400 Subject: [PATCH 2/2] Code review changes --- .../runtimemetrics/java8/JarDetails.java | 63 ++++++------------- 1 file changed, 19 insertions(+), 44 deletions(-) diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarDetails.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarDetails.java index bd624cf4cab2..5d3ec866e6f6 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarDetails.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/JarDetails.java @@ -49,22 +49,13 @@ class JarDetails { throw new IllegalStateException(e); } }); - private static final ThreadLocal SHA512 = - ThreadLocal.withInitial( - () -> { - try { - return MessageDigest.getInstance("SHA-512"); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException(e); - } - }); private final URL url; protected final JarFile jarFile; private final Properties pom; private final Manifest manifest; - private JarDetails(URL url, JarFile jarFile) { + private JarDetails(URL url, JarFile jarFile) throws IOException { this.url = url; this.jarFile = jarFile; this.pom = getPom(); @@ -110,6 +101,7 @@ String packageType() { return null; } + /** Returns the maven package name in the format {@code groupId:artifactId}. */ @Nullable String packageName() { if (pom == null) { @@ -123,6 +115,7 @@ String packageName() { return null; } + /** Returns the version from the pom file, or null if not found. */ @Nullable String version() { if (pom == null) { @@ -157,16 +150,12 @@ String packageDescription() { return packageDescription; } + /** Returns the SHA1 hash of this file. */ @Nullable String computeSha1() { return computeDigest(SHA1.get()); } - @Nullable - String computeSha512() { - return computeDigest(SHA512.get()); - } - private String computeDigest(MessageDigest md) { try (InputStream inputStream = getInputStream()) { DigestInputStream dis = new DigestInputStream(inputStream, md); @@ -180,8 +169,8 @@ private String computeDigest(MessageDigest md) { } /** - * Open an input stream for the given url. If the url points to a jar within a jar, return an - * input stream starting at the embedded jar. + * Returns An open input stream for the associated url. It is the caller's responsibility to close + * the stream on completion. */ protected InputStream getInputStream() throws IOException { return url.openStream(); @@ -201,24 +190,20 @@ protected Manifest getManifest() { * are found or there is an error reading the file, return null. */ @Nullable - protected Properties getPom() { + protected Properties getPom() throws IOException { Properties pom = null; - try { - for (Enumeration entries = jarFile.entries(); entries.hasMoreElements(); ) { - JarEntry jarEntry = entries.nextElement(); - if (jarEntry.getName().startsWith("META-INF/maven") - && jarEntry.getName().endsWith("pom.properties")) { - if (pom != null) { - // we've found multiple pom files. bail! - return null; - } - Properties props = new Properties(); - props.load(jarFile.getInputStream(jarEntry)); - pom = props; + for (Enumeration entries = jarFile.entries(); entries.hasMoreElements(); ) { + JarEntry jarEntry = entries.nextElement(); + if (jarEntry.getName().startsWith("META-INF/maven") + && jarEntry.getName().endsWith("pom.properties")) { + if (pom != null) { + // we've found multiple pom files. bail! + return null; } + Properties props = new Properties(); + props.load(jarFile.getInputStream(jarEntry)); + pom = props; } - } catch (IOException e) { - return null; } return pom; } @@ -227,15 +212,11 @@ private static class Embedded extends JarDetails { private final JarEntry jarEntry; - private Embedded(URL url, JarFile jarFile, JarEntry jarEntry) { + private Embedded(URL url, JarFile jarFile, JarEntry jarEntry) throws IOException { super(url, jarFile); this.jarEntry = jarEntry; } - /** - * Open an input stream for the given url. If the url points to a jar within a jar, return an - * input stream starting at the embedded jar. - */ @Override protected InputStream getInputStream() throws IOException { return jarFile.getInputStream(jarEntry); @@ -250,13 +231,9 @@ protected Manifest getManifest() { } } - /** - * Returns the values from pom.properties if this file is found. If multiple pom.properties - * files are found, return null. - */ @Override @Nullable - protected Properties getPom() { + protected Properties getPom() throws IOException { Properties pom = null; // Need to navigate inside the embedded jar which can't be done via random access. try (JarInputStream jarFile = new JarInputStream(getInputStream())) { @@ -275,8 +252,6 @@ protected Properties getPom() { } } return pom; - } catch (IOException e) { - return null; } } }