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 2000-2003 (C) Exoffice Technologies Inc. All Rights Reserved. 42 * 43 * 44 * $Id: ResourceManager.java,v 1.6 2003/08/17 01:32:25 tanderson Exp $ 45 * 46 * Date Author Changes 47 * 20/11/2001 jima Created 48 * 12/02/2002 jima Changed the package name from .xa to .messagemgr 49 */ 50 package org.exolab.jms.messagemgr; 51 52 import java.io.File; 53 import java.io.FilenameFilter; 54 import java.sql.Connection; 55 import java.util.Comparator; 56 import java.util.HashMap; 57 import java.util.Iterator; 58 import java.util.LinkedList; 59 import java.util.TreeSet; 60 import java.util.Vector; 61 62 import javax.jms.JMSException; 63 import javax.transaction.xa.XAException; 64 import javax.transaction.xa.XAResource; 65 import javax.transaction.xa.Xid; 66 67 import org.apache.commons.logging.Log; 68 import org.apache.commons.logging.LogFactory; 69 70 import org.exolab.core.service.BasicService; 71 import org.exolab.core.service.ServiceException; 72 import org.exolab.core.service.ServiceState; 73 import org.exolab.jms.config.Configuration; 74 import org.exolab.jms.message.MessageHandle; 75 import org.exolab.jms.message.MessageImpl; 76 import org.exolab.jms.persistence.DatabaseService; 77 import org.exolab.jms.persistence.PersistenceException; 78 import org.exolab.jms.tranlog.ExternalXid; 79 import org.exolab.jms.tranlog.StateTransactionLogEntry; 80 import org.exolab.jms.tranlog.TransactionLog; 81 import org.exolab.jms.tranlog.TransactionLogException; 82 import org.exolab.jms.tranlog.TransactionState; 83 import org.exolab.jms.util.UUID; 84 85 86 /*** 87 * The resource manager provides XA support for the JMS Server. 88 * <p> 89 * The resource manager is responsible for managing the various transaction 90 * identifiers and managing the association between transaction ids and 91 * connections. 92 * <p> 93 * The resource manager will store the global XID's and their state in the 94 * database for recovery purposes. 95 * <p> 96 * Messages that arrive, and are associated with an XID are not processed 97 * through the {@link MessageMgr}. Instead they are routed to this resource 98 * managers where they are cached until the associated XID is committed or 99 * rolled back. If the transaction is successfully committed, through the 2PC 100 * protocol the messages will pass through the system. 101 * <p> 102 * Similarly, messages that are sent to consumers, either synchronously or 103 * asynchronously are also cached by the resource manager until the global 104 * transaction completes. 105 * <p> 106 * On startup the resource manager will read all incomplete transactions, which 107 * are incompleted into memory. It will then process trnasactions that have 108 * timed out. 109 * <p> 110 * The transaction manager will call the {@link #recover} method and obtain a 111 * list of incomplete transaction for the purpose of completing them where 112 * possible. 113 * 114 * @version $Revision: 1.6 $ $Date: 2003/08/17 01:32:25 $ 115 * @author <a href="mailto:jima@intalio.com">Jim Alateras</a> 116 */ 117 public class ResourceManager 118 extends BasicService { 119 120 /*** 121 * The name of the service 122 */ 123 private final static String RM_SERVICE_NAME = "XAResourceManager"; 124 125 /*** 126 * The prefix used for all transaction log files, which are created and 127 * managed by the {@link TransactionLog} 128 */ 129 private final static String RM_LOGFILE_PREFIX = "ojmsrm"; 130 131 /*** 132 * The extension for all transaction log files 133 */ 134 public final static String RM_LOGFILE_EXTENSION = ".log"; 135 136 /*** 137 * This is used to indicate the garbage collection has been disabled and 138 * that the client will take responsibility for all aspects of log file 139 * management. This is useful in situations where the client wants to 140 * archive the transaction log files 141 * <p> 142 * This is the default mode for GC. 143 */ 144 public static final int GC_DISABLED = 0; 145 146 /*** 147 * Synchronous gabrage collection is used to remove processed log files 148 * when the last trnasaction, in that log file, has been successfully 149 * processed. This is more efficient means since the log files does not 150 * need to be scanned asynchronously to determine whether all the 151 * transactions have been processed. 152 */ 153 public static final int GC_SYNCHRONOUS = 1; 154 155 /*** 156 * Asynchronous garbage collection is used to remove processed log files 157 * asynchronous (i.e in a different thread context). This is rather 158 * expensive since it must manually scan each log file and determine 159 * whether all transactions, in that file, have been closed. If this is 160 * the case then it will remove the log file. 161 */ 162 public static final int GC_ASYNCHRONOUS = 2; 163 164 /*** 165 * Maintains a singleton instance of the gc service 166 */ 167 private static ResourceManager _instance = null; 168 169 /*** 170 * Used to synchronize the creation of the transaction manager 171 */ 172 private static final Object _initializer = new Object(); 173 174 /*** 175 * This is the maximum size, in bytes, of each transaction log file. The 176 * value can be overriden by the user 177 */ 178 private int _logFileSize = 1000000; 179 180 /*** 181 * Maintains a collection of transaction log files currently in use by this 182 * resource manager 183 */ 184 private TreeSet _logs = new TreeSet(new TranLogFileComparator()); 185 186 /*** 187 * Maintain a mapping between the TRID (transaction id and the log file it 188 * is associated with. 189 */ 190 private HashMap _tridToLogCache = new HashMap(); 191 192 /*** 193 * Maintain a list of open TRIDs for a particular {@link TransactionLog} 194 */ 195 private HashMap _logToTridCache = new HashMap(); 196 197 /*** 198 * This attribute is used to synchronize the modifications to the _tridToLog 199 * _logToTrid attributes 200 */ 201 private final Object _cacheLock = new Object(); 202 203 /*** 204 * This maintains a cache of all open transactions and the corresponding 205 * data. The key is the transaction identifier and the object is a LinkedList 206 * transaction entries, which includes both state and data 207 */ 208 private HashMap _activeTransactions = new HashMap(); 209 210 /*** 211 * The directory where the log files are stored. This can be set by the 212 * client 213 */ 214 private String _logDirectory = "."; 215 216 /*** 217 * This is the number of the last log file created by the ResourceManager 218 */ 219 private long _lastLogNumber = 0; 220 221 /*** 222 * The expiry time for transaction associated with this resource manager. 223 * This will either be configured or passed in with the transaction context 224 * The value is specified in seconds. 225 */ 226 private int _txExpiryTime = 120; 227 228 /*** 229 * This attribute caches the garbage collection mode for the resouce 230 * managers. Valid values are specified by the GC_* constants. 231 * <p> 232 * By default garbage collection is disabled. 233 */ 234 private int _gcMode = GC_SYNCHRONOUS; 235 236 /*** 237 * This is the id associated with this resource...need to work out who 238 * or what sets this. 239 */ 240 private String _rid = UUID.next(); 241 242 /*** 243 * The logger 244 */ 245 private static final Log _log = LogFactory.getLog(ResourceManager.class); 246 247 248 /*** 249 * Return the singleton instance of the ResourceManager 250 * 251 * @return ResourceManager 252 * @throws ResourceManagerException 253 */ 254 public static ResourceManager instance() 255 throws ResourceManagerException { 256 if (_instance == null) { 257 synchronized (_initializer) { 258 // we need to check again if multiple threads 259 // have blocked on the creation of the singleton 260 if (_instance == null) { 261 _instance = new ResourceManager(); 262 } 263 } 264 } 265 266 return _instance; 267 } 268 269 /*** 270 * Construct a resource manager using the default directory for its log 271 * files. 272 * <p> 273 * If there is a problem constructing this instances then throw a 274 * {@link ResourceManagerException} exception. 275 * 276 * @throws ResourceManagerException 277 */ 278 private ResourceManager() 279 throws ResourceManagerException { 280 281 // build the list of existing log files. 282 this("./logs"); 283 } 284 285 /*** 286 * Construct a resource manager using the specified directory, which 287 * must already exist. If the directory does not exist or there is 288 * no permisssion to access it then throw a ResourceManagerException 289 * 290 * @param dir - the base directory 291 * @throws ResourceManagerException 292 */ 293 public ResourceManager(String dir) 294 throws ResourceManagerException { 295 super(RM_SERVICE_NAME); 296 _logDirectory = dir; 297 File file = new File(dir); 298 if ((!file.exists()) || 299 (!file.isDirectory())) { 300 throw new ResourceManagerException(dir + 301 " does not exist or is not a directory"); 302 } 303 304 // build the list of existing log files. 305 buildLogFileList(); 306 307 // recover te list of log files 308 recover(); 309 } 310 311 /*** 312 * Set the log directory. This is the directory where all the log files 313 * are stored 314 * 315 * @param dir - the name of the directory (absolute or relative) 316 * @throws IllegalArgumentException if the string is not a directory 317 */ 318 public void setLogDirectory(String dir) 319 throws IllegalArgumentException { 320 if (!(new File(dir)).isDirectory()) { 321 throw new IllegalArgumentException(dir + " is not a directory"); 322 } else { 323 _logDirectory = dir; 324 } 325 } 326 327 /*** 328 * Retrieve the name of the log directory 329 * 330 * @return String - log dir name 331 */ 332 public String getLogDirectory() { 333 return _logDirectory; 334 } 335 336 /*** 337 * Set the maximum size of each log file. When this size is reached a 338 * new log file is created. The size of the log file can be changed 339 * dynamically during runtime. If it is not specified it will default 340 * to 1MB. 341 * <p> 342 * The size is specified in bytes 343 * 344 * @param size - the max size of each log file 345 */ 346 public void setLogFileSize(int size) { 347 _logFileSize = size; 348 } 349 350 /*** 351 * Return the maximum size that each log file can grow too. 352 * 353 * @return int - log file size 354 */ 355 public int getLogFileSize() { 356 return _logFileSize; 357 } 358 359 /*** 360 * Set the GC mode for the resource manager. Valid values are GC_SYNCHRONOUS, 361 * GC_ASYNCHRONOUS and GC_DISABLED. 362 * <p> 363 * @param mode - one of GC_* 364 * @return boolean - if the specified mode has been correctly set 365 */ 366 public boolean setGCMode(int mode) { 367 boolean result = false; 368 369 if ((mode == GC_DISABLED) || 370 (mode == GC_SYNCHRONOUS) || 371 (mode == GC_ASYNCHRONOUS)) { 372 _gcMode = mode; 373 result = true; 374 } 375 376 return result; 377 } 378 379 /*** 380 * Return the garbage collection mode for the resource manager 381 * 382 * @return int 383 */ 384 public int getGCMode() { 385 return _gcMode; 386 } 387 388 /*** 389 * Check whether garbage collection has been disabled 390 * 391 * @return boolean - true if gc is disabled 392 */ 393 public boolean gcDisabled() { 394 return (_gcMode == GC_DISABLED) ? true : false; 395 } 396 397 /*** 398 * Log this published message so that it can be passed through the system 399 * when the associated global transaction commits. 400 * 401 * @param xid - the global transaction identity 402 * @param message - the message published 403 * @throws TransactionLogException - error adding the entry 404 * @throws ResourceManagerException - error getting the trnasaction log 405 * @throws JMSException - if there is an issue with prep'ing the message 406 */ 407 public synchronized void logPublishedMessage(Xid xid, MessageImpl message) 408 throws TransactionLogException, ResourceManagerException, JMSException { 409 MessageMgr.instance().checkAndPrepareMessage(message); 410 logTransactionData(new ExternalXid(xid), _rid, 411 createPublishedMessageWrapper(message)); 412 } 413 414 /*** 415 * Log that this message handle was sent to the consumer within the specified 416 * global transaction identity. The message will be acknowledged when the 417 * global transaction commits. Alternatively, if the global transaction is 418 * rolled back the message handle will be returned to the destination 419 * 420 * @param txid - the global transaction identity 421 * @param id - the consumer receiving this message 422 * @param handle - the handle of the message received 423 * @throws TransactionLogException - error adding the entry 424 * @throws ResourceManagerException - error getting the transaction log 425 */ 426 public synchronized void logReceivedMessage(Xid xid, String id, MessageHandle handle) 427 throws TransactionLogException, ResourceManagerException { 428 logTransactionData(new ExternalXid(xid), _rid, 429 createReceivedMessageWrapper(id, handle)); 430 } 431 432 /*** 433 * Add an {@link StateTransactionLogEntry} using the specified txid, 434 * rid and state 435 * 436 * @param xid - the transaction identifier 437 * @param state - the transaction log state 438 * @throws TransactionLogException - error adding the entry 439 * @throws ResourceManagerException - error getting the trnasaction log 440 */ 441 public synchronized void logTransactionState(Xid xid, TransactionState state) 442 throws TransactionLogException, ResourceManagerException { 443 ExternalXid txid = new ExternalXid(xid); 444 switch (state.getOrd()) { 445 case TransactionState.OPENED_ORD: 446 { 447 TransactionLog log = getCurrentTransactionLog(); 448 addTridLogEntry(txid, log); 449 log.logTransactionState(txid, _txExpiryTime * 1000, _rid, 450 state); 451 452 // cache the transaction state 453 _activeTransactions.put(txid, new LinkedList()); 454 } 455 break; 456 457 case TransactionState.PREPARED_ORD: 458 // cache the transaction state 459 LinkedList list = (LinkedList) _activeTransactions.get(txid); 460 if (list != null) { 461 list.add(state); 462 } else { 463 throw new ResourceManagerException("Trasaction " + txid + 464 " is not active."); 465 } 466 break; 467 468 case TransactionState.CLOSED_ORD: 469 { 470 TransactionLog log = getTransactionLog(txid); 471 log.logTransactionState(txid, _txExpiryTime * 1000, _rid, 472 state); 473 removeTridLogEntry(txid, log); 474 475 // check whether this log has anymore open transactions 476 synchronized (_cacheLock) { 477 if ((_logToTridCache.get(log) == null) && 478 (!isCurrentTransactionLog(log))) { 479 log.close(); 480 481 // now check if gc mode is GC_SYNCHRONOUS. If it is 482 // remove the log file 483 if (_gcMode == GC_SYNCHRONOUS) { 484 try { 485 log.destroy(); 486 } catch (TransactionLogException exception) { 487 exception.printStackTrace(); 488 } 489 } 490 } 491 } 492 493 // we also want to remove this entry from the list 494 // of active transactions 495 _activeTransactions.remove(txid); 496 } 497 break; 498 499 default: 500 throw new ResourceManagerException("Cannot process tx state " + 501 state); 502 } 503 } 504 505 /*** 506 * Add an {@link DataTransactionLogEntry} using the specified txid, 507 * rid and data 508 * 509 * @param txid - the transaction identifier 510 * @param rid - the resource identifier 511 * @param state - the transaction log state 512 * @throws TransactionLogException - error adding the entry 513 * @throws ResourceManagerException - error getting the trnasaction log 514 */ 515 synchronized void logTransactionData(ExternalXid txid, String rid, 516 Object data) 517 throws ResourceManagerException, TransactionLogException { 518 getTransactionLog(txid).logTransactionData(txid, _txExpiryTime * 1000, 519 rid, data); 520 521 // we also want to add this to the transaction data for that 522 // txid 523 LinkedList list = (LinkedList) _activeTransactions.get(txid); 524 if (list != null) { 525 list.add(data); 526 } else { 527 throw new ResourceManagerException("Trasaction " + txid + 528 " is not active."); 529 } 530 } 531 532 /*** 533 * This is the entry point for the garbage collection callback. It scans 534 * through the each transaction log file and determines whether it can 535 * be garbage collected. If it can then it simply destroys the corresponding 536 * TransactionLog. 537 */ 538 public void garbageCollect() { 539 try { 540 int gcfiles = 0; 541 542 // if there are no transaction log files then return 543 if (_logs.size() == 0) { 544 return; 545 } 546 547 TreeSet copy = null; 548 synchronized (_logs) { 549 copy = new TreeSet(_logs); 550 } 551 552 // remove the current log file, since this is likely to be the 553 // current log file 554 copy.remove(_logs.last()); 555 556 // process each of the remaining log files 557 while (copy.size() > 0) { 558 TransactionLog log = (TransactionLog) copy.first(); 559 copy.remove(log); 560 if (log.canGarbageCollect()) { 561 // destroy the log 562 log.destroy(); 563 564 // remove it from the log cache 565 synchronized (_logs) { 566 _logs.remove(log); 567 } 568 569 // increment the number of garbafe collected files 570 ++gcfiles; 571 } 572 } 573 574 // print an informative message 575 _log.info("[RMGC] Collected " + gcfiles + " files."); 576 } catch (Exception exception) { 577 exception.printStackTrace(); 578 } 579 } 580 581 /*** 582 * Ensure that a transaction with the specified xid is currently active. 583 * If this is the case then commit the transaction based onb the value 584 * of the onePhase flag. 585 * <p> 586 * This will have the effect of passing all messages through 587 * 588 * @param xid - the xa transaction identity 589 * @param onePhase - treu if it is a one phase commit 590 * @throws XAException - if there is a problem completing the call 591 */ 592 public synchronized void commit(Xid id, boolean onePhase) 593 throws XAException { 594 // check that the xid is not null 595 if (id == null) { 596 throw new XAException(XAException.XAER_NOTA); 597 } 598 599 // covert to our internal representation of an xid 600 ExternalXid xid = new ExternalXid(id); 601 602 // check to see that the transaction is active and open. We should 603 // not be allowed to commit a committed transaction. 604 if (!isTransactionActive(xid)) { 605 throw new XAException(XAException.XAER_PROTO); 606 } 607 608 // process all the messages associated with this global transaction 609 // If a message has been published then sent it to the message mgr 610 // for processing. If a message has been consumed then remove it 611 // from the list of unconsumed messages. 612 Connection connection = null; 613 try { 614 // get a connection to the database 615 connection = DatabaseService.getConnection(); 616 617 // retrieve a list of recrods for the specified global transaction 618 // and process them. Ignore the state records and only process the 619 // data records, which are of type TransacitonalObjectWrapper. 620 Object[] records = getTransactionRecords(xid, _rid); 621 for (int index = 0; index < records.length; index++) { 622 if (records[index] instanceof TransactionalObjectWrapper) { 623 TransactionalObjectWrapper wrapper = 624 (TransactionalObjectWrapper) records[index]; 625 if (wrapper.isPublishedMessage()) { 626 // send the published message to the message manager 627 MessageMgr.instance().add(connection, 628 (MessageImpl) wrapper.getObject()); 629 630 } else if (wrapper.isReceivedMessage()) { 631 // if it is a received message handle then simply 632 // delete it and mark it as acknowledged 633 MessageHandle handle = ((ReceivedMessageWrapper) (wrapper)).getMessageHandle(); 634 if (handle instanceof PersistentMessageHandle) { 635 MessageHandleFactory.destroyPersistentHandle(connection, 636 (PersistentMessageHandle) handle); 637 } else { 638 handle.destroy(); 639 } 640 } 641 } else { 642 // ignore since it is a state records. 643 } 644 } 645 connection.commit(); 646 } catch (PersistenceException exception) { 647 if (connection != null) { 648 try { 649 connection.rollback(); 650 } catch (Exception nested) { 651 // ignore 652 } 653 } 654 throw new XAException("Failed in ResourceManager.commit : " + 655 exception.toString()); 656 } catch (Exception exception) { 657 throw new XAException("Failed in ResourceManager.commit : " + 658 exception.toString()); 659 } finally { 660 if (connection != null) { 661 try { 662 connection.close(); 663 } catch (Exception nested) { 664 // ignore 665 } 666 } 667 668 // and now mark the transaction as closed 669 try { 670 logTransactionState(xid, TransactionState.CLOSED); 671 } catch (Exception exception) { 672 throw new XAException("Error processing commit : " + exception); 673 } 674 } 675 } 676 677 /*** 678 * Ends the work performed on behalf of a transaction branch. The resource 679 * manager disassociates the XA resource from the transaction branch 680 * specified and let the transaction be completedCommits an XA transaction 681 * that is in progress. 682 * 683 * @param xid - the xa transaction identity 684 * @param flags - one of TMSUCCESS, TMFAIL, or TMSUSPEND 685 * @throws XAException - if there is a problem completing the call 686 */ 687 public synchronized void end(Xid id, int flags) 688 throws XAException { 689 //check the xid is not null 690 if (id == null) { 691 throw new XAException(XAException.XAER_NOTA); 692 } 693 694 // covert to our internal representation of an xid 695 ExternalXid xid = new ExternalXid(id); 696 697 // check that the flags are valid for this method 698 if ((flags != XAResource.TMSUSPEND) || 699 (flags != XAResource.TMSUCCESS) || 700 (flags != XAResource.TMFAIL)) { 701 throw new XAException(XAException.XAER_PROTO); 702 } 703 704 switch (flags) { 705 case XAResource.TMFAIL: 706 // check that the transaction exists 707 if (!isTransactionActive(xid)) { 708 throw new XAException(XAException.XAER_PROTO); 709 } 710 711 // do not process that associated data, simply rollback 712 rollback(xid); 713 break; 714 715 case XAResource.TMSUSPEND: 716 // check that the transaction is opened 717 if (!isTransactionActive(xid)) { 718 throw new XAException(XAException.XAER_PROTO); 719 } 720 break; 721 722 case XAResource.TMSUCCESS: 723 // nothing to do here but check that the resource manager is 724 // in a consistent state wrt to this xid. The xid should not 725 // be active if it received the commit, forget etc. 726 if (isTransactionActive(xid)) { 727 throw new XAException(XAException.XAER_PROTO); 728 } 729 break; 730 } 731 } 732 733 /*** 734 * Tell the resource manager to forget about a heuristically completed 735 * transaction branch. 736 * 737 * @param xid - the xa transaction identity 738 * @throws XAException - if there is a problem completing the call 739 */ 740 public synchronized void forget(Xid id) 741 throws XAException { 742 //check the xid is not null 743 if (id == null) { 744 throw new XAException(XAException.XAER_NOTA); 745 } 746 747 // covert to our internal representation of an xid 748 ExternalXid xid = new ExternalXid(id); 749 750 // check to see that the xid actually exists 751 if (!isTransactionActive(xid)) { 752 throw new XAException(XAException.XAER_PROTO); 753 } 754 755 // call rollback to complete the work 756 rollback(id); 757 } 758 759 /*** 760 * Return the transaction timeout for this instance of the resource 761 * manager. 762 * 763 * @return int - the timeout in seconds 764 * @throws XAException - if there is a problem completing the call 765 */ 766 public synchronized int getTransactionTimeout() 767 throws XAException { 768 return _txExpiryTime; 769 } 770 771 /*** 772 * Ask the resource manager to prepare for a transaction commit of the 773 * transaction specified in xid 774 * 775 * @param xid - the xa transaction identity 776 * @return int - XA_RDONLY or XA_OK 777 * @throws XAException - if there is a problem completing the call 778 */ 779 public synchronized boolean isSameRM(XAResource xares) 780 throws XAException { 781 boolean result = false; 782 783 if ((xares == this) || 784 ((xares instanceof ResourceManager) && 785 (((ResourceManager) xares)._rid.equals(_rid)))) { 786 result = true; 787 } 788 789 return result; 790 } 791 792 /*** 793 * Obtain a list of prepared transaction branches from a resource manager. 794 * The transaction manager calls this method during recovery to obtain the 795 * list of transaction branches that are currently in prepared or 796 * heuristically completed states. 797 * 798 * @param flag - One of TMSTARTRSCAN, TMENDRSCAN, TMNOFLAGS. TMNOFLAGS 799 * @param Xid[] - the set of Xids to recover 800 * @throws XAException - if there is a problem completing the call 801 */ 802 public synchronized int prepare(Xid id) 803 throws XAException { 804 //check the xid is not null 805 if (id == null) { 806 throw new XAException(XAException.XAER_NOTA); 807 } 808 809 // covert to our internal representation of an xid 810 ExternalXid xid = new ExternalXid(id); 811 812 // check to see that the xid actually exists 813 if (!isTransactionActive(xid)) { 814 throw new XAException(XAException.XAER_PROTO); 815 } 816 817 // can a prepare for the same resource occur multiple times 818 // ???? 819 820 try { 821 logTransactionState(xid, TransactionState.PREPARED); 822 } catch (Exception exception) { 823 throw new XAException("Error processing prepare : " + exception); 824 } 825 826 return XAResource.XA_OK; 827 } 828 829 /*** 830 * Inform the resource manager to roll back work done on behalf of a 831 * transaction branch 832 * 833 * @param xid - the xa transaction identity 834 * @throws XAException - if there is a problem completing the call 835 */ 836 public synchronized Xid[] recover(int flag) 837 throws XAException { 838 839 Xid[] result = new Xid[0]; 840 841 if ((flag == XAResource.TMNOFLAGS) || 842 (flag == XAResource.TMSTARTRSCAN) || 843 (flag == XAResource.TMENDRSCAN)) { 844 LinkedList xids = new LinkedList(); 845 Iterator iter = _activeTransactions.keySet().iterator(); 846 while (iter.hasNext()) { 847 Xid xid = (Xid) iter.next(); 848 LinkedList list = (LinkedList) _activeTransactions.get(xid); 849 if (list.size() > 1) { 850 // need at least a start in the chain. 851 Object last = list.getLast(); 852 if ((last instanceof StateTransactionLogEntry) && 853 (((StateTransactionLogEntry) last).getState().isPrepared())) { 854 xids.add(xid); 855 } 856 } 857 858 } 859 result = (Xid[]) xids.toArray(); 860 } 861 862 return result; 863 } 864 865 /*** 866 * Set the current transaction timeout value for this XAResource instance. 867 * 868 * @param seconds - timeout in seconds 869 * @return boolean - true if the new transaction timeout was accepted 870 * @throws XAException - if there is a problem completing the call 871 */ 872 public synchronized void rollback(Xid id) 873 throws XAException { 874 //check the xid is not null 875 if (id == null) { 876 throw new XAException(XAException.XAER_NOTA); 877 } 878 879 // covert to our internal representation of an xid 880 ExternalXid xid = new ExternalXid(id); 881 882 // check to see that the xid actually exists 883 if (!isTransactionActive(xid)) { 884 throw new XAException(XAException.XAER_PROTO); 885 } 886 887 // process the data in that transaction. If it was a published message 888 // then drop it. If it was a consumed message then return it back to 889 // the destination. 890 Connection connection = null; 891 try { 892 // get a connection to the database 893 connection = DatabaseService.getConnection(); 894 895 // retrieve a list of recrods for the specified global transaction 896 // and process them. Ignore the state records and only process the 897 // data records, which are of type TransacitonalObjectWrapper. 898 Object[] records = getTransactionRecords(xid, _rid); 899 for (int index = 0; index < records.length; index++) { 900 if (records[index] instanceof TransactionalObjectWrapper) { 901 TransactionalObjectWrapper wrapper = 902 (TransactionalObjectWrapper) records[index]; 903 if (wrapper.isPublishedMessage()) { 904 // we don't need to process these messages since the 905 // global transaction has been rolled back. 906 } else if (wrapper.isReceivedMessage()) { 907 // use the ConsumerManager to retrieve an instance of 908 // of the ConsumerEndpoint and then return the message 909 // back to the consumer. 910 ReceivedMessageWrapper rmsg_wrapper = 911 (ReceivedMessageWrapper) wrapper; 912 ConsumerEndpoint endpoint = 913 ConsumerManager.instance().getConsumerEndpoint( 914 rmsg_wrapper.getConsumerId()); 915 if (endpoint != null) { 916 // the endpoint exists so we must return the message 917 // back to it. 918 endpoint.returnMessage( 919 (MessageHandle) rmsg_wrapper.getObject()); 920 } else { 921 // endpoint no longer exists so we can ignore this 922 } 923 } 924 } else { 925 // ignore since it is a state records. 926 } 927 } 928 929 connection.commit(); 930 } catch (PersistenceException exception) { 931 if (connection != null) { 932 try { 933 connection.rollback(); 934 } catch (Exception nested) { 935 // ignore 936 } 937 } 938 throw new XAException("Failed in ResourceManager.rollback : " + 939 exception.toString()); 940 } catch (Exception exception) { 941 throw new XAException("Failed in ResourceManager.rollback : " + 942 exception.toString()); 943 } finally { 944 if (connection != null) { 945 try { 946 connection.close(); 947 } catch (Exception nested) { 948 // ignore 949 } 950 } 951 952 // and now mark the transaction as closed 953 try { 954 logTransactionState(xid, TransactionState.CLOSED); 955 } catch (Exception exception) { 956 throw new XAException("Error processing rollback : " + exception); 957 } 958 } 959 } 960 961 /*** 962 * Start work on behalf of a transaction branch specified in xid If TMJOIN 963 * is specified, the start is for joining a transaction previously seen by 964 * the resource manager 965 * 966 * @param xid - the xa transaction identity 967 * @param flags - One of TMNOFLAGS, TMJOIN, or TMRESUME 968 * @throws XAException - if there is a problem completing the call 969 */ 970 public synchronized boolean setTransactionTimeout(int seconds) 971 throws XAException { 972 _txExpiryTime = seconds; 973 return true; 974 } 975 976 // implementation of XAResource.start 977 public synchronized void start(Xid id, int flags) 978 throws XAException { 979 980 //check the xid is not null 981 if (id == null) { 982 throw new XAException(XAException.XAER_NOTA); 983 } 984 985 // covert to our internal representation of an xid 986 ExternalXid xid = new ExternalXid(id); 987 988 // check that the flags are valid for this method 989 if ((flags != XAResource.TMNOFLAGS) || 990 (flags != XAResource.TMJOIN) || 991 (flags != XAResource.TMRESUME)) { 992 throw new XAException(XAException.XAER_PROTO); 993 } 994 995 switch (flags) { 996 case XAResource.TMNOFLAGS: 997 // check to see that the xid does not already exist 998 if (isTransactionActive(xid)) { 999 throw new XAException(XAException.XAER_DUPID); 1000 } 1001 1002 // otherwise log the start of the transaction 1003 try { 1004 logTransactionState(xid, TransactionState.OPENED); 1005 } catch (Exception exception) { 1006 throw new XAException("Error processing start : " + exception); 1007 } 1008 break; 1009 1010 case XAResource.TMJOIN: 1011 case XAResource.TMRESUME: 1012 // joining a transaction previously seen by the resource 1013 // manager 1014 if (!isTransactionActive(xid)) { 1015 throw new XAException(XAException.XAER_PROTO); 1016 } 1017 break; 1018 } 1019 } 1020 1021 // override ServiceManager.start 1022 public void start() 1023 throws ServiceException { 1024 this.setState(ServiceState.RUNNING); 1025 } 1026 1027 // override ServiceManager.stop 1028 public void stop() 1029 throws ServiceException { 1030 this.setState(ServiceState.STOPPED); 1031 } 1032 1033 // override ServiceManager.run 1034 public void run() { 1035 // do nothing 1036 } 1037 1038 /*** 1039 * Return the resource manager identity 1040 * 1041 * @return the resource manager identity 1042 */ 1043 public String getResourceManagerId() { 1044 return _rid; 1045 } 1046 1047 /*** 1048 * Create the next {@link TransactionLog} and add it to the list of 1049 * managed transaction logs. 1050 * <p> 1051 * The method will throw ResourceManagerException if there is a 1052 * problem completing the request. 1053 * 1054 * @throws ResourceManagerException 1055 */ 1056 protected TransactionLog createNextTransactionLog() 1057 throws ResourceManagerException { 1058 TransactionLog newlog = null; 1059 1060 synchronized (_logs) { 1061 try { 1062 // get the last log number 1063 long last = 1; 1064 if (!_logs.isEmpty()) { 1065 last = getSequenceNumber(((TransactionLog) _logs.last()).getName()); 1066 } 1067 1068 // now that we have the last log number, increment it and use 1069 // it to build the name of the next log file. 1070 String name = _logDirectory + System.getProperty("file.separator") + 1071 RM_LOGFILE_PREFIX + Long.toString(++last) + RM_LOGFILE_EXTENSION; 1072 1073 // create a transaction log and add it to the collection 1074 newlog = new TransactionLog(name, true); 1075 _logs.add(newlog); 1076 } catch (TransactionLogException exception) { 1077 throw new ResourceManagerException( 1078 "Error in createNextTransactionLog " + exception); 1079 } 1080 } 1081 1082 return newlog; 1083 } 1084 1085 /*** 1086 * Build a list of all log files in the specified log directory 1087 * 1088 * @throws IllegalArgumentException - if the directory does not exist. 1089 */ 1090 protected void buildLogFileList() { 1091 File dir = new File(_logDirectory); 1092 if ((!dir.exists()) || 1093 (!dir.isDirectory())) { 1094 throw new IllegalArgumentException(_logDirectory + 1095 " is not a directory"); 1096 } 1097 1098 try { 1099 File[] list = dir.listFiles(new FilenameFilter() { 1100 1101 // implementation of FilenameFilter.accept 1102 public boolean accept(File dir, String name) { 1103 boolean result = false; 1104 1105 if ((name.startsWith(RM_LOGFILE_PREFIX)) && 1106 (name.endsWith(RM_LOGFILE_EXTENSION))) { 1107 result = true; 1108 } 1109 1110 return result; 1111 } 1112 }); 1113 1114 // add the files to the list 1115 synchronized (_logs) { 1116 for (int index = 0; index < list.length; index++) { 1117 _logs.add(new TransactionLog(list[index].getPath(), false)); 1118 } 1119 } 1120 } catch (Exception exception) { 1121 // replace this with the exception strategy 1122 exception.printStackTrace(); 1123 } 1124 1125 } 1126 1127 /*** 1128 * This method will process all the transaction logs, in the log diretory 1129 * and call recover on each of them. 1130 * 1131 * @throws ResourceManagerException - if there is a problem recovering 1132 */ 1133 private synchronized void recover() 1134 throws ResourceManagerException { 1135 try { 1136 if (!_logs.isEmpty()) { 1137 Iterator iter = _logs.iterator(); 1138 while (iter.hasNext()) { 1139 TransactionLog log = (TransactionLog) iter.next(); 1140 HashMap records = log.recover(); 1141 } 1142 } 1143 } catch (Exception exception) { 1144 throw new ResourceManagerException("Error in recover " + 1145 exception.toString()); 1146 } 1147 } 1148 1149 /*** 1150 * Retrieve the transaction log for the specified transaction id 1151 * 1152 * @param txid - the transaction identity 1153 * @return TransactionLog 1154 * @throws TransactionLogException - if there is tx log exception 1155 * @throws ResourceManagerException - if there is a resource problem. 1156 */ 1157 private TransactionLog getTransactionLog(ExternalXid txid) 1158 throws TransactionLogException, ResourceManagerException { 1159 TransactionLog log = (TransactionLog) _tridToLogCache.get(txid); 1160 if (log == null) { 1161 log = getCurrentTransactionLog(); 1162 addTridLogEntry(txid, log); 1163 } 1164 1165 return log; 1166 } 1167 1168 /*** 1169 * Get the current transaction log. It will check the last transaction 1170 * log opened by the resource manager and determine whether there is 1171 * space enough to process another transaction. 1172 * <p> 1173 * If there is space enough then it will return that transaction, 1174 * otherwise it will create a new transaction log for the resource 1175 * 1176 * @return TransactionLog - the transaction log to use 1177 * @throws ResourceManagerException 1178 * @throws TransactionLogException 1179 */ 1180 private TransactionLog getCurrentTransactionLog() 1181 throws TransactionLogException, ResourceManagerException { 1182 TransactionLog log = null; 1183 1184 synchronized (_logs) { 1185 if (_logs.size() > 0) { 1186 log = (TransactionLog) _logs.last(); 1187 } 1188 1189 if ((log == null) || 1190 (log.size() > _logFileSize)) { 1191 log = createNextTransactionLog(); 1192 } 1193 } 1194 1195 return log; 1196 } 1197 1198 /*** 1199 * Add an entry to the trid log cache table for the specified trid and 1200 * transaction log mapping. 1201 * 1202 * @param trid - the transaction identifier 1203 * @param log - the transaction log 1204 */ 1205 private void addTridLogEntry(ExternalXid trid, TransactionLog log) { 1206 synchronized (_cacheLock) { 1207 // one to one relationship 1208 _tridToLogCache.put(trid, log); 1209 1210 // one to many relationship 1211 Vector trids = (Vector) _logToTridCache.get(log); 1212 if (trids == null) { 1213 trids = new Vector(); 1214 _logToTridCache.put(log, trids); 1215 } 1216 trids.addElement(trid); 1217 } 1218 } 1219 1220 /*** 1221 * Check whether the specified log is also the current log 1222 * 1223 * @param log - the log to check 1224 * @return boolean - true if it is 1225 */ 1226 private boolean isCurrentTransactionLog(TransactionLog log) { 1227 boolean result = false; 1228 1229 if (_logs.size() > 0) { 1230 result = log.equals(_logs.last()); 1231 } 1232 1233 return result; 1234 } 1235 1236 /*** 1237 * Remove an entry to the trid log cache table for the specified trid and 1238 * transaction log mapping. 1239 * 1240 * @param trid - the transaction identifier 1241 * @param log - the transaction log 1242 */ 1243 private void removeTridLogEntry(ExternalXid trid, TransactionLog log) { 1244 synchronized (_cacheLock) { 1245 1246 // one to one relationship 1247 _tridToLogCache.remove(trid); 1248 1249 // one to many relationship 1250 Vector trids = (Vector) _logToTridCache.get(log); 1251 if (trids != null) { 1252 trids.remove(trid); 1253 if (trids.size() == 0) { 1254 _logToTridCache.remove(log); 1255 } 1256 } 1257 } 1258 } 1259 1260 /*** 1261 * Return an arrya of records, both state and date, for the specified 1262 * global transaction 1263 * 1264 * @param xid - the global transaction id 1265 * @param rid - the resource id 1266 * @return Object[] - array of records 1267 */ 1268 protected Object[] getTransactionRecords(ExternalXid xid, String rid) { 1269 Object[] records; 1270 1271 // we also want to add this to the transaction data for that 1272 // txid 1273 LinkedList list = (LinkedList) _activeTransactions.get(xid); 1274 if (list != null) { 1275 records = list.toArray(); 1276 } else { 1277 records = new Object[0]; 1278 } 1279 1280 return records; 1281 } 1282 1283 1284 /*** 1285 * Return the sequence number of the file 1286 * files are associated with a unique number 1287 * 1288 * @param name - the file name to investigate 1289 * @return long - the transaction log number 1290 * @throws ResourceManagerException 1291 */ 1292 protected long getSequenceNumber(String name) 1293 throws ResourceManagerException { 1294 int start = name.indexOf(RM_LOGFILE_PREFIX) + 1295 RM_LOGFILE_PREFIX.length(); 1296 int end = name.indexOf(RM_LOGFILE_EXTENSION); 1297 1298 // the number must be between the start and end positions 1299 try { 1300 return Long.parseLong(name.substring(start, end)); 1301 } catch (NumberFormatException exception) { 1302 throw new ResourceManagerException( 1303 "Invalid name assigned to resource manager file " + name); 1304 } 1305 } 1306 1307 /*** 1308 * Return true if the specified transaction is active 1309 * 1310 * @param xid - the gobal transaction identifier 1311 */ 1312 private synchronized boolean isTransactionActive(ExternalXid xid) { 1313 return _activeTransactions.containsKey(xid); 1314 } 1315 1316 /*** 1317 * Dump the specified records to the screen 1318 */ 1319 private void dumpRecovered(HashMap records) { 1320 Iterator iter = records.keySet().iterator(); 1321 while (iter.hasNext()) { 1322 ExternalXid txid = (ExternalXid) iter.next(); 1323 LinkedList list = (LinkedList) records.get(txid); 1324 Iterator oiter = list.iterator(); 1325 while (oiter.hasNext()) { 1326 Object object = oiter.next(); 1327 if (object instanceof StateTransactionLogEntry) { 1328 System.err.println("Recovered [" + txid + "] Class " + 1329 object.getClass().getName() + " [" + 1330 ((StateTransactionLogEntry) object).getState().toString() + "]"); 1331 } else { 1332 System.err.println("Recovered [" + txid + "] Class " + 1333 object.getClass().getName()); 1334 } 1335 } 1336 } 1337 } 1338 1339 1340 /*** 1341 * Helper and type-safe method for creating a wrapper object for published 1342 * messages 1343 * 1344 * @param message - the message published 1345 * @return PublishedMessageWrapper 1346 */ 1347 private PublishedMessageWrapper createPublishedMessageWrapper( 1348 MessageImpl message) { 1349 return new PublishedMessageWrapper(message); 1350 } 1351 1352 /*** 1353 * Helper and type-safe method for creating a wrapper object for received 1354 * messages 1355 * 1356 * @param id - the identity of the consumer receiving the message 1357 * @param handle - the handle of the message received 1358 * @return ReceivedMessageWrapper 1359 */ 1360 private ReceivedMessageWrapper createReceivedMessageWrapper( 1361 String id, MessageHandle handle) { 1362 return new ReceivedMessageWrapper(id, handle); 1363 } 1364 1365 /*** 1366 * This functor is used by various collections to order the transaction log 1367 * files created by this resource manager. The resource manager will create 1368 * log files with sequentially increasing numbers (i.e xxx01.log, xxx2.log 1369 */ 1370 private class TranLogFileComparator 1371 implements Comparator { 1372 1373 // implementation of Comparator.comapre 1374 public int compare(Object o1, Object o2) { 1375 int result = -1; 1376 1377 try { 1378 if ((o1 instanceof TransactionLog) && 1379 (o2 instanceof TransactionLog)) { 1380 long seq1 = getSequenceNumber(((TransactionLog) o1).getName()); 1381 long seq2 = getSequenceNumber(((TransactionLog) o2).getName()); 1382 1383 if (seq1 > seq2) { 1384 result = 1; 1385 } else if (seq1 < seq2) { 1386 result = -1; 1387 } else { 1388 result = 0; 1389 } 1390 } else { 1391 throw new ClassCastException("o1 = " + 1392 o1.getClass().getName() + " and o2 = " + 1393 o2.getClass().getName()); 1394 } 1395 } catch (Exception exception) { 1396 throw new RuntimeException("Error in ResourceManager.compare " + 1397 exception.toString()); 1398 } 1399 1400 return result; 1401 } 1402 1403 // implementation of Comparator.equals 1404 public boolean equals(Object obj) { 1405 if (obj instanceof TranLogFileComparator) { 1406 return true; 1407 } 1408 1409 return false; 1410 } 1411 } 1412 1413 1414 /*** 1415 * This private member class is used to wrap the transactional object, 1416 * which for this particular resource manager is a published message or 1417 * a received message handle. 1418 */ 1419 abstract private class TransactionalObjectWrapper { 1420 1421 /*** 1422 * The transactional object instance 1423 */ 1424 private Object _object; 1425 1426 /*** 1427 * Create an instance of the wrapper using the type and the object 1428 * 1429 * @param object - the associated object 1430 */ 1431 public TransactionalObjectWrapper(Object object) { 1432 _object = object; 1433 } 1434 1435 /*** 1436 * Check whether the wrapper contains a published message. Note that a 1437 * published message has a {@link MessageImpl} a the transactional 1438 * object. 1439 * 1440 * @return boolean - true if it is 1441 */ 1442 public boolean isPublishedMessage() { 1443 return this instanceof PublishedMessageWrapper; 1444 } 1445 1446 /*** 1447 * Check whether the wrapper contains a received message handle. Note 1448 * that a received message contains a {@link MessageHandle} as the 1449 * transactional object. 1450 * 1451 * @return boolean - true if it does 1452 */ 1453 public boolean isReceivedMessage() { 1454 return this instanceof ReceivedMessageWrapper; 1455 } 1456 1457 /*** 1458 * Return the transaction object 1459 * 1460 * @return Object 1461 */ 1462 public Object getObject() { 1463 return _object; 1464 } 1465 1466 } 1467 1468 1469 /*** 1470 * This private member class is used to wrap a published message 1471 */ 1472 private class PublishedMessageWrapper extends TransactionalObjectWrapper { 1473 1474 /*** 1475 * Create an instance of the wrapper using the specified message 1476 * 1477 * @param message - the message to wrap 1478 */ 1479 public PublishedMessageWrapper(MessageImpl message) { 1480 super(message); 1481 } 1482 1483 /*** 1484 * Return an instance of the message object 1485 * 1486 * @return MessageImpl 1487 */ 1488 public MessageImpl getMessage() { 1489 return (MessageImpl) super.getObject(); 1490 } 1491 } 1492 1493 1494 /*** 1495 * This private member class is used to wrap a received message 1496 */ 1497 private class ReceivedMessageWrapper extends TransactionalObjectWrapper { 1498 1499 /*** 1500 * Caches the id of the {@link ConsumerEndpoint} that is processed 1501 * this handle 1502 */ 1503 private String _consumerId; 1504 1505 /*** 1506 * Create an instance of the wrapper using the specified message 1507 * 1508 * @param id - the identity of the consumer endpoint 1509 * @param handle - the handle to the message 1510 */ 1511 public ReceivedMessageWrapper(String id, MessageHandle handle) { 1512 super(handle); 1513 _consumerId = id; 1514 } 1515 1516 /*** 1517 * Return a reference to the consumer identity 1518 * 1519 * @return String 1520 */ 1521 public String getConsumerId() { 1522 return _consumerId; 1523 } 1524 1525 /*** 1526 * Return an instance of the message handle 1527 * 1528 * @return MessageHandle 1529 */ 1530 public MessageHandle getMessageHandle() { 1531 return (MessageHandle) super.getObject(); 1532 } 1533 } 1534 1535 } //-- ResourceManager

This page was automatically generated by Maven