From a8a95b64fe17fa2fd9ac7e4c031e3faaf7fd17a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C5=82awek=20Piotrowski?= Date: Mon, 5 Jan 2015 19:59:50 +0100 Subject: [PATCH] Pluggable BTM context Currently only one instance of BTM can exist in single classloader. This is because class TransactionManagerServices keeps services instances in static fields. This commit adds BitronixContext which is responsible for keeping service instances. The default implementation does not change any semantics. Instances are still kept statically. But different BitronixContext implementations can be selected by the user using ServiceLoader infrastructure. --- .../java/bitronix/tm/ServicesFactory.java | 104 +++++++++++ .../tm/TransactionManagerServices.java | 169 +++-------------- .../java/bitronix/tm/spi/BitronixContext.java | 87 +++++++++ .../tm/spi/DefaultBitronixContext.java | 173 ++++++++++++++++++ 4 files changed, 391 insertions(+), 142 deletions(-) create mode 100644 btm/src/main/java/bitronix/tm/ServicesFactory.java create mode 100644 btm/src/main/java/bitronix/tm/spi/BitronixContext.java create mode 100644 btm/src/main/java/bitronix/tm/spi/DefaultBitronixContext.java diff --git a/btm/src/main/java/bitronix/tm/ServicesFactory.java b/btm/src/main/java/bitronix/tm/ServicesFactory.java new file mode 100644 index 00000000..b0ef7f5e --- /dev/null +++ b/btm/src/main/java/bitronix/tm/ServicesFactory.java @@ -0,0 +1,104 @@ +package bitronix.tm; + +import bitronix.tm.BitronixTransactionManager; +import bitronix.tm.BitronixTransactionSynchronizationRegistry; +import bitronix.tm.Configuration; +import bitronix.tm.TransactionManagerServices; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import bitronix.tm.journal.DiskJournal; +import bitronix.tm.journal.Journal; +import bitronix.tm.journal.NullJournal; +import bitronix.tm.recovery.Recoverer; +import bitronix.tm.resource.ResourceLoader; +import bitronix.tm.timer.TaskScheduler; +import bitronix.tm.twopc.executor.AsyncExecutor; +import bitronix.tm.twopc.executor.Executor; +import bitronix.tm.twopc.executor.SyncExecutor; +import bitronix.tm.utils.ClassLoaderUtils; +import bitronix.tm.utils.DefaultExceptionAnalyzer; +import bitronix.tm.utils.ExceptionAnalyzer; +import bitronix.tm.utils.InitializationException; + +/** + * Factory of services. + */ +public class ServicesFactory { + private final static Logger log = LoggerFactory.getLogger(TransactionManagerServices.class); + + public static BitronixTransactionManager crateTransactionManager() { + return new BitronixTransactionManager(); + } + + public static BitronixTransactionSynchronizationRegistry createTransactionSynchronizationRegistry() { + return new BitronixTransactionSynchronizationRegistry(); + } + + public static Configuration createConfiguration() { + return new Configuration(); + } + + public static Journal createJournal(Journal journal, String configuredJournal) throws InitializationException { + if ("null".equals(configuredJournal) || null == configuredJournal) { + journal = new NullJournal(); + } else if ("disk".equals(configuredJournal)) { + journal = new DiskJournal(); + } else { + try { + Class clazz = ClassLoaderUtils.loadClass(configuredJournal); + journal = (Journal) clazz.newInstance(); + } catch (Exception ex) { + throw new InitializationException("invalid journal implementation '" + configuredJournal + "'", ex); + } + } + if (log.isDebugEnabled()) { + log.debug("using journal " + configuredJournal); + } + return journal; + } + + public static TaskScheduler createTaskScheduler() { + return new TaskScheduler(); + } + + public static ResourceLoader createResourceLoader() { + return new ResourceLoader(); + } + + public static Recoverer createRecoverer() { + return new Recoverer(); + } + + public static Executor createExecutor(boolean isAsynchronouse2Pc) { + Executor executor; + if (isAsynchronouse2Pc) { + if (log.isDebugEnabled()) { + log.debug("using AsyncExecutor"); + } + executor = new AsyncExecutor(); + } else { + if (log.isDebugEnabled()) { + log.debug("using SyncExecutor"); + } + executor = new SyncExecutor(); + } + return executor; + } + + public static ExceptionAnalyzer createExceptionAnalyser(String exceptionAnalyzerName) { + ExceptionAnalyzer analyzer; + analyzer = new DefaultExceptionAnalyzer(); + if (exceptionAnalyzerName != null) { + try { + analyzer = (ExceptionAnalyzer) ClassLoaderUtils.loadClass(exceptionAnalyzerName).newInstance(); + } catch (Exception ex) { + log.warn("failed to initialize custom exception analyzer, using default one instead", ex); + } + } + return analyzer; + } + + +} diff --git a/btm/src/main/java/bitronix/tm/TransactionManagerServices.java b/btm/src/main/java/bitronix/tm/TransactionManagerServices.java index 7b5ac543..3611f1fd 100644 --- a/btm/src/main/java/bitronix/tm/TransactionManagerServices.java +++ b/btm/src/main/java/bitronix/tm/TransactionManagerServices.java @@ -15,25 +15,18 @@ */ package bitronix.tm; -import bitronix.tm.journal.DiskJournal; +import java.util.Iterator; +import java.util.ServiceLoader; + import bitronix.tm.journal.Journal; -import bitronix.tm.journal.NullJournal; import bitronix.tm.recovery.Recoverer; import bitronix.tm.resource.ResourceLoader; +import bitronix.tm.spi.BitronixContext; +import bitronix.tm.spi.DefaultBitronixContext; import bitronix.tm.timer.TaskScheduler; -import bitronix.tm.twopc.executor.AsyncExecutor; import bitronix.tm.twopc.executor.Executor; -import bitronix.tm.twopc.executor.SyncExecutor; -import bitronix.tm.utils.ClassLoaderUtils; -import bitronix.tm.utils.DefaultExceptionAnalyzer; import bitronix.tm.utils.ExceptionAnalyzer; -import bitronix.tm.utils.InitializationException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; /** * Container for all BTM services. @@ -45,34 +38,23 @@ */ public class TransactionManagerServices { - private final static Logger log = LoggerFactory.getLogger(TransactionManagerServices.class); - - private static final Lock transactionManagerLock = new ReentrantLock(); - private static volatile BitronixTransactionManager transactionManager; + private final static BitronixContext context; - private static final AtomicReference transactionSynchronizationRegistryRef = new AtomicReference(); - private static final AtomicReference configurationRef = new AtomicReference(); - private static final AtomicReference journalRef = new AtomicReference(); - private static final AtomicReference taskSchedulerRef = new AtomicReference(); - private static final AtomicReference resourceLoaderRef = new AtomicReference(); - private static final AtomicReference recovererRef = new AtomicReference(); - private static final AtomicReference executorRef = new AtomicReference(); - private static final AtomicReference exceptionAnalyzerRef = new AtomicReference(); + static { + Iterator iterator = ServiceLoader.load(BitronixContext.class).iterator(); + if (iterator.hasNext()) { + context = iterator.next(); + } else { + context = new DefaultBitronixContext(); + } + } /** * Create an initialized transaction manager. * @return the transaction manager. */ public static BitronixTransactionManager getTransactionManager() { - transactionManagerLock.lock(); - try { - if (transactionManager == null) { - transactionManager = new BitronixTransactionManager(); - } - return transactionManager; - } finally { - transactionManagerLock.unlock(); - } + return context.getTransactionManager(); } /** @@ -80,14 +62,7 @@ public static BitronixTransactionManager getTransactionManager() { * @return the TransactionSynchronizationRegistry. */ public static BitronixTransactionSynchronizationRegistry getTransactionSynchronizationRegistry() { - BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry = transactionSynchronizationRegistryRef.get(); - if (transactionSynchronizationRegistry == null) { - transactionSynchronizationRegistry = new BitronixTransactionSynchronizationRegistry(); - if (!transactionSynchronizationRegistryRef.compareAndSet(null, transactionSynchronizationRegistry)) { - transactionSynchronizationRegistry = transactionSynchronizationRegistryRef.get(); - } - } - return transactionSynchronizationRegistry; + return context.getTransactionSynchronizationRegistry(); } /** @@ -95,14 +70,7 @@ public static BitronixTransactionSynchronizationRegistry getTransactionSynchroni * @return the global configuration. */ public static Configuration getConfiguration() { - Configuration configuration = configurationRef.get(); - if (configuration == null) { - configuration = new Configuration(); - if (!configurationRef.compareAndSet(null, configuration)) { - configuration = configurationRef.get(); - } - } - return configuration; + return context.getConfiguration(); } /** @@ -110,28 +78,7 @@ public static Configuration getConfiguration() { * @return the transactions journal. */ public static Journal getJournal() { - Journal journal = journalRef.get(); - if (journal == null) { - String configuredJournal = getConfiguration().getJournal(); - if ("null".equals(configuredJournal) || null == configuredJournal) { - journal = new NullJournal(); - } else if ("disk".equals(configuredJournal)) { - journal = new DiskJournal(); - } else { - try { - Class clazz = ClassLoaderUtils.loadClass(configuredJournal); - journal = (Journal) clazz.newInstance(); - } catch (Exception ex) { - throw new InitializationException("invalid journal implementation '" + configuredJournal + "'", ex); - } - } - if (log.isDebugEnabled()) { log.debug("using journal " + configuredJournal); } - - if (!journalRef.compareAndSet(null, journal)) { - journal = journalRef.get(); - } - } - return journal; + return context.getJournal(); } /** @@ -139,16 +86,7 @@ public static Journal getJournal() { * @return the task scheduler. */ public static TaskScheduler getTaskScheduler() { - TaskScheduler taskScheduler = taskSchedulerRef.get(); - if (taskScheduler == null) { - taskScheduler = new TaskScheduler(); - if (!taskSchedulerRef.compareAndSet(null, taskScheduler)) { - taskScheduler = taskSchedulerRef.get(); - } else { - taskScheduler.start(); - } - } - return taskScheduler; + return context.getTaskScheduler(); } /** @@ -156,14 +94,7 @@ public static TaskScheduler getTaskScheduler() { * @return the resource loader. */ public static ResourceLoader getResourceLoader() { - ResourceLoader resourceLoader = resourceLoaderRef.get(); - if (resourceLoader == null) { - resourceLoader = new ResourceLoader(); - if (!resourceLoaderRef.compareAndSet(null, resourceLoader)) { - resourceLoader = resourceLoaderRef.get(); - } - } - return resourceLoader; + return context.getResourceLoader(); } /** @@ -171,14 +102,7 @@ public static ResourceLoader getResourceLoader() { * @return the transaction recoverer. */ public static Recoverer getRecoverer() { - Recoverer recoverer = recovererRef.get(); - if (recoverer == null) { - recoverer = new Recoverer(); - if (!recovererRef.compareAndSet(null, recoverer)) { - recoverer = recovererRef.get(); - } - } - return recoverer; + return context.getRecoverer(); } /** @@ -186,45 +110,15 @@ public static Recoverer getRecoverer() { * @return the 2PC executor. */ public static Executor getExecutor() { - Executor executor = executorRef.get(); - if (executor == null) { - if (getConfiguration().isAsynchronous2Pc()) { - if (log.isDebugEnabled()) { log.debug("using AsyncExecutor"); } - executor = new AsyncExecutor(); - } else { - if (log.isDebugEnabled()) { log.debug("using SyncExecutor"); } - executor = new SyncExecutor(); - } - if (!executorRef.compareAndSet(null, executor)) { - executor.shutdown(); - executor = executorRef.get(); - } - } - return executor; + return context.getExecutor(); } /** * Create the exception analyzer. * @return the exception analyzer. */ - public static ExceptionAnalyzer getExceptionAnalyzer() { - ExceptionAnalyzer analyzer = exceptionAnalyzerRef.get(); - if (analyzer == null) { - String exceptionAnalyzerName = getConfiguration().getExceptionAnalyzer(); - analyzer = new DefaultExceptionAnalyzer(); - if (exceptionAnalyzerName != null) { - try { - analyzer = (ExceptionAnalyzer) ClassLoaderUtils.loadClass(exceptionAnalyzerName).newInstance(); - } catch (Exception ex) { - log.warn("failed to initialize custom exception analyzer, using default one instead", ex); - } - } - if (!exceptionAnalyzerRef.compareAndSet(null, analyzer)) { - analyzer.shutdown(); - analyzer = exceptionAnalyzerRef.get(); - } - } - return analyzer; + public static ExceptionAnalyzer getExceptionAnalyzer() { + return context.getExceptionAnalyzer(); } /** @@ -232,7 +126,7 @@ public static ExceptionAnalyzer getExceptionAnalyzer() { * @return true if the transaction manager has started. */ public static boolean isTransactionManagerRunning() { - return transactionManager != null; + return context.isTransactionManagerRunning(); } /** @@ -240,23 +134,14 @@ public static boolean isTransactionManagerRunning() { * @return true if the task scheduler has started. */ public static boolean isTaskSchedulerRunning() { - return taskSchedulerRef.get() != null; + return context.isTaskSchedulerRunning(); } /** * Clear services references. Called at the end of the shutdown procedure. */ protected static synchronized void clear() { - transactionManager = null; - - transactionSynchronizationRegistryRef.set(null); - configurationRef.set(null); - journalRef.set(null); - taskSchedulerRef.set(null); - resourceLoaderRef.set(null); - recovererRef.set(null); - executorRef.set(null); - exceptionAnalyzerRef.set(null); + context.clear(); } } diff --git a/btm/src/main/java/bitronix/tm/spi/BitronixContext.java b/btm/src/main/java/bitronix/tm/spi/BitronixContext.java new file mode 100644 index 00000000..0120b2da --- /dev/null +++ b/btm/src/main/java/bitronix/tm/spi/BitronixContext.java @@ -0,0 +1,87 @@ +package bitronix.tm.spi; + +import bitronix.tm.BitronixTransactionManager; +import bitronix.tm.BitronixTransactionSynchronizationRegistry; +import bitronix.tm.Configuration; +import bitronix.tm.journal.Journal; +import bitronix.tm.recovery.Recoverer; +import bitronix.tm.resource.ResourceLoader; +import bitronix.tm.timer.TaskScheduler; +import bitronix.tm.twopc.executor.Executor; +import bitronix.tm.utils.ExceptionAnalyzer; + +/** + * Keeps services instances. + */ +public interface BitronixContext { + /** + * Create an initialized transaction manager. + * @return the transaction manager. + */ + BitronixTransactionManager getTransactionManager(); + + /** + * Create the JTA 1.1 TransactionSynchronizationRegistry. + * @return the TransactionSynchronizationRegistry. + */ + BitronixTransactionSynchronizationRegistry getTransactionSynchronizationRegistry(); + + /** + * Create the configuration of all the components of the transaction manager. + * @return the global configuration. + */ + Configuration getConfiguration(); + + /** + * Create the transactions journal. + * @return the transactions journal. + */ + Journal getJournal(); + + /** + * Create the task scheduler. + * @return the task scheduler. + */ + TaskScheduler getTaskScheduler(); + + /** + * Create the resource loader. + * @return the resource loader. + */ + ResourceLoader getResourceLoader(); + + /** + * Create the transaction recoverer. + * @return the transaction recoverer. + */ + Recoverer getRecoverer(); + + /** + * Create the 2PC executor. + * @return the 2PC executor. + */ + Executor getExecutor(); + + /** + * Create the exception analyzer. + * @return the exception analyzer. + */ + ExceptionAnalyzer getExceptionAnalyzer(); + + /** + * Check if the transaction manager has started. + * @return true if the transaction manager has started. + */ + boolean isTransactionManagerRunning(); + + /** + * Check if the task scheduler has started. + * @return true if the task scheduler has started. + */ + boolean isTaskSchedulerRunning(); + + /** + * Clear services references. Called at the end of the shutdown procedure. + */ + void clear(); +} diff --git a/btm/src/main/java/bitronix/tm/spi/DefaultBitronixContext.java b/btm/src/main/java/bitronix/tm/spi/DefaultBitronixContext.java new file mode 100644 index 00000000..20b43a2e --- /dev/null +++ b/btm/src/main/java/bitronix/tm/spi/DefaultBitronixContext.java @@ -0,0 +1,173 @@ +package bitronix.tm.spi; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import bitronix.tm.BitronixTransactionManager; +import bitronix.tm.BitronixTransactionSynchronizationRegistry; +import bitronix.tm.Configuration; +import bitronix.tm.ServicesFactory; +import bitronix.tm.journal.Journal; +import bitronix.tm.recovery.Recoverer; +import bitronix.tm.resource.ResourceLoader; +import bitronix.tm.timer.TaskScheduler; +import bitronix.tm.twopc.executor.Executor; +import bitronix.tm.utils.ExceptionAnalyzer; + +/** + * Default context which keeps one instance of each service. + */ +public class DefaultBitronixContext implements BitronixContext { + private final Lock transactionManagerLock = new ReentrantLock(); + private volatile BitronixTransactionManager transactionManager; + + private final AtomicReference transactionSynchronizationRegistryRef = new AtomicReference(); + private final AtomicReference configurationRef = new AtomicReference(); + private final AtomicReference journalRef = new AtomicReference(); + private final AtomicReference taskSchedulerRef = new AtomicReference(); + private final AtomicReference resourceLoaderRef = new AtomicReference(); + private final AtomicReference recovererRef = new AtomicReference(); + private final AtomicReference executorRef = new AtomicReference(); + private final AtomicReference exceptionAnalyzerRef = new AtomicReference(); + + @Override + public BitronixTransactionManager getTransactionManager() { + transactionManagerLock.lock(); + try { + if (transactionManager == null) { + transactionManager = ServicesFactory.crateTransactionManager(); + } + return transactionManager; + } finally { + transactionManagerLock.unlock(); + } + } + + @Override + public BitronixTransactionSynchronizationRegistry getTransactionSynchronizationRegistry() { + BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry = transactionSynchronizationRegistryRef.get(); + if (transactionSynchronizationRegistry == null) { + transactionSynchronizationRegistry = ServicesFactory.createTransactionSynchronizationRegistry(); + if (!transactionSynchronizationRegistryRef.compareAndSet(null, transactionSynchronizationRegistry)) { + transactionSynchronizationRegistry = transactionSynchronizationRegistryRef.get(); + } + } + return transactionSynchronizationRegistry; + } + + @Override + public Configuration getConfiguration() { + Configuration configuration = configurationRef.get(); + if (configuration == null) { + configuration = ServicesFactory.createConfiguration(); + if (!configurationRef.compareAndSet(null, configuration)) { + configuration = configurationRef.get(); + } + } + return configuration; + } + + @Override + public Journal getJournal() { + Journal journal = journalRef.get(); + if (journal == null) { + String configuredJournal = getConfiguration().getJournal(); + journal = ServicesFactory.createJournal(journal, configuredJournal); + + if (!journalRef.compareAndSet(null, journal)) { + journal = journalRef.get(); + } + } + return journal; + } + + @Override + public TaskScheduler getTaskScheduler() { + TaskScheduler taskScheduler = taskSchedulerRef.get(); + if (taskScheduler == null) { + taskScheduler = ServicesFactory.createTaskScheduler(); + if (!taskSchedulerRef.compareAndSet(null, taskScheduler)) { + taskScheduler = taskSchedulerRef.get(); + } else { + taskScheduler.start(); + } + } + return taskScheduler; + } + + @Override + public ResourceLoader getResourceLoader() { + ResourceLoader resourceLoader = resourceLoaderRef.get(); + if (resourceLoader == null) { + resourceLoader = ServicesFactory.createResourceLoader(); + if (!resourceLoaderRef.compareAndSet(null, resourceLoader)) { + resourceLoader = resourceLoaderRef.get(); + } + } + return resourceLoader; + } + + @Override + public Recoverer getRecoverer() { + Recoverer recoverer = recovererRef.get(); + if (recoverer == null) { + recoverer = ServicesFactory.createRecoverer(); + if (!recovererRef.compareAndSet(null, recoverer)) { + recoverer = recovererRef.get(); + } + } + return recoverer; + } + + @Override + public Executor getExecutor() { + Executor executor = executorRef.get(); + if (executor == null) { + executor = ServicesFactory.createExecutor(getConfiguration().isAsynchronous2Pc()); + if (!executorRef.compareAndSet(null, executor)) { + executor.shutdown(); + executor = executorRef.get(); + } + } + return executor; + } + + @Override + public ExceptionAnalyzer getExceptionAnalyzer() { + ExceptionAnalyzer analyzer = exceptionAnalyzerRef.get(); + if (analyzer == null) { + String exceptionAnalyzerName = getConfiguration().getExceptionAnalyzer(); + analyzer = ServicesFactory.createExceptionAnalyser(exceptionAnalyzerName); + if (!exceptionAnalyzerRef.compareAndSet(null, analyzer)) { + analyzer.shutdown(); + analyzer = exceptionAnalyzerRef.get(); + } + } + return analyzer; + } + + @Override + public boolean isTransactionManagerRunning() { + return transactionManager != null; + } + + @Override + public boolean isTaskSchedulerRunning() { + return taskSchedulerRef.get() != null; + } + + @Override + public synchronized void clear() { + transactionManager = null; + + transactionSynchronizationRegistryRef.set(null); + configurationRef.set(null); + journalRef.set(null); + taskSchedulerRef.set(null); + resourceLoaderRef.set(null); + recovererRef.set(null); + executorRef.set(null); + exceptionAnalyzerRef.set(null); + } +}