Skip to content

Commit fd4a13a

Browse files
authored
setBody (#2462)
* feat: add `setBody` interceptor for modifying HTTP body content - Introduced `SetBodyInterceptor` to set HTTP message body content using static strings or dynamic expressions. - Added `DollarTemplateParserContext` for SpEL-based template parsing. - Refactored `TemplateExchangeExpression` and related classes to use the centralized `DollarTemplateParserContext`. - Improved error handling in `AbstractSetterInterceptor` with clearer logging and internal responses. * chore: clean up redundant Javadoc comments in `SetBodyInterceptor` and `AbstractSetterInterceptor` * chore: update Javadoc and comments in `SetBodyInterceptor` and `ExchangeExpression` - Clarified UTF-8 encoding in `SetBodyInterceptor` Javadoc and added corresponding comment in method logic. - Added context on null `parserContext` in `SpELExchangeExpression` construction. * feat: handle null expression results in `SetBodyInterceptor` and add tests - Updated `SetBodyInterceptor` to handle cases where expression evaluation returns null, setting the body to "null" instead. - Introduced `SetBodyInterceptorTest` to verify null handling, expression evaluation, and response body modifications. * fix: correct `SetBodyInterceptorTest` to validate response handling logic - Updated test method to use `handleResponse` instead of `handleRequest` in `SetBodyInterceptorTest`. - Adjusted assertions to verify response body content instead of request.
1 parent 25d8f24 commit fd4a13a

7 files changed

Lines changed: 158 additions & 16 deletions

File tree

core/src/main/java/com/predic8/membrane/core/interceptor/lang/AbstractSetterInterceptor.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ private Outcome handleInternal(Exchange exchange, Flow flow) {
5959
internal(getRouter().isProduction(), getDisplayName())
6060
.title("Error evaluating expression!")
6161
.internal("field", fieldName)
62-
.internal("value", expression)
62+
.internal("expression", expression)
6363
.exception(root)
6464
.stacktrace(false)
6565
.buildAndSetResponse(exchange);
@@ -70,10 +70,6 @@ private Outcome handleInternal(Exchange exchange, Flow flow) {
7070
return CONTINUE;
7171
}
7272

73-
/**
74-
*
75-
* @return
76-
*/
7773
protected abstract Class<?> getExpressionReturnType();
7874

7975
protected abstract boolean shouldSetValue(Exchange exchange, Flow flow);
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package com.predic8.membrane.core.interceptor.lang;
2+
3+
import com.predic8.membrane.annot.*;
4+
import com.predic8.membrane.core.exchange.*;
5+
import com.predic8.membrane.core.interceptor.*;
6+
import com.predic8.membrane.core.util.*;
7+
import org.slf4j.*;
8+
9+
import static com.predic8.membrane.core.exceptions.ProblemDetails.*;
10+
import static com.predic8.membrane.core.interceptor.Interceptor.Flow.*;
11+
import static com.predic8.membrane.core.interceptor.Outcome.*;
12+
import static com.predic8.membrane.core.interceptor.Outcome.ABORT;
13+
import static java.nio.charset.StandardCharsets.*;
14+
15+
/**
16+
* setBody sets the content of the HTTP message body to the specified value. The value
17+
* can be a static string, or it can be dynamically generated by an expression.
18+
* Different languages such as SpEL, Groovy, XMLPath or JsonPath are supported.
19+
* setBody does not support conditional processing or loops. When you need these features,
20+
* resort to the template plugin instead.
21+
*
22+
* The content of the message body is set as UTF-8 encoded bytes. Set a corresponding content type header if necessary.
23+
*/
24+
@MCElement(name = "setBody")
25+
public class SetBodyInterceptor extends AbstractExchangeExpressionInterceptor {
26+
27+
private static final Logger log = LoggerFactory.getLogger(SetBodyInterceptor.class);
28+
29+
@Override
30+
public Outcome handleRequest(Exchange exc) {
31+
return handleInternal(exc, REQUEST);
32+
}
33+
34+
@Override
35+
public Outcome handleResponse(Exchange exc) {
36+
return handleInternal(exc, RESPONSE);
37+
}
38+
39+
private Outcome handleInternal(Exchange exchange, Flow flow) {
40+
try {
41+
// The value is typically set from YAML there we can assume UTF-8
42+
var result = exchangeExpression.evaluate(exchange, flow, String.class);
43+
if (result == null) {
44+
result = "null";
45+
}
46+
exchange.getMessage(flow).setBodyContent(result.getBytes(UTF_8));
47+
return CONTINUE;
48+
} catch (Exception e) {
49+
var root = ExceptionUtil.getRootCause(e);
50+
var message = "While evaluating expression %s: %s".formatted(expression, root.getMessage());
51+
log.info(message);
52+
53+
internal(getRouter().isProduction(), getDisplayName())
54+
.title("Error evaluating expression!")
55+
.internal("expression", expression)
56+
.exception(root)
57+
.stacktrace(false)
58+
.buildAndSetResponse(exchange);
59+
return ABORT;
60+
}
61+
}
62+
63+
/**
64+
* Sets the expression to be evaluated for modifying the HTTP message body.
65+
* The provided string value can represent a static text or a dynamic expression
66+
*
67+
* @param value expression or static string
68+
*/
69+
@MCAttribute
70+
public void setValue(String value) {
71+
this.expression = value;
72+
}
73+
74+
public String getValue() {
75+
return expression;
76+
}
77+
78+
@Override
79+
public String getDisplayName() {
80+
return "setBody";
81+
}
82+
83+
@Override
84+
public String getShortDescription() {
85+
return "Sets the content of the HTTP message body to the expression: %s.".formatted(expression);
86+
}
87+
}

core/src/main/java/com/predic8/membrane/core/lang/ExchangeExpression.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ enum Language {GROOVY, SPEL, XPATH, JSONPATH}
6161
static ExchangeExpression expression(Interceptor interceptor, Language language, String expression) {
6262
return switch (language) {
6363
case GROOVY -> new GroovyExchangeExpression(interceptor, expression);
64-
case SPEL -> new SpELExchangeExpression(expression,null);
64+
case SPEL -> new SpELExchangeExpression(expression,null); // parserContext is null on purpose ${} or #{} are not needed here
6565
case XPATH -> new XPathExchangeExpression(interceptor,expression);
6666
case JSONPATH -> new JsonpathExchangeExpression(expression);
6767
};

core/src/main/java/com/predic8/membrane/core/lang/TemplateExchangeExpression.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,18 @@
1313
limitations under the License. */
1414
package com.predic8.membrane.core.lang;
1515

16-
import com.predic8.membrane.core.*;
1716
import com.predic8.membrane.core.exchange.*;
1817
import com.predic8.membrane.core.interceptor.*;
1918
import com.predic8.membrane.core.interceptor.Interceptor.*;
2019
import com.predic8.membrane.core.lang.spel.*;
21-
2220
import org.slf4j.*;
2321

2422
import java.util.*;
2523
import java.util.regex.*;
2624

2725
import static com.predic8.membrane.core.lang.ExchangeExpression.Language.*;
28-
import static com.predic8.membrane.core.lang.ExchangeExpression.expression;
26+
import static com.predic8.membrane.core.lang.ExchangeExpression.*;
27+
import static com.predic8.membrane.core.lang.spel.DollarTemplateParserContext.*;
2928

3029
public class TemplateExchangeExpression extends AbstractExchangeExpression {
3130

@@ -41,7 +40,7 @@ public class TemplateExchangeExpression extends AbstractExchangeExpression {
4140
public static ExchangeExpression newInstance(Interceptor interceptor, Language language, String expression) {
4241
// SpEL comes with its own templating
4342
if (language == SPEL) {
44-
return new SpELExchangeExpression(expression, new SpELExchangeExpression.DollarBracketTemplateParserContext());
43+
return new SpELExchangeExpression(expression, DOLLAR_TEMPLATE_PARSER_CONTEXT);
4544
}
4645
return new TemplateExchangeExpression(interceptor, language, expression);
4746
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.predic8.membrane.core.lang.spel;
2+
3+
import org.springframework.expression.common.*;
4+
5+
public class DollarTemplateParserContext extends TemplateParserContext {
6+
7+
public static final DollarTemplateParserContext DOLLAR_TEMPLATE_PARSER_CONTEXT = new DollarTemplateParserContext();
8+
9+
private DollarTemplateParserContext() {
10+
super("${", "}");
11+
}
12+
13+
}

core/src/main/java/com/predic8/membrane/core/lang/spel/SpELExchangeExpression.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,4 @@ static boolean toBoolean(Object o) {
151151
private @NotNull SpelParserConfiguration getSpelParserConfiguration() {
152152
return new SpelParserConfiguration(MIXED, this.getClass().getClassLoader());
153153
}
154-
155-
public static class DollarBracketTemplateParserContext extends TemplateParserContext {
156-
public DollarBracketTemplateParserContext() {
157-
super("${","}");
158-
}
159-
}
160154
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.predic8.membrane.core.interceptor.lang;
2+
3+
import com.predic8.membrane.core.*;
4+
import com.predic8.membrane.core.exchange.*;
5+
import com.predic8.membrane.core.http.*;
6+
import org.junit.jupiter.api.*;
7+
8+
import java.net.*;
9+
10+
import static com.predic8.membrane.core.http.Response.notImplemented;
11+
import static org.junit.jupiter.api.Assertions.*;
12+
13+
/**
14+
* A simple test is enough to test the logic of the interceptor.
15+
* Complex expressions are tested in the ExchangeExpressionTest, and ...
16+
*/
17+
class SetBodyInterceptorTest {
18+
19+
private SetBodyInterceptor sbi;
20+
private Exchange exc;
21+
22+
@BeforeEach
23+
void setup() throws URISyntaxException {
24+
sbi = new SetBodyInterceptor();
25+
26+
exc = Request.get("/foo").buildExchange();
27+
exc.setResponse(notImplemented().body("bar").build());
28+
}
29+
30+
@Test
31+
void nullResult() {
32+
sbi.setValue("null");
33+
sbi.init(new Router());
34+
sbi.handleRequest(exc);
35+
assertEquals("null", exc.getRequest().getBodyAsStringDecoded());
36+
}
37+
38+
@Test
39+
void evalOfSimpleExpression() {
40+
sbi.setValue("${path}");
41+
sbi.init(new Router());
42+
sbi.handleRequest(exc);
43+
assertEquals("/foo", exc.getRequest().getBodyAsStringDecoded());
44+
}
45+
46+
@Test
47+
void response() {
48+
sbi.setValue("SC: ${statusCode}");
49+
sbi.init(new Router());
50+
sbi.handleResponse(exc);
51+
assertEquals("SC: 501", exc.getResponse().getBodyAsStringDecoded());
52+
}
53+
}

0 commit comments

Comments
 (0)