/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.aries.transaction.internal; import java.io.File; import java.util.ArrayList; import java.util.Dictionary; import java.util.List; import javax.transaction.TransactionManager; import javax.transaction.TransactionSynchronizationRegistry; import javax.transaction.UserTransaction; import javax.transaction.xa.XAException; import org.apache.aries.transaction.AriesTransactionManager; import org.apache.geronimo.transaction.log.HOWLLog; import org.apache.geronimo.transaction.log.UnrecoverableLog; import org.apache.geronimo.transaction.manager.RecoverableTransactionManager; import org.apache.geronimo.transaction.manager.TransactionLog; import org.apache.geronimo.transaction.manager.XidFactory; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.osgi.service.cm.ConfigurationException; /** */ public class TransactionManagerService { public static final String TRANSACTION_TIMEOUT = "aries.transaction.timeout"; public static final String RECOVERABLE = "aries.transaction.recoverable"; public static final String TMID = "aries.transaction.tmid"; public static final String HOWL_BUFFER_CLASS_NAME = "aries.transaction.howl.bufferClassName"; public static final String HOWL_BUFFER_SIZE = "aries.transaction.howl.bufferSize"; public static final String HOWL_CHECKSUM_ENABLED = "aries.transaction.howl.checksumEnabled"; public static final String HOWL_ADLER32_CHECKSUM = "aries.transaction.howl.adler32Checksum"; public static final String HOWL_FLUSH_SLEEP_TIME = "aries.transaction.howl.flushSleepTime"; public static final String HOWL_LOG_FILE_EXT = "aries.transaction.howl.logFileExt"; public static final String HOWL_LOG_FILE_NAME = "aries.transaction.howl.logFileName"; public static final String HOWL_MAX_BLOCKS_PER_FILE = "aries.transaction.howl.maxBlocksPerFile"; public static final String HOWL_MAX_LOG_FILES = "aries.transaction.howl.maxLogFiles"; public static final String HOWL_MAX_BUFFERS = "aries.transaction.howl.maxBuffers"; public static final String HOWL_MIN_BUFFERS = "aries.transaction.howl.minBuffers"; public static final String HOWL_THREADS_WAITING_FORCE_THRESHOLD = "aries.transaction.howl.threadsWaitingForceThreshold"; public static final String HOWL_LOG_FILE_DIR = "aries.transaction.howl.logFileDir"; public static final String HOWL_FLUSH_PARTIAL_BUFFERS = "aries.transaction.flushPartialBuffers"; public static final int DEFAULT_TRANSACTION_TIMEOUT = 600; // 600 seconds -> 10 minutes public static final boolean DEFAULT_RECOVERABLE = false; // not recoverable by default private static final String PLATFORM_TRANSACTION_MANAGER_CLASS = "org.springframework.transaction.PlatformTransactionManager"; @SuppressWarnings("unused") private final String pid; @SuppressWarnings("rawtypes") private final Dictionary properties; private final BundleContext bundleContext; private boolean useSpring; private AriesTransactionManagerImpl transactionManager; private TransactionLog transactionLog; private ServiceRegistration<?> serviceRegistration; public TransactionManagerService(String pid, @SuppressWarnings("rawtypes") Dictionary properties, BundleContext bundleContext) throws ConfigurationException { this.pid = pid; this.properties = properties; this.bundleContext = bundleContext; // Transaction timeout int transactionTimeout = getInt(this.properties, TRANSACTION_TIMEOUT, DEFAULT_TRANSACTION_TIMEOUT); if (transactionTimeout <= 0) { throw new ConfigurationException(TRANSACTION_TIMEOUT, NLS.MESSAGES.getMessage("tx.timeout.greaterthan.zero")); } final String tmid = getString(this.properties, TMID, pid); // the max length of the factory should be 64 XidFactory xidFactory = new XidFactoryImpl(tmid.substring(0, Math.min(tmid.length(), 64)).getBytes()); // Transaction log transactionLog = createTransactionLog(this.properties, xidFactory); // Create transaction manager try { try { transactionManager = new SpringTransactionManagerCreator().create(transactionTimeout, xidFactory, transactionLog); useSpring = true; } catch (NoClassDefFoundError e) { transactionManager = new AriesTransactionManagerImpl(transactionTimeout, xidFactory, transactionLog); } } catch (XAException e) { throw new RuntimeException(NLS.MESSAGES.getMessage("tx.recovery.error"), e); } } public void start() throws Exception { List<String> clazzes = new ArrayList<String>(); clazzes.add(AriesTransactionManager.class.getName()); clazzes.add(TransactionManager.class.getName()); clazzes.add(TransactionSynchronizationRegistry.class.getName()); clazzes.add(UserTransaction.class.getName()); clazzes.add(RecoverableTransactionManager.class.getName()); if (useSpring) { clazzes.add(PLATFORM_TRANSACTION_MANAGER_CLASS); } String[] ifar = clazzes.toArray(new String[clazzes.size()]); serviceRegistration = bundleContext.registerService(ifar, transactionManager, null); } public void close() throws Exception { if(serviceRegistration != null) { try { serviceRegistration.unregister(); } catch (IllegalStateException e) { //This can be safely ignored } } if (transactionLog instanceof HOWLLog) { ((HOWLLog) transactionLog).doStop(); } } static String getString(Dictionary properties, String property, String dflt) { String value = (String) properties.get(property); if (value != null) { return value; } return dflt; } static int getInt(Dictionary properties, String property, int dflt) throws ConfigurationException { String value = (String) properties.get(property); if (value != null) { try { return Integer.parseInt(value); } catch (Exception e) { throw new ConfigurationException(property, NLS.MESSAGES.getMessage("prop.value.not.int", property, value), e); } } return dflt; } static boolean getBool(Dictionary properties, String property, boolean dflt) throws ConfigurationException { String value = (String) properties.get(property); if (value != null) { try { return Boolean.parseBoolean(value); } catch (Exception e) { throw new ConfigurationException(property, NLS.MESSAGES.getMessage("prop.value.not.boolean", property, value), e); } } return dflt; } static TransactionLog createTransactionLog(Dictionary properties, XidFactory xidFactory) throws ConfigurationException { TransactionLog result = null; if (getBool(properties, RECOVERABLE, DEFAULT_RECOVERABLE)) { String bufferClassName = getString(properties, HOWL_BUFFER_CLASS_NAME, "org.objectweb.howl.log.BlockLogBuffer"); int bufferSizeKBytes = getInt(properties, HOWL_BUFFER_SIZE, 4); if (bufferSizeKBytes < 1 || bufferSizeKBytes > 32) { throw new ConfigurationException(HOWL_BUFFER_SIZE, NLS.MESSAGES.getMessage("buffer.size.between.one.and.thirtytwo")); } boolean checksumEnabled = getBool(properties, HOWL_CHECKSUM_ENABLED, true); boolean adler32Checksum = getBool(properties, HOWL_ADLER32_CHECKSUM, true); int flushSleepTimeMilliseconds = getInt(properties, HOWL_FLUSH_SLEEP_TIME, 50); String logFileExt = getString(properties, HOWL_LOG_FILE_EXT, "log"); String logFileName = getString(properties, HOWL_LOG_FILE_NAME, "transaction"); int maxBlocksPerFile = getInt(properties, HOWL_MAX_BLOCKS_PER_FILE, -1); int maxLogFiles = getInt(properties, HOWL_MAX_LOG_FILES, 2); int minBuffers = getInt(properties, HOWL_MIN_BUFFERS, 4); if (minBuffers < 0) { throw new ConfigurationException(HOWL_MIN_BUFFERS, NLS.MESSAGES.getMessage("min.buffers.greaterthan.zero")); } int maxBuffers = getInt(properties, HOWL_MAX_BUFFERS, 0); if (maxBuffers > 0 && minBuffers < maxBuffers) { throw new ConfigurationException(HOWL_MAX_BUFFERS, NLS.MESSAGES.getMessage("max.buffers.greaterthan.min.buffers")); } int threadsWaitingForceThreshold = getInt(properties, HOWL_THREADS_WAITING_FORCE_THRESHOLD, -1); boolean flushPartialBuffers = getBool(properties, HOWL_FLUSH_PARTIAL_BUFFERS, true); String logFileDir = getString(properties, HOWL_LOG_FILE_DIR, null); if (logFileDir == null || logFileDir.length() == 0 || !new File(logFileDir).isAbsolute()) { throw new ConfigurationException(HOWL_LOG_FILE_DIR, NLS.MESSAGES.getMessage("log.file.dir")); } try { result = new HOWLLog(bufferClassName, bufferSizeKBytes, checksumEnabled, adler32Checksum, flushSleepTimeMilliseconds, logFileDir, logFileExt, logFileName, maxBlocksPerFile, maxBuffers, maxLogFiles, minBuffers, threadsWaitingForceThreshold, flushPartialBuffers, xidFactory, null); ((HOWLLog) result).doStart(); } catch (Exception e) { // This should not really happen as we've checked properties earlier throw new ConfigurationException(null, e.getMessage(), e); } } else { result = new UnrecoverableLog(); } return result; } /** * We use an inner static class to decouple this class from the spring-tx classes * in order to not have NoClassDefFoundError if those are not present. */ public static class SpringTransactionManagerCreator { public AriesTransactionManagerImpl create(int defaultTransactionTimeoutSeconds, XidFactory xidFactory, TransactionLog transactionLog) throws XAException { return new AriesPlatformTransactionManager(defaultTransactionTimeoutSeconds, xidFactory, transactionLog); } } }