/* * 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.runtime.core.processor; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.internal.verification.VerificationModeFactory.times; import static org.mule.runtime.api.metadata.DataType.OBJECT; import static org.mule.runtime.api.metadata.DataType.STRING; import org.mule.runtime.api.exception.MuleException; import org.mule.runtime.api.metadata.TypedValue; import org.mule.runtime.core.api.Event; import org.mule.runtime.core.api.MuleContext; import org.mule.runtime.core.api.config.MuleProperties; import org.mule.runtime.core.api.construct.FlowConstruct; import org.mule.runtime.core.internal.message.InternalMessage; import org.mule.runtime.core.api.processor.Processor; import org.mule.runtime.core.api.serialization.ObjectSerializer; import org.mule.runtime.api.store.ObjectStore; import org.mule.runtime.api.store.ObjectStoreException; import org.mule.runtime.api.store.ObjectStoreManager; import org.mule.runtime.core.internal.lock.MuleLockFactory; import org.mule.runtime.core.internal.lock.SingleServerLockProvider; import org.mule.runtime.core.util.concurrent.Latch; import org.mule.tck.SerializationTestUtils; import org.mule.tck.junit4.AbstractMuleTestCase; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Before; import org.junit.Test; import org.mockito.Answers; public class IdempotentRedeliveryPolicyTestCase extends AbstractMuleTestCase { public static final String STRING_MESSAGE = "message"; public static final int MAX_REDELIVERY_COUNT = 5; private static final String UTF_8 = "utf-8"; private static ObjectSerializer serializer; private MuleContext mockMuleContext = mock(MuleContext.class, Answers.RETURNS_DEEP_STUBS.get()); private ObjectStoreManager mockObjectStoreManager = mock(ObjectStoreManager.class, Answers.RETURNS_DEEP_STUBS.get()); private Processor mockFailingMessageProcessor = mock(Processor.class, Answers.RETURNS_DEEP_STUBS.get()); private Processor mockWaitingMessageProcessor = mock(Processor.class, Answers.RETURNS_DEEP_STUBS.get()); private Processor mockDlqMessageProcessor = mock(Processor.class, Answers.RETURNS_DEEP_STUBS.get()); private InternalMessage message = mock(InternalMessage.class, Answers.RETURNS_DEEP_STUBS.get()); private Event event = mock(Event.class, Answers.RETURNS_DEEP_STUBS.get()); private Latch waitLatch = new Latch(); private CountDownLatch waitingMessageProcessorExecutionLatch = new CountDownLatch(2); private final IdempotentRedeliveryPolicy irp = new IdempotentRedeliveryPolicy(); @Before @SuppressWarnings("rawtypes") public void setUpTest() throws MuleException { when(mockFailingMessageProcessor.process(any(Event.class))).thenThrow(new RuntimeException("failing")); when(mockWaitingMessageProcessor.process(event)).thenAnswer(invocationOnMock -> { waitingMessageProcessorExecutionLatch.countDown(); waitLatch.await(2000, TimeUnit.MILLISECONDS); return mockFailingMessageProcessor.process((Event) invocationOnMock.getArguments()[0]); }); MuleLockFactory muleLockFactory = new MuleLockFactory(); muleLockFactory.setMuleContext(mockMuleContext); when(mockMuleContext.getRegistry().get(MuleProperties.OBJECT_LOCK_PROVIDER)).thenReturn(new SingleServerLockProvider()); muleLockFactory.initialise(); when(mockMuleContext.getLockFactory()).thenReturn(muleLockFactory); when(mockMuleContext.getObjectStoreManager()).thenReturn(mockObjectStoreManager); when(mockMuleContext.getConfiguration().getDefaultEncoding()).thenReturn(UTF_8); final InMemoryObjectStore inMemoryObjectStore = new InMemoryObjectStore(); when(mockObjectStoreManager.getObjectStore(anyString(), anyBoolean(), anyInt(), anyInt(), anyInt())) .thenAnswer(invocation -> inMemoryObjectStore); when(event.getMessage()).thenReturn(message); IdempotentRedeliveryPolicyTestCase.serializer = SerializationTestUtils.getJavaSerializerWithMockContext(); irp.setMaxRedeliveryCount(MAX_REDELIVERY_COUNT); irp.setUseSecureHash(true); irp.setFlowConstruct(mock(FlowConstruct.class)); irp.setMuleContext(mockMuleContext); irp.setListener(mockFailingMessageProcessor); } @Test public void messageDigestFailure() throws Exception { when(message.getPayload()).thenReturn(new TypedValue<>(new Object(), OBJECT)); irp.initialise(); Event process = irp.process(event); assertThat(process, nullValue()); } @Test public void testMessageRedeliveryUsingMemory() throws Exception { when(message.getPayload()).thenReturn(new TypedValue<>(STRING_MESSAGE, STRING)); irp.initialise(); processUntilFailure(); verify(mockFailingMessageProcessor, times(MAX_REDELIVERY_COUNT + 1)).process(event); } @Test public void testMessageRedeliveryUsingSerializationStore() throws Exception { when(message.getPayload()).thenReturn(new TypedValue<>(STRING_MESSAGE, STRING)); reset(mockObjectStoreManager); final ObjectStore serializationObjectStore = new SerializationObjectStore(); when(mockObjectStoreManager.getObjectStore(anyString(), anyBoolean(), anyInt(), anyInt(), anyInt())) .thenAnswer(invocation -> serializationObjectStore); irp.initialise(); processUntilFailure(); verify(mockFailingMessageProcessor, times(MAX_REDELIVERY_COUNT + 1)).process(event); } @Test public void testThreadSafeObjectStoreUsage() throws Exception { when(message.getPayload()).thenReturn(new TypedValue<>(STRING_MESSAGE, STRING)); irp.setListener(mockWaitingMessageProcessor); irp.initialise(); ExecuteIrpThread firstIrpExecutionThread = new ExecuteIrpThread(); firstIrpExecutionThread.start(); ExecuteIrpThread threadCausingRedeliveryException = new ExecuteIrpThread(); threadCausingRedeliveryException.start(); waitingMessageProcessorExecutionLatch.await(5000, TimeUnit.MILLISECONDS); waitLatch.release(); firstIrpExecutionThread.join(); threadCausingRedeliveryException.join(); verify(mockFailingMessageProcessor, times(2)).process(event); } private void processUntilFailure() { for (int i = 0; i < MAX_REDELIVERY_COUNT + 2; i++) { try { irp.process(event); } catch (Exception e) { // ignore exception } } } public class ExecuteIrpThread extends Thread { public Exception exception; @Override public void run() { try { irp.process(event); } catch (Exception e) { exception = e; } } } public static class SerializationObjectStore implements ObjectStore<AtomicInteger> { private Map<Serializable, Serializable> store = new HashMap<>(); @Override public boolean contains(Serializable key) throws ObjectStoreException { return store.containsKey(key); } @Override public void store(Serializable key, AtomicInteger value) throws ObjectStoreException { store.put(key, serializer.getExternalProtocol().serialize(value)); } @Override public AtomicInteger retrieve(Serializable key) throws ObjectStoreException { Serializable serializable = store.get(key); return serializer.getExternalProtocol().deserialize((byte[]) serializable); } @Override public AtomicInteger remove(Serializable key) throws ObjectStoreException { Serializable serializable = store.remove(key); return serializer.getExternalProtocol().deserialize((byte[]) serializable); } @Override public boolean isPersistent() { return false; } @Override public void clear() throws ObjectStoreException { this.store.clear(); } } public static class InMemoryObjectStore implements ObjectStore<AtomicInteger> { private Map<Serializable, AtomicInteger> store = new HashMap<>(); @Override public boolean contains(Serializable key) throws ObjectStoreException { return store.containsKey(key); } @Override public void store(Serializable key, AtomicInteger value) throws ObjectStoreException { store.put(key, value); } @Override public AtomicInteger retrieve(Serializable key) throws ObjectStoreException { return store.get(key); } @Override public AtomicInteger remove(Serializable key) throws ObjectStoreException { return store.remove(key); } @Override public void clear() throws ObjectStoreException { this.store.clear(); } @Override public boolean isPersistent() { return false; } } }