Skip to content

Commit c9f1df1

Browse files
committed
Qute: fix template global class generation in the dev mode
- if a non-application template global class is present we have to reflect this fact when assigning the priority for an application template global resolver; otherwise a conflict may occur
1 parent 625a0e2 commit c9f1df1

File tree

11 files changed

+176
-20
lines changed

11 files changed

+176
-20
lines changed

extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java

+7-6
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,13 @@ public void transform(TransformationContext ctx) {
9595
knownMissingClasses, Thread.currentThread().getContextClassLoader());
9696
}
9797
Set<DotName> generatedClassNames = new HashSet<>();
98-
for (GeneratedBeanBuildItem generatedBeanClass : generatedBeans) {
99-
IndexingUtil.indexClass(generatedBeanClass.getName(), additionalBeanIndexer, applicationIndex, additionalIndex,
100-
knownMissingClasses, Thread.currentThread().getContextClassLoader(), generatedBeanClass.getData());
101-
generatedClassNames.add(DotName.createSimple(generatedBeanClass.getName().replace('/', '.')));
102-
generatedClass.produce(new GeneratedClassBuildItem(true, generatedBeanClass.getName(), generatedBeanClass.getData(),
103-
generatedBeanClass.getSource()));
98+
for (GeneratedBeanBuildItem generatedBean : generatedBeans) {
99+
IndexingUtil.indexClass(generatedBean.getName(), additionalBeanIndexer, applicationIndex, additionalIndex,
100+
knownMissingClasses, Thread.currentThread().getContextClassLoader(), generatedBean.getData());
101+
generatedClassNames.add(DotName.createSimple(generatedBean.getName().replace('/', '.')));
102+
generatedClass.produce(new GeneratedClassBuildItem(generatedBean.isApplicationClass(), generatedBean.getName(),
103+
generatedBean.getData(),
104+
generatedBean.getSource()));
104105
}
105106

106107
PersistentClassIndex index = liveReloadBuildItem.getContextObject(PersistentClassIndex.class);

extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/GeneratedBeanBuildItem.java

+10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99
public final class GeneratedBeanBuildItem extends MultiBuildItem {
1010

11+
private final boolean applicationClass;
1112
private final String name;
1213
private final byte[] data;
1314
private final String source;
@@ -17,9 +18,14 @@ public GeneratedBeanBuildItem(String name, byte[] data) {
1718
}
1819

1920
public GeneratedBeanBuildItem(String name, byte[] data, String source) {
21+
this(name, data, source, true);
22+
}
23+
24+
public GeneratedBeanBuildItem(String name, byte[] data, String source, boolean applicationClass) {
2025
this.name = name;
2126
this.data = data;
2227
this.source = source;
28+
this.applicationClass = applicationClass;
2329
}
2430

2531
public String getName() {
@@ -38,4 +44,8 @@ public String getSource() {
3844
return source;
3945
}
4046

47+
public boolean isApplicationClass() {
48+
return applicationClass;
49+
}
50+
4151
}

extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/GeneratedBeanGizmoAdaptor.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.io.Writer;
55
import java.util.Map;
66
import java.util.concurrent.ConcurrentHashMap;
7+
import java.util.function.Predicate;
78

89
import io.quarkus.bootstrap.BootstrapDebug;
910
import io.quarkus.deployment.annotations.BuildProducer;
@@ -13,10 +14,23 @@ public class GeneratedBeanGizmoAdaptor implements ClassOutput {
1314

1415
private final BuildProducer<GeneratedBeanBuildItem> classOutput;
1516
private final Map<String, StringWriter> sources;
17+
private final Predicate<String> applicationClassPredicate;
1618

1719
public GeneratedBeanGizmoAdaptor(BuildProducer<GeneratedBeanBuildItem> classOutput) {
20+
this(classOutput, new Predicate<String>() {
21+
22+
@Override
23+
public boolean test(String t) {
24+
return true;
25+
}
26+
});
27+
}
28+
29+
public GeneratedBeanGizmoAdaptor(BuildProducer<GeneratedBeanBuildItem> classOutput,
30+
Predicate<String> applicationClassPredicate) {
1831
this.classOutput = classOutput;
1932
this.sources = BootstrapDebug.debugSourcesDir() != null ? new ConcurrentHashMap<>() : null;
33+
this.applicationClassPredicate = applicationClassPredicate;
2034
}
2135

2236
@Override
@@ -28,7 +42,7 @@ public void write(String className, byte[] bytes) {
2842
source = sw.toString();
2943
}
3044
}
31-
classOutput.produce(new GeneratedBeanBuildItem(className, bytes, source));
45+
classOutput.produce(new GeneratedBeanBuildItem(className, bytes, source, applicationClassPredicate.test(className)));
3246
}
3347

3448
@Override

extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteDevModeProcessor.java

+40
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
package io.quarkus.qute.deployment;
22

3+
import java.lang.reflect.Modifier;
34
import java.util.HashMap;
45
import java.util.List;
56
import java.util.Map;
7+
import java.util.function.Predicate;
68

9+
import org.jboss.jandex.DotName;
10+
11+
import io.quarkus.arc.deployment.GeneratedBeanBuildItem;
12+
import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor;
713
import io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem;
814
import io.quarkus.deployment.IsDevelopment;
915
import io.quarkus.deployment.annotations.BuildProducer;
1016
import io.quarkus.deployment.annotations.BuildStep;
1117
import io.quarkus.deployment.annotations.BuildSteps;
18+
import io.quarkus.deployment.builditem.ApplicationIndexBuildItem;
1219
import io.quarkus.dev.console.DevConsoleManager;
20+
import io.quarkus.gizmo.ClassCreator;
21+
import io.quarkus.gizmo.FieldCreator;
22+
import io.quarkus.gizmo.MethodCreator;
23+
import io.quarkus.qute.TemplateGlobal;
1324
import io.quarkus.qute.runtime.devmode.QuteErrorPageSetup;
1425

1526
@BuildSteps(onlyIf = IsDevelopment.class)
@@ -28,4 +39,33 @@ void collectGeneratedContents(List<TemplatePathBuildItem> templatePaths,
2839
DevConsoleManager.setGlobal(QuteErrorPageSetup.GENERATED_CONTENTS, contents);
2940
}
3041

42+
// This build step is only used to for a QuarkusDevModeTest that contains the QuteDummyTemplateGlobalMarker interface
43+
@BuildStep
44+
void generateTestTemplateGlobal(ApplicationIndexBuildItem applicationIndex,
45+
BuildProducer<GeneratedBeanBuildItem> generatedBeanClasses) {
46+
if (applicationIndex.getIndex().getClassByName(
47+
DotName.createSimple("io.quarkus.qute.deployment.devmode.QuteDummyTemplateGlobalMarker")) != null) {
48+
// If the marker interface is present then we generate a dummy class annotated with @TemplateGlobal
49+
GeneratedBeanGizmoAdaptor gizmoAdaptor = new GeneratedBeanGizmoAdaptor(generatedBeanClasses,
50+
new Predicate<String>() {
51+
@Override
52+
public boolean test(String t) {
53+
return false;
54+
}
55+
});
56+
try (ClassCreator classCreator = ClassCreator.builder().className("io.quarkus.qute.test.QuteDummyGlobals")
57+
.classOutput(gizmoAdaptor).build()) {
58+
classCreator.addAnnotation(TemplateGlobal.class);
59+
60+
FieldCreator quteDummyFoo = classCreator.getFieldCreator("quteDummyFoo", String.class);
61+
quteDummyFoo.setModifiers(Modifier.STATIC);
62+
63+
MethodCreator staticInitializer = classCreator.getMethodCreator("<clinit>", void.class);
64+
staticInitializer.setModifiers(Modifier.STATIC);
65+
staticInitializer.writeStaticField(quteDummyFoo.getFieldDescriptor(), staticInitializer.load("bar"));
66+
staticInitializer.returnVoid();
67+
}
68+
}
69+
}
70+
3171
}

extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java

+30-5
Original file line numberDiff line numberDiff line change
@@ -2094,7 +2094,13 @@ public Function<FieldInfo, String> apply(ClassInfo clazz) {
20942094
}
20952095

20962096
if (!templateGlobals.isEmpty()) {
2097-
TemplateGlobalGenerator globalGenerator = new TemplateGlobalGenerator(classOutput, GLOBAL_NAMESPACE, -1000, index);
2097+
Set<String> generatedGlobals = new HashSet<>();
2098+
// The initial priority is increased during live reload so that priorities of non-application globals
2099+
// do not conflict with priorities of application globals
2100+
int initialPriority = -1000 + existingValueResolvers.globals.size();
2101+
2102+
TemplateGlobalGenerator globalGenerator = new TemplateGlobalGenerator(classOutput, GLOBAL_NAMESPACE,
2103+
initialPriority, index);
20982104

20992105
Map<DotName, Map<String, AnnotationTarget>> classToTargets = new HashMap<>();
21002106
Map<DotName, List<TemplateGlobalBuildItem>> classToGlobals = templateGlobals.stream()
@@ -2105,12 +2111,19 @@ public Function<FieldInfo, String> apply(ClassInfo clazz) {
21052111
}
21062112

21072113
for (Entry<DotName, Map<String, AnnotationTarget>> e : classToTargets.entrySet()) {
2108-
globalGenerator.generate(index.getClassByName(e.getKey()), e.getValue());
2114+
String generatedClass = existingValueResolvers.getGeneratedGlobalClass(e.getKey());
2115+
if (generatedClass != null) {
2116+
generatedGlobals.add(generatedClass);
2117+
} else {
2118+
generatedClass = globalGenerator.generate(index.getClassByName(e.getKey()), e.getValue());
2119+
existingValueResolvers.addGlobal(e.getKey(), generatedClass, applicationClassPredicate);
2120+
}
21092121
}
2122+
generatedGlobals.addAll(globalGenerator.getGeneratedTypes());
21102123

2111-
for (String generatedType : globalGenerator.getGeneratedTypes()) {
2112-
globalProviders.produce(new TemplateGlobalProviderBuildItem(generatedType));
2113-
reflectiveClass.produce(ReflectiveClassBuildItem.builder(generatedType).build());
2124+
for (String globalType : generatedGlobals) {
2125+
globalProviders.produce(new TemplateGlobalProviderBuildItem(globalType));
2126+
reflectiveClass.produce(ReflectiveClassBuildItem.builder(globalType).build());
21142127
}
21152128
}
21162129
}
@@ -2122,12 +2135,18 @@ public Function<FieldInfo, String> apply(ClassInfo clazz) {
21222135
static class ExistingValueResolvers {
21232136

21242137
final Map<String, String> identifiersToGeneratedClass = new HashMap<>();
2138+
// class declaring globals -> generated type
2139+
final Map<String, String> globals = new HashMap<>();
21252140

21262141
boolean contains(MethodInfo extensionMethod) {
21272142
return identifiersToGeneratedClass
21282143
.containsKey(toKey(extensionMethod));
21292144
}
21302145

2146+
String getGeneratedGlobalClass(DotName declaringClassName) {
2147+
return globals.get(declaringClassName.toString());
2148+
}
2149+
21312150
String getGeneratedClass(MethodInfo extensionMethod) {
21322151
return identifiersToGeneratedClass.get(toKey(extensionMethod));
21332152
}
@@ -2138,6 +2157,12 @@ void add(MethodInfo extensionMethod, String className, Predicate<DotName> applic
21382157
}
21392158
}
21402159

2160+
void addGlobal(DotName declaringClassName, String generatedClassName, Predicate<DotName> applicationClassPredicate) {
2161+
if (!applicationClassPredicate.test(declaringClassName)) {
2162+
globals.put(declaringClassName.toString(), generatedClassName);
2163+
}
2164+
}
2165+
21412166
private String toKey(MethodInfo extensionMethod) {
21422167
return extensionMethod.declaringClass().toString() + "#" + extensionMethod.toString();
21432168
}

extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/ExistingValueResolversDevModeTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class ExistingValueResolversDevModeTest {
2020
.addClass(TestRoute.class)
2121
.addAsResource(new StringAsset(
2222
"{#let a = 3}{#let b = a.minus(2)}b={b}{/}{/}"),
23-
"templates/let.html"));
23+
"templates/test.html"));
2424

2525
@Test
2626
public void testExistingValueResolvers() {
@@ -29,7 +29,7 @@ public void testExistingValueResolvers() {
2929
.statusCode(200)
3030
.body(Matchers.equalTo("b=1"));
3131

32-
config.modifyResourceFile("templates/let.html", t -> t.concat("::MODIFIED"));
32+
config.modifyResourceFile("templates/test.html", t -> t.concat("::MODIFIED"));
3333

3434
given().get("test")
3535
.then()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.quarkus.qute.deployment.devmode;
2+
3+
/**
4+
* Marker interface for {@link TemplateGlobalDevModeTest}.
5+
*/
6+
public interface QuteDummyTemplateGlobalMarker {
7+
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.quarkus.qute.deployment.devmode;
2+
3+
import static io.restassured.RestAssured.given;
4+
5+
import org.hamcrest.Matchers;
6+
import org.jboss.shrinkwrap.api.asset.StringAsset;
7+
import org.junit.jupiter.api.Test;
8+
import org.junit.jupiter.api.extension.RegisterExtension;
9+
10+
import io.quarkus.test.QuarkusDevModeTest;
11+
12+
/**
13+
* Test that template globals added in the dev mode are generated correctly after live reload.
14+
* <p>
15+
* The {@link QuteDummyTemplateGlobalMarker} is used to identify an application archive where a dummy built-in class with
16+
* template globals is added.
17+
*/
18+
public class TemplateGlobalDevModeTest {
19+
20+
@RegisterExtension
21+
static final QuarkusDevModeTest config = new QuarkusDevModeTest()
22+
.withApplicationRoot(root -> root
23+
.addClasses(TestRoute.class, QuteDummyTemplateGlobalMarker.class)
24+
.addAsResource(new StringAsset(
25+
"{quteDummyFoo}:{testFoo ?: 'NA'}"),
26+
"templates/test.html"));
27+
28+
@Test
29+
public void testTemplateGlobals() {
30+
given().get("test")
31+
.then()
32+
.statusCode(200)
33+
.body(Matchers.equalTo("bar:NA"));
34+
35+
// Add application globals - the priority sequence should be automatically
36+
// increased before it's used for TestGlobals
37+
config.addSourceFile(TestGlobals.class);
38+
39+
given().get("test")
40+
.then()
41+
.statusCode(200)
42+
.body(Matchers.equalTo("bar:baz"));
43+
}
44+
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.quarkus.qute.deployment.devmode;
2+
3+
import io.quarkus.qute.TemplateGlobal;
4+
5+
@TemplateGlobal
6+
public class TestGlobals {
7+
8+
public static String testFoo() {
9+
return "baz";
10+
}
11+
}

extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/devmode/TestRoute.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
public class TestRoute {
1010

1111
@Inject
12-
Template let;
12+
Template test;
1313

1414
@Route(path = "test")
1515
public void test(RoutingContext ctx) {
16-
ctx.end(let.render());
16+
ctx.end(test.render());
1717
}
1818

1919
}

independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/TemplateGlobalGenerator.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ public class TemplateGlobalGenerator extends AbstractGenerator {
4848
private final String namespace;
4949
private int priority;
5050

51-
public TemplateGlobalGenerator(ClassOutput classOutput, String namespace, int priority, IndexView index) {
51+
public TemplateGlobalGenerator(ClassOutput classOutput, String namespace, int initialPriority, IndexView index) {
5252
super(index, classOutput);
5353
this.namespace = namespace;
54-
this.priority = priority;
54+
this.priority = initialPriority;
5555
}
5656

57-
public void generate(ClassInfo declaringClass, Map<String, AnnotationTarget> targets) {
57+
public String generate(ClassInfo declaringClass, Map<String, AnnotationTarget> targets) {
5858

5959
String baseName;
6060
if (declaringClass.enclosingClass() != null) {
@@ -65,7 +65,8 @@ public void generate(ClassInfo declaringClass, Map<String, AnnotationTarget> tar
6565
}
6666
String targetPackage = packageName(declaringClass.name());
6767
String generatedName = generatedNameFromTarget(targetPackage, baseName, SUFFIX);
68-
generatedTypes.add(generatedName.replace('/', '.'));
68+
String generatedClassName = generatedName.replace('/', '.');
69+
generatedTypes.add(generatedClassName);
6970

7071
ClassCreator provider = ClassCreator.builder().classOutput(classOutput).className(generatedName)
7172
.interfaces(TemplateGlobalProvider.class).build();
@@ -141,6 +142,7 @@ public void accept(BytecodeCreator bc) {
141142
resolve.returnValue(resolve.invokeStaticMethod(Descriptors.RESULTS_NOT_FOUND_EC, evalContext));
142143

143144
provider.close();
145+
return generatedClassName;
144146
}
145147

146148
public Set<String> getGeneratedTypes() {

0 commit comments

Comments
 (0)