From dc2fc83860267af57a0a4dbd71312247f9d8bd3d Mon Sep 17 00:00:00 2001 From: Nicholas Kolatsis Date: Wed, 11 Sep 2024 07:39:47 +0200 Subject: [PATCH 1/7] nitpicks & cleanup of some of the code (cherry picked from commit 75d8c4abe071d018507ffc3cea00dd368980e910) --- docs/src/main/asciidoc/security-jwt.adoc | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/docs/src/main/asciidoc/security-jwt.adoc b/docs/src/main/asciidoc/security-jwt.adoc index 3b5515a5f7691..e91ef195e7c9d 100644 --- a/docs/src/main/asciidoc/security-jwt.adoc +++ b/docs/src/main/asciidoc/security-jwt.adoc @@ -84,8 +84,6 @@ Create a REST endpoint in `src/main/java/org/acme/security/jwt/TokenSecuredResou ---- package org.acme.security.jwt; -import java.security.Principal; - import jakarta.annotation.security.PermitAll; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; @@ -105,7 +103,7 @@ public class TokenSecuredResource { @Inject JsonWebToken jwt; // <1> - @GET() + @GET @Path("permit-all") @PermitAll // <2> @Produces(MediaType.TEXT_PLAIN) @@ -122,7 +120,7 @@ public class TokenSecuredResource { } else { name = ctx.getUserPrincipal().getName(); // <6> } - return String.format("hello + %s," + return String.format("hello %s," + " isHttps: %s," + " authScheme: %s," + " hasJWT: %s", @@ -172,7 +170,7 @@ Now that the REST endpoint is running, we can access it using a command line too [source,shell] ---- $ curl http://127.0.0.1:8080/secured/permit-all; echo -hello + anonymous, isHttps: false, authScheme: null, hasJWT: false +hello anonymous, isHttps: false, authScheme: null, hasJWT: false ---- We have not provided any JWT in our request, so we would not expect that there is any security state seen by the endpoint, @@ -194,7 +192,6 @@ package org.acme.security.jwt; import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.RolesAllowed; -import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.InternalServerErrorException; @@ -207,7 +204,6 @@ import jakarta.ws.rs.core.SecurityContext; import org.eclipse.microprofile.jwt.JsonWebToken; @Path("/secured") -@RequestScoped public class TokenSecuredResource { @Inject @@ -238,7 +234,7 @@ public class TokenSecuredResource { } else { name = ctx.getUserPrincipal().getName(); } - return String.format("hello + %s," + return String.format("hello %s," + " isHttps: %s," + " authScheme: %s," + " hasJWT: %s", @@ -455,7 +451,7 @@ curl -H "Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUI [source,shell] ---- $ curl -H "Authorization: Bearer eyJraWQ..." http://127.0.0.1:8080/secured/roles-allowed; echo -hello + jdoe@quarkus.io, isHttps: false, authScheme: Bearer, hasJWT: true, birthdate: 2001-07-13 +hello jdoe@quarkus.io, isHttps: false, authScheme: Bearer, hasJWT: true, birthdate: 2001-07-13 ---- Success! We now have: @@ -542,7 +538,7 @@ public class TokenSecuredResource { } else { name = ctx.getUserPrincipal().getName(); } - return String.format("hello + %s," + return String.format("hello %s," + " isHttps: %s," + " authScheme: %s," + " hasJWT: %s", @@ -568,7 +564,7 @@ curl -H "Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUI [source,shell] ---- $ curl -H "Authorization: Bearer eyJraWQ..." http://127.0.0.1:8080/secured/roles-allowed-admin; echo -hello + jdoe@quarkus.io, isHttps: false, authScheme: Bearer, hasJWT: true, birthdate: 2001-07-13 +hello jdoe@quarkus.io, isHttps: false, authScheme: Bearer, hasJWT: true, birthdate: 2001-07-13 ---- === Package and run the application From a6f23d78afabbe87e822087b4b9cfe52d69e5914 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Sun, 15 Sep 2024 12:36:12 +0100 Subject: [PATCH 2/7] Highlight when RequestScoped must be used (cherry picked from commit 17436032b544efb758203064d41a57bd450cbd02) --- docs/src/main/asciidoc/security-jwt.adoc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/src/main/asciidoc/security-jwt.adoc b/docs/src/main/asciidoc/security-jwt.adoc index e91ef195e7c9d..d7e5900a76e91 100644 --- a/docs/src/main/asciidoc/security-jwt.adoc +++ b/docs/src/main/asciidoc/security-jwt.adoc @@ -85,7 +85,6 @@ Create a REST endpoint in `src/main/java/org/acme/security/jwt/TokenSecuredResou package org.acme.security.jwt; import jakarta.annotation.security.PermitAll; -import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.InternalServerErrorException; @@ -496,14 +495,14 @@ import org.eclipse.microprofile.jwt.Claims; import org.eclipse.microprofile.jwt.JsonWebToken; @Path("/secured") -@RequestScoped +@RequestScoped <1> public class TokenSecuredResource { @Inject - JsonWebToken jwt; // <1> + JsonWebToken jwt; // <2> @Inject @Claim(standard = Claims.birthdate) - String birthdate; // <2> + String birthdate; // <3> @GET @Path("permit-all") @@ -526,7 +525,7 @@ public class TokenSecuredResource { @RolesAllowed("Admin") @Produces(MediaType.TEXT_PLAIN) public String helloRolesAllowedAdmin(@Context SecurityContext ctx) { - return getResponseString(ctx) + ", birthdate: " + birthdate; // <3> + return getResponseString(ctx) + ", birthdate: " + birthdate; // <4> } private String getResponseString(SecurityContext ctx) { @@ -550,9 +549,10 @@ public class TokenSecuredResource { } } ---- -<1> Here we inject the JsonWebToken. -<2> Here we inject the `birthday` claim as `String` - this is why the `@RequestScoped` scope is now required. -<3> Here we use the injected `birthday` claim to build the final reply. +<1> `RequestScoped` scope is required to support an injection of the `birthday` claim as `String`. +<2> Here we inject the JsonWebToken. +<3> Here we inject the `birthday` claim as `String` - this is why the `@RequestScoped` scope is now required. +<4> Here we use the injected `birthday` claim to build the final reply. Now generate the token again and run: From 9947b27fa01673dc959c6206eeaddce56c2b1f09 Mon Sep 17 00:00:00 2001 From: Roman Lovakov Date: Mon, 16 Sep 2024 12:22:13 +0300 Subject: [PATCH 3/7] Smallrye GraphQL: add missing federation annotations to index (cherry picked from commit 1215fde3383ae5c3a759c209fa7fa952df7012b5) --- .../deployment/SmallRyeGraphQLProcessor.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java index 03ee25fd6e059..792edd0eba119 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java @@ -83,9 +83,11 @@ import io.smallrye.graphql.api.Entry; import io.smallrye.graphql.api.ErrorExtensionProvider; import io.smallrye.graphql.api.OneOf; +import io.smallrye.graphql.api.federation.Authenticated; import io.smallrye.graphql.api.federation.ComposeDirective; import io.smallrye.graphql.api.federation.Extends; import io.smallrye.graphql.api.federation.External; +import io.smallrye.graphql.api.federation.FieldSet; import io.smallrye.graphql.api.federation.Inaccessible; import io.smallrye.graphql.api.federation.InterfaceObject; import io.smallrye.graphql.api.federation.Key; @@ -93,6 +95,15 @@ import io.smallrye.graphql.api.federation.Requires; import io.smallrye.graphql.api.federation.Shareable; import io.smallrye.graphql.api.federation.Tag; +import io.smallrye.graphql.api.federation.link.Import; +import io.smallrye.graphql.api.federation.link.Link; +import io.smallrye.graphql.api.federation.link.Purpose; +import io.smallrye.graphql.api.federation.policy.Policy; +import io.smallrye.graphql.api.federation.policy.PolicyGroup; +import io.smallrye.graphql.api.federation.policy.PolicyItem; +import io.smallrye.graphql.api.federation.requiresscopes.RequiresScopes; +import io.smallrye.graphql.api.federation.requiresscopes.ScopeGroup; +import io.smallrye.graphql.api.federation.requiresscopes.ScopeItem; import io.smallrye.graphql.cdi.config.MicroProfileConfig; import io.smallrye.graphql.cdi.producer.GraphQLProducer; import io.smallrye.graphql.cdi.tracing.TracingService; @@ -298,6 +309,17 @@ void buildFinalIndex( indexer.indexClass(io.smallrye.graphql.api.federation.Override.class); indexer.indexClass(Tag.class); indexer.indexClass(OneOf.class); + indexer.indexClass(Authenticated.class); + indexer.indexClass(FieldSet.class); + indexer.indexClass(Link.class); + indexer.indexClass(Import.class); + indexer.indexClass(Purpose.class); + indexer.indexClass(Policy.class); + indexer.indexClass(PolicyGroup.class); + indexer.indexClass(PolicyItem.class); + indexer.indexClass(RequiresScopes.class); + indexer.indexClass(ScopeGroup.class); + indexer.indexClass(ScopeItem.class); } catch (IOException ex) { LOG.warn("Failure while creating index", ex); } From 0c4a1d507e9af3d211c572728c16ecac3ca63588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Mal=C3=A9=C5=99?= Date: Mon, 16 Sep 2024 13:52:43 +0200 Subject: [PATCH 4/7] TLS reference guide IDs' unification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michal Maléř (cherry picked from commit fa370b3c47ab3f83c2732963753f6dcda04fb662) --- docs/src/main/asciidoc/tls-registry-reference.adoc | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/src/main/asciidoc/tls-registry-reference.adoc b/docs/src/main/asciidoc/tls-registry-reference.adoc index db480222b323d..8774141b5c93e 100644 --- a/docs/src/main/asciidoc/tls-registry-reference.adoc +++ b/docs/src/main/asciidoc/tls-registry-reference.adoc @@ -25,7 +25,7 @@ The TLS Registry extension is automatically included in your project when you us As a result, applications that use the TLS Registry can be ready to handle secure communications out of the box. TLS Registry also provides features like automatic certificate reloading, Let's Encrypt (ACME) integration, Kubernetes Cert-Manager support, and compatibility with various keystore formats, such as PKCS12, PEM, and JKS. -[#using-the-tls-registry] +[[using-the-tls-registry]] == Using the TLS registry To configure a TLS connection, including key and truststores, use the `+quarkus.tls.*+` properties. @@ -126,7 +126,7 @@ quarkus.grpc.server.plain-text=false + This configuration enables mTLS by ensuring that both the server and client validate each other's certificates, which provides an additional layer of security. -[#referencing-a-tls-configuration] +[[referencing-a-tls-configuration]] == Referencing a TLS configuration To reference an example _named_ configuration that you created by using the `quarkus.tls..*` properties as explained in <> @@ -257,7 +257,7 @@ quarkus.tls.key-store.jks.alias-password=my-alias-password * Alternatively, use SNI to select the appropriate certificate and private key. Note that all keys must use the same password. -[#sni] +[[sni]] ==== SNI Server Name Indication (SNI) is a TLS extension that makes it possible for a client to specify the host name to which it attempts to connect during the TLS handshake. @@ -585,7 +585,7 @@ When an application that uses the TLS extension starts, the TLS registry perform If any of these checks fail, the application will not start. -[#reloading-certificates] +[[reloading-certificates]] == Reloading certificates The `TlsConfiguration` obtained from the `TLSConfigurationRegistry` includes a mechanism for reloading certificates. @@ -1267,12 +1267,11 @@ quarkus.http.insecure-requests=redirect ==== -[[lets-encrypt-prepare]] - The challenge is served from the primary HTTP interface (accessible from your DNS domain name). IMPORTANT: Do not start your application yet. +[[lets-encrypt-prepare]] === Application preparation Before you request a Let's Encrypt certificate: From fb185624b446ba699af2a55e829d9b76b3450ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20Kr=C3=BCger?= Date: Mon, 16 Sep 2024 21:30:11 +1000 Subject: [PATCH 5/7] Make sure server log load on Dev UI start (cherry picked from commit 0b670d88842cb12271bf7e2f769235f6e7d94099) --- .../src/main/resources/dev-ui/qwc/qwc-server-log.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-server-log.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-server-log.js index 67eee71367278..5a3b9e8fcb2bc 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-server-log.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-server-log.js @@ -136,6 +136,7 @@ export class QwcServerLog extends QwcAbstractLogElement { connectedCallback() { super.connectedCallback(); this._toggleOnOff(true); + this._history(); this._loadAllLoggers(); } @@ -744,4 +745,4 @@ export class QwcServerLog extends QwcAbstractLogElement { } -customElements.define('qwc-server-log', QwcServerLog); \ No newline at end of file +customElements.define('qwc-server-log', QwcServerLog); From c543035810c1976f0d10832b7f56cd08ee08fc15 Mon Sep 17 00:00:00 2001 From: Thomas Canava Date: Sat, 14 Sep 2024 23:48:56 +0200 Subject: [PATCH 6/7] Set filtered jar's manifest time to epoch (cherry picked from commit a7aa35489e92fea967016a3954e7f8199d69f405) --- .../pkg/steps/JarResultBuildStep.java | 10 +++++++++- .../pkg/steps/JarResultBuildStepTest.java | 20 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java index 3323c0824516a..bb61fbcea28ae 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java @@ -1290,7 +1290,13 @@ static void filterJarFile(Path resolvedDep, Path targetPath, Set transfo } else { manifest = new Manifest(); } - try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(targetPath), manifest)) { + try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(targetPath))) { + JarEntry manifestEntry = new JarEntry(JarFile.MANIFEST_NAME); + // Set manifest time to epoch to always make the same jar + manifestEntry.setTime(0); + out.putNextEntry(manifestEntry); + manifest.write(out); + out.closeEntry(); Enumeration entries = in.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); @@ -1306,6 +1312,8 @@ static void filterJarFile(Path resolvedDep, Path targetPath, Set transfo while ((r = inStream.read(buffer)) > 0) { out.write(buffer, 0, r); } + } finally { + out.closeEntry(); } } else { log.debugf("Removed %s from %s", entryName, resolvedDep); diff --git a/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/JarResultBuildStepTest.java b/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/JarResultBuildStepTest.java index 9e50d306377ff..fc5180b34e9a7 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/JarResultBuildStepTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/JarResultBuildStepTest.java @@ -68,6 +68,26 @@ void should_unsign_jar_when_filtered(@TempDir Path tempDir) throws Exception { } } + @Test + void manifestTimeShouldAlwaysBeSetToEpoch(@TempDir Path tempDir) throws Exception { + JavaArchive archive = ShrinkWrap.create(JavaArchive.class, "myarchive.jar") + .addClasses(Integer.class) + .addManifest(); + Path initialJar = tempDir.resolve("initial.jar"); + Path filteredJar = tempDir.resolve("filtered.jar"); + archive.as(ZipExporter.class).exportTo(new File(initialJar.toUri()), true); + JarResultBuildStep.filterJarFile(initialJar, filteredJar, Set.of("java/lang/Integer.class")); + try (JarFile jarFile = new JarFile(filteredJar.toFile())) { + assertThat(jarFile.stream()) + .filteredOn(jarEntry -> jarEntry.getName().equals(JarFile.MANIFEST_NAME)) + .isNotEmpty() + .allMatch(jarEntry -> jarEntry.getTime() == 0); + // Check that the manifest is still has attributes + Manifest manifest = jarFile.getManifest(); + assertThat(manifest.getMainAttributes()).isNotEmpty(); + } + } + private static KeyStore.PrivateKeyEntry createPrivateKeyEntry() throws NoSuchAlgorithmException, CertificateException, OperatorCreationException, CertIOException { KeyPairGenerator ky = KeyPairGenerator.getInstance("RSA"); From e80c0ab98ce30e6e0ed6cf54b4a6c4a7766ab4ce Mon Sep 17 00:00:00 2001 From: mariofusco Date: Mon, 16 Sep 2024 10:04:05 +0200 Subject: [PATCH 7/7] Follow up of the fix making jar file reference close idempotent with minor comments and refactor (cherry picked from commit b8e7cd8252a81355332f2a9ec99641f456bf2642) --- .../bootstrap/runner/JarFileReference.java | 55 ++++++++----------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/runner/JarFileReference.java b/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/runner/JarFileReference.java index 8c515413071a7..07add0fad2b0a 100644 --- a/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/runner/JarFileReference.java +++ b/independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/runner/JarFileReference.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.nio.file.Path; -import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; import java.util.jar.JarFile; @@ -14,7 +13,8 @@ public class JarFileReference { // This is required to perform cleanup of JarResource::jarFileReference without breaking racy updates - private CompletableFuture completedFuture; + private final CompletableFuture completedFuture; + // Guarded by an atomic reader counter that emulate the behaviour of a read/write lock. // To enable virtual threads compatibility and avoid pinning it is not possible to use an explicit read/write lock // because the jarFile access may happen inside a native call (for example triggered by the RunnerClassLoader) @@ -26,22 +26,10 @@ public class JarFileReference { // The JarFileReference is created as already acquired and that's why the referenceCounter starts from 2 private final AtomicInteger referenceCounter = new AtomicInteger(2); - private JarFileReference(JarFile jarFile) { + private JarFileReference(JarFile jarFile, CompletableFuture completedFuture) { this.jarFile = jarFile; - } - - public static JarFileReference completeWith(CompletableFuture completableFuture, JarFile jarFile) { - Objects.requireNonNull(completableFuture); - var jarFileRef = new JarFileReference(jarFile); - jarFileRef.completedFuture = completableFuture; - completableFuture.complete(jarFileRef); - return jarFileRef; - } - - public static CompletableFuture completedWith(JarFile jarFile) { - var jarFileRef = new JarFileReference(jarFile); - jarFileRef.completedFuture = CompletableFuture.completedFuture(jarFileRef); - return jarFileRef.completedFuture; + this.completedFuture = completedFuture; + this.completedFuture.complete(this); } /** @@ -57,21 +45,20 @@ private boolean acquire() { if (count == 0) { return false; } - if (referenceCounter.compareAndSet(count, addCount(count, 1))) { + if (referenceCounter.compareAndSet(count, changeReferenceCount(count, 1))) { return true; } } } /** - * This is not allowed to change the sign of count (unless put it to 0) + * Change the absolute value of the provided reference count of the given delta, that can only be 1 when the reference is + * acquired by a new reader or -1 when the reader releases the reference or the reference itself is marked for closing. + * A negative reference count means that this reference has been marked for closing. */ - private static int addCount(final int count, int delta) { + private static int changeReferenceCount(final int count, int delta) { assert count != 0; - if (count < 0) { - delta = -delta; - } - return count + delta; + return count < 0 ? count - delta : count + delta; } /** @@ -89,9 +76,10 @@ private boolean release(JarResource jarResource) { if (count == 1 || count == 0) { throw new IllegalStateException("Duplicate release? The reference counter cannot be " + count); } - if (referenceCounter.compareAndSet(count, addCount(count, -1))) { + if (referenceCounter.compareAndSet(count, changeReferenceCount(count, -1))) { if (count == -1) { - silentCloseJarResources(jarResource); + // The reference has been already marked to be closed (the counter is negative) and this is the last reader releasing it + closeJarResources(jarResource); return true; } return false; @@ -99,7 +87,7 @@ private boolean release(JarResource jarResource) { } } - private void silentCloseJarResources(JarResource jarResource) { + private void closeJarResources(JarResource jarResource) { // we need to make sure we're not deleting others state jarResource.jarFileReference.compareAndSet(completedFuture, null); try { @@ -110,7 +98,7 @@ private void silentCloseJarResources(JarResource jarResource) { } /** - * Ask to close this reference. + * Mark this jar reference as ready to be closed. * If there are no readers currently accessing the jarFile also close it, otherwise defer the closing when the last reader * will leave. */ @@ -122,9 +110,10 @@ void markForClosing(JarResource jarResource) { return; } // close must change the value into a negative one or zeroing - if (referenceCounter.compareAndSet(count, addCount(-count, -1))) { + // the reference counter is turned into a negative value to indicate (in an idempotent way) that the resource has been marked to be closed. + if (referenceCounter.compareAndSet(count, changeReferenceCount(-count, -1))) { if (count == 1) { - silentCloseJarResources(jarResource); + closeJarResources(jarResource); } } } @@ -145,6 +134,7 @@ static T withJarFile(JarResource jarResource, String resource, JarFileConsum if (jarFileReference.acquire()) { return consumeSharedJarFile(jarFileReference, jarResource, resource, fileConsumer); } + // The acquire failure implies that the reference is already marked to be closed. closingLocalJarFileRef = true; } @@ -199,7 +189,8 @@ private static T consumeUnsharedJarFile(CompletableFuture private static CompletableFuture syncLoadAcquiredJarFile(JarResource jarResource) { try { - return JarFileReference.completedWith(JarFiles.create(jarResource.jarPath.toFile())); + return new JarFileReference(JarFiles.create(jarResource.jarPath.toFile()), + new CompletableFuture<>()).completedFuture; } catch (IOException e) { throw new RuntimeException("Failed to open " + jarResource.jarPath, e); } @@ -213,7 +204,7 @@ private static JarFileReference asyncLoadAcquiredJarFile(JarResource jarResource do { if (jarResource.jarFileReference.compareAndSet(null, newJarRefFuture)) { try { - return JarFileReference.completeWith(newJarRefFuture, JarFiles.create(jarResource.jarPath.toFile())); + return new JarFileReference(JarFiles.create(jarResource.jarPath.toFile()), newJarRefFuture); } catch (IOException e) { throw new RuntimeException(e); }