/* * #%L * Wisdom-Framework * %% * Copyright (C) 2013 - 2014 Wisdom Framework * %% * Licensed 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. * #L% */ package org.wisdom.framework.transaction.impl; import org.apache.felix.ipojo.annotations.*; import org.apache.geronimo.transaction.log.UnrecoverableLog; import org.apache.geronimo.transaction.manager.*; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.wisdom.api.configuration.ApplicationConfiguration; import javax.transaction.TransactionManager; import javax.transaction.TransactionSynchronizationRegistry; import javax.transaction.UserTransaction; import javax.transaction.xa.XAException; import java.io.File; @SuppressWarnings("UnusedDeclaration") @Component(immediate = true) @Instantiate public class TransactionManagerService { public static final String TRANSACTION_TIMEOUT = "wisdom.transaction.timeout"; public static final String RECOVERABLE = "wisdom.transaction.recoverable"; public static final String TMID = "wisdom.transaction.tmid"; public static final String HOWL_BUFFER_CLASS_NAME = "wisdom.transaction.howl.bufferClassName"; public static final String HOWL_BUFFER_SIZE = "wisdom.transaction.howl.bufferSize"; public static final String HOWL_CHECKSUM_ENABLED = "wisdom.transaction.howl.checksumEnabled"; public static final String HOWL_ADLER32_CHECKSUM = "wisdom.transaction.howl.adler32Checksum"; public static final String HOWL_FLUSH_SLEEP_TIME = "wisdom.transaction.howl.flushSleepTime"; public static final String HOWL_LOG_FILE_EXT = "wisdom.transaction.howl.logFileExt"; public static final String HOWL_LOG_FILE_NAME = "wisdom.transaction.howl.logFileName"; public static final String HOWL_MAX_BLOCKS_PER_FILE = "wisdom.transaction.howl.maxBlocksPerFile"; public static final String HOWL_MAX_LOG_FILES = "wisdom.transaction.howl.maxLogFiles"; public static final String HOWL_MAX_BUFFERS = "wisdom.transaction.howl.maxBuffers"; public static final String HOWL_MIN_BUFFERS = "wisdom.transaction.howl.minBuffers"; public static final String HOWL_THREADS_WAITING_FORCE_THRESHOLD = "wisdom.transaction.howl.threadsWaitingForceThreshold"; public static final String HOWL_LOG_FILE_DIR = "wisdom.transaction.howl.logFileDir"; public static final String HOWL_FLUSH_PARTIAL_BUFFERS = "wisdom.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 public static TransactionManager transactionManager; private final TransactionLog transactionLog; @Requires ApplicationConfiguration configuration; private final BundleContext bundleContext; private ServiceRegistration<?> registration; public static TransactionManager get() { return transactionManager; } public TransactionManagerService(@Context BundleContext bundleContext, @Requires ApplicationConfiguration configuration) { this.bundleContext = bundleContext; // Transaction timeout int transactionTimeout = configuration.getIntegerWithDefault(TRANSACTION_TIMEOUT, DEFAULT_TRANSACTION_TIMEOUT); if (transactionTimeout <= 0) { throw new IllegalArgumentException("The transaction timeout cannot be negative or zero"); } final String tmid = configuration.getWithDefault(TMID, "wisdom-transaction-manager"); // the max length of the factory should be 64 XidFactory xidFactory = new XidFactoryImpl(tmid.substring(0, Math.min(tmid.length(), 64)).getBytes()); // Transaction log if (configuration.getBooleanWithDefault(RECOVERABLE, DEFAULT_RECOVERABLE)) { String bufferClassName = configuration.getWithDefault(HOWL_BUFFER_CLASS_NAME, "org.objectweb.howl.log.BlockLogBuffer"); int bufferSizeKBytes = configuration.getIntegerWithDefault(HOWL_BUFFER_SIZE, 4); if (bufferSizeKBytes < 1 || bufferSizeKBytes > 32) { throw new IllegalArgumentException("The buffer size must be between 1 and 32"); } boolean checksumEnabled = configuration.getBooleanWithDefault(HOWL_CHECKSUM_ENABLED, true); boolean adler32Checksum = configuration.getBooleanWithDefault(HOWL_ADLER32_CHECKSUM, true); int flushSleepTimeMilliseconds = configuration.getIntegerWithDefault(HOWL_FLUSH_SLEEP_TIME, 50); String logFileExt = configuration.getWithDefault(HOWL_LOG_FILE_EXT, "log"); String logFileName = configuration.getWithDefault(HOWL_LOG_FILE_NAME, "transaction"); int maxBlocksPerFile = configuration.getIntegerWithDefault(HOWL_MAX_BLOCKS_PER_FILE, -1); int maxLogFiles = configuration.getIntegerWithDefault(HOWL_MAX_LOG_FILES, 2); int minBuffers = configuration.getIntegerWithDefault(HOWL_MIN_BUFFERS, 4); if (minBuffers < 0) { throw new IllegalArgumentException("The min buffer must be strictly greater than 0"); } int maxBuffers = configuration.getIntegerWithDefault(HOWL_MAX_BUFFERS, 0); if (maxBuffers > 0 && minBuffers < maxBuffers) { throw new IllegalArgumentException("The max buffer must be strictly greater than the min buffer (" + minBuffers + ")"); } int threadsWaitingForceThreshold = configuration.getIntegerWithDefault(HOWL_THREADS_WAITING_FORCE_THRESHOLD, -1); boolean flushPartialBuffers = configuration.getBooleanWithDefault(HOWL_FLUSH_PARTIAL_BUFFERS, true); final File dir = new File(configuration.getBaseDir(), configuration.getWithDefault(HOWL_LOG_FILE_DIR, ".howl")); dir.mkdirs(); String logFileDir = dir.getAbsolutePath(); try { transactionLog = new HowlLog(bufferClassName, bufferSizeKBytes, checksumEnabled, adler32Checksum, flushSleepTimeMilliseconds, logFileDir, logFileExt, logFileName, maxBlocksPerFile, maxBuffers, maxLogFiles, minBuffers, threadsWaitingForceThreshold, flushPartialBuffers, xidFactory, null); ((HowlLog) transactionLog).start(); } catch (Exception e) { // This should not really happen as we've checked properties earlier throw new IllegalArgumentException("Cannot instantiate the transaction log", e); } } else { transactionLog = new UnrecoverableLog(); } // Create transaction manager try { // Because of OpenJPA, we store it in a static field (we need a way to retrieve it from a static method). transactionManager = new TransactionManagerImpl(transactionTimeout, xidFactory, transactionLog); //NOSONAR } catch (XAException e) { throw new IllegalStateException("Cannot instantiate the transaction manager", e); } } @Validate public void register() { registration = bundleContext.registerService(new String[]{ UserTransaction.class.getName(), TransactionManager.class.getName(), TransactionSynchronizationRegistry.class.getName(), XidImporter.class.getName(), MonitorableTransactionManager.class.getName(), RecoverableTransactionManager.class.getName() }, transactionManager, null); } @Invalidate public void unregister() throws Exception { if (registration != null) { registration.unregister(); registration = null; } if (transactionLog instanceof HowlLog) { ((HowlLog) transactionLog).stop(); } } }