blob: 22997b24ffe220eab5f116f1b13b805bfe75e7fa [file] [log] [blame]
/*
* 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.felix.transaction.internal;
import java.util.Dictionary;
import java.util.List;
import java.util.ArrayList;
import java.util.Properties;
import java.io.File;
import java.io.IOException;
import javax.transaction.xa.XAException;
import javax.transaction.TransactionManager;
import javax.transaction.TransactionSynchronizationRegistry;
import javax.transaction.UserTransaction;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationException;
import org.apache.geronimo.transaction.manager.GeronimoTransactionManager;
import org.apache.geronimo.transaction.manager.TransactionLog;
import org.apache.geronimo.transaction.manager.XidFactory;
import org.apache.geronimo.transaction.manager.XidFactoryImpl;
import org.apache.geronimo.transaction.manager.RecoverableTransactionManager;
import org.apache.geronimo.transaction.log.HOWLLog;
import org.apache.geronimo.transaction.log.UnrecoverableLog;
import org.objectweb.howl.log.LogConfigurationException;
/**
*/
public class TransactionManagerService {
public static final String TRANSACTION_TIMEOUT = "felix.transaction.timeout";
public static final String RECOVERABLE = "felix.transaction.recoverable";
public static final String HOWL_BUFFER_CLASS_NAME = "felix.transaction.howl.bufferClassName";
public static final String HOWL_BUFFER_SIZE = "felix.transaction.howl.bufferSize";
public static final String HOWL_CHECKSUM_ENABLED = "felix.transaction.howl.checksumEnabled";
public static final String HOWL_ADLER32_CHECKSUM = "felix.transaction.howl.adler32Checksum";
public static final String HOWL_FLUSH_SLEEP_TIME = "felix.transaction.howl.flushSleepTime";
public static final String HOWL_LOG_FILE_EXT = "felix.transaction.howl.logFileExt";
public static final String HOWL_LOG_FILE_NAME = "felix.transaction.howl.logFileName";
public static final String HOWL_MAX_BLOCKS_PER_FILE = "felix.transaction.howl.maxBlocksPerFile";
public static final String HOWL_MAX_LOG_FILES = "felix.transaction.howl.maxLogFiles";
public static final String HOWL_MAX_BUFFERS = "felix.transaction.howl.maxBuffers";
public static final String HOWL_MIN_BUFFERS = "felix.transaction.howl.minBuffers";
public static final String HOWL_THREADS_WAITING_FORCE_THRESHOLD = "felix.transaction.howl.threadsWaitingForceThreshold";
public static final String HOWL_LOG_FILE_DIR = "felix.transaction.howl.logFileDir";
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";
private final String pid;
private final Dictionary properties;
private final BundleContext bundleContext;
private boolean useSpring;
private GeronimoTransactionManager transactionManager;
private TransactionLog transactionLog;
private ServiceRegistration serviceRegistration;
public TransactionManagerService(String pid, Dictionary properties, BundleContext bundleContext) throws ConfigurationException {
this.pid = pid;
this.properties = properties;
this.bundleContext = bundleContext;
// Transaction timeout
int transactionTimeout = getInt(TRANSACTION_TIMEOUT, DEFAULT_TRANSACTION_TIMEOUT);
if (transactionTimeout <= 0) {
throw new ConfigurationException(TRANSACTION_TIMEOUT, "Property " + TRANSACTION_TIMEOUT + " must be > 0");
}
// XID factory
XidFactory xidFactory = new XidFactoryImpl(pid.getBytes());
// Transaction log
if (getBool(RECOVERABLE, DEFAULT_RECOVERABLE)) {
String bufferClassName = getString(HOWL_BUFFER_CLASS_NAME, "org.objectweb.howl.log.BlockLogBuffer");
int bufferSizeKBytes = getInt(HOWL_BUFFER_SIZE, 32);
if (bufferSizeKBytes < 1 || bufferSizeKBytes > 32) {
throw new ConfigurationException(HOWL_BUFFER_SIZE, "bufferSize must be between 1 and 32");
}
boolean checksumEnabled = getBool(HOWL_CHECKSUM_ENABLED, true);
boolean adler32Checksum = getBool(HOWL_ADLER32_CHECKSUM, true);
int flushSleepTimeMilliseconds = getInt(HOWL_FLUSH_SLEEP_TIME, 50);
String logFileExt = getString(HOWL_LOG_FILE_EXT, "log");
String logFileName = getString(HOWL_LOG_FILE_NAME, "transaction");
int maxBlocksPerFile = getInt(HOWL_MAX_BLOCKS_PER_FILE, -1);
int maxLogFiles = getInt(HOWL_MAX_LOG_FILES, 2);
int minBuffers = getInt(HOWL_MIN_BUFFERS, 4);
if (minBuffers < 0) {
throw new ConfigurationException(HOWL_MIN_BUFFERS, "minBuffers must be > 0");
}
int maxBuffers = getInt(HOWL_MAX_BUFFERS, 0);
if (maxBuffers > 0 && minBuffers < maxBuffers) {
throw new ConfigurationException(HOWL_MAX_BUFFERS, "minBuffers must be <= maxBuffers");
}
int threadsWaitingForceThreshold = getInt(HOWL_THREADS_WAITING_FORCE_THRESHOLD, -1);
String logFileDir = getString(HOWL_LOG_FILE_DIR, null);
if (logFileDir == null || logFileDir.length() == 0 || !new File(logFileDir).isAbsolute()) {
throw new ConfigurationException(HOWL_LOG_FILE_DIR, "Property should be set to an absolute directory");
}
try {
transactionLog = new HOWLLog(bufferClassName,
bufferSizeKBytes,
checksumEnabled,
adler32Checksum,
flushSleepTimeMilliseconds,
logFileDir,
logFileExt,
logFileName,
maxBlocksPerFile,
maxBuffers,
maxLogFiles,
minBuffers,
threadsWaitingForceThreshold,
xidFactory != null ? xidFactory : new XidFactoryImpl(),
null);
} catch (LogConfigurationException e) {
// This should not really happen as we've checked properties earlier
throw new ConfigurationException(null, null, e);
} catch (IOException e) {
// This should not really happen as we've checked properties earlier
throw new ConfigurationException(null, null, e);
}
} else {
transactionLog = new UnrecoverableLog();
}
// Create transaction manager
try {
try {
transactionManager = new SpringTransactionManagerCreator().create(transactionTimeout, xidFactory, transactionLog);
useSpring = true;
} catch (NoClassDefFoundError e) {
transactionManager = new GeronimoTransactionManager(transactionTimeout, xidFactory, transactionLog);
}
} catch (XAException e) {
throw new RuntimeException("Error recovering transaction log", e);
}
}
public void start() throws Exception {
if (transactionLog instanceof HOWLLog) {
((HOWLLog) transactionLog).doStart();
}
List<String> clazzes = new ArrayList<String>();
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);
}
serviceRegistration = bundleContext.registerService(clazzes.toArray(new String[clazzes.size()]), transactionManager, new Properties());
}
public void close() throws Exception {
if (serviceRegistration != null) {
serviceRegistration.unregister();
}
if (transactionLog instanceof HOWLLog) {
((HOWLLog) transactionLog).doStop();
}
}
private String getString(String property, String dflt) throws ConfigurationException {
String value = (String) properties.get(property);
if (value != null) {
return value;
}
return dflt;
}
private int getInt(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, "Error parsing " + property + "(" + value + ") property as an integer", e);
}
}
return dflt;
}
private boolean getBool(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, "Error parsing " + property + "(" + value + ") property as a boolean", e);
}
}
return dflt;
}
/**
* 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 GeronimoTransactionManager create(int defaultTransactionTimeoutSeconds, XidFactory xidFactory, TransactionLog transactionLog) throws XAException {
return new GeronimoPlatformTransactionManager(defaultTransactionTimeoutSeconds, xidFactory, transactionLog);
}
}
}