Skip to content

Instantly share code, notes, and snippets.

@stephan-landers
Created July 8, 2019 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stephan-landers/26bf39eadd3ffe28214f8964ff1cb52b to your computer and use it in GitHub Desktop.
Save stephan-landers/26bf39eadd3ffe28214f8964ff1cb52b to your computer and use it in GitHub Desktop.
/*
* © 2019 Ricston Ltd is protected under international copyright law. The software in this package
* is published under the terms of the Ricston End User Licensing Agreement (EULA), a copy of which
* has been included with this distribution in the LICENSE.md file.
*/
package com.ricston.munit.mock;
import static org.mule.api.transport.PropertyScope.INBOUND;
import java.util.Set;
import java.util.function.Consumer;
import org.aspectj.lang.ProceedingJoinPoint;
import org.mule.api.MuleContext;
import org.mule.api.MuleMessage;
import org.mule.api.context.MuleContextAware;
import org.mule.api.transformer.DataType;
import org.mule.api.transport.PropertyScope;
import org.mule.modules.interceptor.processors.MessageProcessorBehavior;
import org.mule.modules.interceptor.processors.MessageProcessorCall;
import org.mule.modules.interceptor.processors.MessageProcessorManager;
import org.mule.modules.interceptor.processors.MuleMessageTransformer;
/**
* Hijack the {@link MessageProcessorBehavior} handed out by {@link MessageProcessorManager} to
* process messages as templates. Any MEL expressions embedded in the message payload and/or
* properties will be evaluated against the current flow context. Content not containing expressions
* will go unchanged.
*
* @author stephan.landers
*
*/
public class MockInterceptor implements MuleContextAware {
/**
* expression that needs to resolve to true to trigger evaluation. If not, no MEL will be
* evaluated and message payload will never be read as String.
*/
private String triggerExpression = "#[true]";
/** MuleContext to access MEL evaluation. */
private MuleContext muleContext;
@Override
public void setMuleContext(final MuleContext context) {
muleContext = context;
}
public MuleContext getMuleContext() {
return muleContext;
}
/**
* Intercept behaviour lookup and return an infected wrapper.
*
* @param joinPoint method invocation of
* {@link MessageProcessorManager#getBetterMatchingBehavior(MessageProcessorCall)}
* @return behaviour wrapper that will produce modified {@link MuleMessageTransformer}s
* @throws Throwable
*/
public MessageProcessorBehavior aroundGetBetterMatchingBehavior(final ProceedingJoinPoint joinPoint) throws Throwable {
if (joinPoint.getTarget() instanceof MessageProcessorManager == false
|| joinPoint.getArgs()[0] instanceof MessageProcessorCall == false) {
throw new IllegalArgumentException(
"MockInterceptor needs signature org.mule.modules.interceptor.processors.MessageProcessorManager.getBetterMatchingBehavior(MessageProcessorCall)");
}
final MessageProcessorBehavior result = (MessageProcessorBehavior) joinPoint.proceed();
if (result == null) {
return result;
}
return new MessageProcessorBehaviorWrapper(result, muleContext);
}
public String getTriggerExpression() {
return triggerExpression;
}
public void setTriggerExpression(final String triggerExpression) {
this.triggerExpression = triggerExpression;
}
/**
* The MessageProcessorBehavior does not seem to be managed by Spring, so we start one step
* higher. The only thing changed is that we'll return our MessageTransformerWrapper.
*
* @author stephan.landers
*
*/
public class MessageProcessorBehaviorWrapper extends MessageProcessorBehavior {
public MessageProcessorBehaviorWrapper(final MessageProcessorBehavior original, final MuleContext muleContext) {
super(original.getMessageProcessorCall(),
new MessageTransformerWrapper(original.getMuleMessageTransformer(), MockInterceptor.this));
}
}
/**
* Since the initialization of mocked payload is happening outside of any test flow context,
* we'll add the ability to include MEL expressions in test data payload. These will be
* evaluated during the test flow, allowing us to return data fitting to repeated calls.
*
* At this stage, it is not possible to change metadata or throw an exception.
*
* @author stephan.landers
*
*/
public static class MessageTransformerWrapper implements MuleMessageTransformer {
/** Start of a MEL expression. */
private static final String MEL_START = "#[";
/** Keep the original transformer to delegate to. */
private MuleMessageTransformer originalTransformer;
/** Parent MockInterceptor */
private MockInterceptor parent;
/**
* Constructor
*
* @param originalTransformer this will still do its transformation before ours.
*/
public MessageTransformerWrapper(final MuleMessageTransformer originalTransformer, final MockInterceptor parent) {
super();
this.originalTransformer = originalTransformer;
this.parent = parent;
}
/**
* Evaluate the expresssion if it contains at least one MEL expression. Evaluated product
* will be typed if the entire string is one MEL expression. Otherwise, the product will be
* a String.
*
* @param expression the given content, either a MEL expression, or a string containing MEL
* expressions
* @param target the original object to return when there is no MEL expression.
* @param message the mule messgge to work on
* @param setter the setter to call should the result differ from the original
* @return the evaluated content or the target if no MEL expression was there
*/
private Object evaluate(final String expression, final Object target, final MuleMessage message, final Consumer<Object> setter) {
if (expression.startsWith(MEL_START) && expression.endsWith("]") && expression.indexOf(MEL_START, 2) < 0) {
final Object result = parent.getMuleContext().getExpressionManager().evaluate(expression, message, false);
setIfChanged(target, result, setter);
return result;
}
if (expression.contains(MEL_START)) {
final Object result = parent.getMuleContext().getExpressionManager().parse(expression, message, false);
setIfChanged(target, result, setter);
return result;
}
return target;
}
private void setIfChanged(final Object value, final Object replacement, final Consumer<Object> setter) {
if (setter == null) {
return;
}
if (value == null) {
if (replacement == null) {
return;
}
} else if (value.equals(replacement)) {
return;
}
setter.accept(replacement);
}
@Override
public MuleMessage transform(final MuleMessage original) {
/** MuleContext to access MEL evaluation. */
final MuleMessage mockedMessage = this.originalTransformer.transform(original);
if (!Boolean.TRUE.equals(
parent.getMuleContext().getExpressionManager().evaluate(parent.getTriggerExpression(), mockedMessage))) {
return mockedMessage;
}
final DataType<?> dataType = mockedMessage.getDataType();
try {
// check for MEL expressions in Payload and evaluate if there are any
final String payloadAsString = mockedMessage.getPayloadAsString();
evaluate(payloadAsString, original.getPayload(), mockedMessage,
(value) -> mockedMessage.setPayload(value, dataType));
// check for MEL expressions in inbound properties and evaluate if there are any
Set<String> propertyNames = mockedMessage.getPropertyNames(INBOUND);
for (final String oneInboundProperty : propertyNames) {
final Object inboundProperty = original.getInboundProperty(oneInboundProperty, (Object) null);
evaluate(String.valueOf(inboundProperty),
original.getInboundProperty(oneInboundProperty),
mockedMessage,
(value) -> mockedMessage.setProperty(oneInboundProperty, value, PropertyScope.INBOUND));
}
// check for MEL expressions in outbound properties and evaluate if there are any
propertyNames = mockedMessage.getPropertyNames(PropertyScope.OUTBOUND);
for (final String oneOutboundProperty : propertyNames) {
final Object outboundProperty = original.getOutboundProperty(oneOutboundProperty, (Object) null);
evaluate(String.valueOf(outboundProperty),
original.getOutboundProperty(oneOutboundProperty),
mockedMessage,
(value) -> mockedMessage.setProperty(oneOutboundProperty, value, PropertyScope.OUTBOUND));
}
// check for MEL expressions in invocation properties and evaluate if there are any
propertyNames = mockedMessage.getPropertyNames(PropertyScope.INVOCATION);
for (final String oneInvocationProperty : propertyNames) {
final Object invocationProperty = original.getInvocationProperty(oneInvocationProperty, (Object) null);
evaluate(String.valueOf(invocationProperty),
original.getInvocationProperty(oneInvocationProperty),
mockedMessage,
(value) -> mockedMessage.setProperty(oneInvocationProperty, value, PropertyScope.INVOCATION));
}
} catch (final Exception e) {
e.printStackTrace();
}
return mockedMessage;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment