From 5c9cbdfe76132aea2b5c8b983520623d3144a85a Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Mon, 28 Oct 2024 12:48:39 +0100 Subject: [PATCH 1/2] Introduce QuarkusClassLoader.visitRuntimeResources(name) --- .../classloading/QuarkusClassLoader.java | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java index bcbbc2a491ca2..9368790bf7509 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java @@ -23,11 +23,13 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.function.Consumer; import org.jboss.logging.Logger; import io.quarkus.commons.classloading.ClassLoaderHelper; import io.quarkus.paths.ManifestAttributes; +import io.quarkus.paths.PathVisit; /** * The ClassLoader used for non production Quarkus applications (i.e. dev and test mode). @@ -48,14 +50,42 @@ public class QuarkusClassLoader extends ClassLoader implements Closeable { registerAsParallelCapable(); } + private static RuntimeException nonQuarkusClassLoaderError() { + return new IllegalStateException("The current classloader is not an instance of " + + QuarkusClassLoader.class.getName() + " but " + + Thread.currentThread().getContextClassLoader().getClass().getName()); + } + + /** + * Visits every found runtime resource with a given name. If a resource is not found, the visitor will + * simply not be called. + *

+ * IMPORTANT: this method works only when the current class loader is an instance of {@link QuarkusClassLoader}, + * otherwise it throws an error with the corresponding message. + * + * @param resourceName runtime resource name to visit + * @param visitor runtime resource visitor + */ + public static void visitRuntimeResources(String resourceName, Consumer visitor) { + if (Thread.currentThread().getContextClassLoader() instanceof QuarkusClassLoader classLoader) { + for (var element : classLoader.getElementsWithResource(resourceName)) { + if (element.isRuntime()) { + element.apply(tree -> { + tree.accept(resourceName, visitor); + return null; + }); + } + } + } else { + throw nonQuarkusClassLoaderError(); + } + } + public static List getElements(String resourceName, boolean onlyFromCurrentClassLoader) { if (Thread.currentThread().getContextClassLoader() instanceof QuarkusClassLoader classLoader) { return classLoader.getElementsWithResource(resourceName, onlyFromCurrentClassLoader); } - - throw new IllegalStateException("The current classloader is not an instance of " - + QuarkusClassLoader.class.getName() + " but " - + Thread.currentThread().getContextClassLoader().getClass().getName()); + throw nonQuarkusClassLoaderError(); } /** @@ -78,10 +108,7 @@ public static boolean isApplicationClass(String className) { return classPathResourceIndex.getFirstClassPathElement(resourceName) != null; } - - throw new IllegalStateException("The current classloader is not an instance of " - + QuarkusClassLoader.class.getName() + " but " - + Thread.currentThread().getContextClassLoader().getClass().getName()); + throw nonQuarkusClassLoaderError(); } /** From 693cdcaa792dec1c3a421e31046880de0bc1f8c6 Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Mon, 28 Oct 2024 11:21:38 +0100 Subject: [PATCH 2/2] Safer lookup and generate static resources for web dep locator --- .../main/asciidoc/web-dependency-locator.adoc | 2 +- .../WebDependencyLocatorProcessor.java | 149 ++++++++---------- 2 files changed, 64 insertions(+), 87 deletions(-) diff --git a/docs/src/main/asciidoc/web-dependency-locator.adoc b/docs/src/main/asciidoc/web-dependency-locator.adoc index 4e9b7820eaa1a..6e86ff1743448 100644 --- a/docs/src/main/asciidoc/web-dependency-locator.adoc +++ b/docs/src/main/asciidoc/web-dependency-locator.adoc @@ -113,7 +113,7 @@ This means adding the following to your `index.html` will allow you to import we ===== Automatic imports -You can also automate the imports above. To do this, move your web assets from `src/main/resources/META-INF/resources` to `src/main/web` +You can also automate the imports above. To do this, move your web assets from `src/main/resources/META-INF/resources` to `src/main/resources/web` and now replace the above scripts and imports with `{#bundle /}`: [source,html] diff --git a/extensions/web-dependency-locator/deployment/src/main/java/io/quarkus/webdependency/locator/deployment/WebDependencyLocatorProcessor.java b/extensions/web-dependency-locator/deployment/src/main/java/io/quarkus/webdependency/locator/deployment/WebDependencyLocatorProcessor.java index 5954664dea853..d0a840540fb2d 100644 --- a/extensions/web-dependency-locator/deployment/src/main/java/io/quarkus/webdependency/locator/deployment/WebDependencyLocatorProcessor.java +++ b/extensions/web-dependency-locator/deployment/src/main/java/io/quarkus/webdependency/locator/deployment/WebDependencyLocatorProcessor.java @@ -7,7 +7,6 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -30,10 +29,10 @@ import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; -import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; import io.quarkus.maven.dependency.ArtifactKey; import io.quarkus.maven.dependency.ResolvedDependency; import io.quarkus.vertx.http.deployment.RouteBuildItem; +import io.quarkus.vertx.http.deployment.spi.GeneratedStaticResourceBuildItem; import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; import io.quarkus.webdependency.locator.runtime.WebDependencyLocatorRecorder; import io.vertx.core.Handler; @@ -43,96 +42,81 @@ public class WebDependencyLocatorProcessor { private static final Logger log = Logger.getLogger(WebDependencyLocatorProcessor.class.getName()); @BuildStep - public void findRelevantFiles(BuildProducer feature, - BuildProducer hotDeploymentWatchedProducer, - WebDependencyLocatorConfig config, - OutputTargetBuildItem outputTarget) throws IOException { - - Path web = outputTarget.getOutputDirectory().getParent() - .resolve(SRC) - .resolve(MAIN) - .resolve(RESOURCES) - .resolve(config.webRoot); - - if (Files.exists(web)) { - hotDeploymentWatchedProducer.produce(new HotDeploymentWatchedFileBuildItem(config.webRoot + SLASH + STAR + STAR)); - // Find all css and js (under /app) - Path app = web - .resolve(config.appRoot); + public void feature(BuildProducer feature) { + feature.produce(new FeatureBuildItem(Feature.WEB_DEPENDENCY_LOCATOR)); + } - List cssFiles = new ArrayList<>(); - List jsFiles = new ArrayList<>(); + @BuildStep + public void findRelevantFiles(BuildProducer generatedStaticProducer, + BuildProducer hotDeploymentWatchedProducer, + WebDependencyLocatorConfig config) throws IOException { - if (Files.exists(app)) { + QuarkusClassLoader.visitRuntimeResources(config.webRoot, visit -> { + final Path web = visit.getPath(); + if (Files.isDirectory(web)) { hotDeploymentWatchedProducer - .produce(new HotDeploymentWatchedFileBuildItem( - config.webRoot + SLASH + config.appRoot + SLASH + STAR + STAR)); - try (Stream appstream = Files.walk(app)) { - appstream.forEach(path -> { - if (Files.isRegularFile(path) && path.toString().endsWith(DOT_CSS)) { - cssFiles.add(web.relativize(path)); - } else if (Files.isRegularFile(path) && path.toString().endsWith(DOT_JS)) { - jsFiles.add(web.relativize(path)); + .produce(new HotDeploymentWatchedFileBuildItem(config.webRoot + SLASH + STAR + STAR)); + // Find all css and js (under /app) + Path app = web + .resolve(config.appRoot); + + List cssFiles = new ArrayList<>(); + List jsFiles = new ArrayList<>(); + + if (Files.exists(app)) { + hotDeploymentWatchedProducer + .produce(new HotDeploymentWatchedFileBuildItem( + config.webRoot + SLASH + config.appRoot + SLASH + STAR + STAR)); + try (Stream appstream = Files.walk(app)) { + appstream.forEach(path -> { + if (Files.isRegularFile(path) && path.toString().endsWith(DOT_CSS)) { + cssFiles.add(web.relativize(path)); + } else if (Files.isRegularFile(path) && path.toString().endsWith(DOT_JS)) { + jsFiles.add(web.relativize(path)); + } + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + try (Stream webstream = Files.walk(web)) { + + webstream.forEach(path -> { + if (Files.isRegularFile(path)) { + String endpoint = SLASH + web.relativize(path); + try { + if (path.toString().endsWith(DOT_HTML)) { + generatedStaticProducer.produce(new GeneratedStaticResourceBuildItem(endpoint, + processHtml(path, cssFiles, jsFiles))); + } else { + generatedStaticProducer.produce(new GeneratedStaticResourceBuildItem(endpoint, path)); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } } }); + } catch (IOException e) { + throw new UncheckedIOException(e); } } + }); - try (Stream webstream = Files.walk(web)) { - - final Path resourcesDirectory = outputTarget.getOutputDirectory() - .resolve(CLASSES) - .resolve(META_INF) - .resolve(RESOURCES); - Files.createDirectories(resourcesDirectory); - - webstream.forEach(path -> { - if (Files.isRegularFile(path)) { - try { - copyResource(resourcesDirectory, web, path, cssFiles, jsFiles, path.toString().endsWith(DOT_HTML)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } else if (Files.isRegularFile(path)) { - - } - }); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - feature.produce(new FeatureBuildItem(Feature.WEB_DEPENDENCY_LOCATOR)); } - private void copyResource(Path resourcesDirectory, Path webRoot, Path path, List cssFiles, List jsFiles, - boolean filter) + private byte[] processHtml( + Path path, List cssFiles, List jsFiles) throws IOException { - try { + StringJoiner modifiedContent = new StringJoiner(System.lineSeparator()); - Path relativizePath = webRoot.relativize(path); + Files.lines(path).forEach(line -> { + String modifiedLine = processLine(line, cssFiles, jsFiles); + modifiedContent.add(modifiedLine); + }); - byte[] toBeCopied; - if (filter) { - StringJoiner modifiedContent = new StringJoiner(System.lineSeparator()); - - Files.lines(path).forEach(line -> { - String modifiedLine = processLine(line, cssFiles, jsFiles); - modifiedContent.add(modifiedLine); - }); - - String result = modifiedContent.toString(); - toBeCopied = result.getBytes(); - } else { - toBeCopied = Files.readAllBytes(path); - } - - final Path resourceFile = resourcesDirectory.resolve(relativizePath); - Files.createDirectories(resourceFile.getParent()); - Files.write(resourceFile, toBeCopied, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - - } catch (IOException e) { - throw new UncheckedIOException(e); - } + String result = modifiedContent.toString(); + return result.getBytes(); } private static String processLine(String line, List cssFiles, List jsFiles) { @@ -310,13 +294,6 @@ static class LibInfo { private static final String TAB = "\t"; private static final String TAB2 = TAB + TAB; - private static final String CLASSES = "classes"; - private static final String META_INF = "META-INF"; - private static final String RESOURCES = "resources"; - - private static final String SRC = "src"; - private static final String MAIN = "main"; - private static final String SLASH = "/"; private static final String STAR = "*";