001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Stephane Lacoin
018 */
019package org.nuxeo.ecm.core.test;
020
021import java.io.File;
022import java.io.IOException;
023import java.util.LinkedList;
024import java.util.List;
025import java.util.concurrent.TimeUnit;
026
027import org.apache.commons.logging.LogFactory;
028import org.apache.log4j.Logger;
029import org.junit.Assert;
030import org.nuxeo.ecm.core.repository.RepositoryFactory;
031import org.nuxeo.ecm.core.test.annotations.Granularity;
032import org.nuxeo.ecm.core.test.annotations.RepositoryConfig;
033import org.nuxeo.ecm.core.test.annotations.TransactionalConfig;
034import org.nuxeo.runtime.management.jvm.ThreadDeadlocksDetector;
035import org.nuxeo.runtime.test.runner.ContainerFeature;
036import org.nuxeo.runtime.test.runner.Features;
037import org.nuxeo.runtime.test.runner.FeaturesRunner;
038import org.nuxeo.runtime.test.runner.SimpleFeature;
039import org.nuxeo.runtime.transaction.TransactionHelper;
040
041@RepositoryConfig(cleanup = Granularity.METHOD)
042@Features(ContainerFeature.class)
043public class TransactionalFeature extends SimpleFeature {
044
045    protected TransactionalConfig config;
046
047    protected String autoactivationValue;
048
049    protected boolean nsOwner;
050
051    protected boolean txStarted;
052
053    final List<Waiter> waiters = new LinkedList<>();
054
055    public interface Waiter {
056        boolean await(long deadline) throws InterruptedException;
057    }
058
059    public void addWaiter(Waiter waiter) {
060        waiters.add(waiter);
061    }
062
063    public void nextTransaction() {
064        nextTransaction(2, TimeUnit.MINUTES);
065    }
066
067    public void nextTransaction(long duration, TimeUnit unit) {
068        long deadline = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(duration, unit);
069        boolean tx = TransactionHelper.isTransactionActive();
070        boolean rb = TransactionHelper.isTransactionMarkedRollback();
071        if (tx || rb) {
072            // there may be tx synchronizer pending, so we
073            // have to commit the transaction
074            TransactionHelper.commitOrRollbackTransaction();
075        }
076        try {
077            for (Waiter provider : waiters) {
078                try {
079                    Assert.assertTrue(await(provider, deadline));
080                } catch (InterruptedException cause) {
081                    Thread.currentThread().interrupt();
082                    throw new AssertionError("interrupted while awaiting for asynch completion", cause);
083                }
084            }
085        } finally {
086            if (tx || rb) {
087                // restore previous tx status
088                TransactionHelper.startTransaction();
089                if (rb) {
090                    TransactionHelper.setTransactionRollbackOnly();
091                }
092            }
093        }
094    }
095
096    boolean await(Waiter waiter, long deadline) throws InterruptedException {
097        if (waiter.await(deadline)) {
098            return true;
099        }
100        try {
101            File file = new ThreadDeadlocksDetector().dump(new long[0]);
102            LogFactory.getLog(TransactionalFeature.class).warn("timed out in " + waiter.getClass() + ", thread dump available in " + file);
103        } catch (IOException cause) {
104            LogFactory.getLog(TransactionalFeature.class).warn("timed out in " + waiter.getClass() + ", cannot take thread dump", cause);
105        }
106        return false;
107    }
108
109    protected Class<? extends RepositoryFactory> defaultFactory;
110
111    @Override
112    public void initialize(FeaturesRunner runner) throws Exception {
113        config = runner.getConfig(TransactionalConfig.class);
114    }
115
116    @Override
117    public void beforeSetup(FeaturesRunner runner) throws Exception {
118        if (config.autoStart() == false) {
119            return;
120        }
121        txStarted = TransactionHelper.startTransaction();
122    }
123
124    @Override
125    public void afterTeardown(FeaturesRunner runner) throws Exception {
126        if (txStarted == false) {
127            if (TransactionHelper.isTransactionActive()) {
128                try {
129                    TransactionHelper.setTransactionRollbackOnly();
130                    TransactionHelper.commitOrRollbackTransaction();
131                } finally {
132                    Logger.getLogger(TransactionalFeature.class).warn(
133                            "Committing a transaction for your, please do it yourself");
134                }
135            }
136            return;
137        }
138        TransactionHelper.commitOrRollbackTransaction();
139    }
140
141}