Skip to content

Commit 86dff36

Browse files
mkoubafranz1981
authored andcommitted
Qute: introduce InjectableFragment
- type-safe fragments should honor the selected variant - fixes quarkiverse/quarkus-renarde#165
1 parent 90e2b5d commit 86dff36

File tree

4 files changed

+157
-10
lines changed

4 files changed

+157
-10
lines changed

extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentTest.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io.quarkus.qute.CheckedTemplate;
1212
import io.quarkus.qute.TemplateGlobal;
1313
import io.quarkus.qute.TemplateInstance;
14+
import io.quarkus.qute.Variant;
1415
import io.quarkus.test.QuarkusUnitTest;
1516

1617
public class CheckedTemplateFragmentTest {
@@ -26,10 +27,21 @@ public class CheckedTemplateFragmentTest {
2627
"{#fragment id=foo}{#for i in bar}{i_count}. <{i}>{#if i_hasNext}, {/if}{/for}{/fragment}"),
2728
"templates/CheckedTemplateFragmentTest/foos.html"));
2829

30+
@SuppressWarnings("unchecked")
2931
@Test
3032
public void testFragment() {
31-
assertEquals("Foo", Templates.items(null).getFragment("item").data("it", new Item("Foo")).render());
32-
assertEquals("Foo", Templates.items$item(new Item("Foo")).render());
33+
TemplateInstance items = Templates.items(null);
34+
List<Variant> variants = (List<Variant>) items.getAttribute(TemplateInstance.VARIANTS);
35+
assertEquals(1, variants.size());
36+
assertEquals("text/html", variants.get(0).getContentType());
37+
assertEquals("Foo", items.getFragment("item").data("it", new Item("Foo")).render());
38+
39+
TemplateInstance fragment = Templates.items$item(new Item("Foo"));
40+
variants = (List<Variant>) fragment.getAttribute(TemplateInstance.VARIANTS);
41+
assertEquals(1, variants.size());
42+
assertEquals("text/html", variants.get(0).getContentType());
43+
assertEquals("Foo", fragment.render());
44+
3345
assertEquals("FooAndBar is a long name", Templates.items$item(new Item("FooAndBar")).render());
3446
assertEquals("1. <1>, 2. <2>, 3. <3>, 4. <4>, 5. <5>", Templates.foos$foo().render());
3547
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package io.quarkus.qute.deployment.typesafe.fragment;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.util.List;
6+
7+
import org.jboss.shrinkwrap.api.asset.StringAsset;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.extension.RegisterExtension;
10+
11+
import io.quarkus.qute.CheckedTemplate;
12+
import io.quarkus.qute.TemplateInstance;
13+
import io.quarkus.qute.Variant;
14+
import io.quarkus.test.QuarkusUnitTest;
15+
16+
public class CheckedTemplateFragmentVariantTest {
17+
18+
@RegisterExtension
19+
static final QuarkusUnitTest config = new QuarkusUnitTest()
20+
.withApplicationRoot(root -> root
21+
.addClasses(Templates.class, Item.class)
22+
.addAsResource(new StringAsset(
23+
"{#each items}{#fragment id='item'}<p>{it.name}</p>{/fragment}{/each}"),
24+
"templates/CheckedTemplateFragmentVariantTest/items.html")
25+
.addAsResource(new StringAsset(
26+
"{#each items}{#fragment id='item'}{it.name}{/fragment}{/each}"),
27+
"templates/CheckedTemplateFragmentVariantTest/items.txt"));
28+
29+
@SuppressWarnings("unchecked")
30+
@Test
31+
public void testFragment() {
32+
TemplateInstance fragment = Templates.items$item(new Item("Foo"));
33+
List<Variant> variants = (List<Variant>) fragment.getAttribute(TemplateInstance.VARIANTS);
34+
assertEquals(2, variants.size());
35+
36+
assertEquals("<p>Foo</p>",
37+
fragment.setAttribute(TemplateInstance.SELECTED_VARIANT, Variant.forContentType("text/html")).render());
38+
assertEquals("Foo",
39+
fragment.setAttribute(TemplateInstance.SELECTED_VARIANT, Variant.forContentType("text/plain")).render());
40+
// A variant for application/json does not exist, use the default - html wins
41+
assertEquals("<p>Foo</p>",
42+
fragment.setAttribute(TemplateInstance.SELECTED_VARIANT, Variant.forContentType("application/json")).render());
43+
}
44+
45+
@CheckedTemplate
46+
public static class Templates {
47+
48+
static native TemplateInstance items(List<Item> items);
49+
50+
static native TemplateInstance items$item(Item it);
51+
52+
}
53+
54+
}

extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/TemplateProducer.java

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,6 @@ Template getTemplate(InjectionPoint injectionPoint) {
8787
if (path == null || path.isEmpty()) {
8888
throw new IllegalStateException("No template location specified");
8989
}
90-
// We inject a delegating template in order to:
91-
// 1. Be able to select an appropriate variant if needed
92-
// 2. Be able to reload the template when needed, i.e. when the cache is cleared
9390
return new InjectableTemplate(path, templateVariants, engine);
9491
}
9592

@@ -100,11 +97,18 @@ public Template getInjectableTemplate(String path) {
10097
return new InjectableTemplate(path, templateVariants, engine);
10198
}
10299

100+
/**
101+
* We inject a delegating template in order to:
102+
*
103+
* 1. Be able to select an appropriate variant if needed
104+
* 2. Be able to reload the template when needed, i.e. when the cache is cleared
105+
*/
103106
static class InjectableTemplate implements Template {
104107

105108
private final String path;
106109
private final TemplateVariants variants;
107110
private final Engine engine;
111+
// Some methods may only work if a single template variant is found
108112
private final LazyValue<Template> unambiguousTemplate;
109113

110114
public InjectableTemplate(String path, Map<String, TemplateVariants> templateVariants, Engine engine) {
@@ -179,10 +183,7 @@ public String getId() {
179183

180184
@Override
181185
public Fragment getFragment(String identifier) {
182-
if (unambiguousTemplate != null) {
183-
return unambiguousTemplate.get().getFragment(identifier);
184-
}
185-
throw ambiguousTemplates("getFragment()");
186+
return new InjectableFragment(identifier);
186187
}
187188

188189
@Override
@@ -202,6 +203,66 @@ public String toString() {
202203
return "Injectable template [path=" + path + "]";
203204
}
204205

206+
class InjectableFragment implements Fragment {
207+
208+
private final String identifier;
209+
210+
InjectableFragment(String identifier) {
211+
this.identifier = identifier;
212+
}
213+
214+
@Override
215+
public List<Expression> getExpressions() {
216+
return InjectableTemplate.this.getExpressions();
217+
}
218+
219+
@Override
220+
public Expression findExpression(Predicate<Expression> predicate) {
221+
return InjectableTemplate.this.findExpression(predicate);
222+
}
223+
224+
@Override
225+
public String getGeneratedId() {
226+
return InjectableTemplate.this.getGeneratedId();
227+
}
228+
229+
@Override
230+
public Optional<Variant> getVariant() {
231+
return InjectableTemplate.this.getVariant();
232+
}
233+
234+
@Override
235+
public List<ParameterDeclaration> getParameterDeclarations() {
236+
return InjectableTemplate.this.getParameterDeclarations();
237+
}
238+
239+
@Override
240+
public String getId() {
241+
return identifier;
242+
}
243+
244+
@Override
245+
public Template getOriginalTemplate() {
246+
return InjectableTemplate.this;
247+
}
248+
249+
@Override
250+
public Fragment getFragment(String id) {
251+
return InjectableTemplate.this.getFragment(id);
252+
}
253+
254+
@Override
255+
public Set<String> getFragmentIds() {
256+
return InjectableTemplate.this.getFragmentIds();
257+
}
258+
259+
@Override
260+
public TemplateInstance instance() {
261+
return new InjectableFragmentTemplateInstanceImpl(identifier);
262+
}
263+
264+
}
265+
205266
class InjectableTemplateInstanceImpl extends TemplateInstanceBase {
206267

207268
InjectableTemplateInstanceImpl() {
@@ -261,7 +322,7 @@ private TemplateInstance templateInstance() {
261322
return instance;
262323
}
263324

264-
private Template template() {
325+
protected Template template() {
265326
if (unambiguousTemplate != null) {
266327
return unambiguousTemplate.get();
267328
}
@@ -280,6 +341,22 @@ private Template template() {
280341
}
281342

282343
}
344+
345+
class InjectableFragmentTemplateInstanceImpl extends InjectableTemplateInstanceImpl {
346+
347+
private final String identifier;
348+
349+
private InjectableFragmentTemplateInstanceImpl(String identifier) {
350+
this.identifier = identifier;
351+
}
352+
353+
@Override
354+
protected Template template() {
355+
return super.template().getFragment(identifier);
356+
}
357+
358+
}
359+
283360
}
284361

285362
static class TemplateVariants {

independent-projects/qute/core/src/main/java/io/quarkus/qute/Template.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,14 @@ default String render() {
117117
}
118118

119119
/**
120+
* If invoked upon a fragment instance then delegate to the defining template.
120121
*
121122
* @return an immutable list of expressions used in the template
122123
*/
123124
List<Expression> getExpressions();
124125

125126
/**
127+
* If invoked upon a fragment instance then delegate to the defining template.
126128
*
127129
* @param predicate
128130
* @return the first expression matching the given predicate or {@code null} if no such expression is used in the template
@@ -146,12 +148,14 @@ default String render() {
146148
String getId();
147149

148150
/**
151+
* If invoked upon a fragment instance then delegate to the defining template.
149152
*
150153
* @return the template variant
151154
*/
152155
Optional<Variant> getVariant();
153156

154157
/**
158+
* If invoked upon a fragment instance then delegate to the defining template.
155159
*
156160
* @return an immutable list of all parameter declarations defined in the template
157161
*/

0 commit comments

Comments
 (0)