001 /** 002 * 003 * Copyright 2004 The Apache Software Foundation 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.geronimo.transaction.log; 019 020 import java.io.IOException; 021 import java.io.File; 022 import java.util.Collection; 023 import java.util.HashMap; 024 import java.util.Iterator; 025 import java.util.List; 026 import java.util.Map; 027 import javax.transaction.xa.Xid; 028 029 import org.apache.commons.logging.Log; 030 import org.apache.commons.logging.LogFactory; 031 import org.apache.geronimo.transaction.manager.LogException; 032 import org.apache.geronimo.transaction.manager.Recovery; 033 import org.apache.geronimo.transaction.manager.TransactionBranchInfo; 034 import org.apache.geronimo.transaction.manager.TransactionBranchInfoImpl; 035 import org.apache.geronimo.transaction.manager.TransactionLog; 036 import org.apache.geronimo.transaction.manager.XidFactory; 037 import org.objectweb.howl.log.Configuration; 038 import org.objectweb.howl.log.LogClosedException; 039 import org.objectweb.howl.log.LogConfigurationException; 040 import org.objectweb.howl.log.LogFileOverflowException; 041 import org.objectweb.howl.log.LogRecord; 042 import org.objectweb.howl.log.LogRecordSizeException; 043 import org.objectweb.howl.log.LogRecordType; 044 import org.objectweb.howl.log.ReplayListener; 045 import org.objectweb.howl.log.xa.XACommittingTx; 046 import org.objectweb.howl.log.xa.XALogRecord; 047 import org.objectweb.howl.log.xa.XALogger; 048 049 /** 050 * @version $Rev: 472646 $ $Date: 2006-11-08 13:11:28 -0800 (Wed, 08 Nov 2006) $ 051 */ 052 public class HOWLLog implements TransactionLog { 053 // static final byte PREPARE = 1; 054 //these are used as debugging aids only 055 private static final byte COMMIT = 2; 056 private static final byte ROLLBACK = 3; 057 058 static final String[] TYPE_NAMES = {null, "PREPARE", "COMMIT", "ROLLBACK"}; 059 060 private static final Log log = LogFactory.getLog(HOWLLog.class); 061 062 private File serverBaseDir; 063 private String logFileDir; 064 065 private final XidFactory xidFactory; 066 067 private final XALogger logger; 068 private final Configuration configuration = new Configuration(); 069 private boolean started = false; 070 private HashMap recovered; 071 072 public HOWLLog(String bufferClassName, 073 int bufferSize, 074 boolean checksumEnabled, 075 boolean adler32Checksum, 076 int flushSleepTimeMilliseconds, 077 String logFileDir, 078 String logFileExt, 079 String logFileName, 080 int maxBlocksPerFile, 081 int maxBuffers, 082 int maxLogFiles, 083 int minBuffers, 084 int threadsWaitingForceThreshold, 085 XidFactory xidFactory, 086 File serverBaseDir) throws IOException, LogConfigurationException { 087 this.serverBaseDir = serverBaseDir; 088 setBufferClassName(bufferClassName); 089 setBufferSizeKBytes(bufferSize); 090 setChecksumEnabled(checksumEnabled); 091 setAdler32Checksum(adler32Checksum); 092 setFlushSleepTimeMilliseconds(flushSleepTimeMilliseconds); 093 //setLogFileDir(logFileDir); 094 this.logFileDir = logFileDir; 095 setLogFileExt(logFileExt); 096 setLogFileName(logFileName); 097 setMaxBlocksPerFile(maxBlocksPerFile); 098 setMaxBuffers(maxBuffers); 099 setMaxLogFiles(maxLogFiles); 100 setMinBuffers(minBuffers); 101 setThreadsWaitingForceThreshold(threadsWaitingForceThreshold); 102 this.xidFactory = xidFactory; 103 this.logger = new XALogger(configuration); 104 } 105 106 public String getLogFileDir() { 107 return logFileDir; 108 } 109 110 public void setLogFileDir(String logDirName) { 111 File logDir = new File(logDirName); 112 if (!logDir.isAbsolute()) { 113 logDir = new File(serverBaseDir, logDirName); 114 } 115 116 this.logFileDir = logDirName; 117 if (started) { 118 configuration.setLogFileDir(logDir.getAbsolutePath()); 119 } 120 } 121 122 public String getLogFileExt() { 123 return configuration.getLogFileExt(); 124 } 125 126 public void setLogFileExt(String logFileExt) { 127 configuration.setLogFileExt(logFileExt); 128 } 129 130 public String getLogFileName() { 131 return configuration.getLogFileName(); 132 } 133 134 public void setLogFileName(String logFileName) { 135 configuration.setLogFileName(logFileName); 136 } 137 138 public boolean isChecksumEnabled() { 139 return configuration.isChecksumEnabled(); 140 } 141 142 public void setChecksumEnabled(boolean checksumOption) { 143 configuration.setChecksumEnabled(checksumOption); 144 } 145 146 public boolean isAdler32ChecksumEnabled() { 147 return configuration.isAdler32ChecksumEnabled(); 148 } 149 150 public void setAdler32Checksum(boolean checksumOption) { 151 configuration.setAdler32Checksum(checksumOption); 152 } 153 154 public int getBufferSizeKBytes() { 155 return configuration.getBufferSize(); 156 } 157 158 public void setBufferSizeKBytes(int bufferSize) throws LogConfigurationException { 159 configuration.setBufferSize(bufferSize); 160 } 161 162 public String getBufferClassName() { 163 return configuration.getBufferClassName(); 164 } 165 166 public void setBufferClassName(String bufferClassName) { 167 configuration.setBufferClassName(bufferClassName); 168 } 169 170 public int getMaxBuffers() { 171 return configuration.getMaxBuffers(); 172 } 173 174 public void setMaxBuffers(int maxBuffers) throws LogConfigurationException { 175 configuration.setMaxBuffers(maxBuffers); 176 } 177 178 public int getMinBuffers() { 179 return configuration.getMinBuffers(); 180 } 181 182 public void setMinBuffers(int minBuffers) throws LogConfigurationException { 183 configuration.setMinBuffers(minBuffers); 184 } 185 186 public int getFlushSleepTimeMilliseconds() { 187 return configuration.getFlushSleepTime(); 188 } 189 190 public void setFlushSleepTimeMilliseconds(int flushSleepTime) { 191 configuration.setFlushSleepTime(flushSleepTime); 192 } 193 194 public int getThreadsWaitingForceThreshold() { 195 return configuration.getThreadsWaitingForceThreshold(); 196 } 197 198 public void setThreadsWaitingForceThreshold(int threadsWaitingForceThreshold) { 199 configuration.setThreadsWaitingForceThreshold(threadsWaitingForceThreshold == -1 ? Integer.MAX_VALUE : threadsWaitingForceThreshold); 200 } 201 202 public int getMaxBlocksPerFile() { 203 return configuration.getMaxBlocksPerFile(); 204 } 205 206 public void setMaxBlocksPerFile(int maxBlocksPerFile) { 207 configuration.setMaxBlocksPerFile(maxBlocksPerFile == -1 ? Integer.MAX_VALUE : maxBlocksPerFile); 208 } 209 210 public int getMaxLogFiles() { 211 return configuration.getMaxLogFiles(); 212 } 213 214 public void setMaxLogFiles(int maxLogFiles) { 215 configuration.setMaxLogFiles(maxLogFiles); 216 } 217 218 public void doStart() throws Exception { 219 started = true; 220 setLogFileDir(logFileDir); 221 log.debug("Initiating transaction manager recovery"); 222 recovered = new HashMap(); 223 224 logger.open(null); 225 226 ReplayListener replayListener = new GeronimoReplayListener(xidFactory, recovered); 227 logger.replayActiveTx(replayListener); 228 229 log.debug("In doubt transactions recovered from log"); 230 } 231 232 public void doStop() throws Exception { 233 started = false; 234 logger.close(); 235 recovered = null; 236 } 237 238 public void doFail() { 239 } 240 241 public void begin(Xid xid) throws LogException { 242 } 243 244 public Object prepare(Xid xid, List branches) throws LogException { 245 int branchCount = branches.size(); 246 byte[][] data = new byte[3 + 2 * branchCount][]; 247 data[0] = intToBytes(xid.getFormatId()); 248 data[1] = xid.getGlobalTransactionId(); 249 data[2] = xid.getBranchQualifier(); 250 int i = 3; 251 for (Iterator iterator = branches.iterator(); iterator.hasNext();) { 252 TransactionBranchInfo transactionBranchInfo = (TransactionBranchInfo) iterator.next(); 253 data[i++] = transactionBranchInfo.getBranchXid().getBranchQualifier(); 254 data[i++] = transactionBranchInfo.getResourceName().getBytes(); 255 } 256 try { 257 XACommittingTx committingTx = logger.putCommit(data); 258 return committingTx; 259 } catch (LogClosedException e) { 260 throw (IllegalStateException) new IllegalStateException().initCause(e); 261 } catch (LogRecordSizeException e) { 262 throw (IllegalStateException) new IllegalStateException().initCause(e); 263 } catch (LogFileOverflowException e) { 264 throw (IllegalStateException) new IllegalStateException().initCause(e); 265 } catch (InterruptedException e) { 266 throw (IllegalStateException) new IllegalStateException().initCause(e); 267 } catch (IOException e) { 268 throw new LogException(e); 269 } 270 } 271 272 public void commit(Xid xid, Object logMark) throws LogException { 273 //the data is theoretically unnecessary but is included to help with debugging and because HOWL currently requires it. 274 byte[][] data = new byte[4][]; 275 data[0] = new byte[]{COMMIT}; 276 data[1] = intToBytes(xid.getFormatId()); 277 data[2] = xid.getGlobalTransactionId(); 278 data[3] = xid.getBranchQualifier(); 279 try { 280 logger.putDone(data, (XACommittingTx) logMark); 281 // logger.putDone(null, (XACommittingTx) logMark); 282 } catch (LogClosedException e) { 283 throw (IllegalStateException) new IllegalStateException().initCause(e); 284 } catch (LogRecordSizeException e) { 285 throw (IllegalStateException) new IllegalStateException().initCause(e); 286 } catch (LogFileOverflowException e) { 287 throw (IllegalStateException) new IllegalStateException().initCause(e); 288 } catch (InterruptedException e) { 289 throw (IllegalStateException) new IllegalStateException().initCause(e); 290 } catch (IOException e) { 291 throw new LogException(e); 292 } 293 } 294 295 public void rollback(Xid xid, Object logMark) throws LogException { 296 //the data is theoretically unnecessary but is included to help with debugging and because HOWL currently requires it. 297 byte[][] data = new byte[4][]; 298 data[0] = new byte[]{ROLLBACK}; 299 data[1] = intToBytes(xid.getFormatId()); 300 data[2] = xid.getGlobalTransactionId(); 301 data[3] = xid.getBranchQualifier(); 302 try { 303 logger.putDone(data, (XACommittingTx) logMark); 304 // logger.putDone(null, (XACommittingTx) logMark); 305 } catch (LogClosedException e) { 306 throw (IllegalStateException) new IllegalStateException().initCause(e); 307 } catch (LogRecordSizeException e) { 308 throw (IllegalStateException) new IllegalStateException().initCause(e); 309 } catch (LogFileOverflowException e) { 310 throw (IllegalStateException) new IllegalStateException().initCause(e); 311 } catch (InterruptedException e) { 312 throw (IllegalStateException) new IllegalStateException().initCause(e); 313 } catch (IOException e) { 314 throw new LogException(e); 315 } 316 } 317 318 public Collection recover(XidFactory xidFactory) throws LogException { 319 log.debug("Initiating transaction manager recovery"); 320 Map recovered = new HashMap(); 321 ReplayListener replayListener = new GeronimoReplayListener(xidFactory, recovered); 322 logger.replayActiveTx(replayListener); 323 log.debug("In doubt transactions recovered from log"); 324 return recovered.values(); 325 } 326 327 public String getXMLStats() { 328 return logger.getStats(); 329 } 330 331 public int getAverageForceTime() { 332 return 0;//logger.getAverageForceTime(); 333 } 334 335 public int getAverageBytesPerForce() { 336 return 0;//logger.getAverageBytesPerForce(); 337 } 338 339 private byte[] intToBytes(int formatId) { 340 byte[] buffer = new byte[4]; 341 buffer[0] = (byte) (formatId >> 24); 342 buffer[1] = (byte) (formatId >> 16); 343 buffer[2] = (byte) (formatId >> 8); 344 buffer[3] = (byte) (formatId >> 0); 345 return buffer; 346 } 347 348 private int bytesToInt(byte[] buffer) { 349 return ((int) buffer[0]) << 24 + ((int) buffer[1]) << 16 + ((int) buffer[2]) << 8 + ((int) buffer[3]) << 0; 350 } 351 352 private class GeronimoReplayListener implements ReplayListener { 353 354 private final XidFactory xidFactory; 355 private final Map recoveredTx; 356 357 public GeronimoReplayListener(XidFactory xidFactory, Map recoveredTx) { 358 this.xidFactory = xidFactory; 359 this.recoveredTx = recoveredTx; 360 } 361 362 public void onRecord(LogRecord plainlr) { 363 XALogRecord lr = (XALogRecord) plainlr; 364 short recordType = lr.type; 365 XACommittingTx tx = lr.getTx(); 366 if (recordType == LogRecordType.XACOMMIT) { 367 368 byte[][] data = tx.getRecord(); 369 370 assert data[0].length == 4; 371 int formatId = bytesToInt(data[1]); 372 byte[] globalId = data[1]; 373 byte[] branchId = data[2]; 374 Xid masterXid = xidFactory.recover(formatId, globalId, branchId); 375 376 Recovery.XidBranchesPair xidBranchesPair = new Recovery.XidBranchesPair(masterXid, tx); 377 recoveredTx.put(masterXid, xidBranchesPair); 378 log.debug("recovered prepare record for master xid: " + masterXid); 379 for (int i = 3; i < data.length; i += 2) { 380 byte[] branchBranchId = data[i]; 381 String name = new String(data[i + 1]); 382 383 Xid branchXid = xidFactory.recover(formatId, globalId, branchBranchId); 384 TransactionBranchInfoImpl branchInfo = new TransactionBranchInfoImpl(branchXid, name); 385 xidBranchesPair.addBranch(branchInfo); 386 log.debug("recovered branch for resource manager, branchId " + name + ", " + branchXid); 387 } 388 } else { 389 if(recordType != LogRecordType.END_OF_LOG) { // This value crops up every time the server is started 390 log.warn("Received unexpected log record: " + lr +" ("+recordType+")"); 391 } 392 } 393 } 394 395 public void onError(org.objectweb.howl.log.LogException exception) { 396 log.error("Error during recovery: ", exception); 397 } 398 399 public LogRecord getLogRecord() { 400 //TODO justify this size estimate 401 return new LogRecord(10 * 2 * Xid.MAXBQUALSIZE); 402 } 403 404 } 405 }