Skip to content

Commit 157aac4

Browse files
committed
Qute - fix a regression introduced in #32653 (3.0.1)
- value resolvers are not registered for non-application classes after hot reload - fixes #32959
1 parent 2c6c42b commit 157aac4

File tree

4 files changed

+101
-34
lines changed

4 files changed

+101
-34
lines changed

Diff for: extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java

+27-33
Original file line numberDiff line numberDiff line change
@@ -1742,11 +1742,14 @@ public String apply(String name) {
17421742
}
17431743
});
17441744

1745+
// NOTE: We can't use this optimization for classes generated by ValueResolverGenerator because we cannot easily
1746+
// map a target class to a specific set of generated classes
17451747
ExistingValueResolvers existingValueResolvers = liveReloadBuildItem.getContextObject(ExistingValueResolvers.class);
17461748
if (existingValueResolvers == null) {
17471749
existingValueResolvers = new ExistingValueResolvers();
17481750
liveReloadBuildItem.setContextObject(ExistingValueResolvers.class, existingValueResolvers);
17491751
}
1752+
Set<String> generatedValueResolvers = new HashSet<>();
17501753

17511754
ValueResolverGenerator.Builder builder = ValueResolverGenerator.builder()
17521755
.setIndex(index).setClassOutput(classOutput);
@@ -1770,15 +1773,11 @@ public Function<FieldInfo, String> apply(ClassInfo clazz) {
17701773
Set<DotName> controlled = new HashSet<>();
17711774
Map<DotName, AnnotationInstance> uncontrolled = new HashMap<>();
17721775
for (TemplateDataBuildItem data : templateData) {
1773-
processTemplateData(data, controlled, uncontrolled, builder, existingValueResolvers, applicationClassPredicate);
1776+
processTemplateData(data, controlled, uncontrolled, builder);
17741777
}
17751778

17761779
for (ImplicitValueResolverBuildItem implicit : implicitClasses) {
17771780
DotName implicitClassName = implicit.getClazz().name();
1778-
if (existingValueResolvers.contains(implicitClassName)) {
1779-
// A non-application value resolver already generated
1780-
continue;
1781-
}
17821781
if (controlled.contains(implicitClassName)) {
17831782
LOGGER.debugf("Implicit value resolver for %s ignored: class is annotated with @TemplateData",
17841783
implicitClassName);
@@ -1790,24 +1789,23 @@ public Function<FieldInfo, String> apply(ClassInfo clazz) {
17901789
continue;
17911790
}
17921791
builder.addClass(implicit.getClazz(), implicit.getTemplateData());
1793-
existingValueResolvers.add(implicitClassName, applicationClassPredicate);
17941792
}
17951793

17961794
ValueResolverGenerator generator = builder.build();
17971795
generator.generate();
1798-
1799-
Set<String> generatedValueResolvers = new HashSet<>();
18001796
generatedValueResolvers.addAll(generator.getGeneratedTypes());
18011797

18021798
ExtensionMethodGenerator extensionMethodGenerator = new ExtensionMethodGenerator(index, classOutput);
18031799
Map<DotName, Map<String, List<TemplateExtensionMethodBuildItem>>> classToNamespaceExtensions = new HashMap<>();
18041800
Map<String, DotName> namespaceToClass = new HashMap<>();
18051801

18061802
for (TemplateExtensionMethodBuildItem templateExtension : templateExtensionMethods) {
1807-
if (existingValueResolvers.contains(templateExtension.getMethod())) {
1803+
String generatedValueResolverClass = existingValueResolvers.getGeneratedClass(templateExtension.getMethod());
1804+
if (generatedValueResolverClass != null) {
1805+
// A ValueResolver of a non-application class was already generated
1806+
generatedValueResolvers.add(generatedValueResolverClass);
18081807
continue;
18091808
}
1810-
existingValueResolvers.add(templateExtension.getMethod(), applicationClassPredicate);
18111809

18121810
if (templateExtension.hasNamespace()) {
18131811
// Group extension methods declared on the same class by namespace
@@ -1835,8 +1833,10 @@ public Function<FieldInfo, String> apply(ClassInfo clazz) {
18351833
namespaceMethods.add(templateExtension);
18361834
} else {
18371835
// Generate ValueResolver per extension method
1838-
extensionMethodGenerator.generate(templateExtension.getMethod(), templateExtension.getMatchName(),
1836+
String generatedClass = extensionMethodGenerator.generate(templateExtension.getMethod(),
1837+
templateExtension.getMatchName(),
18391838
templateExtension.getMatchNames(), templateExtension.getMatchRegex(), templateExtension.getPriority());
1839+
existingValueResolvers.add(templateExtension.getMethod(), generatedClass, applicationClassPredicate);
18401840
}
18411841
}
18421842

@@ -1853,6 +1853,10 @@ public Function<FieldInfo, String> apply(ClassInfo clazz) {
18531853
try (NamespaceResolverCreator namespaceResolverCreator = extensionMethodGenerator
18541854
.createNamespaceResolver(priorityEntry.getValue().get(0).getMethod().declaringClass(),
18551855
nsEntry.getKey(), priorityEntry.getKey())) {
1856+
for (TemplateExtensionMethodBuildItem extensionMethod : priorityEntry.getValue()) {
1857+
existingValueResolvers.add(extensionMethod.getMethod(), namespaceResolverCreator.getClassName(),
1858+
applicationClassPredicate);
1859+
}
18561860
try (ResolveCreator resolveCreator = namespaceResolverCreator.implementResolve()) {
18571861
for (TemplateExtensionMethodBuildItem method : priorityEntry.getValue()) {
18581862
resolveCreator.addMethod(method.getMethod(), method.getMatchName(), method.getMatchNames(),
@@ -1901,28 +1905,25 @@ public Function<FieldInfo, String> apply(ClassInfo clazz) {
19011905
*/
19021906
static class ExistingValueResolvers {
19031907

1904-
final Set<String> identifiers = new HashSet<>();
1905-
1906-
boolean contains(DotName className) {
1907-
return identifiers.contains(className.toString());
1908-
}
1908+
final Map<String, String> identifiersToGeneratedClass = new HashMap<>();
19091909

19101910
boolean contains(MethodInfo extensionMethod) {
1911-
return identifiers.contains(extensionMethod.declaringClass().toString() + "#" + extensionMethod.toString());
1911+
return identifiersToGeneratedClass
1912+
.containsKey(toKey(extensionMethod));
19121913
}
19131914

1914-
boolean add(DotName className, Predicate<DotName> applicationClassPredicate) {
1915-
if (!applicationClassPredicate.test(className)) {
1916-
return identifiers.add(className.toString());
1917-
}
1918-
return false;
1915+
String getGeneratedClass(MethodInfo extensionMethod) {
1916+
return identifiersToGeneratedClass.get(toKey(extensionMethod));
19191917
}
19201918

1921-
boolean add(MethodInfo extensionMethod, Predicate<DotName> applicationClassPredicate) {
1919+
void add(MethodInfo extensionMethod, String className, Predicate<DotName> applicationClassPredicate) {
19221920
if (!applicationClassPredicate.test(extensionMethod.declaringClass().name())) {
1923-
return identifiers.add(extensionMethod.declaringClass().toString() + "#" + extensionMethod.toString());
1921+
identifiersToGeneratedClass.put(toKey(extensionMethod), className);
19241922
}
1925-
return false;
1923+
}
1924+
1925+
private String toKey(MethodInfo extensionMethod) {
1926+
return extensionMethod.declaringClass().toString() + "#" + extensionMethod.toString();
19261927
}
19271928
}
19281929

@@ -2942,22 +2943,15 @@ private static boolean methodMatches(MethodInfo method, VirtualMethodPart virtua
29422943
}
29432944

29442945
private void processTemplateData(TemplateDataBuildItem templateData,
2945-
Set<DotName> controlled, Map<DotName, AnnotationInstance> uncontrolled, ValueResolverGenerator.Builder builder,
2946-
ExistingValueResolvers existingValueResolvers,
2947-
CompletedApplicationClassPredicateBuildItem applicationClassPredicate) {
2946+
Set<DotName> controlled, Map<DotName, AnnotationInstance> uncontrolled, ValueResolverGenerator.Builder builder) {
29482947
DotName targetClassName = templateData.getTargetClass().name();
2949-
if (existingValueResolvers.contains(targetClassName)) {
2950-
return;
2951-
}
29522948
if (templateData.isTargetAnnotatedType()) {
29532949
controlled.add(targetClassName);
29542950
builder.addClass(templateData.getTargetClass(), templateData.getAnnotationInstance());
2955-
existingValueResolvers.add(targetClassName, applicationClassPredicate);
29562951
} else {
29572952
// At this point we can be sure that multiple unequal @TemplateData do not exist for a specific target
29582953
uncontrolled.computeIfAbsent(targetClassName, name -> {
29592954
builder.addClass(templateData.getTargetClass(), templateData.getAnnotationInstance());
2960-
existingValueResolvers.add(targetClassName, applicationClassPredicate);
29612955
return templateData.getAnnotationInstance();
29622956
});
29632957
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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 built-in extension value resolvers are correctly registered after live reload.
14+
*/
15+
public class ExistingValueResolversDevModeTest {
16+
17+
@RegisterExtension
18+
static final QuarkusDevModeTest config = new QuarkusDevModeTest()
19+
.withApplicationRoot(root -> root
20+
.addClass(TestRoute.class)
21+
.addAsResource(new StringAsset(
22+
"{#let a = 3}{#let b = a.minus(2)}b={b}{/}{/}"),
23+
"templates/let.html"));
24+
25+
@Test
26+
public void testExistingValueResolvers() {
27+
given().get("test")
28+
.then()
29+
.statusCode(200)
30+
.body(Matchers.equalTo("b=1"));
31+
32+
config.modifyResourceFile("templates/let.html", t -> t.concat("::MODIFIED"));
33+
34+
given().get("test")
35+
.then()
36+
.statusCode(200)
37+
.body(Matchers.equalTo("b=1::MODIFIED"));
38+
}
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.quarkus.qute.deployment.devmode;
2+
3+
import jakarta.inject.Inject;
4+
5+
import io.quarkus.qute.Template;
6+
import io.quarkus.vertx.web.Route;
7+
import io.vertx.ext.web.RoutingContext;
8+
9+
public class TestRoute {
10+
11+
@Inject
12+
Template let;
13+
14+
@Route(path = "test")
15+
public void test(RoutingContext ctx) {
16+
ctx.end(let.render());
17+
}
18+
19+
}

Diff for: independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,16 @@ public static void validate(MethodInfo method, String namespace) {
103103
}
104104
}
105105

106-
public void generate(MethodInfo method, String matchName, List<String> matchNames, String matchRegex, Integer priority) {
106+
/**
107+
*
108+
* @param method
109+
* @param matchName
110+
* @param matchNames
111+
* @param matchRegex
112+
* @param priority
113+
* @return the fully qualified name of the generated class
114+
*/
115+
public String generate(MethodInfo method, String matchName, List<String> matchNames, String matchRegex, Integer priority) {
107116

108117
AnnotationInstance extensionAnnotation = method.annotation(TEMPLATE_EXTENSION);
109118
List<Type> parameters = method.parameterTypes();
@@ -199,6 +208,7 @@ public void generate(MethodInfo method, String matchName, List<String> matchName
199208
implementResolve(valueResolver, declaringClass, method, matchName, matchNames, patternField, params);
200209

201210
valueResolver.close();
211+
return generatedName.replace('/', '.');
202212
}
203213

204214
public NamespaceResolverCreator createNamespaceResolver(ClassInfo declaringClass, String namespace, int priority) {
@@ -449,6 +459,10 @@ public NamespaceResolverCreator(ClassInfo declaringClass, String namespace, int
449459
implementGetPriority(namespaceResolver, priority);
450460
}
451461

462+
public String getClassName() {
463+
return namespaceResolver.getClassName().replace('/', '.');
464+
}
465+
452466
public ResolveCreator implementResolve() {
453467
return new ResolveCreator();
454468
}

0 commit comments

Comments
 (0)