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