Created
July 8, 2019 14:03
-
-
Save stephan-landers/26bf39eadd3ffe28214f8964ff1cb52b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* © 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