Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Safer lookup and generate static resources for web dep locator #44128

Merged
merged 2 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/src/main/asciidoc/web-dependency-locator.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -43,96 +42,81 @@ public class WebDependencyLocatorProcessor {
private static final Logger log = Logger.getLogger(WebDependencyLocatorProcessor.class.getName());

@BuildStep
public void findRelevantFiles(BuildProducer<FeatureBuildItem> feature,
BuildProducer<HotDeploymentWatchedFileBuildItem> 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<FeatureBuildItem> feature) {
feature.produce(new FeatureBuildItem(Feature.WEB_DEPENDENCY_LOCATOR));
}

List<Path> cssFiles = new ArrayList<>();
List<Path> jsFiles = new ArrayList<>();
@BuildStep
public void findRelevantFiles(BuildProducer<GeneratedStaticResourceBuildItem> generatedStaticProducer,
BuildProducer<HotDeploymentWatchedFileBuildItem> 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<Path> 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<Path> cssFiles = new ArrayList<>();
List<Path> jsFiles = new ArrayList<>();

if (Files.exists(app)) {
hotDeploymentWatchedProducer
.produce(new HotDeploymentWatchedFileBuildItem(
config.webRoot + SLASH + config.appRoot + SLASH + STAR + STAR));
try (Stream<Path> 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<Path> 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<Path> 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<Path> cssFiles, List<Path> jsFiles,
boolean filter)
private byte[] processHtml(
Path path, List<Path> cssFiles, List<Path> 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<Path> cssFiles, List<Path> jsFiles) {
Expand Down Expand Up @@ -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 = "*";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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.
* <p>
* 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<PathVisit> 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<ClassPathElement> 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();
}

/**
Expand All @@ -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();
}

/**
Expand Down
Loading