org.springframework.amqp.rabbit.core.RabbitTemplateIntegrationTests.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.amqp.rabbit.core.RabbitTemplateIntegrationTests.java

Source

/*
 * Copyright 2010-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

package org.springframework.amqp.rabbit.core;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;

import java.lang.reflect.Field;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.logging.Log;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.SingleConnectionFactory;
import org.springframework.amqp.rabbit.support.DefaultMessagePropertiesConverter;
import org.springframework.amqp.rabbit.support.MessagePropertiesConverter;
import org.springframework.amqp.rabbit.test.BrokerRunning;
import org.springframework.amqp.rabbit.test.BrokerTestUtils;
import org.springframework.amqp.support.converter.SimpleMessageConverter;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
import org.springframework.util.ReflectionUtils.FieldFilter;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.GetResponse;

public class RabbitTemplateIntegrationTests {

    private static final String ROUTE = "test.queue";

    private RabbitTemplate template;

    @Before
    public void create() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
        connectionFactory.setPort(BrokerTestUtils.getPort());
        template = new RabbitTemplate(connectionFactory);
    }

    @Rule
    public BrokerRunning brokerIsRunning = BrokerRunning.isRunningWithEmptyQueues(ROUTE);

    @Test
    public void testSendToNonExistentAndThenReceive() throws Exception {
        // If transacted then the commit fails on send, so we get a nice synchronous exception
        template.setChannelTransacted(true);
        try {
            template.convertAndSend("", "no.such.route", "message");
            // fail("Expected AmqpException");
        } catch (AmqpException e) {
            // e.printStackTrace();
        }
        // Now send the real message, and all should be well...
        template.convertAndSend(ROUTE, "message");
        String result = (String) template.receiveAndConvert(ROUTE);
        assertEquals("message", result);
        result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @Test
    public void testSendAndReceiveWithPostProcessor() throws Exception {
        template.convertAndSend(ROUTE, (Object) "message", new MessagePostProcessor() {
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setContentType("text/other");
                // message.getMessageProperties().setUserId("foo");
                return message;
            }
        });
        String result = (String) template.receiveAndConvert(ROUTE);
        assertEquals("message", result);
        result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @Test
    public void testSendAndReceive() throws Exception {
        template.convertAndSend(ROUTE, "message");
        String result = (String) template.receiveAndConvert(ROUTE);
        assertEquals("message", result);
        result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @Test
    public void testSendAndReceiveTransacted() throws Exception {
        template.setChannelTransacted(true);
        template.convertAndSend(ROUTE, "message");
        String result = (String) template.receiveAndConvert(ROUTE);
        assertEquals("message", result);
        result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @Test
    public void testSendAndReceiveTransactedWithUncachedConnection() throws Exception {
        RabbitTemplate template = new RabbitTemplate(new SingleConnectionFactory());
        template.setChannelTransacted(true);
        template.convertAndSend(ROUTE, "message");
        String result = (String) template.receiveAndConvert(ROUTE);
        assertEquals("message", result);
        result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @Test
    public void testSendAndReceiveTransactedWithImplicitRollback() throws Exception {
        template.setChannelTransacted(true);
        template.convertAndSend(ROUTE, "message");
        // Rollback of manual receive is implicit because the channel is
        // closed...
        try {
            template.execute(new ChannelCallback<String>() {
                public String doInRabbit(Channel channel) throws Exception {
                    // Switch off the auto-ack so the message is rolled back...
                    channel.basicGet(ROUTE, false);
                    // This is the way to rollback with a cached channel (it is
                    // the way the ConnectionFactoryUtils
                    // handles it via a synchronization):
                    channel.basicRecover(true);
                    throw new PlannedException();
                }
            });
            fail("Expected PlannedException");
        } catch (Exception e) {
            assertTrue(e.getCause() instanceof PlannedException);
        }
        String result = (String) template.receiveAndConvert(ROUTE);
        assertEquals("message", result);
        result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @Test
    public void testSendAndReceiveInCallback() throws Exception {
        template.convertAndSend(ROUTE, "message");
        final MessagePropertiesConverter messagePropertiesConverter = new DefaultMessagePropertiesConverter();
        String result = template.execute(new ChannelCallback<String>() {
            public String doInRabbit(Channel channel) throws Exception {
                // We need noAck=false here for the message to be expicitly
                // acked
                GetResponse response = channel.basicGet(ROUTE, false);
                MessageProperties messageProps = messagePropertiesConverter.toMessageProperties(response.getProps(),
                        response.getEnvelope(), "UTF-8");
                // Explicit ack
                channel.basicAck(response.getEnvelope().getDeliveryTag(), false);
                return (String) new SimpleMessageConverter()
                        .fromMessage(new Message(response.getBody(), messageProps));
            }
        });
        assertEquals("message", result);
        result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @Test
    public void testReceiveInExternalTransaction() throws Exception {
        template.convertAndSend(ROUTE, "message");
        template.setChannelTransacted(true);
        String result = new TransactionTemplate(new TestTransactionManager())
                .execute(new TransactionCallback<String>() {
                    public String doInTransaction(TransactionStatus status) {
                        return (String) template.receiveAndConvert(ROUTE);
                    }
                });
        assertEquals("message", result);
        result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @Test
    public void testReceiveInExternalTransactionAutoAck() throws Exception {
        template.convertAndSend(ROUTE, "message");
        // Should just result in auto-ack (not synched with external tx)
        template.setChannelTransacted(true);
        String result = new TransactionTemplate(new TestTransactionManager())
                .execute(new TransactionCallback<String>() {
                    public String doInTransaction(TransactionStatus status) {
                        return (String) template.receiveAndConvert(ROUTE);
                    }
                });
        assertEquals("message", result);
        result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @Test
    public void testReceiveInExternalTransactionWithRollback() throws Exception {
        // Makes receive (and send in principle) transactional
        template.setChannelTransacted(true);
        template.convertAndSend(ROUTE, "message");
        try {
            new TransactionTemplate(new TestTransactionManager()).execute(new TransactionCallback<String>() {
                public String doInTransaction(TransactionStatus status) {
                    template.receiveAndConvert(ROUTE);
                    throw new PlannedException();
                }
            });
            fail("Expected PlannedException");
        } catch (PlannedException e) {
            // Expected
        }
        String result = (String) template.receiveAndConvert(ROUTE);
        assertEquals("message", result);
        result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @Test
    public void testReceiveInExternalTransactionWithNoRollback() throws Exception {
        // Makes receive non-transactional
        template.setChannelTransacted(false);
        template.convertAndSend(ROUTE, "message");
        try {
            new TransactionTemplate(new TestTransactionManager()).execute(new TransactionCallback<String>() {
                public String doInTransaction(TransactionStatus status) {
                    template.receiveAndConvert(ROUTE);
                    throw new PlannedException();
                }
            });
            fail("Expected PlannedException");
        } catch (PlannedException e) {
            // Expected
        }
        // No rollback
        String result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @Test
    public void testSendInExternalTransaction() throws Exception {
        template.setChannelTransacted(true);
        new TransactionTemplate(new TestTransactionManager()).execute(new TransactionCallback<Void>() {
            public Void doInTransaction(TransactionStatus status) {
                template.convertAndSend(ROUTE, "message");
                return null;
            }
        });
        String result = (String) template.receiveAndConvert(ROUTE);
        assertEquals("message", result);
        result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @Test
    public void testSendInExternalTransactionWithRollback() throws Exception {
        template.setChannelTransacted(true);
        try {
            new TransactionTemplate(new TestTransactionManager()).execute(new TransactionCallback<Void>() {
                public Void doInTransaction(TransactionStatus status) {
                    template.convertAndSend(ROUTE, "message");
                    throw new PlannedException();
                }
            });
            fail("Expected PlannedException");
        } catch (PlannedException e) {
            // Expected
        }
        String result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @Test
    public void testAtomicSendAndReceive() throws Exception {
        final RabbitTemplate template = new RabbitTemplate(new CachingConnectionFactory());
        template.setRoutingKey(ROUTE);
        template.setQueue(ROUTE);
        ExecutorService executor = Executors.newFixedThreadPool(1);
        // Set up a consumer to respond to our producer
        Future<Message> received = executor.submit(new Callable<Message>() {

            public Message call() throws Exception {
                Message message = null;
                for (int i = 0; i < 10; i++) {
                    message = template.receive();
                    if (message != null) {
                        break;
                    }
                    Thread.sleep(100L);
                }
                assertNotNull("No message received", message);
                template.send(message.getMessageProperties().getReplyTo(), message);
                return message;
            }

        });
        Message message = new Message("test-message".getBytes(), new MessageProperties());
        Message reply = template.sendAndReceive(message);
        assertEquals(new String(message.getBody()),
                new String(received.get(1000, TimeUnit.MILLISECONDS).getBody()));
        assertNotNull("Reply is expected", reply);
        assertEquals(new String(message.getBody()), new String(reply.getBody()));
        // Message was consumed so nothing left on queue
        reply = template.receive();
        assertEquals(null, reply);
    }

    @Test
    public void testAtomicSendAndReceiveExternalExecutor() throws Exception {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
        ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
        final String execName = "make-sure-exec-passed-in";
        exec.setBeanName(execName);
        exec.afterPropertiesSet();
        connectionFactory.setExecutor(exec);
        final Field[] fields = new Field[1];
        ReflectionUtils.doWithFields(RabbitTemplate.class, new FieldCallback() {
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                field.setAccessible(true);
                fields[0] = field;
            }
        }, new FieldFilter() {
            public boolean matches(Field field) {
                return field.getName().equals("logger");
            }
        });
        Log logger = Mockito.mock(Log.class);
        when(logger.isTraceEnabled()).thenReturn(true);

        final AtomicBoolean execConfiguredOk = new AtomicBoolean();

        doAnswer(new Answer<Object>() {
            public Object answer(InvocationOnMock invocation) throws Throwable {
                String log = (String) invocation.getArguments()[0];
                if (log.startsWith("Message received") && Thread.currentThread().getName().startsWith(execName)) {
                    execConfiguredOk.set(true);
                }
                return null;
            }
        }).when(logger).trace(Mockito.anyString());
        final RabbitTemplate template = new RabbitTemplate(connectionFactory);
        ReflectionUtils.setField(fields[0], template, logger);
        template.setRoutingKey(ROUTE);
        template.setQueue(ROUTE);
        ExecutorService executor = Executors.newFixedThreadPool(1);
        // Set up a consumer to respond to our producer
        Future<Message> received = executor.submit(new Callable<Message>() {

            public Message call() throws Exception {
                Message message = null;
                for (int i = 0; i < 10; i++) {
                    message = template.receive();
                    if (message != null) {
                        break;
                    }
                    Thread.sleep(100L);
                }
                assertNotNull("No message received", message);
                template.send(message.getMessageProperties().getReplyTo(), message);
                return message;
            }

        });
        Message message = new Message("test-message".getBytes(), new MessageProperties());
        Message reply = template.sendAndReceive(message);
        assertEquals(new String(message.getBody()),
                new String(received.get(1000, TimeUnit.MILLISECONDS).getBody()));
        assertNotNull("Reply is expected", reply);
        assertEquals(new String(message.getBody()), new String(reply.getBody()));
        // Message was consumed so nothing left on queue
        reply = template.receive();
        assertEquals(null, reply);

        assertTrue(execConfiguredOk.get());
    }

    @Test
    public void testAtomicSendAndReceiveWithRoutingKey() throws Exception {
        final RabbitTemplate template = new RabbitTemplate(new CachingConnectionFactory());
        ExecutorService executor = Executors.newFixedThreadPool(1);
        // Set up a consumer to respond to our producer
        Future<Message> received = executor.submit(new Callable<Message>() {

            public Message call() throws Exception {
                Message message = null;
                for (int i = 0; i < 10; i++) {
                    message = template.receive(ROUTE);
                    if (message != null) {
                        break;
                    }
                    Thread.sleep(100L);
                }
                assertNotNull("No message received", message);
                template.send(message.getMessageProperties().getReplyTo(), message);
                return message;
            }

        });
        Message message = new Message("test-message".getBytes(), new MessageProperties());
        Message reply = template.sendAndReceive(ROUTE, message);
        assertEquals(new String(message.getBody()),
                new String(received.get(1000, TimeUnit.MILLISECONDS).getBody()));
        assertNotNull("Reply is expected", reply);
        assertEquals(new String(message.getBody()), new String(reply.getBody()));
        // Message was consumed so nothing left on queue
        reply = template.receive(ROUTE);
        assertEquals(null, reply);
    }

    @Test
    public void testAtomicSendAndReceiveWithExchangeAndRoutingKey() throws Exception {
        final RabbitTemplate template = new RabbitTemplate(new CachingConnectionFactory());
        ExecutorService executor = Executors.newFixedThreadPool(1);
        // Set up a consumer to respond to our producer
        Future<Message> received = executor.submit(new Callable<Message>() {

            public Message call() throws Exception {
                Message message = null;
                for (int i = 0; i < 10; i++) {
                    message = template.receive(ROUTE);
                    if (message != null) {
                        break;
                    }
                    Thread.sleep(100L);
                }
                assertNotNull("No message received", message);
                template.send(message.getMessageProperties().getReplyTo(), message);
                return message;
            }

        });
        Message message = new Message("test-message".getBytes(), new MessageProperties());
        Message reply = template.sendAndReceive("", ROUTE, message);
        assertEquals(new String(message.getBody()),
                new String(received.get(1000, TimeUnit.MILLISECONDS).getBody()));
        assertNotNull("Reply is expected", reply);
        assertEquals(new String(message.getBody()), new String(reply.getBody()));
        // Message was consumed so nothing left on queue
        reply = template.receive(ROUTE);
        assertEquals(null, reply);
    }

    @Test
    public void testAtomicSendAndReceiveWithConversion() throws Exception {
        final RabbitTemplate template = new RabbitTemplate(new CachingConnectionFactory());
        template.setRoutingKey(ROUTE);
        template.setQueue(ROUTE);
        ExecutorService executor = Executors.newFixedThreadPool(1);
        // Set up a consumer to respond to our producer
        Future<String> received = executor.submit(new Callable<String>() {

            public String call() throws Exception {
                Message message = null;
                for (int i = 0; i < 10; i++) {
                    message = template.receive();
                    if (message != null) {
                        break;
                    }
                    Thread.sleep(100L);
                }
                assertNotNull("No message received", message);
                template.send(message.getMessageProperties().getReplyTo(), message);
                return (String) template.getMessageConverter().fromMessage(message);
            }

        });
        String result = (String) template.convertSendAndReceive("message");
        assertEquals("message", received.get(1000, TimeUnit.MILLISECONDS));
        assertEquals("message", result);
        // Message was consumed so nothing left on queue
        result = (String) template.receiveAndConvert();
        assertEquals(null, result);
    }

    @Test
    public void testAtomicSendAndReceiveWithConversionUsingRoutingKey() throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(1);
        // Set up a consumer to respond to our producer
        Future<String> received = executor.submit(new Callable<String>() {

            public String call() throws Exception {
                Message message = null;
                for (int i = 0; i < 10; i++) {
                    message = template.receive(ROUTE);
                    if (message != null) {
                        break;
                    }
                    Thread.sleep(100L);
                }
                assertNotNull("No message received", message);
                template.send(message.getMessageProperties().getReplyTo(), message);
                return (String) template.getMessageConverter().fromMessage(message);
            }

        });
        String result = (String) template.convertSendAndReceive(ROUTE, "message");
        assertEquals("message", received.get(1000, TimeUnit.MILLISECONDS));
        assertEquals("message", result);
        // Message was consumed so nothing left on queue
        result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @Test
    public void testAtomicSendAndReceiveWithConversionUsingExchangeAndRoutingKey() throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(1);
        // Set up a consumer to respond to our producer
        Future<String> received = executor.submit(new Callable<String>() {

            public String call() throws Exception {
                Message message = null;
                for (int i = 0; i < 10; i++) {
                    message = template.receive(ROUTE);
                    if (message != null) {
                        break;
                    }
                    Thread.sleep(100L);
                }
                assertNotNull("No message received", message);
                template.send(message.getMessageProperties().getReplyTo(), message);
                return (String) template.getMessageConverter().fromMessage(message);
            }

        });
        String result = (String) template.convertSendAndReceive("", ROUTE, "message");
        assertEquals("message", received.get(1000, TimeUnit.MILLISECONDS));
        assertEquals("message", result);
        // Message was consumed so nothing left on queue
        result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @Test
    public void testAtomicSendAndReceiveWithConversionAndMessagePostProcessor() throws Exception {
        final RabbitTemplate template = new RabbitTemplate(new CachingConnectionFactory());
        template.setRoutingKey(ROUTE);
        template.setQueue(ROUTE);
        ExecutorService executor = Executors.newFixedThreadPool(1);
        // Set up a consumer to respond to our producer
        Future<String> received = executor.submit(new Callable<String>() {

            public String call() throws Exception {
                Message message = null;
                for (int i = 0; i < 10; i++) {
                    message = template.receive();
                    if (message != null) {
                        break;
                    }
                    Thread.sleep(100L);
                }
                assertNotNull("No message received", message);
                template.send(message.getMessageProperties().getReplyTo(), message);
                return (String) template.getMessageConverter().fromMessage(message);
            }

        });
        String result = (String) template.convertSendAndReceive((Object) "message", new MessagePostProcessor() {
            public Message postProcessMessage(Message message) throws AmqpException {
                try {
                    byte[] newBody = new String(message.getBody(), "UTF-8").toUpperCase().getBytes("UTF-8");
                    return new Message(newBody, message.getMessageProperties());
                } catch (Exception e) {
                    throw new AmqpException("unexpected failure in test", e);
                }
            }
        });
        assertEquals("MESSAGE", received.get(1000, TimeUnit.MILLISECONDS));
        assertEquals("MESSAGE", result);
        // Message was consumed so nothing left on queue
        result = (String) template.receiveAndConvert();
        assertEquals(null, result);
    }

    @Test
    public void testAtomicSendAndReceiveWithConversionAndMessagePostProcessorUsingRoutingKey() throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(1);
        // Set up a consumer to respond to our producer
        Future<String> received = executor.submit(new Callable<String>() {

            public String call() throws Exception {
                Message message = null;
                for (int i = 0; i < 10; i++) {
                    message = template.receive(ROUTE);
                    if (message != null) {
                        break;
                    }
                    Thread.sleep(100L);
                }
                assertNotNull("No message received", message);
                template.send(message.getMessageProperties().getReplyTo(), message);
                return (String) template.getMessageConverter().fromMessage(message);
            }

        });
        String result = (String) template.convertSendAndReceive(ROUTE, (Object) "message",
                new MessagePostProcessor() {
                    public Message postProcessMessage(Message message) throws AmqpException {
                        try {
                            byte[] newBody = new String(message.getBody(), "UTF-8").toUpperCase().getBytes("UTF-8");
                            return new Message(newBody, message.getMessageProperties());
                        } catch (Exception e) {
                            throw new AmqpException("unexpected failure in test", e);
                        }
                    }
                });
        assertEquals("MESSAGE", received.get(1000, TimeUnit.MILLISECONDS));
        assertEquals("MESSAGE", result);
        // Message was consumed so nothing left on queue
        result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @Test
    public void testAtomicSendAndReceiveWithConversionAndMessagePostProcessorUsingExchangeAndRoutingKey()
            throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(1);
        // Set up a consumer to respond to our producer
        Future<String> received = executor.submit(new Callable<String>() {

            public String call() throws Exception {
                Message message = null;
                for (int i = 0; i < 10; i++) {
                    message = template.receive(ROUTE);
                    if (message != null) {
                        break;
                    }
                    Thread.sleep(100L);
                }
                assertNotNull("No message received", message);
                template.send(message.getMessageProperties().getReplyTo(), message);
                return (String) template.getMessageConverter().fromMessage(message);
            }

        });
        String result = (String) template.convertSendAndReceive("", ROUTE, "message", new MessagePostProcessor() {

            public Message postProcessMessage(Message message) throws AmqpException {
                try {
                    byte[] newBody = new String(message.getBody(), "UTF-8").toUpperCase().getBytes("UTF-8");
                    return new Message(newBody, message.getMessageProperties());
                } catch (Exception e) {
                    throw new AmqpException("unexpected failure in test", e);
                }
            }
        });
        assertEquals("MESSAGE", received.get(1000, TimeUnit.MILLISECONDS));
        assertEquals("MESSAGE", result);
        // Message was consumed so nothing left on queue
        result = (String) template.receiveAndConvert(ROUTE);
        assertEquals(null, result);
    }

    @SuppressWarnings("serial")
    private class PlannedException extends RuntimeException {
        public PlannedException() {
            super("Planned");
        }
    }

    @SuppressWarnings("serial")
    private class TestTransactionManager extends AbstractPlatformTransactionManager {

        @Override
        protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
        }

        @Override
        protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
        }

        @Override
        protected Object doGetTransaction() throws TransactionException {
            return new Object();
        }

        @Override
        protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
        }

    }

}