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