Author: ggrzybek Date: Tue May 9 09:03:56 2017 New Revision: 1794508 URL: http://svn.apache.org/viewvc?rev=1794508&view=rev Log: [ARIES-1719] Adjust HOWL log on configuration change
Added: aries/trunk/transaction/transaction-manager/src/main/java/org/apache/aries/transaction/internal/TransactionLogUtils.java aries/trunk/transaction/transaction-manager/src/test/java/org/apache/aries/transaction/internal/LogConversionTest.java Modified: aries/trunk/transaction/transaction-itests/src/test/java/org/apache/aries/transaction/itests/AbstractIntegrationTest.java aries/trunk/transaction/transaction-manager/pom.xml aries/trunk/transaction/transaction-manager/src/main/java/org/apache/aries/transaction/internal/Activator.java aries/trunk/transaction/transaction-manager/src/main/java/org/apache/aries/transaction/internal/TransactionManagerService.java aries/trunk/transaction/transaction-manager/src/main/resources/org/apache/aries/transaction/internal/txManager.properties Modified: aries/trunk/transaction/transaction-itests/src/test/java/org/apache/aries/transaction/itests/AbstractIntegrationTest.java URL: http://svn.apache.org/viewvc/aries/trunk/transaction/transaction-itests/src/test/java/org/apache/aries/transaction/itests/AbstractIntegrationTest.java?rev=1794508&r1=1794507&r2=1794508&view=diff ============================================================================== --- aries/trunk/transaction/transaction-itests/src/test/java/org/apache/aries/transaction/itests/AbstractIntegrationTest.java (original) +++ aries/trunk/transaction/transaction-itests/src/test/java/org/apache/aries/transaction/itests/AbstractIntegrationTest.java Tue May 9 09:03:56 2017 @@ -74,6 +74,8 @@ public abstract class AbstractIntegratio // this is how you set the default log level when using pax // logging (logProfile) systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("INFO"), + // this option helps with debugging + //vmOption("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5006"), when(localRepo != null).useOptions(vmOption("-Dorg.ops4j.pax.url.mvn.localRepository=" + localRepo)) ); } Modified: aries/trunk/transaction/transaction-manager/pom.xml URL: http://svn.apache.org/viewvc/aries/trunk/transaction/transaction-manager/pom.xml?rev=1794508&r1=1794507&r2=1794508&view=diff ============================================================================== --- aries/trunk/transaction/transaction-manager/pom.xml (original) +++ aries/trunk/transaction/transaction-manager/pom.xml Tue May 9 09:03:56 2017 @@ -135,6 +135,12 @@ <artifactId>slf4j-simple</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <version>2.5</version> + <scope>test</scope> + </dependency> </dependencies> <build> Modified: aries/trunk/transaction/transaction-manager/src/main/java/org/apache/aries/transaction/internal/Activator.java URL: http://svn.apache.org/viewvc/aries/trunk/transaction/transaction-manager/src/main/java/org/apache/aries/transaction/internal/Activator.java?rev=1794508&r1=1794507&r2=1794508&view=diff ============================================================================== --- aries/trunk/transaction/transaction-manager/src/main/java/org/apache/aries/transaction/internal/Activator.java (original) +++ aries/trunk/transaction/transaction-manager/src/main/java/org/apache/aries/transaction/internal/Activator.java Tue May 9 09:03:56 2017 @@ -16,6 +16,7 @@ */ package org.apache.aries.transaction.internal; +import java.io.IOException; import java.util.Dictionary; import java.util.Enumeration; import java.util.Hashtable; @@ -31,6 +32,9 @@ import org.osgi.service.cm.ManagedServic import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.aries.transaction.internal.TransactionManagerService.DEFAULT_RECOVERABLE; +import static org.apache.aries.transaction.internal.TransactionManagerService.RECOVERABLE; + /** */ public class Activator implements BundleActivator, ManagedService { @@ -41,12 +45,12 @@ public class Activator implements Bundle private BundleContext bundleContext; private TransactionManagerService manager; - private Dictionary properties; + private Dictionary<String, ?> properties; public void start(BundleContext bundleContext) throws Exception { this.bundleContext = bundleContext; // Make sure TransactionManager comes up even if no config admin is installed - Dictionary properties = getInitialConfig(); + Dictionary<String, Object> properties = getInitialConfig(); updated(properties); bundleContext.registerService(ManagedService.class.getName(), this, getProps()); } @@ -81,12 +85,25 @@ public class Activator implements Bundle deleted(); } - public synchronized void updated(@SuppressWarnings("rawtypes") Dictionary properties) throws ConfigurationException { + @SuppressWarnings("unchecked") + public synchronized void updated(Dictionary<String, ?> properties) throws ConfigurationException { if (properties == null) { properties = getProps(); } if (!equals(this.properties, properties)) { deleted(); + + // ARIES-1719 - copy tx log with different configuration + // we can move active transactions (LogRecordType.XACOMMIT without XADONE) + // to different tx log + try { + if (TransactionManagerService.getBool(properties, RECOVERABLE, DEFAULT_RECOVERABLE)) { + TransactionLogUtils.copyActiveTransactions((Dictionary<String, Object>) this.properties, properties); + } + } catch (IOException e) { + log.error(NLS.MESSAGES.getMessage("exception.tx.manager.start"), e); + } + this.properties = properties; manager = new TransactionManagerService(PID, properties, bundleContext); try { @@ -97,7 +114,7 @@ public class Activator implements Bundle } } - private boolean equals(Dictionary<String, Object> d1, Dictionary<String, Object> d2) { + private boolean equals(Dictionary<String, ?> d1, Dictionary<String, ?> d2) { if (d1 == d2) { return true; } else if (d1 == null ^ d2 == null) { Added: aries/trunk/transaction/transaction-manager/src/main/java/org/apache/aries/transaction/internal/TransactionLogUtils.java URL: http://svn.apache.org/viewvc/aries/trunk/transaction/transaction-manager/src/main/java/org/apache/aries/transaction/internal/TransactionLogUtils.java?rev=1794508&view=auto ============================================================================== --- aries/trunk/transaction/transaction-manager/src/main/java/org/apache/aries/transaction/internal/TransactionLogUtils.java (added) +++ aries/trunk/transaction/transaction-manager/src/main/java/org/apache/aries/transaction/internal/TransactionLogUtils.java Tue May 9 09:03:56 2017 @@ -0,0 +1,361 @@ +/* + * 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.io.FilenameFilter; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.geronimo.transaction.log.HOWLLog; +import org.apache.geronimo.transaction.manager.Recovery; +import org.apache.geronimo.transaction.manager.TransactionBranchInfo; +import org.apache.geronimo.transaction.manager.XidFactory; +import org.objectweb.howl.log.LogRecordType; +import org.osgi.service.cm.ConfigurationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.aries.transaction.internal.TransactionManagerService.*; + +public class TransactionLogUtils { + + public static Logger log = LoggerFactory.getLogger(TransactionLogUtils.class); + private static Pattern TX_FILE_NAME = Pattern.compile("(.*)_([0-9]+)\\.([^.]+)"); + + /** + * <p>When <code>org.apache.aries.transaction</code> PID changes, there may be a need to copy + * entries from transaction log when some important configuration changed (like block size)</p> + * @param oldConfiguration previous configuration when configuration changed, may be <code>null</code> when starting bundle + * @param newConfiguration configuration to create new transaction manager + * @return <code>true</code> if there was conversion performed + */ + @SuppressWarnings("unchecked") + public static boolean copyActiveTransactions(Dictionary<String, Object> oldConfiguration, Dictionary<String, ?> newConfiguration) + throws ConfigurationException, IOException { + boolean initialConfiguration = false; + if (oldConfiguration == null) { + oldConfiguration = new Hashtable<String, Object>(); + // initialConfiguration means we don't know the location of "old" logs (if there are any) and + // assume there may be logs in newLogDirectory + initialConfiguration = true; + } + if (oldConfiguration.get(HOWL_LOG_FILE_DIR) == null) { + // we will be adjusting oldConfiguration to be able to create "old HOWLLog" + oldConfiguration.put(HOWL_LOG_FILE_DIR, newConfiguration.get(HOWL_LOG_FILE_DIR)); + } + String oldLogDirectory = (String) oldConfiguration.get(HOWL_LOG_FILE_DIR); + String newLogDirectory = (String) newConfiguration.get(HOWL_LOG_FILE_DIR); + + if (newLogDirectory == null || oldLogDirectory == null) { + // handle with exceptions at TM creation time + return false; + } + + File oldDir = new File(oldLogDirectory); + File newDir = new File(newLogDirectory); + // a file which may tell us what's the previous configuation + File transaction_1 = null; + + if (!oldDir.equals(newDir)) { + // recent logs are in oldDir, so even if newDir contains some logs, we will remove them + deleteDirectory(newDir); + transaction_1 = new File(oldDir, configuredTransactionLogName(oldConfiguration, 1)); + } else { + // we may need to move oldDir to some temporary location, if the configuration is changed + // we'll then have to copy old tx log to new one + transaction_1 = new File(oldDir, configuredTransactionLogName(oldConfiguration, 1)); + if (!transaction_1.exists() || transaction_1.length() == 0L) { + oldConfiguration.put(HOWL_LOG_FILE_NAME, getString(newConfiguration, HOWL_LOG_FILE_NAME, "transaction")); + oldConfiguration.put(HOWL_LOG_FILE_EXT, getString(newConfiguration, HOWL_LOG_FILE_EXT, "log")); + transaction_1 = new File(oldDir, configuredTransactionLogName(newConfiguration, 1)); + } + } + + if (!transaction_1.exists() || transaction_1.length() == 0L) { + // no need to copy anything + return false; + } + + BaseTxLogConfig oldTxConfig = transactionLogFileConfig(transaction_1); + BaseTxLogConfig newTxConfig = transactionLogFileConfig(newConfiguration); + + if (oldTxConfig == null || oldTxConfig.equals(newTxConfig)) { + // old log files compatible, but maybe we have to copy them + if (!oldDir.equals(newDir)) { + if (!oldDir.renameTo(newDir)) { + log.warn(NLS.MESSAGES.getMessage("tx.log.problem.renaming", oldDir.getAbsolutePath())); + return false; + } + } + // files are compatible - we'll check one more thing - name_N.extension + String oldName = configuredTransactionLogName(oldConfiguration, 1); + String newName = configuredTransactionLogName(newConfiguration, 1); + if (!oldName.equals(newName)) { + final Dictionary<String, Object> finalOldConfiguration = oldConfiguration; + final Dictionary<String, ?> finalNewConfiguration = newConfiguration; + final Map<String, String> changes = new HashMap<String, String>(); + newDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + Matcher matcher = TX_FILE_NAME.matcher(name); + if (matcher.matches()) { + if (matcher.group(1).equals(getString(finalOldConfiguration, HOWL_LOG_FILE_NAME, "transaction")) + && matcher.group(3).equals(getString(finalOldConfiguration, HOWL_LOG_FILE_EXT, "log"))) { + changes.put(name, String.format("%s_%d.%s", + getString(finalNewConfiguration, HOWL_LOG_FILE_NAME, "transaction"), + Integer.parseInt(matcher.group(2)), + getString(finalNewConfiguration, HOWL_LOG_FILE_EXT, "log"))); + } + } + return false; + } + }); + + for (String old : changes.keySet()) { + new File(newDir, old).renameTo(new File(newDir, changes.get(old))); + } + + return true; + } + return false; + } + + File backupDir = null; + if (oldDir.equals(newDir)) { + // move old dir to backup dir + backupDir = new File(newLogDirectory + String.format("-%016x", System.currentTimeMillis())); + if (!oldDir.renameTo(backupDir)) { + log.warn(NLS.MESSAGES.getMessage("tx.log.problem.renaming", oldDir.getAbsolutePath())); + return false; + } + oldConfiguration = copy(oldConfiguration); + oldConfiguration.put(HOWL_LOG_FILE_DIR, backupDir.getAbsolutePath()); + } + + log.info(NLS.MESSAGES.getMessage("tx.log.conversion", oldDir.getAbsolutePath(), newDir.getAbsolutePath())); + + oldConfiguration.put(RECOVERABLE, newConfiguration.get(RECOVERABLE)); + oldConfiguration.put(HOWL_MAX_LOG_FILES, Integer.toString(oldTxConfig.maxLogFiles)); + oldConfiguration.put(HOWL_MAX_BLOCKS_PER_FILE, Integer.toString(oldTxConfig.maxBlocksPerFile)); + oldConfiguration.put(HOWL_BUFFER_SIZE, Integer.toString(oldTxConfig.bufferSizeKBytes)); + + String tmid1 = TransactionManagerService.getString(oldConfiguration, TMID, Activator.PID); + XidFactory xidFactory1 = new XidFactoryImpl(tmid1.substring(0, Math.min(tmid1.length(), 64)).getBytes()); + String tmid2 = TransactionManagerService.getString(newConfiguration, TMID, Activator.PID); + XidFactory xidFactory2 = new XidFactoryImpl(tmid2.substring(0, Math.min(tmid2.length(), 64)).getBytes()); + + org.apache.geronimo.transaction.manager.TransactionLog oldLog = null; + org.apache.geronimo.transaction.manager.TransactionLog newLog = null; + try { + oldLog = TransactionManagerService.createTransactionLog(oldConfiguration, xidFactory1); + newLog = TransactionManagerService.createTransactionLog(newConfiguration, xidFactory2); + + if (!(oldLog instanceof HOWLLog)) { + log.info(NLS.MESSAGES.getMessage("tx.log.notrecoverable", oldLogDirectory)); + return false; + } + if (!(newLog instanceof HOWLLog)) { + log.info(NLS.MESSAGES.getMessage("tx.log.notrecoverable", newLogDirectory)); + return false; + } + + HOWLLog from = (HOWLLog) oldLog; + HOWLLog to = (HOWLLog) newLog; + + Collection<Recovery.XidBranchesPair> pairs = from.recover(xidFactory1); + for (Recovery.XidBranchesPair xidBranchesPair : pairs) { + log.info(NLS.MESSAGES.getMessage("tx.log.migrate.xid", xidBranchesPair.getXid())); + for (TransactionBranchInfo branchInfo : xidBranchesPair.getBranches()) { + log.info(NLS.MESSAGES.getMessage("tx.log.migrate.xid.branch", branchInfo.getBranchXid(), branchInfo.getResourceName())); + } + to.prepare(xidBranchesPair.getXid(), new ArrayList<TransactionBranchInfo>(xidBranchesPair.getBranches())); + } + log.info(NLS.MESSAGES.getMessage("tx.log.migrate.complete")); + deleteDirectory(backupDir); + + return !pairs.isEmpty(); + } catch (Exception e) { + log.error(NLS.MESSAGES.getMessage("exception.tx.log.migration"), e); + if (backupDir != null) { + deleteDirectory(newDir); + backupDir.renameTo(oldDir); + } + return false; + } finally { + try { + if (oldLog instanceof HOWLLog) { + ((HOWLLog)oldLog).doStop(); + } + if (newLog instanceof HOWLLog) { + ((HOWLLog)newLog).doStop(); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + } + + /** + * Retrieves 3 important configuration parameters from single HOWL transaction log file + * @param txFile existing HOWL file + * @return + */ + private static BaseTxLogConfig transactionLogFileConfig(File txFile) throws IOException { + FileChannel channel = new RandomAccessFile(txFile, "r").getChannel(); + try { + ByteBuffer bb = ByteBuffer.wrap(new byte[1024]); + int read = channel.read(bb); + if (read < 0x47) { // enough data to have HOWL block header and FILE_HEADER record + return null; + } + + bb.rewind(); + if (bb.getInt() != 0x484f574c) { // HOWL + return null; + } + bb.getInt(); // BSN + int bufferSizeKBytes = bb.getInt() / 1024; + bb.getInt(); // size + bb.getInt(); // checksum + bb.getLong(); // timestamp + bb.getShort(); // 0x0d0a + if (bb.getShort() != LogRecordType.FILE_HEADER) { + return null; + } + bb.getShort(); // size + bb.getShort(); // size + bb.get(); // automark + bb.getLong(); // active mark + bb.getLong(); // log key + bb.getLong(); // timestamp + int maxLogFiles = bb.getInt(); + int maxBlocksPerFile = bb.getInt(); + if (maxBlocksPerFile == Integer.MAX_VALUE) { + maxBlocksPerFile = -1; + } + bb.getShort(); // 0x0d0a + + return new BaseTxLogConfig(maxLogFiles, maxBlocksPerFile, bufferSizeKBytes); + } finally { + channel.close(); + } + } + + /** + * Retrieves 3 important configuration parameters from configuration + * @param configuration + * @return + */ + private static BaseTxLogConfig transactionLogFileConfig(Dictionary<String, ?> configuration) throws ConfigurationException { + BaseTxLogConfig result = new BaseTxLogConfig(); + result.maxLogFiles = getInt(configuration, HOWL_MAX_LOG_FILES, 2); + result.maxBlocksPerFile = getInt(configuration, HOWL_MAX_BLOCKS_PER_FILE, -1); + result.bufferSizeKBytes = getInt(configuration, HOWL_BUFFER_SIZE, 4); + return result; + } + + private static String configuredTransactionLogName(Dictionary<String, ?> configuration, int number) throws ConfigurationException { + String logFileName = getString(configuration, HOWL_LOG_FILE_NAME, "transaction"); + String logFileExt = getString(configuration, HOWL_LOG_FILE_EXT, "log"); + return String.format("%s_%d.%s", logFileName, number, logFileExt); + } + + private static Dictionary<String, Object> copy(Dictionary<String, Object> configuration) { + Dictionary<String, Object> result = new Hashtable<String, Object>(); + for (Enumeration<String> keys = configuration.keys(); keys.hasMoreElements(); ) { + String k = keys.nextElement(); + result.put(k, configuration.get(k)); + } + return result; + } + + /** + * Recursively delete directory with content + * @param file + */ + private static boolean deleteDirectory(File file) { + if (file == null) { + return false; + } + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + for (File f : files) { + deleteDirectory(f); + } + } + return file.delete(); + } else { + if (!file.delete()) { + return false; + } + return true; + } + } + + private static class BaseTxLogConfig { + + public int maxLogFiles; + public int maxBlocksPerFile; + public int bufferSizeKBytes; + + public BaseTxLogConfig() { + } + + public BaseTxLogConfig(int maxLogFiles, int maxBlocksPerFile, int bufferSizeKBytes) { + this.maxLogFiles = maxLogFiles; + this.maxBlocksPerFile = maxBlocksPerFile; + this.bufferSizeKBytes = bufferSizeKBytes; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BaseTxLogConfig that = (BaseTxLogConfig) o; + + if (maxLogFiles != that.maxLogFiles) return false; + if (maxBlocksPerFile != that.maxBlocksPerFile) return false; + if (bufferSizeKBytes != that.bufferSizeKBytes) return false; + + return true; + } + + @Override + public int hashCode() { + int result = maxLogFiles; + result = 31 * result + maxBlocksPerFile; + result = 31 * result + bufferSizeKBytes; + return result; + } + + } + +} Modified: aries/trunk/transaction/transaction-manager/src/main/java/org/apache/aries/transaction/internal/TransactionManagerService.java URL: http://svn.apache.org/viewvc/aries/trunk/transaction/transaction-manager/src/main/java/org/apache/aries/transaction/internal/TransactionManagerService.java?rev=1794508&r1=1794507&r2=1794508&view=diff ============================================================================== --- aries/trunk/transaction/transaction-manager/src/main/java/org/apache/aries/transaction/internal/TransactionManagerService.java (original) +++ aries/trunk/transaction/transaction-manager/src/main/java/org/apache/aries/transaction/internal/TransactionManagerService.java Tue May 9 09:03:56 2017 @@ -78,67 +78,16 @@ public class TransactionManagerService { this.properties = properties; this.bundleContext = bundleContext; // Transaction timeout - int transactionTimeout = getInt(TRANSACTION_TIMEOUT, DEFAULT_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(TMID, pid); + 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 - if (getBool(RECOVERABLE, DEFAULT_RECOVERABLE)) { - String bufferClassName = getString(HOWL_BUFFER_CLASS_NAME, "org.objectweb.howl.log.BlockLogBuffer"); - int bufferSizeKBytes = getInt(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(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, NLS.MESSAGES.getMessage("min.buffers.greaterthan.zero")); - } - int maxBuffers = getInt(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(HOWL_THREADS_WAITING_FORCE_THRESHOLD, -1); - boolean flushPartialBuffers = getBool(HOWL_FLUSH_PARTIAL_BUFFERS, true); - 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, NLS.MESSAGES.getMessage("log.file.dir")); - } - try { - transactionLog = new HOWLLog(bufferClassName, - bufferSizeKBytes, - checksumEnabled, - adler32Checksum, - flushSleepTimeMilliseconds, - logFileDir, - logFileExt, - logFileName, - maxBlocksPerFile, - maxBuffers, - maxLogFiles, - minBuffers, - threadsWaitingForceThreshold, - flushPartialBuffers, - xidFactory, - null); - ((HOWLLog) transactionLog).doStart(); - } catch (Exception e) { - // This should not really happen as we've checked properties earlier - throw new ConfigurationException(null, e.getMessage(), e); - } - } else { - transactionLog = new UnrecoverableLog(); - } + transactionLog = createTransactionLog(this.properties, xidFactory); // Create transaction manager try { try { @@ -180,7 +129,7 @@ public class TransactionManagerService { } } - private String getString(String property, String dflt) throws ConfigurationException { + static String getString(Dictionary properties, String property, String dflt) { String value = (String) properties.get(property); if (value != null) { return value; @@ -188,7 +137,7 @@ public class TransactionManagerService { return dflt; } - private int getInt(String property, int dflt) throws ConfigurationException { + static int getInt(Dictionary properties, String property, int dflt) throws ConfigurationException { String value = (String) properties.get(property); if (value != null) { try { @@ -200,7 +149,7 @@ public class TransactionManagerService { return dflt; } - private boolean getBool(String property, boolean dflt) throws ConfigurationException { + static boolean getBool(Dictionary properties, String property, boolean dflt) throws ConfigurationException { String value = (String) properties.get(property); if (value != null) { try { @@ -212,6 +161,64 @@ public class TransactionManagerService { 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. Modified: aries/trunk/transaction/transaction-manager/src/main/resources/org/apache/aries/transaction/internal/txManager.properties URL: http://svn.apache.org/viewvc/aries/trunk/transaction/transaction-manager/src/main/resources/org/apache/aries/transaction/internal/txManager.properties?rev=1794508&r1=1794507&r2=1794508&view=diff ============================================================================== --- aries/trunk/transaction/transaction-manager/src/main/resources/org/apache/aries/transaction/internal/txManager.properties (original) +++ aries/trunk/transaction/transaction-manager/src/main/resources/org/apache/aries/transaction/internal/txManager.properties Tue May 9 09:03:56 2017 @@ -32,5 +32,10 @@ prop.value.not.int=The property {0} shou # {1} The property value prop.value.not.boolean=The property {0} should have an boolean value, but the value {1} is not a boolean. - - +tx.log.problem.renaming=Can't backup old transaction logs directory: {0} +tx.log.conversion=Copying transaction log from {0} to {1} +tx.log.notrecoverable=TransactionLog {0} is not recoverable +tx.log.migrate.xid=Copying active transaction with XID {0} +tx.log.migrate.xid.branch=- Copying branch {0} for resource {1} +tx.log.migrate.complete=Migration of active transactions finished +exception.tx.log.migration=An exception occurred while trying to migrate transaction log after changing configuration. Added: aries/trunk/transaction/transaction-manager/src/test/java/org/apache/aries/transaction/internal/LogConversionTest.java URL: http://svn.apache.org/viewvc/aries/trunk/transaction/transaction-manager/src/test/java/org/apache/aries/transaction/internal/LogConversionTest.java?rev=1794508&view=auto ============================================================================== --- aries/trunk/transaction/transaction-manager/src/test/java/org/apache/aries/transaction/internal/LogConversionTest.java (added) +++ aries/trunk/transaction/transaction-manager/src/test/java/org/apache/aries/transaction/internal/LogConversionTest.java Tue May 9 09:03:56 2017 @@ -0,0 +1,408 @@ +/* + * 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.io.RandomAccessFile; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.LinkedList; +import java.util.List; +import javax.transaction.xa.Xid; + +import org.apache.commons.io.FileUtils; +import org.apache.geronimo.transaction.log.HOWLLog; +import org.apache.geronimo.transaction.manager.TransactionBranchInfo; +import org.apache.geronimo.transaction.manager.XidFactory; +import org.apache.geronimo.transaction.manager.XidImpl; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.not; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +public class LogConversionTest { + + public static Logger LOG = LoggerFactory.getLogger(LogConversionTest.class); + private static XidFactory xidFactory = new TestXidFactoryImpl("org.apache.aries.transaction.test".getBytes()); + private static File BASE = new File(System.getProperty("user.dir"), "txlogs"); + + private static long start = 42L; + private static long count = start; + + @Test + public void initialConfiguration() throws Exception { + File logDir = new File(BASE, "initialConfiguration"); + FileUtils.deleteDirectory(logDir); + logDir.mkdirs(); + Dictionary<String, Object> properties = new Hashtable<String, Object>(); + HOWLLog txLog = createLog("initialConfiguration", "transaction", 2, -1, 1, properties); + + assertFalse(TransactionLogUtils.copyActiveTransactions(null, properties)); + } + + @Test + public void initialConfigurationEmptyTransactionLog() throws Exception { + File logDir = new File(BASE, "initialConfigurationEmptyTransactionLog"); + FileUtils.deleteDirectory(logDir); + logDir.mkdirs(); + Dictionary<String, Object> properties = new Hashtable<String, Object>(); + HOWLLog txLog = createLog("initialConfigurationEmptyTransactionLog", "transaction", 2, -1, 1, properties); + new RandomAccessFile(new File(logDir, "transaction_1.log"), "rw").close(); + new RandomAccessFile(new File(logDir, "transaction_2.log"), "rw").close(); + + assertFalse(TransactionLogUtils.copyActiveTransactions(null, properties)); + } + + @Test + public void initialConfigurationExistingTransactionLogNoChanges() throws Exception { + File logDir = new File(BASE, "initialConfigurationExistingTransactionLogNoChanges"); + FileUtils.deleteDirectory(logDir); + logDir.mkdirs(); + Dictionary<String, Object> properties = new Hashtable<String, Object>(); + HOWLLog txLog = createLog("initialConfigurationExistingTransactionLogNoChanges", "transaction", 2, -1, 1, properties); + txLog.doStart(); + transaction(txLog, 1, false); + txLog.doStop(); + + long lm = earlierLastModified(new File(logDir, "transaction_1.log")); + + assertFalse(TransactionLogUtils.copyActiveTransactions(null, properties)); + assertThat("Transaction log should not be touched", new File(logDir, "transaction_1.log").lastModified(), equalTo(lm)); + } + + @Test + @SuppressWarnings("unchecked") + public void existingTransactionLogChangedLogDir() throws Exception { + File logDir = new File(BASE, "existingTransactionLogChangedLogDir"); + File newLogDir = new File(BASE, "existingTransactionLogChangedLogDir-new"); + FileUtils.deleteDirectory(logDir); + FileUtils.deleteDirectory(newLogDir); + logDir.mkdirs(); + Hashtable<String, Object> properties = new Hashtable<String, Object>(); + HOWLLog txLog = createLog("existingTransactionLogChangedLogDir", "transaction", 2, -1, 1, properties); + txLog.doStart(); + transaction(txLog, 1, false); + txLog.doStop(); + + long lm = earlierLastModified(new File(logDir, "transaction_1.log")); + + Hashtable<String, Object> newConfig = (Hashtable<String, Object>) properties.clone(); + newConfig.put("aries.transaction.howl.logFileDir", newLogDir.getAbsolutePath()); + + assertFalse(TransactionLogUtils.copyActiveTransactions(properties, newConfig)); + assertThat("Transaction log should not be touched", new File(newLogDir, "transaction_1.log").lastModified(), equalTo(lm)); + assertFalse("Old transaction log should be moved", logDir.exists()); + assertTrue("New transaction log should be created", newLogDir.exists()); + } + + @Test + @SuppressWarnings("unchecked") + public void unknownExistingTransactionLogChangedLogDir() throws Exception { + File logDir = new File(BASE, "unknownExistingTransactionLogChangedLogDir"); + File newLogDir = new File(BASE, "unknownExistingTransactionLogChangedLogDir-new"); + FileUtils.deleteDirectory(logDir); + FileUtils.deleteDirectory(newLogDir); + logDir.mkdirs(); + Hashtable<String, Object> properties = new Hashtable<String, Object>(); + HOWLLog txLog = createLog("unknownExistingTransactionLogChangedLogDir", "transaction", 2, -1, 1, properties); + txLog.doStart(); + transaction(txLog, 1, false); + txLog.doStop(); + + Hashtable<String, Object> newConfig = (Hashtable<String, Object>) properties.clone(); + newConfig.put("aries.transaction.howl.logFileDir", newLogDir.getAbsolutePath()); + + assertFalse(TransactionLogUtils.copyActiveTransactions(null, newConfig)); + assertFalse("Transaction log should not exist", new File(newLogDir, "transaction_1.log").exists()); + assertTrue("Old transaction log should not be removed", logDir.exists()); + assertFalse("New transaction log should not be created (will be created on TX Manager startup)", newLogDir.exists()); + } + + @Test + @SuppressWarnings("unchecked") + public void changedNumberOfFiles() throws Exception { + File logDir = new File(BASE, "changedNumberOfFiles"); + FileUtils.deleteDirectory(logDir); + logDir.mkdirs(); + Hashtable<String, Object> properties = new Hashtable<String, Object>(); + HOWLLog txLog = createLog("changedNumberOfFiles", "transaction", 2, -1, 1, properties); + txLog.doStart(); + transaction(txLog, 1, false); + txLog.doStop(); + + Hashtable<String, Object> newConfig = (Hashtable<String, Object>) properties.clone(); + newConfig.put("aries.transaction.howl.maxLogFiles", "20"); + + long lm = earlierLastModified(new File(logDir, "transaction_1.log")); + + assertTrue(TransactionLogUtils.copyActiveTransactions(properties, newConfig)); + assertTrue("Transaction log should exist", new File(logDir, "transaction_1.log").exists()); + assertTrue("There should be 20 transaction log files", new File(logDir, "transaction_20.log").exists()); + assertThat("Transaction log should be processed", new File(logDir, "transaction_1.log").lastModified(), not(equalTo(lm))); + } + + private long earlierLastModified(File file) { + file.setLastModified(file.lastModified() - 10000L); + return file.lastModified(); + } + + @Test + @SuppressWarnings("unchecked") + public void changedMaxBlocksPerFile() throws Exception { + File logDir = new File(BASE, "changedMaxBlocksPerFile"); + FileUtils.deleteDirectory(logDir); + logDir.mkdirs(); + Hashtable<String, Object> properties = new Hashtable<String, Object>(); + HOWLLog txLog = createLog("changedMaxBlocksPerFile", "transaction", 3, -1, 1, properties); + txLog.doStart(); + transaction(txLog, 1, false); + txLog.doStop(); + + Hashtable<String, Object> newConfig = (Hashtable<String, Object>) properties.clone(); + newConfig.put("aries.transaction.howl.maxBlocksPerFile", "20"); + + long lm = earlierLastModified(new File(logDir, "transaction_1.log")); + + assertTrue(TransactionLogUtils.copyActiveTransactions(properties, newConfig)); + assertTrue("Transaction log should exist", new File(logDir, "transaction_1.log").exists()); + assertFalse("There should be 3 transaction log files", new File(logDir, "transaction_4.log").exists()); + assertThat("Transaction log should be processed", new File(logDir, "transaction_1.log").lastModified(), not(equalTo(lm))); + } + + @Test + @SuppressWarnings("unchecked") + public void changedBlockSize() throws Exception { + File logDir = new File(BASE, "changedBlockSize"); + FileUtils.deleteDirectory(logDir); + logDir.mkdirs(); + Hashtable<String, Object> properties = new Hashtable<String, Object>(); + HOWLLog txLog = createLog("changedBlockSize", "transaction", 3, -1, 1, properties); + txLog.doStart(); + transaction(txLog, 1, false); + txLog.doStop(); + + Hashtable<String, Object> newConfig = (Hashtable<String, Object>) properties.clone(); + newConfig.put("aries.transaction.howl.bufferSize", "32"); + + long lm = earlierLastModified(new File(logDir, "transaction_1.log")); + + assertTrue(TransactionLogUtils.copyActiveTransactions(properties, newConfig)); + assertTrue("Transaction log should exist", new File(logDir, "transaction_1.log").exists()); + assertThat("Transaction log should be processed", new File(logDir, "transaction_1.log").lastModified(), not(equalTo(lm))); + } + + @Test + @SuppressWarnings("unchecked") + public void changed3ParametersAndLogDir() throws Exception { + File logDir = new File(BASE, "changed3ParametersAndLogDir"); + File newLogDir = new File(BASE, "changed3ParametersAndLogDir-new"); + FileUtils.deleteDirectory(logDir); + FileUtils.deleteDirectory(newLogDir); + logDir.mkdirs(); + Hashtable<String, Object> properties = new Hashtable<String, Object>(); + HOWLLog txLog = createLog("changed3ParametersAndLogDir", "transaction", 3, -1, 1, properties); + txLog.doStart(); + transaction(txLog, 1, false); + txLog.doStop(); + + Hashtable<String, Object> newConfig = (Hashtable<String, Object>) properties.clone(); + newConfig.put("aries.transaction.howl.maxLogFiles", "4"); + newConfig.put("aries.transaction.howl.maxBlocksPerFile", "4"); + newConfig.put("aries.transaction.howl.bufferSize", "4"); + newConfig.put("aries.transaction.howl.logFileDir", newLogDir.getAbsolutePath()); + + long lm = earlierLastModified(new File(logDir, "transaction_1.log")); + + assertTrue(TransactionLogUtils.copyActiveTransactions(properties, newConfig)); + assertTrue("Old transaction log should exist", new File(logDir, "transaction_1.log").exists()); + assertTrue("New transaction log should exist", new File(newLogDir, "transaction_1.log").exists()); + assertThat("Old transaction log should be touched (HOWL Log opened)", new File(logDir, "transaction_1.log").lastModified(), not(equalTo(lm))); + } + + @Test + @SuppressWarnings("unchecked") + public void existingTransactionLogChangedLogFileName() throws Exception { + File logDir = new File(BASE, "existingTransactionLogChangedLogFileName"); + FileUtils.deleteDirectory(logDir); + logDir.mkdirs(); + Hashtable<String, Object> properties = new Hashtable<String, Object>(); + HOWLLog txLog = createLog("existingTransactionLogChangedLogFileName", "transaction", 2, -1, 1, properties); + txLog.doStart(); + transaction(txLog, 1, false); + txLog.doStop(); + + long lm = earlierLastModified(new File(logDir, "transaction_1.log")); + + Hashtable<String, Object> newConfig = (Hashtable<String, Object>) properties.clone(); + newConfig.put("aries.transaction.howl.logFileName", "megatransaction"); + + assertTrue(TransactionLogUtils.copyActiveTransactions(properties, newConfig)); + assertFalse("Old transaction log should not exist", new File(logDir, "transaction_1.log").exists()); + assertTrue("New transaction log should exist", new File(logDir, "megatransaction_1.log").exists()); + } + + @Test + @SuppressWarnings("unchecked") + public void existingTransactionLogChangedLogFileNameAndLogDir() throws Exception { + File logDir = new File(BASE, "existingTransactionLogChangedLogFileNameAndLogDir"); + File newLogDir = new File(BASE, "existingTransactionLogChangedLogFileNameAndLogDir-new"); + FileUtils.deleteDirectory(logDir); + logDir.mkdirs(); + Hashtable<String, Object> properties = new Hashtable<String, Object>(); + HOWLLog txLog = createLog("existingTransactionLogChangedLogFileNameAndLogDir", "transaction", 2, -1, 1, properties); + txLog.doStart(); + transaction(txLog, 1, false); + txLog.doStop(); + + long lm = earlierLastModified(new File(logDir, "transaction_1.log")); + + Hashtable<String, Object> newConfig = (Hashtable<String, Object>) properties.clone(); + newConfig.put("aries.transaction.howl.logFileName", "megatransaction"); + newConfig.put("aries.transaction.howl.logFileDir", newLogDir.getAbsolutePath()); + + assertTrue(TransactionLogUtils.copyActiveTransactions(properties, newConfig)); + assertFalse("Old transaction log should not exist", new File(logDir, "transaction_1.log").exists()); + assertTrue("New transaction log should exist", new File(newLogDir, "megatransaction_1.log").exists()); + } + + @Test + @SuppressWarnings("unchecked") + public void existingTransactionLogChangedLogFileNameAndBlockSize() throws Exception { + File logDir = new File(BASE, "existingTransactionLogChangedLogFileNameAndBlockSize"); + FileUtils.deleteDirectory(logDir); + logDir.mkdirs(); + Hashtable<String, Object> properties = new Hashtable<String, Object>(); + HOWLLog txLog = createLog("existingTransactionLogChangedLogFileNameAndBlockSize", "transaction", 2, -1, 1, properties); + txLog.doStart(); + transaction(txLog, 1, false); + txLog.doStop(); + + long lm = earlierLastModified(new File(logDir, "transaction_1.log")); + + Hashtable<String, Object> newConfig = (Hashtable<String, Object>) properties.clone(); + newConfig.put("aries.transaction.howl.logFileName", "megatransaction"); + newConfig.put("aries.transaction.howl.bufferSize", "4"); + + assertTrue(TransactionLogUtils.copyActiveTransactions(properties, newConfig)); + assertThat("Old transaction log should be touched (HOWL Log opened)", new File(logDir, "transaction_1.log").lastModified(), not(equalTo(lm))); + assertTrue("New transaction log should exist", new File(logDir, "megatransaction_1.log").exists()); + } + + private HOWLLog createLog(String logFileDir, String logFileName, + int maxLogFiles, int maxBlocksPerFile, int bufferSizeInKB, + Dictionary<String, Object> properties) throws Exception { + properties.put("aries.transaction.recoverable", "true"); + properties.put("aries.transaction.howl.bufferClassName", "org.objectweb.howl.log.BlockLogBuffer"); + properties.put("aries.transaction.howl.checksumEnabled", "true"); + properties.put("aries.transaction.howl.adler32Checksum", "true"); + properties.put("aries.transaction.howl.flushSleepTime", "50"); + properties.put("aries.transaction.howl.logFileExt", "log"); + properties.put("aries.transaction.howl.logFileName", logFileName); + properties.put("aries.transaction.howl.minBuffers", "1"); + properties.put("aries.transaction.howl.maxBuffers", "0"); + properties.put("aries.transaction.howl.threadsWaitingForceThreshold", "-1"); + properties.put("aries.transaction.flushPartialBuffers", "true"); + String absoluteLogFileDir = new File(BASE, logFileDir).getAbsolutePath(); + properties.put("aries.transaction.howl.logFileDir", absoluteLogFileDir); + properties.put("aries.transaction.howl.bufferSize", Integer.toString(bufferSizeInKB)); + properties.put("aries.transaction.howl.maxBlocksPerFile", Integer.toString(maxBlocksPerFile)); + properties.put("aries.transaction.howl.maxLogFiles", Integer.toString(maxLogFiles)); + + return new HOWLLog("org.objectweb.howl.log.BlockLogBuffer", bufferSizeInKB, + true, true, 50, + absoluteLogFileDir, "log", logFileName, + maxBlocksPerFile, 0, maxLogFiles, 1, -1, true, xidFactory, null); + } + + private void transaction(HOWLLog log, int transactionBranchCount, boolean commit) throws Exception { + Xid xid = xidFactory.createXid(); + List<TransactionBranchInfo> txBranches = new LinkedList<TransactionBranchInfo>(); + for (int b = 1; b <= transactionBranchCount; b++) { + // TransactionImpl.enlistResource() + Xid branchXid = xidFactory.createBranch(xid, b); + txBranches.add(new TestTransactionBranchInfo(branchXid, String.format("res-%02d", b))); + } + + // org.apache.geronimo.transaction.manager.TransactionImpl.internalPrepare() + Object logMark = log.prepare(xid, txBranches); + if (commit) { + // org.apache.geronimo.transaction.manager.CommitTask.run() + log.commit(xid, logMark); + } + } + + private static class TestTransactionBranchInfo implements TransactionBranchInfo { + + private final Xid xid; + private final String name; + + public TestTransactionBranchInfo(Xid xid, String name) { + this.xid = xid; + this.name = name; + } + + @Override + public String getResourceName() { + return name; + } + + @Override + public Xid getBranchXid() { + return xid; + } + + } + + private static class TestXidFactoryImpl extends XidFactoryImpl { + + private final byte[] baseId = new byte[Xid.MAXGTRIDSIZE]; + + public TestXidFactoryImpl(byte[] tmId) { + super(tmId); + System.arraycopy(tmId, 0, baseId, 8, tmId.length); + } + + @Override + public Xid createXid() { + byte[] globalId = baseId.clone(); + long id; + synchronized (this) { + id = count++; + } + insertLong(id, globalId, 0); + return new XidImpl(globalId); + } + + @Override + public Xid createBranch(Xid globalId, int branch) { + byte[] branchId = baseId.clone(); + branchId[0] = (byte) branch; + branchId[1] = (byte) (branch >>> 8); + branchId[2] = (byte) (branch >>> 16); + branchId[3] = (byte) (branch >>> 24); + insertLong(start, branchId, 4); + return new XidImpl(globalId, branchId); + } + + } + +}