/*
* 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);
}
}
}