Index: opensaml-core-api/src/main/resources-filtered/org/opensaml/core/version.properties IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/opensaml-core-api/src/main/resources-filtered/org/opensaml/core/version.properties b/opensaml-core-api/src/main/resources-filtered/org/opensaml/core/version.properties new file mode 100644 --- /dev/null (date 1787995420086) +++ b/opensaml-core-api/src/main/resources-filtered/org/opensaml/core/version.properties (date 1787995420086) @@ -0,0 +1,3 @@ +# Generated at build time by Maven resource filtering; do not edit. +# See the "resources-filtered" resource directory declared in opensaml-core-api/pom.xml. +version=${project.version} Index: opensaml-core-api/pom.xml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/opensaml-core-api/pom.xml b/opensaml-core-api/pom.xml --- a/opensaml-core-api/pom.xml (revision 0020ffcf6e2e339bb9a07ce8bf23d781c8e33d18) +++ b/opensaml-core-api/pom.xml (date 1787995444943) @@ -54,6 +54,17 @@ + + + + src/main/resources + + + + src/main/resources-filtered + true + + maven-jar-plugin Index: opensaml-core-api/src/main/java/org/opensaml/core/Version.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/opensaml-core-api/src/main/java/org/opensaml/core/Version.java b/opensaml-core-api/src/main/java/org/opensaml/core/Version.java --- a/opensaml-core-api/src/main/java/org/opensaml/core/Version.java (revision 0020ffcf6e2e339bb9a07ce8bf23d781c8e33d18) +++ b/opensaml-core-api/src/main/java/org/opensaml/core/Version.java (date 1787995460169) @@ -14,13 +14,26 @@ package org.opensaml.core; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +import javax.annotation.Nonnull; import javax.annotation.Nullable; +import net.shibboleth.shared.annotation.constraint.NotEmpty; + /** Class for printing the version of this library. */ public final class Version { + /** Build-generated resource carrying the library version. */ + @Nonnull @NotEmpty private static final String VERSION_RESOURCE = "version.properties"; + + /** Name of the property within {@link #VERSION_RESOURCE} holding the version. */ + @Nonnull @NotEmpty private static final String VERSION_PROPERTY = "version"; + /** IdP version. */ - @Nullable private static final String VERSION = Version.class.getPackage().getImplementationVersion(); + @Nullable private static final String VERSION = loadVersion(); /** Constructor. */ private Version() { @@ -43,5 +56,37 @@ @Nullable public static String getVersion() { return VERSION; } - -} \ No newline at end of file + + /** + * Read the library version from the build-generated resource alongside this class. + * + *

The version cannot be obtained from {@link Package#getImplementationVersion()} alone: a + * {@link Package} belonging to a named module never carries the manifest's versioning attributes, so + * that method returns null whenever this library is loaded from the module path. The module + * descriptor is no substitute either, because for an automatic module its version is derived from + * the JAR file name, which is absent or wrong once the JAR is renamed or repackaged.

+ * + * @return version of the library, or null if it cannot be determined + */ + @Nullable private static String loadVersion() { + // NOTE: the resource name deliberately has no leading slash, so that it resolves relative to this + // class's own package. Unlike a ClassLoader lookup, that keeps the read inside this library's + // module when it is loaded from the module path, and picks the right JAR on the class path. + try (final InputStream is = Version.class.getResourceAsStream(VERSION_RESOURCE)) { + if (is != null) { + final Properties props = new Properties(); + props.load(is); + final String version = props.getProperty(VERSION_PROPERTY); + if (version != null && !version.isEmpty()) { + return version; + } + } + } catch (final IOException e) { + // Ignored, in favour of the class path fallback below. + } + + // Only reached if the resource is missing or unreadable, and then only useful on the class path. + return Version.class.getPackage().getImplementationVersion(); + } + +}