/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.functional.functional; import static java.lang.Thread.currentThread; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mule.functional.functional.FlowAssert.addAssertion; import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.setFlowConstructIfNeeded; import static reactor.core.Exceptions.propagate; import static reactor.core.publisher.Flux.from; import org.mule.runtime.api.el.ValidationResult; import org.mule.runtime.api.exception.MuleException; import org.mule.runtime.api.lifecycle.InitialisationException; import org.mule.runtime.api.lifecycle.Startable; import org.mule.runtime.core.api.Event; import org.mule.runtime.core.api.construct.FlowConstruct; import org.mule.runtime.core.api.construct.FlowConstructAware; import org.mule.runtime.core.api.expression.InvalidExpressionException; import org.mule.runtime.core.api.processor.InterceptingMessageProcessor; import org.mule.runtime.core.api.processor.Processor; import org.mule.runtime.core.exception.MessagingException; import com.eaio.uuid.UUID; import java.util.concurrent.CountDownLatch; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; public class ResponseAssertionMessageProcessor extends AssertionMessageProcessor implements InterceptingMessageProcessor, FlowConstructAware, Startable { private static final ThreadLocal<String> taskTokenInThread = new ThreadLocal<>(); protected String responseExpression = "#[mel:true]"; private int responseCount = 1; private boolean responseSameTask = true; private Processor next; private String requestTaskToken; private String responseTaskToken; private CountDownLatch responseLatch; private int responseInvocationCount = 0; private boolean responseResult = true; @Override public void start() throws InitialisationException { super.start(); ValidationResult result = this.expressionManager.validate(responseExpression); if (!result.isSuccess()) { throw new InvalidExpressionException(expression, result.errorMessage().orElse("Invalid expression")); } responseLatch = new CountDownLatch(responseCount); addAssertion(flowConstruct.getName(), this); } @Override public Event process(Event event) throws MuleException { if (event == null) { return null; } return processResponse(processNext(processRequest(event))); } @Override public Publisher<Event> apply(Publisher<Event> publisher) { Flux<Event> flux = from(publisher).map(event -> { try { return processRequest(event); } catch (MuleException e) { throw propagate(new MessagingException(event, e)); } }); flux = from(flux.transform(next)); return flux.map(event -> { try { return processResponse(event); } catch (MuleException e) { throw propagate(new MessagingException(event, e)); } }); } private Event processRequest(Event event) throws MuleException { if (taskTokenInThread.get() != null) { requestTaskToken = taskTokenInThread.get(); } else { requestTaskToken = generateTaskToken(); taskTokenInThread.set(requestTaskToken); } return super.process(event); } private Event processResponse(Event event) throws MuleException { if (event == null) { return event; } if (taskTokenInThread.get() != null) { responseTaskToken = taskTokenInThread.get(); } else { responseTaskToken = generateTaskToken(); } responseResult = responseResult && expressionManager.evaluateBoolean(responseExpression, event, flowConstruct, false, true); increaseResponseCount(); responseLatch.countDown(); return event; } protected String generateTaskToken() { return currentThread().getName() + " - " + new UUID().toString(); } private Event processNext(Event event) throws MuleException { if (event != null) { return next.process(event); } else { return event; } } @Override public void verify() throws InterruptedException { super.verify(); if (responseCountFailOrNullEvent()) { fail(failureMessagePrefix() + "No response message received or if responseCount " + "attribute was set then it was no matched."); } else if (responseExpressionFailed()) { fail(failureMessagePrefix() + "Response expression " + expression + " evaluated false."); } else if (responseCount > 0 && responseSameTask) { assertThat(failureMessagePrefix() + "Response thread was not same as request thread", responseTaskToken, is(requestTaskToken)); } else if (responseCount > 0 && !responseSameTask) { assertThat(failureMessagePrefix() + "Response thread was same as request thread", responseTaskToken, not(is(requestTaskToken))); } } public Boolean responseCountFailOrNullEvent() throws InterruptedException { return !isResponseProcessesCountCorrect(); } // added for testing (can't assert on asserts) public Boolean responseExpressionFailed() { return !responseResult; } @Override public void setListener(Processor listener) { this.next = listener; } private void increaseResponseCount() { responseInvocationCount++; } public void setResponseExpression(String responseExpression) { this.responseExpression = responseExpression; } public void setResponseCount(int responseCount) { this.responseCount = responseCount; } public void setResponseSameTask(boolean responseSameTask) { this.responseSameTask = responseSameTask; } /** * The semantics of the count are as follows: - count was set & count processes were done => ok - count was set & count * processes were not done => fail - count was not set & at least one processing were done => ok * * @return * @throws InterruptedException */ synchronized private boolean isResponseProcessesCountCorrect() throws InterruptedException { boolean countReached = responseLatch.await(timeout, MILLISECONDS); if (needToMatchCount) { return responseCount == responseInvocationCount; } else { return countReached; } } @Override public void setFlowConstruct(FlowConstruct flowConstruct) { super.setFlowConstruct(flowConstruct); setFlowConstructIfNeeded(next, flowConstruct); } }