1 /***
2 * Redistribution and use of this software and associated documentation
3 * ("Software"), with or without modification, are permitted provided
4 * that the following conditions are met:
5 *
6 * 1. Redistributions of source code must retain copyright
7 * statements and notices. Redistributions must also contain a
8 * copy of this document.
9 *
10 * 2. Redistributions in binary form must reproduce the
11 * above copyright notice, this list of conditions and the
12 * following disclaimer in the documentation and/or other
13 * materials provided with the distribution.
14 *
15 * 3. The name "Exolab" must not be used to endorse or promote
16 * products derived from this Software without prior written
17 * permission of Exoffice Technologies. For written permission,
18 * please contact info@exolab.org.
19 *
20 * 4. Products derived from this Software may not be called "Exolab"
21 * nor may "Exolab" appear in their names without prior written
22 * permission of Exoffice Technologies. Exolab is a registered
23 * trademark of Exoffice Technologies.
24 *
25 * 5. Due credit should be given to the Exolab Project
26 * (http://www.exolab.org/).
27 *
28 * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
32 * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39 * OF THE POSSIBILITY OF SUCH DAMAGE.
40 *
41 * Copyright 2001-2003 (C) Exoffice Technologies Inc. All Rights Reserved.
42 *
43 *
44 * $Id: TransactionLog.java,v 1.1 2004/11/26 01:51:01 tanderson Exp $
45 *
46 * Date Author Changes
47 * 20/11/2001 jima Created
48 */
49 package org.exolab.jms.tranlog;
50
51
52 import java.io.BufferedInputStream;
53 import java.io.BufferedOutputStream;
54 import java.io.DataInputStream;
55 import java.io.DataOutputStream;
56 import java.io.File;
57 import java.io.FileInputStream;
58 import java.io.FileNotFoundException;
59 import java.io.FileOutputStream;
60 import java.io.IOException;
61 import java.util.HashMap;
62 import java.util.LinkedList;
63
64 import org.apache.commons.logging.Log;
65 import org.apache.commons.logging.LogFactory;
66
67
68 /***
69 * The resource manager uses transaction logs to record the persistent records
70 * for the resource manager in case of recovery. All records are logged
71 * sequentially and each records has an associated XID. Log files have a finite
72 * size, after which they are closed and a new log file is opened. There is
73 * only one current transaction log file per resource manager.
74 */
75 public class TransactionLog {
76
77 /***
78 * The name of this log file
79 */
80 private String _name = null;
81
82 /***
83 * Maintains the running total of the file size
84 */
85 private long _size = 0;
86
87 /***
88 * Cache the DataOutputStream handle
89 */
90 private transient DataOutputStream _dos = null;
91
92 /***
93 * The logger
94 */
95 private static final Log _log = LogFactory.getLog(TransactionLog.class);
96
97
98 /***
99 * Create a transaction log with the specified name, which includes the
100 * directory it will reside in. If the create flag is true then it will
101 * create the log file. If the create flag is false then it will assume
102 * that the log file already exists and will attempt to open it.
103 * <p>
104 * Attempting to create a file that already exists or open a non-exisitent
105 * log file with throw the TransactionLogException exception.
106 *
107 * @param name - the name of the transaction log absolute or final
108 * @param vreate - flag inidicating whether to open or create the log
109 * @throws TransactionLogException
110 */
111 public TransactionLog(String name, boolean create)
112 throws TransactionLogException {
113 if ((name == null) ||
114 (name.length() == 0)) {
115 throw new IllegalArgumentException("Can't specify a null or empty name");
116 }
117
118 _name = name;
119 File file = new File(name);
120
121
122
123 if (create) {
124 if (file.exists()) {
125 throw new TransactionLogException(name +
126 " already exists");
127 } else {
128 try {
129 (new FileOutputStream(file)).close();
130 } catch (Exception exception) {
131
132 throw new TransactionLogException(
133 "Failed to create the log file " + name + " b/c" +
134 exception);
135 }
136 }
137 } else {
138
139
140 if (!file.exists()) {
141 throw new TransactionLogException(name + " does not exists");
142 }
143 }
144
145
146 _size = (new File(name)).length();
147 }
148
149 /***
150 * Return the name of the transaction log file
151 *
152 * @return String
153 */
154 public String getName() {
155 return _name;
156 }
157
158 /***
159 * Add an {@link StateTransactionLogEntry} using the specified txid,
160 * rid and state
161 *
162 * @param txid - the transaction identifier
163 * @param expiry - expiry time for the transaction
164 * @param rid - the resource identifier
165 * @param state - the transaction log state
166 * @throws TransactionLogException - if the entry cannot be created
167 */
168 public synchronized void logTransactionState(ExternalXid txid, long expiry,
169 String rid,
170 TransactionState state)
171 throws TransactionLogException {
172 try {
173 StateTransactionLogEntry entry = new StateTransactionLogEntry(txid, rid);
174 entry.setState(state);
175 entry.setExpiryTime(expiry);
176
177 DataOutputStream dos = getOutputStream();
178 byte[] blob = SerializationHelper.serialize(entry);
179 dos.writeLong(blob.length);
180 dos.write(blob, 0, blob.length);
181 dos.flush();
182
183
184 _size += blob.length;
185 } catch (Exception exception) {
186 throw new TransactionLogException("Error in logTransactionState " +
187 exception.toString());
188 }
189 }
190
191 /***
192 * Add an {@link DataTransactionLogEntry} using the specified txid,
193 * rid and data
194 *
195 * @param txid - the transaction identifier
196 * @param expiry - transaction expiry time
197 * @param rid - the resource identifier
198 * @param data - the opaque data to write
199 * @throws TransactionLogException - if the entry cannot be created
200 */
201 public synchronized void logTransactionData(ExternalXid txid, long expiry, String rid,
202 Object data)
203 throws TransactionLogException {
204 try {
205 DataTransactionLogEntry entry = new DataTransactionLogEntry(txid, rid);
206 entry.setData(data);
207 entry.setExpiryTime(expiry);
208
209 DataOutputStream dos = getOutputStream();
210 byte[] blob = SerializationHelper.serialize(entry);
211 dos.writeLong(blob.length);
212 dos.write(blob, 0, blob.length);
213 dos.flush();
214
215
216 _size += blob.length;
217 } catch (Exception exception) {
218 throw new TransactionLogException("Error in logTransactionData " +
219 exception.toString());
220 }
221 }
222
223 /***
224 * Close the transaction log
225 *
226 * @throws TransactionLogException - if it fails to close the log
227 */
228 public void close()
229 throws TransactionLogException {
230 try {
231 if (_dos != null) {
232 _dos.close();
233 }
234 } catch (IOException exception) {
235 throw new TransactionLogException("Error in close " +
236 exception.toString());
237 }
238 }
239
240 /***
241 * Return the size of the transaction log file.
242 *
243 * @return long - the length of the file
244 */
245 public long size() {
246 return _size;
247 }
248
249 /***
250 * Force a recovery of this log file. This will close the output file stream
251 * if one is opened and then read each entry from the log file and send it to
252 * the specified listener, if one is allocated.
253 * <p>
254 * The returned data structure is a HashMap, where the key is a
255 * {@link ExternalXid} and the entries are LinkedList of {@link
256 * BaseTransactionLogEntry} objects
257 *
258 * @return HashMap - a list of open transactions
259 * @throws TransactionLogException - if there is a prob recovering
260 */
261 public synchronized HashMap recover()
262 throws TransactionLogException {
263 return getOpenTransactionList();
264 }
265
266 /***
267 * Check if we can garbage collect this transaction log. It will go through
268 * the log file and check to see whether there are any open transaction. If
269 * there are no open transactions the it is a candidate for garage collection
270 *
271 * @return boolean - true if we can garbage collect; false otherwise
272 */
273 public synchronized boolean canGarbageCollect() {
274 boolean result = false;
275
276 try {
277 HashMap records = getOpenTransactionList();
278 if (records.size() == 0) {
279 result = true;
280 }
281 } catch (Exception ignore) {
282 ignore.printStackTrace();
283 }
284
285 return result;
286 }
287
288 /***
289 * Destroy this transaction log, which basically removes it from the
290 * file system
291 *
292 * @throws TransactionLogException
293 */
294 public synchronized void destroy()
295 throws TransactionLogException {
296 try {
297 close();
298 if (!(new File(_name)).delete()) {
299 _log.error("Failed to destroy " + _name);
300 }
301 } catch (Exception exception) {
302 throw new TransactionLogException("Error in destroy " +
303 exception.toString());
304
305 }
306 }
307
308
309 public boolean equals(Object obj) {
310 boolean result = false;
311
312 if ((obj instanceof TransactionLog) &&
313 (((TransactionLog) obj)._name.equals(_name))) {
314 result = true;
315 }
316
317 return result;
318 }
319
320 /***
321 * Return an instance of the output stream. If one does not exist then
322 * create it.
323 *
324 * @return DataOutputStream - the output stream
325 */
326 private DataOutputStream getOutputStream()
327 throws IOException, FileNotFoundException {
328 if (_dos == null) {
329 _dos = new DataOutputStream(
330 new BufferedOutputStream(
331 new FileOutputStream(_name, true)));
332 }
333
334 return _dos;
335 }
336
337 /***
338 * Return a list of open transactions in a HashMap. The key is the transaction
339 * id and the data is a vector of associated data records in a LinkedList
340 *
341 * @return HashMap
342 * @throws TransactionLogException - if there is a prob recovering
343 */
344 private HashMap getOpenTransactionList()
345 throws TransactionLogException {
346
347 HashMap records = new HashMap();
348
349
350 try {
351 if (_dos != null) {
352 _dos.close();
353 _dos = null;
354 }
355 } catch (Exception exception) {
356 throw new TransactionLogException("Error in recover " +
357 exception.toString());
358 }
359
360
361 FileInputStream fis = null;
362 try {
363 fis = new FileInputStream(_name);
364 DataInputStream dis = new DataInputStream(new BufferedInputStream(fis));
365
366 while (dis.available() > 0) {
367 byte[] blob = new byte[(int) dis.readLong()];
368 dis.readFully(blob);
369 Object object = SerializationHelper.deserialize(blob);
370 if (object instanceof StateTransactionLogEntry) {
371 StateTransactionLogEntry state = (StateTransactionLogEntry) object;
372 LinkedList list = null;
373 switch (state.getState().getOrd()) {
374 case TransactionState.OPENED_ORD:
375 if (records.containsKey(state.getExternalXid())) {
376 _log.error("OPENED_ORD : Transaction log is inconsistent");
377 continue;
378 }
379
380 list = new LinkedList();
381 records.put(state.getExternalXid(), list);
382 list.add(state);
383 break;
384
385 case TransactionState.PREPARED_ORD:
386 list = (LinkedList) records.get(state.getExternalXid());
387 if (list == null) {
388 _log.error("PREPARED_ORD : Transaction log is inconsistent");
389 continue;
390 }
391
392 list.add(state);
393 break;
394
395 case TransactionState.CLOSED_ORD:
396 if (records.get(state.getExternalXid()) == null) {
397 _log.error("CLOSED_ORD : Transaction log is inconsistent");
398 continue;
399 }
400
401 records.remove(state.getExternalXid());
402 break;
403
404 default:
405 break;
406 }
407 } else if (object instanceof DataTransactionLogEntry) {
408 DataTransactionLogEntry data = (DataTransactionLogEntry) object;
409 LinkedList list = (LinkedList) records.get(data.getExternalXid());
410 if (list == null) {
411 _log.error("DATA : Transaction log is inconsistent");
412 continue;
413 }
414
415 list.add(data);
416 } else {
417 System.err.println("There is no support for log entry " +
418 "records of type " + object.getClass().getName());
419 }
420
421 }
422 } catch (Exception exception) {
423 throw new TransactionLogException("Error in recover " +
424 exception.toString());
425 } finally {
426 if (fis != null) {
427 try {
428 fis.close();
429 } catch (Exception exception) {
430 throw new TransactionLogException("Error in recover " +
431 exception.toString());
432 }
433 }
434 }
435
436 return records;
437
438 }
439
440 }