1 /**
2 *
3 * Copyright 2004 The Apache Software Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.geronimo.transaction.log;
19
20 import java.io.IOException;
21 import java.io.File;
22 import java.util.Collection;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27 import javax.transaction.xa.Xid;
28
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.apache.geronimo.transaction.manager.LogException;
32 import org.apache.geronimo.transaction.manager.Recovery;
33 import org.apache.geronimo.transaction.manager.TransactionBranchInfo;
34 import org.apache.geronimo.transaction.manager.TransactionBranchInfoImpl;
35 import org.apache.geronimo.transaction.manager.TransactionLog;
36 import org.apache.geronimo.transaction.manager.XidFactory;
37 import org.objectweb.howl.log.Configuration;
38 import org.objectweb.howl.log.LogClosedException;
39 import org.objectweb.howl.log.LogConfigurationException;
40 import org.objectweb.howl.log.LogFileOverflowException;
41 import org.objectweb.howl.log.LogRecord;
42 import org.objectweb.howl.log.LogRecordSizeException;
43 import org.objectweb.howl.log.LogRecordType;
44 import org.objectweb.howl.log.ReplayListener;
45 import org.objectweb.howl.log.xa.XACommittingTx;
46 import org.objectweb.howl.log.xa.XALogRecord;
47 import org.objectweb.howl.log.xa.XALogger;
48
49 /**
50 * @version $Rev: 472646 $ $Date: 2006-11-08 13:11:28 -0800 (Wed, 08 Nov 2006) $
51 */
52 public class HOWLLog implements TransactionLog {
53
54
55 private static final byte COMMIT = 2;
56 private static final byte ROLLBACK = 3;
57
58 static final String[] TYPE_NAMES = {null, "PREPARE", "COMMIT", "ROLLBACK"};
59
60 private static final Log log = LogFactory.getLog(HOWLLog.class);
61
62 private File serverBaseDir;
63 private String logFileDir;
64
65 private final XidFactory xidFactory;
66
67 private final XALogger logger;
68 private final Configuration configuration = new Configuration();
69 private boolean started = false;
70 private HashMap recovered;
71
72 public HOWLLog(String bufferClassName,
73 int bufferSize,
74 boolean checksumEnabled,
75 boolean adler32Checksum,
76 int flushSleepTimeMilliseconds,
77 String logFileDir,
78 String logFileExt,
79 String logFileName,
80 int maxBlocksPerFile,
81 int maxBuffers,
82 int maxLogFiles,
83 int minBuffers,
84 int threadsWaitingForceThreshold,
85 XidFactory xidFactory,
86 File serverBaseDir) throws IOException, LogConfigurationException {
87 this.serverBaseDir = serverBaseDir;
88 setBufferClassName(bufferClassName);
89 setBufferSizeKBytes(bufferSize);
90 setChecksumEnabled(checksumEnabled);
91 setAdler32Checksum(adler32Checksum);
92 setFlushSleepTimeMilliseconds(flushSleepTimeMilliseconds);
93
94 this.logFileDir = logFileDir;
95 setLogFileExt(logFileExt);
96 setLogFileName(logFileName);
97 setMaxBlocksPerFile(maxBlocksPerFile);
98 setMaxBuffers(maxBuffers);
99 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
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
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
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
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;
333 }
334
335 public int getAverageBytesPerForce() {
336 return 0;
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) {
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
401 return new LogRecord(10 * 2 * Xid.MAXBQUALSIZE);
402 }
403
404 }
405 }