org.springframework.batch.support.transaction.ConcurrentTransactionAwareProxyTests.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.batch.support.transaction.ConcurrentTransactionAwareProxyTests.java

Source

/*
 * Copyright 2006-2009 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.batch.support.transaction;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;

/**
 * @author Dave Syer
 * 
 */
public class ConcurrentTransactionAwareProxyTests {

    private static Log logger = LogFactory.getLog(ConcurrentTransactionAwareProxyTests.class);

    private PlatformTransactionManager transactionManager = new ResourcelessTransactionManager();

    int outerMax = 20;

    int innerMax = 30;

    private ExecutorService executor;

    private CompletionService<List<String>> completionService;

    @Before
    public void init() {
        executor = Executors.newFixedThreadPool(outerMax);
        completionService = new ExecutorCompletionService<List<String>>(executor);
    }

    @After
    public void close() {
        executor.shutdown();
    }

    @Test(expected = Throwable.class)
    public void testConcurrentTransactionalSet() throws Exception {
        Set<String> set = TransactionAwareProxyFactory.createTransactionalSet();
        testSet(set);
    }

    @Test
    public void testConcurrentTransactionalAppendOnlySet() throws Exception {
        Set<String> set = TransactionAwareProxyFactory.createAppendOnlyTransactionalSet();
        testSet(set);
    }

    @Test
    public void testConcurrentTransactionalAppendOnlyList() throws Exception {
        List<String> list = TransactionAwareProxyFactory.createAppendOnlyTransactionalList();
        testList(list, false);
    }

    @Ignore("This fails too often and is a false negative")
    @Test
    public void testConcurrentTransactionalList() throws Exception {
        List<String> list = TransactionAwareProxyFactory.createTransactionalList();
        try {
            testList(list, true);
            fail("Expected ExecutionException or AssertionError (but don't panic if it didn't happen: it probably just means we got lucky for a change)");
        } catch (ExecutionException e) {
            String message = e.getCause().getMessage();
            assertTrue("Wrong message: " + message, message.startsWith("Lost update"));
        } catch (AssertionError e) {
            String message = e.getMessage();
            assertTrue("Wrong message: " + message, message.startsWith("Wrong number of results"));
        }
    }

    @Test
    public void testConcurrentTransactionalAppendOnlyMap() throws Exception {
        Map<Long, Map<String, String>> map = TransactionAwareProxyFactory.createAppendOnlyTransactionalMap();
        testMap(map);
    }

    @Test(expected = ExecutionException.class)
    public void testConcurrentTransactionalMap() throws Exception {
        Map<Long, Map<String, String>> map = TransactionAwareProxyFactory.createTransactionalMap();
        testMap(map);
    }

    @Test
    public void testTransactionalContains() throws Exception {
        final Map<Long, Map<String, String>> map = TransactionAwareProxyFactory.createAppendOnlyTransactionalMap();
        boolean result = new TransactionTemplate(transactionManager).execute(new TransactionCallback<Boolean>() {
            @Override
            public Boolean doInTransaction(TransactionStatus status) {
                return map.containsKey("foo");
            }
        });
        assertFalse(result);
    }

    private void testSet(final Set<String> set) throws Exception {

        for (int i = 0; i < outerMax; i++) {

            final int count = i;
            completionService.submit(new Callable<List<String>>() {
                @Override
                public List<String> call() throws Exception {
                    List<String> list = new ArrayList<String>();
                    for (int i = 0; i < innerMax; i++) {
                        String value = count + "bar" + i;
                        saveInSetAndAssert(set, value);
                        list.add(value);
                    }
                    return list;
                }
            });

        }

        for (int i = 0; i < outerMax; i++) {
            List<String> result = completionService.take().get();
            assertEquals(innerMax, result.size());
        }

        assertEquals(innerMax * outerMax, set.size());

    }

    private void testList(final List<String> list, final boolean mutate) throws Exception {

        for (int i = 0; i < outerMax; i++) {

            completionService.submit(new Callable<List<String>>() {
                @Override
                public List<String> call() throws Exception {
                    List<String> result = new ArrayList<String>();
                    for (int i = 0; i < innerMax; i++) {
                        String value = "bar" + i;
                        saveInListAndAssert(list, value);
                        result.add(value);
                        // Need to slow it down to allow threads to interleave
                        Thread.sleep(10L);
                        if (mutate) {
                            list.remove(value);
                            list.add(value);
                        }
                    }
                    logger.info("Added: " + innerMax + " values");
                    return result;
                }
            });

        }

        for (int i = 0; i < outerMax; i++) {
            List<String> result = completionService.take().get();
            assertEquals("Wrong number of results in inner task", innerMax, result.size());
        }

        assertEquals("Wrong number of results in aggregate", innerMax * outerMax, list.size());

    }

    private void testMap(final Map<Long, Map<String, String>> map) throws Exception {

        int numberOfKeys = outerMax;

        for (int i = 0; i < outerMax; i++) {

            for (int j = 0; j < numberOfKeys; j++) {
                final long id = j * 1000 + 123L + i;

                completionService.submit(new Callable<List<String>>() {
                    @Override
                    public List<String> call() throws Exception {
                        List<String> list = new ArrayList<String>();
                        for (int i = 0; i < innerMax; i++) {
                            String value = "bar" + i;
                            list.add(saveInMapAndAssert(map, id, value).get("foo"));
                        }
                        return list;
                    }
                });
            }

            for (int j = 0; j < numberOfKeys; j++) {
                completionService.take().get();
            }

        }

    }

    private String saveInSetAndAssert(final Set<String> set, final String value) {

        new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() {
            @Override
            public Void doInTransaction(TransactionStatus status) {
                set.add(value);
                return null;
            }
        });

        Assert.state(set.contains(value), "Lost update: value=" + value);

        return value;

    }

    private String saveInListAndAssert(final List<String> list, final String value) {

        new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() {
            @Override
            public Void doInTransaction(TransactionStatus status) {
                list.add(value);
                return null;
            }
        });

        Assert.state(list.contains(value), "Lost update: value=" + value);

        return value;

    }

    private Map<String, String> saveInMapAndAssert(final Map<Long, Map<String, String>> map, final Long id,
            final String value) {

        new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() {
            @Override
            public Void doInTransaction(TransactionStatus status) {
                if (!map.containsKey(id)) {
                    map.put(id, new HashMap<String, String>());
                }
                map.get(id).put("foo", value);
                return null;
            }
        });

        Map<String, String> result = map.get(id);
        Assert.state(result != null, "Lost insert: null String at value=" + value);
        String foo = result.get("foo");
        Assert.state(value.equals(foo), "Lost update: wrong value=" + value + " (found " + foo + ") for id=" + id);

        return result;

    }

}