Skip to content

Commit

Permalink
Add ability to return a template for the current action, and define t…
Browse files Browse the repository at this point in the history
…ype-safe template constructors
  • Loading branch information
FroMage committed Mar 10, 2020
1 parent decb48d commit 02febd8
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.qute.deployment;

import java.util.Map;

import io.quarkus.builder.item.MultiBuildItem;

public final class CheckedTemplateBuildItem extends MultiBuildItem {

public final String templateId;
public final Map<String, String> bindings;

public CheckedTemplateBuildItem(String templateId, Map<String, String> bindings) {
this.templateId = templateId;
this.bindings = bindings;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package io.quarkus.qute.deployment;

import java.lang.reflect.Modifier;
import java.util.List;
import java.util.function.BiFunction;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import io.quarkus.gizmo.Gizmo;

public class CheckedTemplateEnhancer implements BiFunction<String, ClassVisitor, ClassVisitor> {

private String templateId;
private List<String> parameterNames;

public CheckedTemplateEnhancer(String templateId, List<String> parameterNames) {
this.templateId = templateId;
this.parameterNames = parameterNames;
}

@Override
public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) {
return new DynamicTemplateClassVisitor(className, templateId, parameterNames, outputClassVisitor);
}

public static class DynamicTemplateClassVisitor extends ClassVisitor {

private String className;
private String templateId;
private List<String> parameterNames;

public DynamicTemplateClassVisitor(String className, String templateId, List<String> parameterNames,
ClassVisitor outputClassVisitor) {
super(Gizmo.ASM_API_VERSION, outputClassVisitor);
this.className = className;
this.templateId = templateId;
this.parameterNames = parameterNames;
}

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor ret = super.visitMethod(access, name, descriptor, signature, exceptions);
if (name.equals("<init>")) {
return new ConstructorVisitor(className, parameterNames, ret);
}
return ret;
}

public static class ConstructorVisitor extends MethodVisitor {

private String className;
private List<String> parameterNames;

public ConstructorVisitor(String className, List<String> parameterNames, MethodVisitor outputVisitor) {
super(Gizmo.ASM_API_VERSION, outputVisitor);
this.className = className;
this.parameterNames = parameterNames;
}

@Override
public void visitInsn(int opcode) {
// inject our code right before the end of the constructor
if (opcode == Opcodes.RETURN) {
// FIXME: can we have more than one return in a constructor?
// add some code
visitVarInsn(Opcodes.ALOAD, 0); // this
visitTypeInsn(Opcodes.NEW, "java/util/HashMap");
super.visitInsn(Opcodes.DUP);
visitMethodInsn(Opcodes.INVOKESPECIAL, "java/util/HashMap", "<init>", "()V", false);
visitFieldInsn(Opcodes.PUTFIELD, className.replace('.', '/'), "__bindings", "Ljava/util/Map;"); // this.bindings

for (int i = 0; i < parameterNames.size(); i++) {
visitVarInsn(Opcodes.ALOAD, 0); // this
visitFieldInsn(Opcodes.GETFIELD, className.replace('.', '/'), "__bindings", "Ljava/util/Map;"); // this.bindings
visitLdcInsn(parameterNames.get(i)); // first arg name
visitVarInsn(Opcodes.ALOAD, i + 1); // i-th arg value (1-based)
// this.bindings.put("name", name)
visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Map", "put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
super.visitInsn(Opcodes.POP); // ignore return value
}
super.visitInsn(Opcodes.RETURN);
}
super.visitInsn(opcode);
}
}

@Override
public void visitEnd() {
// Add the bindings field
FieldVisitor field = super.visitField(Modifier.PRIVATE | Modifier.FINAL | Opcodes.ACC_SYNTHETIC,
"__bindings", "Ljava/util/Map;", "Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;", null);
field.visitEnd();

// Add the getBindings method
MethodVisitor method = super.visitMethod(Modifier.PROTECTED | Opcodes.ACC_SYNTHETIC,
"getBindings", "()Ljava/util/Map;", "()Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;", null);
method.visitCode();
method.visitVarInsn(Opcodes.ALOAD, 0); // this
method.visitFieldInsn(Opcodes.GETFIELD, className.replace('.', '/'), "__bindings", "Ljava/util/Map;");
method.visitInsn(Opcodes.ARETURN);
method.visitMaxs(0, 0);
method.visitEnd();

// Add the getTemplateId method
method = super.visitMethod(Modifier.PROTECTED | Opcodes.ACC_SYNTHETIC,
"getTemplateId", "()Ljava/lang/String;", null, null);
method.visitCode();
method.visitLdcInsn(templateId);
method.visitInsn(Opcodes.ARETURN);
method.visitMaxs(0, 0);
method.visitEnd();

super.visitEnd();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem;
import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
Expand All @@ -73,6 +74,8 @@
import io.quarkus.qute.Engine;
import io.quarkus.qute.Expression;
import io.quarkus.qute.LoopSectionHelper;
import io.quarkus.qute.ParserHelper;
import io.quarkus.qute.ParserHook;
import io.quarkus.qute.PublisherFactory;
import io.quarkus.qute.ResultNode;
import io.quarkus.qute.SectionHelper;
Expand All @@ -82,6 +85,7 @@
import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.TemplateLocator;
import io.quarkus.qute.Variant;
import io.quarkus.qute.api.CheckedTemplateInstance;
import io.quarkus.qute.api.ResourcePath;
import io.quarkus.qute.api.VariantTemplate;
import io.quarkus.qute.deployment.TemplatesAnalysisBuildItem.TemplateAnalysis;
Expand Down Expand Up @@ -120,6 +124,8 @@ public class QuteProcessor {
static final DotName MAP_ENTRY = DotName.createSimple(Entry.class.getName());
static final DotName COLLECTION = DotName.createSimple(Collection.class.getName());

static final DotName DOTNAME_CHECKED_TEMPLATE_INSTANCE = DotName.createSimple(CheckedTemplateInstance.class.getName());

private static final String MATCH_NAME = "matchName";

@BuildStep
Expand Down Expand Up @@ -168,7 +174,46 @@ AdditionalBeanBuildItem additionalBeans() {
}

@BuildStep
TemplatesAnalysisBuildItem analyzeTemplates(List<TemplatePathBuildItem> templatePaths) {
List<CheckedTemplateBuildItem> collectTemplateTypeInfo(BeanArchiveIndexBuildItem index,
BuildProducer<BytecodeTransformerBuildItem> transformers) {
List<CheckedTemplateBuildItem> ret = new ArrayList<>();

for (ClassInfo classInfo : index.getIndex().getAllKnownSubclasses(DOTNAME_CHECKED_TEMPLATE_INSTANCE)) {
String methodName = classInfo.simpleName();
ClassInfo enclosingClass = index.getIndex().getClassByName(classInfo.enclosingClass());
String className = enclosingClass.simpleName();
String templatePath = className + "/" + methodName;
MethodInfo constructor = null;
for (MethodInfo method : classInfo.methods()) {
if (!method.name().equals("<init>"))
continue;
if (constructor != null)
throw new IllegalStateException(
"Checked template cannot have more than one constructor: " + classInfo.name());
constructor = method;
}
if (constructor == null)
throw new IllegalStateException("Checked template must have one constructor: " + classInfo.name());

Map<String, String> bindings = new HashMap<>();
List<String> parameterNames = new ArrayList<>(10);
List<Type> parameters = constructor.parameters();
for (int i = 0; i < parameters.size(); i++) {
Type type = parameters.get(i);
String name = constructor.parameterName(i);
bindings.put(name, type.toString());
parameterNames.add(name);
}
ret.add(new CheckedTemplateBuildItem(templatePath, bindings));
transformers.produce(new BytecodeTransformerBuildItem(classInfo.name().toString(),
new CheckedTemplateEnhancer(templatePath, parameterNames)));
}
return ret;
}

@BuildStep
TemplatesAnalysisBuildItem analyzeTemplates(List<TemplatePathBuildItem> templatePaths,
List<CheckedTemplateBuildItem> dynamicTemplates) {
long start = System.currentTimeMillis();
List<TemplateAnalysis> analysis = new ArrayList<>();

Expand Down Expand Up @@ -212,6 +257,20 @@ public Optional<Variant> getVariant() {
;
return Optional.empty();
}
}).addParserHook(new ParserHook() {

@Override
public void beforeParsing(ParserHelper parserHelper, String id) {
for (CheckedTemplateBuildItem dynamicTemplate : dynamicTemplates) {
// FIXME: check for dot/extension?
if (id.startsWith(dynamicTemplate.templateId)) {
for (Entry<String, String> entry : dynamicTemplate.bindings.entrySet()) {
parserHelper.addParameter(entry.getKey(), entry.getValue());
}
}
}
}

}).build();

for (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.quarkus.qute.api;

import java.util.Map;
import java.util.Map.Entry;

import io.quarkus.arc.Arc;
import io.quarkus.qute.Engine;
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;

public class CheckedTemplateInstance {

public TemplateInstance getTemplateInstance() {
Template template = Arc.container().instance(Engine.class).get().getTemplate(getTemplateId());
TemplateInstance instance = template.instance();
for (Entry<String, Object> entry : getBindings().entrySet()) {
instance.data(entry.getKey(), entry.getValue());
}
return instance;
}

protected Map<String, Object> getBindings() {
throw new IllegalStateException("Method should be instrumented");
}

protected String getTemplateId() {
throw new IllegalStateException("Method should be instrumented");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.resteasy.qute.runtime;

import javax.ws.rs.container.ResourceInfo;

import org.jboss.resteasy.core.ResteasyContext;

import io.quarkus.arc.Arc;
import io.quarkus.qute.Engine;
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;

public class ResteasyTemplate {

private static String getActionName() {
ResourceInfo resourceMethod = ResteasyContext.getContextData(ResourceInfo.class);
return resourceMethod.getResourceClass().getSimpleName() + "/" + resourceMethod.getResourceMethod().getName();
}

public static TemplateInstance data(String name, Object value) {
Template template = Arc.container().instance(Engine.class).get().getTemplate(getActionName());
return template.data(name, value);
}

public static TemplateInstance data(Object data) {
Template template = Arc.container().instance(Engine.class).get().getTemplate(getActionName());
return template.data(data);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Variant;
import io.quarkus.qute.api.CheckedTemplateInstance;
import io.quarkus.qute.api.VariantTemplate;

@Provider
Expand All @@ -27,6 +28,9 @@ public class TemplateResponseFilter implements ContainerResponseFilter {
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
Object entity = responseContext.getEntity();
if (entity instanceof CheckedTemplateInstance) {
entity = ((CheckedTemplateInstance) entity).getTemplateInstance();
}
if (entity instanceof TemplateInstance) {
SuspendableContainerResponseContext ctx = (SuspendableContainerResponseContext) responseContext;
ctx.suspend();
Expand Down

0 comments on commit 02febd8

Please sign in to comment.