diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractSetterInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractSetterInterceptor.java index e89e2c4e28..f6c867b2ef 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractSetterInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractSetterInterceptor.java @@ -59,7 +59,7 @@ private Outcome handleInternal(Exchange exchange, Flow flow) { internal(getRouter().isProduction(), getDisplayName()) .title("Error evaluating expression!") .internal("field", fieldName) - .internal("value", expression) + .internal("expression", expression) .exception(root) .stacktrace(false) .buildAndSetResponse(exchange); @@ -70,10 +70,6 @@ private Outcome handleInternal(Exchange exchange, Flow flow) { return CONTINUE; } - /** - * - * @return - */ protected abstract Class getExpressionReturnType(); protected abstract boolean shouldSetValue(Exchange exchange, Flow flow); diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/lang/SetBodyInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/SetBodyInterceptor.java new file mode 100644 index 0000000000..3fd63a3116 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/lang/SetBodyInterceptor.java @@ -0,0 +1,87 @@ +package com.predic8.membrane.core.interceptor.lang; + +import com.predic8.membrane.annot.*; +import com.predic8.membrane.core.exchange.*; +import com.predic8.membrane.core.interceptor.*; +import com.predic8.membrane.core.util.*; +import org.slf4j.*; + +import static com.predic8.membrane.core.exceptions.ProblemDetails.*; +import static com.predic8.membrane.core.interceptor.Interceptor.Flow.*; +import static com.predic8.membrane.core.interceptor.Outcome.*; +import static com.predic8.membrane.core.interceptor.Outcome.ABORT; +import static java.nio.charset.StandardCharsets.*; + +/** + * setBody sets the content of the HTTP message body to the specified value. The value + * can be a static string, or it can be dynamically generated by an expression. + * Different languages such as SpEL, Groovy, XMLPath or JsonPath are supported. + * setBody does not support conditional processing or loops. When you need these features, + * resort to the template plugin instead. + * + * The content of the message body is set as UTF-8 encoded bytes. Set a corresponding content type header if necessary. + */ +@MCElement(name = "setBody") +public class SetBodyInterceptor extends AbstractExchangeExpressionInterceptor { + + private static final Logger log = LoggerFactory.getLogger(SetBodyInterceptor.class); + + @Override + public Outcome handleRequest(Exchange exc) { + return handleInternal(exc, REQUEST); + } + + @Override + public Outcome handleResponse(Exchange exc) { + return handleInternal(exc, RESPONSE); + } + + private Outcome handleInternal(Exchange exchange, Flow flow) { + try { + // The value is typically set from YAML there we can assume UTF-8 + var result = exchangeExpression.evaluate(exchange, flow, String.class); + if (result == null) { + result = "null"; + } + exchange.getMessage(flow).setBodyContent(result.getBytes(UTF_8)); + return CONTINUE; + } catch (Exception e) { + var root = ExceptionUtil.getRootCause(e); + var message = "While evaluating expression %s: %s".formatted(expression, root.getMessage()); + log.info(message); + + internal(getRouter().isProduction(), getDisplayName()) + .title("Error evaluating expression!") + .internal("expression", expression) + .exception(root) + .stacktrace(false) + .buildAndSetResponse(exchange); + return ABORT; + } + } + + /** + * Sets the expression to be evaluated for modifying the HTTP message body. + * The provided string value can represent a static text or a dynamic expression + * + * @param value expression or static string + */ + @MCAttribute + public void setValue(String value) { + this.expression = value; + } + + public String getValue() { + return expression; + } + + @Override + public String getDisplayName() { + return "setBody"; + } + + @Override + public String getShortDescription() { + return "Sets the content of the HTTP message body to the expression: %s.".formatted(expression); + } +} diff --git a/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java index b96947bc32..108876fa5e 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java @@ -61,7 +61,7 @@ enum Language {GROOVY, SPEL, XPATH, JSONPATH} static ExchangeExpression expression(Interceptor interceptor, Language language, String expression) { return switch (language) { case GROOVY -> new GroovyExchangeExpression(interceptor, expression); - case SPEL -> new SpELExchangeExpression(expression,null); + case SPEL -> new SpELExchangeExpression(expression,null); // parserContext is null on purpose ${} or #{} are not needed here case XPATH -> new XPathExchangeExpression(interceptor,expression); case JSONPATH -> new JsonpathExchangeExpression(expression); }; diff --git a/core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java index 546b4ac591..a53bb24635 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java @@ -13,19 +13,18 @@ limitations under the License. */ package com.predic8.membrane.core.lang; -import com.predic8.membrane.core.*; import com.predic8.membrane.core.exchange.*; import com.predic8.membrane.core.interceptor.*; import com.predic8.membrane.core.interceptor.Interceptor.*; import com.predic8.membrane.core.lang.spel.*; - import org.slf4j.*; import java.util.*; import java.util.regex.*; import static com.predic8.membrane.core.lang.ExchangeExpression.Language.*; -import static com.predic8.membrane.core.lang.ExchangeExpression.expression; +import static com.predic8.membrane.core.lang.ExchangeExpression.*; +import static com.predic8.membrane.core.lang.spel.DollarTemplateParserContext.*; public class TemplateExchangeExpression extends AbstractExchangeExpression { @@ -41,7 +40,7 @@ public class TemplateExchangeExpression extends AbstractExchangeExpression { public static ExchangeExpression newInstance(Interceptor interceptor, Language language, String expression) { // SpEL comes with its own templating if (language == SPEL) { - return new SpELExchangeExpression(expression, new SpELExchangeExpression.DollarBracketTemplateParserContext()); + return new SpELExchangeExpression(expression, DOLLAR_TEMPLATE_PARSER_CONTEXT); } return new TemplateExchangeExpression(interceptor, language, expression); } diff --git a/core/src/main/java/com/predic8/membrane/core/lang/spel/DollarTemplateParserContext.java b/core/src/main/java/com/predic8/membrane/core/lang/spel/DollarTemplateParserContext.java new file mode 100644 index 0000000000..724a0b54f9 --- /dev/null +++ b/core/src/main/java/com/predic8/membrane/core/lang/spel/DollarTemplateParserContext.java @@ -0,0 +1,13 @@ +package com.predic8.membrane.core.lang.spel; + +import org.springframework.expression.common.*; + +public class DollarTemplateParserContext extends TemplateParserContext { + + public static final DollarTemplateParserContext DOLLAR_TEMPLATE_PARSER_CONTEXT = new DollarTemplateParserContext(); + + private DollarTemplateParserContext() { + super("${", "}"); + } + +} diff --git a/core/src/main/java/com/predic8/membrane/core/lang/spel/SpELExchangeExpression.java b/core/src/main/java/com/predic8/membrane/core/lang/spel/SpELExchangeExpression.java index e936389b46..fddc272193 100644 --- a/core/src/main/java/com/predic8/membrane/core/lang/spel/SpELExchangeExpression.java +++ b/core/src/main/java/com/predic8/membrane/core/lang/spel/SpELExchangeExpression.java @@ -151,10 +151,4 @@ static boolean toBoolean(Object o) { private @NotNull SpelParserConfiguration getSpelParserConfiguration() { return new SpelParserConfiguration(MIXED, this.getClass().getClassLoader()); } - - public static class DollarBracketTemplateParserContext extends TemplateParserContext { - public DollarBracketTemplateParserContext() { - super("${","}"); - } - } } diff --git a/core/src/test/java/com/predic8/membrane/core/interceptor/lang/SetBodyInterceptorTest.java b/core/src/test/java/com/predic8/membrane/core/interceptor/lang/SetBodyInterceptorTest.java new file mode 100644 index 0000000000..484cfa09b2 --- /dev/null +++ b/core/src/test/java/com/predic8/membrane/core/interceptor/lang/SetBodyInterceptorTest.java @@ -0,0 +1,53 @@ +package com.predic8.membrane.core.interceptor.lang; + +import com.predic8.membrane.core.*; +import com.predic8.membrane.core.exchange.*; +import com.predic8.membrane.core.http.*; +import org.junit.jupiter.api.*; + +import java.net.*; + +import static com.predic8.membrane.core.http.Response.notImplemented; +import static org.junit.jupiter.api.Assertions.*; + +/** + * A simple test is enough to test the logic of the interceptor. + * Complex expressions are tested in the ExchangeExpressionTest, and ... + */ +class SetBodyInterceptorTest { + + private SetBodyInterceptor sbi; + private Exchange exc; + + @BeforeEach + void setup() throws URISyntaxException { + sbi = new SetBodyInterceptor(); + + exc = Request.get("/foo").buildExchange(); + exc.setResponse(notImplemented().body("bar").build()); + } + + @Test + void nullResult() { + sbi.setValue("null"); + sbi.init(new Router()); + sbi.handleRequest(exc); + assertEquals("null", exc.getRequest().getBodyAsStringDecoded()); + } + + @Test + void evalOfSimpleExpression() { + sbi.setValue("${path}"); + sbi.init(new Router()); + sbi.handleRequest(exc); + assertEquals("/foo", exc.getRequest().getBodyAsStringDecoded()); + } + + @Test + void response() { + sbi.setValue("SC: ${statusCode}"); + sbi.init(new Router()); + sbi.handleResponse(exc); + assertEquals("SC: 501", exc.getResponse().getBodyAsStringDecoded()); + } +} \ No newline at end of file