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(10, 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}