Skip to content

Commit

Permalink
Add link support to QuteWebTemplateBuildItem
Browse files Browse the repository at this point in the history
  • Loading branch information
ia3andy committed Aug 27, 2024
1 parent 05a10fa commit 1590ed1
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkiverse.qute.web.deployment;

import static java.util.function.Predicate.not;

import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -42,7 +44,7 @@ AdditionalBeanBuildItem beans() {

@BuildStep
public void collectTemplatePaths(TemplateFilePathsBuildItem templateFilePaths,
QuteWebBuildTimeConfig config, BuildProducer<QuteWebTemplatePathBuildItem> paths) {
QuteWebBuildTimeConfig config, BuildProducer<QuteWebTemplateBuildItem> paths) {
String publicPathPrefix = "";
String publicDir = config.publicDir();
if (!publicDir.equals("/") && !publicDir.isBlank()) {
Expand All @@ -60,24 +62,28 @@ public void collectTemplatePaths(TemplateFilePathsBuildItem templateFilePaths,
continue;
}
LOG.debugf("Web template found: %s", path);
paths.produce(new QuteWebTemplatePathBuildItem(path));
paths.produce(new QuteWebTemplateBuildItem(path, null));
}
}

@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
@Consume(SyntheticBeansRuntimeInitBuildItem.class)
public RouteBuildItem produceTemplatesRoute(QuteWebRecorder recorder, List<QuteWebTemplatePathBuildItem> templatePaths,
public RouteBuildItem produceTemplatesRoute(QuteWebRecorder recorder, List<QuteWebTemplateBuildItem> templates,
HttpRootPathBuildItem httpRootPath, QuteWebBuildTimeConfig config) {
if (templatePaths.isEmpty()) {
if (templates.isEmpty()) {
// There are no templates to serve
return null;
}
final var templateLinks = templates.stream().filter(QuteWebTemplateBuildItem::hasLink)
.collect(Collectors.toMap(QuteWebTemplateBuildItem::link, QuteWebTemplateBuildItem::templatePath));
final var templatePaths = templates.stream().filter(not(QuteWebTemplateBuildItem::hasLink))
.map(QuteWebTemplateBuildItem::templatePath)
.collect(Collectors.toSet());
return httpRootPath.routeBuilder()
.routeFunction(httpRootPath.relativePath(config.rootPath() + "/*"), recorder.initializeRoute())
.handlerType(config.useBlockingHandler() ? HandlerType.BLOCKING : HandlerType.NORMAL)
.handler(recorder.handler(httpRootPath.relativePath(config.rootPath()),
templatePaths.stream().map(QuteWebTemplatePathBuildItem::getPath).collect(Collectors.toSet())))
.handler(recorder.handler(httpRootPath.relativePath(config.rootPath()), templatePaths, templateLinks))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.quarkiverse.qute.web.deployment;

import static io.quarkiverse.qute.web.runtime.PathUtils.removeExtension;
import static io.quarkiverse.qute.web.runtime.PathUtils.removeLeadingSlash;
import static io.quarkiverse.qute.web.runtime.PathUtils.removeTrailingSlash;

import io.quarkus.builder.item.MultiBuildItem;

public final class QuteWebTemplateBuildItem extends MultiBuildItem {

/**
* templatePath is used also as path if link is null
*/
private final String templatePath;

/**
* The link to use for this template
*/
private final String link;

public QuteWebTemplateBuildItem(String templatePath, String link) {
this.templatePath = removeExtension(templatePath);
this.link = normalizeLink(link);
}

public String templatePath() {
return templatePath;
}

public String link() {
return link;
}

public boolean hasLink() {
return link != null;
}

private static String normalizeLink(String link) {
if (link == null) {
return null;
}
return removeTrailingSlash(removeLeadingSlash(link));
}

}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import io.quarkiverse.qute.web.deployment.QuteWebTemplatePathBuildItem;
import io.quarkiverse.qute.web.deployment.QuteWebTemplateBuildItem;
import io.quarkiverse.qute.web.runtime.PathUtils;
import io.quarkiverse.qute.web.runtime.QuteWebBuildTimeConfig;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
Expand All @@ -13,23 +13,25 @@
import io.quarkus.devui.spi.page.Page;
import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;

public class QuteWebDevUIProcessor {

@BuildStep(onlyIf = IsDevelopment.class)
public void pages(List<QuteWebTemplatePathBuildItem> templatePaths, HttpRootPathBuildItem httpRootPath,
public void pages(List<QuteWebTemplateBuildItem> templatePaths, HttpRootPathBuildItem httpRootPath,
QuteWebBuildTimeConfig config, BuildProducer<CardPageBuildItem> cardPages) {

CardPageBuildItem pageBuildItem = new CardPageBuildItem();

final String publicDir = config.publicDir();
JsonArray paths = new JsonArray();
for (String path : templatePaths.stream().map(QuteWebTemplatePathBuildItem::getPath)
.sorted(Comparator.comparing(p -> p.toLowerCase())).collect(Collectors.toList())) {
paths.add(path);
for (QuteWebTemplateBuildItem item : templatePaths.stream()
.sorted(Comparator.comparing(p -> p.templatePath().toLowerCase())).toList()) {
var link = item.link() == null ? PathUtils.removeLeadingSlash(item.templatePath().replace(publicDir, ""))
: item.link();
link = PathUtils.join(httpRootPath.relativePath(config.rootPath()), link);
paths.add(new JsonObject().put("templateId", item.templatePath()).put("link", link));
}

pageBuildItem.addBuildTimeData("paths", paths);
pageBuildItem.addBuildTimeData("rootPrefix", httpRootPath.relativePath(config.rootPath()) + "/");

pageBuildItem.addPage(Page.webComponentPageBuilder()
.title("Pages")
Expand Down
15 changes: 9 additions & 6 deletions deployment/src/main/resources/dev-ui/qwc-qsp-paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { columnBodyRenderer } from '@vaadin/grid/lit.js';
import '@vaadin/grid';
import '@vaadin/text-field';
import { paths } from 'build-time-data';
import { rootPrefix } from 'build-time-data';


/**
Expand Down Expand Up @@ -33,18 +32,22 @@ export class QwcQspPaths extends LitElement {
render() {
return html`
<vaadin-grid .items="${paths}" class="paths-table" theme="no-border" all-rows-visible>
<vaadin-grid-column auto-width
header="Path"
${columnBodyRenderer(this._renderPath, [])}
<vaadin-grid-column auto-width
path="templateId"
resizable>
</vaadin-grid-column>
<vaadin-grid-column auto-width
header="Link"
${columnBodyRenderer(this._renderLink, [])}
resizable>
</vaadin-grid-column>
</vaadin-grid>
`;
}

_renderPath(path) {
_renderLink(item) {
return html`
<a href="${rootPrefix}${path}" target="_blank" class="path-link">${rootPrefix}${path}</a>
<a href="${item.link}" target="_blank" class="path-link">${item.link}</a>
`;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.quarkiverse.qute.web.test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.containsString;

import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkiverse.qute.web.deployment.QuteWebTemplateBuildItem;
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.builder.BuildStepBuilder;
import io.quarkus.test.QuarkusUnitTest;

public class LinkedTemplateTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest().withApplicationRoot(root -> {
root.addAsResource(new StringAsset(
"Hello {name ?: 'world'}!"),
"templates/pub/hello.txt");
}).addBuildChainCustomizer(buildChainBuilder -> {
final BuildStepBuilder stepBuilder = buildChainBuilder.addBuildStep(new BuildStep() {
@Override
public void execute(BuildContext context) {
context.produce(new QuteWebTemplateBuildItem("pub/hello.txt", "/foo/bar"));
context.produce(new QuteWebTemplateBuildItem("pub/hello.txt", "/"));
}
});
stepBuilder.produces(QuteWebTemplateBuildItem.class).build();
});

@Test
public void testFixedLink() {
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(containsString("Hello world!"));
given()
.when().get("/foo/bar")
.then()
.statusCode(200)
.body(containsString("Hello world!"));
given()
.when().get("/")
.then()
.statusCode(200)
.body(containsString("Hello world!"));

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.quarkiverse.qute.web.runtime;

public final class PathUtils {

public static String toUnixPath(String path) {
return path.replaceAll("\\\\", "/");
}

public static String prefixWithSlash(String path) {
return path.startsWith("/") ? path : "/" + path;
}

public static String surroundWithSlashes(String path) {
return prefixWithSlash(addTrailingSlash(path));
}

public static String addTrailingSlash(String path) {
return path.endsWith("/") ? path : path + "/";
}

public static String join(String path1, String path2) {
return addTrailingSlash(path1) + removeLeadingSlash(path2);
}

public static String removeLeadingSlash(String path) {
return path.startsWith("/") ? path.substring(1) : path;
}

public static String removeTrailingSlash(String path) {
return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
}

public static String removeExtension(String path) {
final int i = path.lastIndexOf(".");
return i > 0 ? path.substring(0, i) : path;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package io.quarkiverse.qute.web.runtime;

import static io.quarkiverse.qute.web.runtime.PathUtils.removeLeadingSlash;
import static io.quarkiverse.qute.web.runtime.PathUtils.removeTrailingSlash;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -40,8 +42,8 @@ public class QuteWebHandler implements Handler<RoutingContext> {
private static final String FRAGMENT_PARAM = "frag";

private final String rootPath;
private final String webTemplatesPath;
private final Set<String> templatePaths;
private final String webTemplatesPath;
private final List<String> compressMediaTypes;
// request path to template path
private final Map<String, String> extractedPaths;
Expand All @@ -52,16 +54,18 @@ public class QuteWebHandler implements Handler<RoutingContext> {
private final ManagedContext requestContext;
private final LazyValue<TemplateProducer> templateProducer;
private final QuteContext quteContext;
private final Map<String, String> templateLinks;

public QuteWebHandler(String rootPath, String publicDir, Set<String> templatePaths,
public QuteWebHandler(String rootPath, String publicDir, Set<String> templatePaths, Map<String, String> templateLinks,
HttpBuildTimeConfig httpBuildTimeConfig) {
this.rootPath = rootPath;
this.templatePaths = templatePaths;
if (publicDir.equals("/") || publicDir.isBlank()) {
this.webTemplatesPath = "";
} else {
this.webTemplatesPath = publicDir.startsWith("/") ? publicDir.substring(1) : publicDir;
}
this.templatePaths = templatePaths;
this.templateLinks = templateLinks;
this.compressMediaTypes = httpBuildTimeConfig.enableCompression
? httpBuildTimeConfig.compressMediaTypes.orElse(List.of())
: null;
Expand Down Expand Up @@ -126,8 +130,7 @@ private void handlePage(RoutingContext rc) {

// Extract the real template path, e.g. /item.html -> web/item
String path = extractedPaths.computeIfAbsent(requestPath, this::extractTemplatePath);

if (path != null && templatePaths.contains(path)) {
if (path != null) {
Template template = templateProducer.get().getInjectableTemplate(path);
TemplateInstance originalInstance = template.instance();
TemplateInstance instance = originalInstance;
Expand Down Expand Up @@ -224,34 +227,44 @@ private Variant trySelectVariant(RoutingContext rc, TemplateInstance instance, L
* <li>{@code /qsp/item?id=1} => {@code web/item}</li>
* <li>{@code /nested/item.html?foo=bar} => {@code web/nested/item}</li>
* </ul>
*
* <p>
* Note that a path that ends with {@code /} is handled specifically. The {@code index} is appended to the path.
*
* @param path
* @return the template path without suffix
*/
private String extractTemplatePath(String path) {
if (path.length() >= rootPath.length()) {
if (path.endsWith("/")) {
path = path + "index";
}
path = path.substring(rootPath.length());
if (path.startsWith("/")) {
// /item.html => item.html
path = path.substring(1);
path = removeLeadingSlash(path);

// Check if we have a matching linked template
final String link = removeTrailingSlash(path);
if (templateLinks.containsKey(link)) {
return templateLinks.get(link);
}

// Check if we have a matching template path
if (path.isEmpty()) {
path = "index";
} else if (path.endsWith("/")) {
path = path + "index";
}

if (!webTemplatesPath.isEmpty()) {
path = webTemplatesPath + "/" + path;
}
if (path.contains(".")) {
for (Entry<String, List<String>> e : quteContext.getVariants().entrySet()) {
for (Map.Entry<String, List<String>> e : quteContext.getVariants().entrySet()) {
if (e.getValue().contains(path)) {
path = e.getKey();
break;
}
}
}
return path;
if (templatePaths.contains(path)) {
return path;
}
}
return null;
}
Expand Down
Loading

0 comments on commit 1590ed1

Please sign in to comment.