This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch maintenance-branch
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/maintenance-branch by this
push:
new 72884ff8761 CAUSEWAY-3969: [v2] backports JPA Weaving Safeguard from v4
72884ff8761 is described below
commit 72884ff87617f8c08645c842114ef1fef824ef4d
Author: andi-huber <[email protected]>
AuthorDate: Thu Feb 19 11:06:04 2026 +0100
CAUSEWAY-3969: [v2] backports JPA Weaving Safeguard from v4
---
adoc/changelog.adoc | 16 ++-
.../core/config/CausewayConfiguration.java | 24 ++++
.../config/beans/CausewayBeanTypeRegistry.java | 2 +
.../beans/CausewayBeanTypeRegistryDefault.java | 9 ++
.../CausewayModulePersistenceJpaIntegration.java | 9 +-
.../integration/services/JpaWeavingSafeguard.java | 136 +++++++++++++++++++++
.../services/JpaWeavingSafeguardService.java | 53 ++++++++
.../VerifyExtensionEntitiesAreEnhancedTest.java | 114 ++++++++++++++---
8 files changed, 339 insertions(+), 24 deletions(-)
diff --git a/adoc/changelog.adoc b/adoc/changelog.adoc
index 1a72c42628f..09bd86e2a89 100644
--- a/adoc/changelog.adoc
+++ b/adoc/changelog.adoc
@@ -2,7 +2,21 @@
⭐ New Features
-- Static weaving support for CI; enables via `mvn ... -DenhanceEclipselink`
https://issues.apache.org/jira/browse/CAUSEWAY-3968[CAUSEWAY-3968]
+- Static weaving support for CI; enabled via `mvn ... -DenhanceEclipselink`
https://issues.apache.org/jira/browse/CAUSEWAY-3968[CAUSEWAY-3968]
+- JPA Weaving Safeguard
https://issues.apache.org/jira/browse/CAUSEWAY-3969[CAUSEWAY-3969]
+; configure using
`causeway.persistence.weaving.safeguardMode=(LOG_ONLY|REQUIRE_WEAVED|..)`
+----
+LOG_ONLY:
+ Safeguard only logs warnings, but otherwise does not prevent an application
from launching.
+
+REQUIRE_WEAVED_WHEN_ANY_SUB_IS_WEAVED:
+ (Default) Requires for any entity type hierarchy that when classes are
weaved,
+ their super classes are also weaved.
+ Prevents entity type hierarchies from failing later at runtime.
+
+REQUIRE_WEAVED:
+ Enforces weaving on all encountered entity type hierarchies.
+----
📔 Documentation
diff --git
a/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
b/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
index 810afd2fdae..b6c863dd5a6 100644
---
a/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
+++
b/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java
@@ -2230,6 +2230,30 @@ public static class Schema {
private String createSchemaSqlTemplate = "CREATE SCHEMA IF NOT
EXISTS %S";
}
+
+ private final Weaving weaving = new Weaving();
+ @Data
+ public static class Weaving {
+ public enum SafeguardMode {
+ /**
+ * Safeguard only logs warnings, but otherwise does not
prevent an application from launching.
+ */
+ LOG_ONLY,
+ /**
+ * (Default) Requires for any entity type hierarchy that when
classes are weaved,
+ * their super classes are also weaved.
+ *
+ * <p>Prevents entity type hierarchies from failing later at
runtime.
+ */
+ REQUIRE_WEAVED_WHEN_ANY_SUB_IS_WEAVED,
+ /**
+ * Enforces weaving on all encountered entity type hierarchies.
+ */
+ REQUIRE_WEAVED
+ }
+ private SafeguardMode safeguardMode =
SafeguardMode.REQUIRE_WEAVED_WHEN_ANY_SUB_IS_WEAVED;
+
+ }
}
private final Prototyping prototyping = new Prototyping();
diff --git
a/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeRegistry.java
b/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeRegistry.java
index d55365d3dc6..86d5dabae96 100644
---
a/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeRegistry.java
+++
b/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeRegistry.java
@@ -76,5 +76,7 @@ default PersistenceStack determineCurrentPersistenceStack() {
.findFirst()
.orElse(PersistenceStack.UNSPECIFIED);
}
+
+ Stream<Class<?>> streamEntityTypes(PersistenceStack selectedStack);
}
\ No newline at end of file
diff --git
a/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeRegistryDefault.java
b/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeRegistryDefault.java
index c7bdc277639..1fbe8ed746a 100644
---
a/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeRegistryDefault.java
+++
b/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeRegistryDefault.java
@@ -20,6 +20,7 @@
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
@@ -126,6 +127,14 @@ public CausewayBeanTypeRegistryDefault(final @NonNull
Can<CausewayBeanMetaData>
}
+ // -- STREAMS
+ @Override
+ public Stream<Class<?>> streamEntityTypes(final PersistenceStack
selectedStack) {
+ if(selectedStack==null || !selectedStack.isJpa()) return
Stream.empty();
+ return entityTypes.values().stream()
+
.filter(typeMeta->Objects.equals(typeMeta.getPersistenceStack().orElse(null),
selectedStack))
+ .<Class<?>>map(CausewayBeanMetaData::getCorrespondingClass);
+ }
}
diff --git
a/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/CausewayModulePersistenceJpaIntegration.java
b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/CausewayModulePersistenceJpaIntegration.java
index fff16c18d5b..79bf71175ff 100644
---
a/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/CausewayModulePersistenceJpaIntegration.java
+++
b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/CausewayModulePersistenceJpaIntegration.java
@@ -18,14 +18,11 @@
*/
package org.apache.causeway.persistence.jpa.integration;
-import org.springframework.boot.autoconfigure.domain.EntityScan;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Import;
-
import org.apache.causeway.core.runtime.CausewayModuleCoreRuntime;
import
org.apache.causeway.persistence.commons.CausewayModulePersistenceCommons;
import
org.apache.causeway.persistence.jpa.integration.entity.JpaEntityIntegration;
import
org.apache.causeway.persistence.jpa.integration.services.JpaSupportServiceUsingSpring;
+import
org.apache.causeway.persistence.jpa.integration.services.JpaWeavingSafeguardService;
import
org.apache.causeway.persistence.jpa.integration.typeconverters.applib.CausewayBookmarkConverter;
import
org.apache.causeway.persistence.jpa.integration.typeconverters.applib.CausewayLocalResourcePathConverter;
import
org.apache.causeway.persistence.jpa.integration.typeconverters.applib.CausewayMarkupConverter;
@@ -40,6 +37,9 @@
import
org.apache.causeway.persistence.jpa.integration.typeconverters.schema.v2.CausewayInteractionDtoConverter;
import
org.apache.causeway.persistence.jpa.integration.typeconverters.schema.v2.CausewayOidDtoConverter;
import
org.apache.causeway.persistence.jpa.metamodel.CausewayModulePersistenceJpaMetamodel;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
@Configuration
@Import({
@@ -53,6 +53,7 @@
// @Service's
JpaSupportServiceUsingSpring.class,
+ JpaWeavingSafeguardService.class,
})
@EntityScan(basePackageClasses = {
diff --git
a/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/services/JpaWeavingSafeguard.java
b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/services/JpaWeavingSafeguard.java
new file mode 100644
index 00000000000..328a77ef89c
--- /dev/null
+++
b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/services/JpaWeavingSafeguard.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.causeway.persistence.jpa.integration.services;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.causeway.commons.internal.exceptions._Exceptions;
+import org.apache.causeway.commons.internal.reflection._ClassCache;
+import org.apache.causeway.commons.internal.reflection._Reflect;
+import
org.apache.causeway.commons.internal.reflection._Reflect.InterfacePolicy;
+import org.apache.causeway.core.config.CausewayConfiguration;
+import
org.apache.causeway.core.config.CausewayConfiguration.Persistence.Weaving.SafeguardMode;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.Accessors;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@RequiredArgsConstructor
+public final class JpaWeavingSafeguard {
+
+ final CausewayConfiguration.Persistence.Weaving.SafeguardMode mode;
+
+//debug
+// {
+// mode = SafeguardMode.REQUIRE_WEAVED;
+// }
+
+ public void checkAll(final Iterable<Class<?>> entityTypes) {
+ var cache = new Cache();
+
+ for(final Class<?> entityType : entityTypes) {
+ var chainOfInheritance =
cache.streamTypeHierarchyEntries(entityType).collect(Collectors.toList());
+
+ boolean isRemainingRequiredToBeEnhanced = mode ==
SafeguardMode.REQUIRE_WEAVED;
+ var it = chainOfInheritance.iterator();
+ while(it.hasNext()) {
+ var next = it.next();
+ if(isRemainingRequiredToBeEnhanced
+ && !next.isEnhanced()) {
+ fail(next, chainOfInheritance);
+ }
+ if(next.isEnhanced()) {
+ isRemainingRequiredToBeEnhanced = true;
+ }
+ }
+ }
+
+ }
+
+ private void fail(final EnhancementDescriptor offender, final
List<EnhancementDescriptor> chainOfInheritance) {
+ var chainAsMultiline = " * " + chainOfInheritance.stream()
+ .map(EnhancementDescriptor::toString)
+ .collect(Collectors.joining("\n * "));
+
+ switch (mode) {
+ case LOG_ONLY:
+ log.warn("found non-weaved\n {}\n in chain of inheritance\n{}",
+ offender.cls().getName(),
+ chainAsMultiline);
+ break;
+ case REQUIRE_WEAVED_WHEN_ANY_SUB_IS_WEAVED:
+ throw _Exceptions.unrecoverable("found non-weaved\n %s\n in
chain of inheritance\n%s",
+ offender.cls().getName(),
+ chainAsMultiline);
+ case REQUIRE_WEAVED:
+ throw _Exceptions.unrecoverable("while weaving is enforced,
found non-weaved\n %s\n in chain of inheritance\n%s",
+ offender.cls().getName(),
+ chainAsMultiline);
+ }
+ }
+
+ @RequiredArgsConstructor
+ @Getter @Accessors(fluent = true)
+ private final class EnhancementDescriptor{
+
+ final Class<?> cls;
+ final boolean isEnhanced;
+
+ @Override public final String toString() {
+ return String.format("%s%s",
+ isEnhanced
+ ? "[#]"
+ : "[-]",
+ cls.getName());
+ }
+ }
+
+ @RequiredArgsConstructor
+ @Getter @Accessors(fluent = true)
+ private final class Cache {
+
+ final Map<Class<?>, EnhancementDescriptor> descriptorsByClass;
+ final _ClassCache classCache;
+
+ Cache() {
+ this(new HashMap<>(), _ClassCache.getInstance());
+ }
+
+ Stream<EnhancementDescriptor> streamTypeHierarchyEntries(final
Class<?> cls) {
+ return streamTypeHierarchy(cls)
+ .map(type->
+ descriptorsByClass.computeIfAbsent(type, __->new
EnhancementDescriptor(type, classCache.isByteCodeEnhanced(type))));
+ }
+
+ // -- HELPER
+
+ private Stream<Class<?>> streamTypeHierarchy(final Class<?> cls) {
+ return _Reflect.streamTypeHierarchy(cls, InterfacePolicy.EXCLUDE)
+ .filter(type->!Object.class.equals(type));
+ }
+
+ }
+
+}
diff --git
a/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/services/JpaWeavingSafeguardService.java
b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/services/JpaWeavingSafeguardService.java
new file mode 100644
index 00000000000..422ec0540c3
--- /dev/null
+++
b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/services/JpaWeavingSafeguardService.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.causeway.persistence.jpa.integration.services;
+
+import java.util.HashSet;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+import org.apache.causeway.applib.events.metamodel.MetamodelListener;
+import org.apache.causeway.core.config.CausewayConfiguration;
+import org.apache.causeway.core.config.beans.CausewayBeanTypeRegistry;
+import org.apache.causeway.core.config.beans.PersistenceStack;
+import org.springframework.stereotype.Service;
+
+import lombok.extern.log4j.Log4j2;
+
+@Service
+@Log4j2
+public class JpaWeavingSafeguardService implements MetamodelListener {
+
+ @Inject CausewayConfiguration config;
+ @Inject CausewayBeanTypeRegistry causewayBeanTypeRegistry;
+
+ @Override public void onMetamodelLoaded() { }
+ @Override public void onMetamodelAboutToBeLoaded() {
+ var mode = config.getPersistence().getWeaving().getSafeguardMode();
+ log.info("running JPA Weaving Safeguard ({})", mode.name());
+
+ var jpaWeavingSafeguard = new JpaWeavingSafeguard(mode);
+ jpaWeavingSafeguard.checkAll(causewayBeanTypeRegistry
+ .streamEntityTypes(PersistenceStack.JPA)
+ .collect(Collectors.toCollection(HashSet::new)));
+ }
+
+}
+
diff --git
a/regressiontests/persistence-jpa/src/test/java/org/apache/causeway/testdomain/persistence/jpa/enhance/VerifyExtensionEntitiesAreEnhancedTest.java
b/regressiontests/persistence-jpa/src/test/java/org/apache/causeway/testdomain/persistence/jpa/enhance/VerifyExtensionEntitiesAreEnhancedTest.java
index 52e13b310bf..30bfd2d996a 100644
---
a/regressiontests/persistence-jpa/src/test/java/org/apache/causeway/testdomain/persistence/jpa/enhance/VerifyExtensionEntitiesAreEnhancedTest.java
+++
b/regressiontests/persistence-jpa/src/test/java/org/apache/causeway/testdomain/persistence/jpa/enhance/VerifyExtensionEntitiesAreEnhancedTest.java
@@ -18,8 +18,13 @@
*/
package org.apache.causeway.testdomain.persistence.jpa.enhance;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
import org.apache.causeway.commons.internal.reflection._ClassCache;
import org.apache.causeway.extensions.audittrail.jpa.dom.AuditTrailEntry;
import org.apache.causeway.extensions.commandlog.jpa.dom.CommandLogEntry;
@@ -34,6 +39,9 @@
import org.junit.jupiter.api.condition.EnabledIf;
import org.springframework.util.StringUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+
@EnabledIf(value = "isWeavingEnabled")
class VerifyExtensionEntitiesAreEnhancedTest {
@@ -42,33 +50,33 @@ static boolean isWeavingEnabled() {
|| StringUtils.hasLength(System.getenv("enhanceEclipselink"));
}
- private _ClassCache classCache = _ClassCache.getInstance();
+ private Verifier verifier = new
Verifier(Verifier.getVersion(VerifyExtensionEntitiesAreEnhancedTest.class));
@Test
void audittrail() {
- assertTrue(classCache.isByteCodeEnhanced(AuditTrailEntry.class));
+ verifier.verify(AuditTrailEntry.class);
}
@Test
void audittrail_applib() {
-
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.audittrail.applib.dom.AuditTrailEntry.class));
+
verifier.verify(org.apache.causeway.extensions.audittrail.applib.dom.AuditTrailEntry.class);
}
@Test
void commandlog() {
- assertTrue(classCache.isByteCodeEnhanced(CommandLogEntry.class));
+ verifier.verify(CommandLogEntry.class);
}
@Test
void commandlog_applib() {
-
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntry.class));
+
verifier.verify(org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntry.class);
}
@Test
void executionlog() {
- assertTrue(classCache.isByteCodeEnhanced(ExecutionLogEntry.class));
+ verifier.verify(ExecutionLogEntry.class);
}
@Test
void executionlog_applib() {
-
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.executionlog.applib.dom.ExecutionLogEntry.class));
+
verifier.verify(org.apache.causeway.extensions.executionlog.applib.dom.ExecutionLogEntry.class);
}
//no JPA variant of ExcelDemoToDoItem in v2 yet (however, could be backported
from main)
@@ -79,45 +87,113 @@ void executionlog_applib() {
@Test
void executionoutbox() {
- assertTrue(classCache.isByteCodeEnhanced(ExecutionOutboxEntry.class));
+ verifier.verify(ExecutionOutboxEntry.class);
}
@Test
void executionoutbox_applib() {
-
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.executionoutbox.applib.dom.ExecutionOutboxEntry.class));
+
verifier.verify(org.apache.causeway.extensions.executionoutbox.applib.dom.ExecutionOutboxEntry.class);
}
@Test void secman_role() {
- assertTrue(classCache.isByteCodeEnhanced(ApplicationRole.class));
+ verifier.verify(ApplicationRole.class);
}
@Test void secman_permission() {
- assertTrue(classCache.isByteCodeEnhanced(ApplicationPermission.class));
+ verifier.verify(ApplicationPermission.class);
}
@Test void secman_tenancy() {
- assertTrue(classCache.isByteCodeEnhanced(ApplicationTenancy.class));
+ verifier.verify(ApplicationTenancy.class);
}
@Test void secman_user() {
- assertTrue(classCache.isByteCodeEnhanced(ApplicationUser.class));
+ verifier.verify(ApplicationUser.class);
}
@Test void secman_role_applib() {
-
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.secman.applib.role.dom.ApplicationRole.class));
+
verifier.verify(org.apache.causeway.extensions.secman.applib.role.dom.ApplicationRole.class);
}
@Test void secman_permission_applib() {
-
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermission.class));
+
verifier.verify(org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermission.class);
}
@Test void secman_tenancy_applib() {
-
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.secman.applib.tenancy.dom.ApplicationTenancy.class));
+
verifier.verify(org.apache.causeway.extensions.secman.applib.tenancy.dom.ApplicationTenancy.class);
}
@Test void secman_user_applib() {
-
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.secman.applib.user.dom.ApplicationUser.class));
+
verifier.verify(org.apache.causeway.extensions.secman.applib.user.dom.ApplicationUser.class);
}
@Test
void sessionlog() {
- assertTrue(classCache.isByteCodeEnhanced(SessionLogEntry.class));
+ verifier.verify(SessionLogEntry.class);
}
@Test
void sessionlog_applib() {
-
assertTrue(classCache.isByteCodeEnhanced(org.apache.causeway.extensions.sessionlog.applib.dom.SessionLogEntry.class));
+
verifier.verify(org.apache.causeway.extensions.sessionlog.applib.dom.SessionLogEntry.class);
+ }
+
+ // -- HELPER
+
+ @RequiredArgsConstructor
+ final static class Verifier {
+
+ final Version expectedVersion;
+ final _ClassCache classCache;
+
+ @RequiredArgsConstructor
+ final static class Version {
+ final int major; final int minor;
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof Version
+ ? ((Version)obj).major == major
+ && ((Version)obj).minor == minor
+ : false;
+ }
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+ }
+
+ Verifier(final Version expectedVersion) {
+ this(expectedVersion, _ClassCache.getInstance());
+ }
+
+ void verify(final Class<?> cls) {
+ assertEquals(expectedVersion, getVersion(cls), ()->"java byte-code
version mismatch");
+ assertTrue(classCache.isByteCodeEnhanced(cls), ()->"not enhanced");
+ }
+
+ //EXPERIMENTAL code suggested by Copilot
+ @SneakyThrows
+ static Version getVersion(final Class<?> clazz) {
+ // Build the resource path (handles inner classes too)
+ String resource = clazz.getName().replace('.', '/') + ".class";
+
+ try (InputStream in = clazz.getClassLoader() != null
+ ? clazz.getClassLoader().getResourceAsStream(resource)
+ : ClassLoader.getSystemResourceAsStream(resource)) {
+
+ if (in == null) {
+ // Fallback: try via the class itself (works with
bootstrap/jrt classes)
+ try (InputStream in2 = clazz.getResourceAsStream("/" +
resource)) {
+ if (in2 == null)
+ throw new IOException("Unable to locate class
resource: " + resource);
+ return readHeader(in2);
+ }
+ }
+ return readHeader(in);
+ }
+ }
+
+ private static Version readHeader(final InputStream in) throws
IOException {
+ try (DataInputStream dis = new DataInputStream(in)) {
+ int magic = dis.readInt();
+ if (magic != 0xCAFEBABE)
+ throw new IOException("Not a valid class file (bad magic):
0x" + Integer.toHexString(magic));
+ int minor = dis.readUnsignedShort();
+ int major = dis.readUnsignedShort();
+ return new Version(major, minor);
+ }
+ }
+
}
}