001/* 002 * (C) Copyright 2006-2017 Nuxeo (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 * Kevin Leturc <kleturc@nuxeo.com> 019 */ 020package org.nuxeo.ecm.core.test; 021 022import java.io.File; 023import java.io.IOException; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.concurrent.TimeUnit; 027 028import org.apache.commons.logging.LogFactory; 029import org.apache.log4j.Logger; 030import org.junit.Assert; 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.HotDeployer.ActionHandler; 039import org.nuxeo.runtime.test.runner.RuntimeFeature; 040import org.nuxeo.runtime.test.runner.SimpleFeature; 041import org.nuxeo.runtime.transaction.TransactionHelper; 042 043@RepositoryConfig(cleanup = Granularity.METHOD) 044@Features(ContainerFeature.class) 045public class TransactionalFeature extends SimpleFeature { 046 047 protected TransactionalConfig config; 048 049 protected boolean txStarted; 050 051 protected final List<Waiter> waiters = new LinkedList<>(); 052 053 @FunctionalInterface 054 public interface Waiter { 055 056 boolean await(long deadline) throws InterruptedException; 057 058 } 059 060 public void addWaiter(Waiter waiter) { 061 waiters.add(waiter); 062 } 063 064 public void nextTransaction() { 065 nextTransaction(10, TimeUnit.MINUTES); 066 } 067 068 public void nextTransaction(long duration, TimeUnit unit) { 069 long deadline = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(duration, unit); 070 boolean tx = TransactionHelper.isTransactionActive(); 071 boolean rb = TransactionHelper.isTransactionMarkedRollback(); 072 if (tx || rb) { 073 // there may be tx synchronizer pending, so we 074 // have to commit the transaction 075 TransactionHelper.commitOrRollbackTransaction(); 076 } 077 try { 078 for (Waiter provider : waiters) { 079 try { 080 Assert.assertTrue(await(provider, deadline)); 081 } catch (InterruptedException cause) { 082 Thread.currentThread().interrupt(); 083 throw new AssertionError("interrupted while awaiting for asynch completion", cause); 084 } 085 } 086 } finally { 087 if (tx || rb) { 088 // restore previous tx status 089 TransactionHelper.startTransaction(); 090 if (rb) { 091 TransactionHelper.setTransactionRollbackOnly(); 092 } 093 } 094 } 095 } 096 097 boolean await(Waiter waiter, long deadline) throws InterruptedException { 098 if (waiter.await(deadline)) { 099 return true; 100 } 101 try { 102 File file = new ThreadDeadlocksDetector().dump(new long[0]); 103 LogFactory.getLog(TransactionalFeature.class) 104 .warn("timed out in " + waiter.getClass() + ", thread dump available in " + file); 105 } catch (IOException cause) { 106 LogFactory.getLog(TransactionalFeature.class) 107 .warn("timed out in " + waiter.getClass() + ", cannot take thread dump", cause); 108 } 109 return false; 110 } 111 112 @Override 113 public void initialize(FeaturesRunner runner) throws Exception { 114 config = runner.getConfig(TransactionalConfig.class); 115 runner.getFeature(RuntimeFeature.class).registerHandler(new TransactionalDeployer()); 116 } 117 118 @Override 119 public void beforeSetup(FeaturesRunner runner) throws Exception { 120 startTransactionBefore(); 121 } 122 123 @Override 124 public void afterTeardown(FeaturesRunner runner) throws Exception { 125 commitOrRollbackTransactionAfter(); 126 } 127 128 protected void startTransactionBefore() { 129 if (config.autoStart()) { 130 txStarted = TransactionHelper.startTransaction(); 131 } 132 } 133 134 protected void commitOrRollbackTransactionAfter() { 135 if (txStarted) { 136 TransactionHelper.commitOrRollbackTransaction(); 137 } else { 138 if (TransactionHelper.isTransactionActive()) { 139 try { 140 TransactionHelper.setTransactionRollbackOnly(); 141 TransactionHelper.commitOrRollbackTransaction(); 142 } finally { 143 Logger.getLogger(TransactionalFeature.class) 144 .warn("Committing a transaction for your, please do it yourself"); 145 } 146 } 147 } 148 } 149 150 /** 151 * Handler used to commit transaction before next action and start a new one after next action if 152 * {@link TransactionalConfig#autoStart()} is true. This is because framework is about to be reloaded, then a new 153 * transaction manager will be installed. 154 * 155 * @since 9.3 156 */ 157 public class TransactionalDeployer extends ActionHandler { 158 159 @Override 160 public void exec(String action, String... args) throws Exception { 161 commitOrRollbackTransactionAfter(); 162 next.exec(action, args); 163 startTransactionBefore(); 164 } 165 166 } 167 168}