View Javadoc

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         // check if the file needs to be created and whether it already
122         // exists.
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                     // rethrow the exception
132                     throw new TransactionLogException(
133                         "Failed to create the log file " + name + " b/c" +
134                         exception);
135                 }
136             }
137         } else {
138             // check to see if a file needs to be open and that it actually
139             // exists.
140             if (!file.exists()) {
141                 throw new TransactionLogException(name + " does not exists");
142             }
143         }
144 
145         // set the size of the file
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             // update the size
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             // update the size
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     // override Object.equals
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         // if the output stream is opened then close it
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 } //-- TransactionLog