Skip to content

Commit

Permalink
Merge pull request #13471 from mkouba/issue-12933
Browse files Browse the repository at this point in the history
Qute: implement strategies if a property is not found in an expression
  • Loading branch information
mkouba authored Nov 30, 2020
2 parents dd8c39d + be44922 commit 4f17da3
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1014,7 +1014,7 @@ public boolean test(TypeCheck check) {

@BuildStep
@Record(value = STATIC_INIT)
void initialize(QuteConfig config, BuildProducer<SyntheticBeanBuildItem> syntheticBeans, QuteRecorder recorder,
void initialize(BuildProducer<SyntheticBeanBuildItem> syntheticBeans, QuteRecorder recorder,
List<GeneratedValueResolverBuildItem> generatedValueResolvers, List<TemplatePathBuildItem> templatePaths,
Optional<TemplateVariantsBuildItem> templateVariants) {

Expand All @@ -1037,7 +1037,7 @@ void initialize(QuteConfig config, BuildProducer<SyntheticBeanBuildItem> synthet
}

syntheticBeans.produce(SyntheticBeanBuildItem.configure(QuteContext.class)
.supplier(recorder.createContext(config, generatedValueResolvers.stream()
.supplier(recorder.createContext(generatedValueResolvers.stream()
.map(GeneratedValueResolverBuildItem::getClassName).collect(Collectors.toList()), templates,
tags, variants))
.done());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.quarkus.qute.deployment.propertynotfound;

import static org.junit.jupiter.api.Assertions.assertEquals;

import javax.inject.Inject;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.qute.Template;
import io.quarkus.test.QuarkusUnitTest;

public class PropertyNotFoundNoopTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addAsResource(new StringAsset("foos:{foos}"), "templates/test.html")
.addAsResource(new StringAsset("quarkus.qute.property-not-found-strategy=noop"), "application.properties"));

@Inject
Template test;

@Test
public void testNoop() {
assertEquals("foos:", test.render());
}

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

import static org.junit.jupiter.api.Assertions.assertEquals;

import javax.inject.Inject;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.qute.Template;
import io.quarkus.test.QuarkusUnitTest;

public class PropertyNotFoundOutputOriginalTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addAsResource(new StringAsset("foos:{foos}"), "templates/test.html")
.addAsResource(new StringAsset("quarkus.qute.property-not-found-strategy=output-original"),
"application.properties"));

@Inject
Template test;

@Test
public void testOriginal() {
assertEquals("foos:{foos}", test.render());
}

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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import javax.inject.Inject;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateException;
import io.quarkus.runtime.util.ExceptionUtil;
import io.quarkus.test.QuarkusUnitTest;

public class PropertyNotFoundThrowExceptionTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addAsResource(new StringAsset("foos:{foos}"), "templates/test.html")
.addAsResource(new StringAsset("quarkus.qute.property-not-found-strategy=throw-exception"),
"application.properties"));

@Inject
Template test;

@Test
public void testException() {
try {
test.render();
fail();
} catch (Exception expected) {
Throwable rootCause = ExceptionUtil.getRootCause(expected);
assertEquals(TemplateException.class, rootCause.getClass());
assertTrue(rootCause.getMessage().contains("{foos}"));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ public class EngineProducer {
private final String basePath;
private final String tagPath;

public EngineProducer(QuteContext context, Event<EngineBuilder> builderReady, Event<Engine> engineReady,
ContentTypes contentTypes) {
public EngineProducer(QuteContext context, QuteConfig config, QuteRuntimeConfig runtimeConfig,
Event<EngineBuilder> builderReady, Event<Engine> engineReady, ContentTypes contentTypes) {
this.contentTypes = contentTypes;
this.suffixes = context.getConfig().suffixes;
this.suffixes = config.suffixes;
this.basePath = "templates/";
this.tagPath = basePath + TAGS;
this.tags = context.getTags();
Expand All @@ -76,14 +76,30 @@ public EngineProducer(QuteContext context, Event<EngineBuilder> builderReady, Ev
builder.addValueResolver(ValueResolvers.logicalAndResolver());
builder.addValueResolver(ValueResolvers.logicalOrResolver());

// If needed use a specific result mapper for the selected strategy
switch (runtimeConfig.propertyNotFoundStrategy) {
case THROW_EXCEPTION:
builder.addResultMapper(new PropertyNotFoundThrowException());
break;
case NOOP:
builder.addResultMapper(new PropertyNotFoundNoop());
break;
case OUTPUT_ORIGINAL:
builder.addResultMapper(new PropertyNotFoundOutputOriginal());
break;
default:
// Use the default strategy
break;
}

// Escape some characters for HTML templates
builder.addResultMapper(new HtmlEscaper());

// Fallback reflection resolver
builder.addValueResolver(new ReflectionValueResolver());

// Remove standalone lines if desired
builder.removeStandaloneLines(context.getConfig().removeStandaloneLines);
builder.removeStandaloneLines(runtimeConfig.removeStandaloneLines);

// Allow anyone to customize the builder
builderReady.fire(builder);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkus.qute.runtime;

import io.quarkus.qute.Expression;
import io.quarkus.qute.ResultMapper;
import io.quarkus.qute.Results.Result;
import io.quarkus.qute.TemplateNode.Origin;

class PropertyNotFoundNoop implements ResultMapper {

@Override
public int getPriority() {
return 10;
}

@Override
public boolean appliesTo(Origin origin, Object result) {
return result.equals(Result.NOT_FOUND);
}

@Override
public String map(Object result, Expression expression) {
return "";
}

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

import io.quarkus.qute.Expression;
import io.quarkus.qute.ResultMapper;
import io.quarkus.qute.Results.Result;
import io.quarkus.qute.TemplateNode.Origin;

class PropertyNotFoundOutputOriginal implements ResultMapper {

@Override
public int getPriority() {
return 10;
}

@Override
public boolean appliesTo(Origin origin, Object result) {
return result.equals(Result.NOT_FOUND);
}

@Override
public String map(Object result, Expression expression) {
return "{" + expression.toOriginalString() + "}";
}

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

import io.quarkus.qute.Expression;
import io.quarkus.qute.ResultMapper;
import io.quarkus.qute.Results.Result;
import io.quarkus.qute.TemplateException;
import io.quarkus.qute.TemplateNode.Origin;

class PropertyNotFoundThrowException implements ResultMapper {

@Override
public int getPriority() {
return 10;
}

@Override
public boolean appliesTo(Origin origin, Object result) {
return result.equals(Result.NOT_FOUND);
}

@Override
public String map(Object result, Expression expression) {
throw new TemplateException(expression.getOrigin(),
String.format("Property not found in expression {%s} in template %s on line %s", expression.toOriginalString(),
expression.getOrigin().getTemplateId(), expression.getOrigin().getLine()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,6 @@ public class QuteConfig {
@ConfigItem(defaultValue = "qute.html,qute.txt,html,txt")
public List<String> suffixes;

/**
* Specify whether the parser should remove standalone lines from the output. A standalone line is a line that contains at
* least one section tag, parameter declaration, or comment but no expression and no non-whitespace character.
*/
@ConfigItem(defaultValue = "true")
public boolean removeStandaloneLines;

/**
* The additional map of suffixes to content types. This map is used when working with template variants. By default, the
* {@link java.net.URLConnection#getFileNameMap()} is used to determine the content type of a template file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
@Recorder
public class QuteRecorder {

public Supplier<Object> createContext(QuteConfig config, List<String> resolverClasses,
public Supplier<Object> createContext(List<String> resolverClasses,
List<String> templatePaths, List<String> tags, Map<String, List<String>> variants) {
return new Supplier<Object>() {

Expand All @@ -32,11 +32,6 @@ public List<String> getResolverClasses() {
return resolverClasses;
}

@Override
public QuteConfig getConfig() {
return config;
}

@Override
public Map<String, List<String>> getVariants() {
return variants;
Expand All @@ -48,8 +43,6 @@ public Map<String, List<String>> getVariants() {

public interface QuteContext {

QuteConfig getConfig();

List<String> getResolverClasses();

List<String> getTemplatePaths();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.quarkus.qute.runtime;

import io.quarkus.qute.TemplateException;
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;

@ConfigRoot(name = "qute", phase = ConfigPhase.RUN_TIME)
public class QuteRuntimeConfig {

/**
* The strategy used if a property is not found when evaluating a standalone expression at runtime.
* <p>
* This strategy is not used when evaluating an expression that is used in a section parameter, e.g.
* <code>{#if foo.name}</code>. In such case, it's the responsibility of the section to handle this situation appropriately.
*/
@ConfigItem(defaultValue = "default")
public PropertyNotFoundStrategy propertyNotFoundStrategy;
/**
* Specify whether the parser should remove standalone lines from the output. A standalone line is a line that contains at
* least one section tag, parameter declaration, or comment but no expression and no non-whitespace character.
*/
@ConfigItem(defaultValue = "true")
public boolean removeStandaloneLines;

public enum PropertyNotFoundStrategy {
/**
* Output the {@code NOT_FOUND} constant.
*/
DEFAULT,
/**
* No operation - no output.
*/
NOOP,
/**
* Throw a {@link TemplateException}.
*/
THROW_EXCEPTION,
/**
* Output the original expression string, e.g. <code>{foo.name}</code>.
*/
OUTPUT_ORIGINAL
}

}

0 comments on commit 4f17da3

Please sign in to comment.