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.4 2003/08/07 13:33:11 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

This page was automatically generated by Maven