Skip to content

Commit

Permalink
Merge pull request quarkusio#42220 from mkouba/qute-named-synthetic-b…
Browse files Browse the repository at this point in the history
…eans

Qute: support synthetic named CDI beans injected in templates
  • Loading branch information
ia3andy authored Jul 30, 2024
2 parents 261cc87 + 31848b8 commit 545c7d7
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,17 @@

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem;
import io.quarkus.arc.deployment.BeanDiscoveryFinishedBuildItem;
import io.quarkus.arc.deployment.CompletedApplicationClassPredicateBuildItem;
import io.quarkus.arc.deployment.QualifierRegistrarBuildItem;
import io.quarkus.arc.deployment.SynthesisFinishedBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.deployment.ValidationPhaseBuildItem;
import io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem;
import io.quarkus.arc.processor.Annotations;
import io.quarkus.arc.processor.BeanInfo;
import io.quarkus.arc.processor.BuiltinScope;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.arc.processor.InjectionPointInfo;
import io.quarkus.arc.processor.QualifierRegistrar;
Expand Down Expand Up @@ -951,7 +953,7 @@ void validateExpressions(TemplatesAnalysisBuildItem templatesAnalysis,
BuildProducer<IncorrectExpressionBuildItem> incorrectExpressions,
BuildProducer<ImplicitValueResolverBuildItem> implicitClasses,
BuildProducer<TemplateExpressionMatchesBuildItem> expressionMatches,
BeanDiscoveryFinishedBuildItem beanDiscovery,
SynthesisFinishedBuildItem synthesisFinished,
List<CheckedTemplateBuildItem> checkedTemplates,
List<TemplateDataBuildItem> templateData,
QuteConfig config,
Expand All @@ -970,10 +972,7 @@ public String apply(String id) {
return findTemplatePath(templatesAnalysis, id);
}
};
// IMPLEMENTATION NOTE:
// We do not support injection of synthetic beans with names
// Dependency on the ValidationPhaseBuildItem would result in a cycle in the build chain
Map<String, BeanInfo> namedBeans = beanDiscovery.beanStream().withName()
Map<String, BeanInfo> namedBeans = synthesisFinished.beanStream().withName()
.collect(toMap(BeanInfo::getName, Function.identity()));
// Map implicit class -> set of used members
Map<DotName, Set<String>> implicitClassToMembersUsed = new HashMap<>();
Expand Down Expand Up @@ -2447,9 +2446,7 @@ public boolean test(TypeCheck check) {
@BuildStep
@Record(value = STATIC_INIT)
void initialize(BuildProducer<SyntheticBeanBuildItem> syntheticBeans, QuteRecorder recorder,
List<GeneratedValueResolverBuildItem> generatedValueResolvers, List<TemplatePathBuildItem> templatePaths,
Optional<TemplateVariantsBuildItem> templateVariants,
List<TemplateGlobalProviderBuildItem> templateInitializers,
List<TemplatePathBuildItem> templatePaths, Optional<TemplateVariantsBuildItem> templateVariants,
TemplateRootsBuildItem templateRoots) {

List<String> templates = new ArrayList<>();
Expand All @@ -2475,14 +2472,25 @@ void initialize(BuildProducer<SyntheticBeanBuildItem> syntheticBeans, QuteRecord
}

syntheticBeans.produce(SyntheticBeanBuildItem.configure(QuteContext.class)
.supplier(recorder.createContext(generatedValueResolvers.stream()
.map(GeneratedValueResolverBuildItem::getClassName).collect(Collectors.toList()), templates,
tags, variants, templateInitializers.stream()
.map(TemplateGlobalProviderBuildItem::getClassName).collect(Collectors.toList()),
.scope(BuiltinScope.SINGLETON.getInfo())
.supplier(recorder.createContext(templates,
tags, variants,
templateRoots.getPaths().stream().map(p -> p + "/").collect(Collectors.toSet()), templateContents))
.done());
}

@BuildStep
@Record(value = STATIC_INIT)
void initializeGeneratedClasses(BeanContainerBuildItem beanContainer, QuteRecorder recorder,
List<GeneratedValueResolverBuildItem> generatedValueResolvers,
List<TemplateGlobalProviderBuildItem> templateInitializers) {
// The generated classes must be initialized after the template expressions are validated in order to break the cycle in the build chain
recorder.initializeGeneratedClasses(generatedValueResolvers.stream()
.map(GeneratedValueResolverBuildItem::getClassName).collect(Collectors.toList()),
templateInitializers.stream()
.map(TemplateGlobalProviderBuildItem::getClassName).collect(Collectors.toList()));
}

@BuildStep
QualifierRegistrarBuildItem turnLocationIntoQualifier() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.qute.Qute;
import io.quarkus.qute.Template;
import io.quarkus.qute.deployment.Hello;
import io.quarkus.test.QuarkusUnitTest;
import io.smallrye.common.annotation.Identifier;

public class InjectNamespaceResolverTest {

Expand All @@ -28,7 +32,21 @@ public class InjectNamespaceResolverTest {
.addAsResource(
new StringAsset(
"{inject:hello.ping} != {inject:simple.ping} and {cdi:hello.ping} != {cdi:simple.ping}"),
"templates/foo.html"));
"templates/foo.html"))
.addBuildChainCustomizer(bcb -> {
bcb.addBuildStep(new BuildStep() {
@Override
public void execute(BuildContext context) {
context.produce(SyntheticBeanBuildItem.configure(String.class)
.addQualifier().annotation(Identifier.class).addValue("value", "synthetic").done()
.name("synthetic")
.creator(mc -> {
mc.returnValue(mc.load("Yes!"));
})
.done());
}
}).produces(SyntheticBeanBuildItem.class).build();
});

@Inject
Template foo;
Expand All @@ -45,6 +63,9 @@ public void testInjection() {
assertEquals("pong::&lt;br&gt;",
Qute.fmt("{cdi:hello.ping}::{newLine}").contentType("text/html").data("newLine", "<br>").render());
assertEquals(2, SimpleBean.DESTROYS.longValue());

// Test a synthetic named bean injected in a template
assertEquals("YES!", Qute.fmt("{cdi:synthetic.toUpperCase}").render());
}

@Named("simple")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,23 @@
import java.util.Set;
import java.util.function.Supplier;

import io.quarkus.arc.Arc;
import io.quarkus.runtime.annotations.Recorder;

@Recorder
public class QuteRecorder {

public Supplier<Object> createContext(List<String> resolverClasses,
List<String> templatePaths, List<String> tags, Map<String, List<String>> variants,
List<String> templateGlobalProviderClasses, Set<String> templateRoots, Map<String, String> templateContents) {
public Supplier<Object> createContext(List<String> templatePaths, List<String> tags, Map<String, List<String>> variants,
Set<String> templateRoots, Map<String, String> templateContents) {
return new Supplier<Object>() {

@Override
public Object get() {
return new QuteContext() {

volatile List<String> resolverClasses;
volatile List<String> templateGlobalProviderClasses;

@Override
public List<String> getTemplatePaths() {
return templatePaths;
Expand All @@ -31,6 +34,9 @@ public List<String> getTags() {

@Override
public List<String> getResolverClasses() {
if (resolverClasses == null) {
throw generatedClassesNotInitialized();
}
return resolverClasses;
}

Expand All @@ -41,6 +47,9 @@ public Map<String, List<String>> getVariants() {

@Override
public List<String> getTemplateGlobalProviderClasses() {
if (templateGlobalProviderClasses == null) {
throw generatedClassesNotInitialized();
}
return templateGlobalProviderClasses;
}

Expand All @@ -53,11 +62,27 @@ public Set<String> getTemplateRoots() {
public Map<String, String> getTemplateContents() {
return templateContents;
}

@Override
public void setGeneratedClasses(List<String> resolverClasses, List<String> templateGlobalProviderClasses) {
this.resolverClasses = resolverClasses;
this.templateGlobalProviderClasses = templateGlobalProviderClasses;
}

private IllegalStateException generatedClassesNotInitialized() {
return new IllegalStateException("Generated classes not initialized yet!");
}

};
}
};
}

public void initializeGeneratedClasses(List<String> resolverClasses, List<String> templateGlobalProviderClasses) {
QuteContext context = Arc.container().instance(QuteContext.class).get();
context.setGeneratedClasses(resolverClasses, templateGlobalProviderClasses);
}

public interface QuteContext {

List<String> getResolverClasses();
Expand All @@ -74,6 +99,15 @@ public interface QuteContext {

Map<String, String> getTemplateContents();

/**
* The generated classes must be initialized after the template expressions are validated (later during the STATIC_INIT
* bootstrap phase) in order to break the cycle in the build chain.
*
* @param resolverClasses
* @param templateGlobalProviderClasses
*/
void setGeneratedClasses(List<String> resolverClasses, List<String> templateGlobalProviderClasses);

}

}

0 comments on commit 545c7d7

Please sign in to comment.