org.alfresco.repo.node.ConcurrentNodeServiceTest.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.node.ConcurrentNodeServiceTest.java

Source

/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.node;

import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import junit.framework.TestCase;

import org.alfresco.repo.dictionary.DictionaryDAO;
import org.alfresco.repo.dictionary.M2Model;
import org.alfresco.repo.domain.node.NodeDAO;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.util.ApplicationContextHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.experimental.categories.Category;
import org.springframework.context.ApplicationContext;

/**
 * @author Nick Burch
 * @author Derek Hulley
 */
@Category(OwnJVMTestsCategory.class)
public class ConcurrentNodeServiceTest extends TestCase {
    public static final String NAMESPACE = "http://www.alfresco.org/test/BaseNodeServiceTest";
    public static final String TEST_PREFIX = "test";
    public static final QName TYPE_QNAME_TEST_CONTENT = QName.createQName(NAMESPACE, "content");
    public static final QName ASPECT_QNAME_TEST_TITLED = QName.createQName(NAMESPACE, "titled");
    public static final QName PROP_QNAME_TEST_TITLE = QName.createQName(NAMESPACE, "title");
    public static final QName PROP_QNAME_TEST_MIMETYPE = QName.createQName(NAMESPACE, "mimetype");

    public static final int COUNT = 10;
    public static final int REPEATS = 20;

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

    static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();

    private NodeService nodeService;
    private NodeDAO nodeDAO;
    private TransactionService transactionService;
    private NodeRef rootNodeRef;
    private AuthenticationComponent authenticationComponent;

    public ConcurrentNodeServiceTest() {
        super();
    }

    protected void setUp() throws Exception {
        DictionaryDAO dictionaryDao = (DictionaryDAO) ctx.getBean("dictionaryDAO");
        // load the system model
        ClassLoader cl = BaseNodeServiceTest.class.getClassLoader();
        InputStream modelStream = cl.getResourceAsStream("alfresco/model/systemModel.xml");
        assertNotNull(modelStream);
        M2Model model = M2Model.createModel(modelStream);
        dictionaryDao.putModel(model);
        // load the test model
        modelStream = cl.getResourceAsStream("org/alfresco/repo/node/BaseNodeServiceTest_model.xml");
        assertNotNull(modelStream);
        model = M2Model.createModel(modelStream);
        dictionaryDao.putModel(model);

        ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
        nodeService = serviceRegistry.getNodeService();
        nodeDAO = (NodeDAO) ctx.getBean("nodeDAO");
        transactionService = serviceRegistry.getTransactionService();
        authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent");

        this.authenticationComponent.setSystemUserAsCurrentUser();

        // create a first store directly
        RetryingTransactionCallback<Object> createRootNodeCallback = new RetryingTransactionCallback<Object>() {
            public Object execute() throws Exception {
                StoreRef storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE,
                        "Test_" + System.currentTimeMillis());
                rootNodeRef = nodeService.getRootNode(storeRef);
                return null;
            }
        };
        transactionService.getRetryingTransactionHelper().doInTransaction(createRootNodeCallback);
    }

    @Override
    protected void tearDown() throws Exception {
        authenticationComponent.clearCurrentSecurityContext();
        super.tearDown();
    }

    /**
     * Tests that when multiple threads try to edit different
     *  properties on a node, that transactions + retries always
     *  mean that every change always ends up on the node. 
     *  
     * @since 3.4 
     */
    public void testMultiThreaded_PropertyWrites() throws Exception {
        final List<Thread> threads = new ArrayList<Thread>();
        final int loops = 1000;

        // Have 5 threads, each trying to edit their own properties on the same node
        // Loop repeatedly
        final QName[] properties = new QName[] { QName.createQName("test1", "MadeUp1"),
                QName.createQName("test2", "MadeUp2"), QName.createQName("test3", "MadeUp3"),
                QName.createQName("test4", "MadeUp4"), QName.createQName("test5", "MadeUp5"), };
        final int[] propCounts = new int[properties.length];
        for (int propNum = 0; propNum < properties.length; propNum++) {
            final QName property = properties[propNum];
            final int propNumFinal = propNum;

            // Zap the property if it is there
            transactionService.getRetryingTransactionHelper()
                    .doInTransaction(new RetryingTransactionCallback<Void>() {
                        public Void execute() throws Throwable {
                            nodeService.removeProperty(rootNodeRef, property);
                            return null;
                        }
                    });

            // Prep the thread
            Thread t = new Thread(new Runnable() {
                @Override
                public synchronized void run() {
                    // Let everything catch up
                    try {
                        wait();
                    } catch (InterruptedException e) {
                    }
                    logger.info("About to start updating property " + property);

                    // Loop, incrementing each time
                    // If we miss an update, then at the end it'll be obvious
                    AuthenticationUtil.setRunAsUserSystem();
                    for (int i = 0; i < loops; i++) {
                        RetryingTransactionCallback<Integer> callback = new RetryingTransactionCallback<Integer>() {
                            @Override
                            public Integer execute() throws Throwable {
                                nodeDAO.setCheckNodeConsistency();
                                // Grab the current value
                                int current = 0;
                                Object obj = (Object) nodeService.getProperty(rootNodeRef, property);
                                if (obj != null && obj instanceof Integer) {
                                    current = ((Integer) obj).intValue();
                                }
                                // Increment by one. Really should be this!
                                current++;
                                nodeService.setProperty(rootNodeRef, property, Integer.valueOf(current));
                                // Check that the value is what we expected it to be
                                // We do this after the update so that we don't fall on retries
                                int expectedCurrent = propCounts[propNumFinal];
                                if (expectedCurrent != (current - 1)) {
                                    // We have a difference here already
                                    // It will never catch up, but we'll detect that later
                                    System.out.println("Found difference: " + Thread.currentThread().getName() + " "
                                            + current);
                                }
                                return current;
                            }
                        };
                        try {
                            RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
                            txnHelper.setMaxRetries(loops);
                            Integer newCount = txnHelper.doInTransaction(callback, false, true);
                            //                            System.out.println("Set value: " + Thread.currentThread().getName() + " " + newCount);
                            propCounts[propNumFinal] = newCount;
                        } catch (Throwable e) {
                            logger.error("Failed to set value: ", e);
                        }
                    }

                    // Report us as finished
                    logger.info("Finished updating property " + property);
                }
            }, "Thread-" + property);
            threads.add(t);
            t.start();
        }

        // Release the threads
        logger.info("Releasing the property update threads");
        for (Thread t : threads) {
            t.interrupt();
        }

        // Wait for the threads to finish
        for (Thread t : threads) {
            t.join();
        }

        Map<QName, Serializable> nodeProperties = nodeService.getProperties(rootNodeRef);
        List<String> errors = new ArrayList<String>();
        for (int i = 0; i < properties.length; i++) {
            Integer value = (Integer) nodeProperties.get(properties[i]);
            if (value == null) {
                errors.add("\n   Prop " + properties[i] + " : " + value);
            } else if (!value.equals(new Integer(loops))) {
                errors.add("\n   Prop " + properties[i] + " : " + value);
            }
        }
        if (errors.size() > 0) {
            StringBuilder sb = new StringBuilder();
            sb.append("Incorrect counts recieved for " + loops + " loops.");
            for (String error : errors) {
                sb.append(error);
            }
            fail(sb.toString());
        }
    }

    /**
     * Adds 'residual' aspects that are named according to the thread.  Multiple threads should all
     * get their changes in. 
     */
    public void testMultithreaded_AspectWrites() throws Exception {
        final Thread[] threads = new Thread[2];
        final int loops = 10;

        for (int i = 0; i < threads.length; i++) {
            final String name = "Thread-" + i + "-";
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    AuthenticationUtil.setRunAsUserSystem();
                    for (int loop = 0; loop < loops; loop++) {
                        final String nameWithLoop = name + loop;
                        RetryingTransactionCallback<Void> runCallback = new RetryingTransactionCallback<Void>() {
                            @Override
                            public Void execute() throws Throwable {
                                // Add another aspect to the node
                                QName qname = QName.createQName(NAMESPACE, nameWithLoop);
                                nodeService.addAspect(rootNodeRef, qname, null);
                                return null;
                            }
                        };
                        RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
                        txnHelper.setMaxRetries(40);
                        try {
                            txnHelper.doInTransaction(runCallback);
                        } catch (Throwable e) {
                            logger.error(e);
                        }
                    }
                }
            };
            threads[i] = new Thread(runnable, name);
        }
        // Start all the threads
        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
        // Wait for them all to finish
        for (int i = 0; i < threads.length; i++) {
            threads[i].join();
        }
        // Check the aspects
        Set<QName> aspects = nodeService.getAspects(rootNodeRef);
        for (int i = 0; i < threads.length; i++) {
            for (int j = 0; j < loops; j++) {
                String nameWithLoop = "Thread-" + i + "-" + j;
                QName qname = QName.createQName(NAMESPACE, nameWithLoop);
                assertTrue("Missing aspect: " + nameWithLoop, aspects.contains(qname));
            }
        }
    }
}