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 2002-2004 (C) Exoffice Technologies Inc. All Rights Reserved. 42 * 43 * $Id: BatchingRdbmsAdapter.java,v 1.11 2004/01/08 05:55:05 tanderson Exp $ 44 * 45 * Date Author Changes 46 * $Date jimm Created 47 */ 48 49 50 package org.exolab.jms.persistence; 51 52 53 import java.sql.Connection; 54 import java.util.Enumeration; 55 import java.util.HashMap; 56 import java.util.Iterator; 57 import java.util.LinkedList; 58 import java.util.Vector; 59 60 import javax.jms.JMSException; 61 import javax.sql.ConnectionPoolDataSource; 62 import javax.transaction.Transaction; 63 import javax.transaction.TransactionManager; 64 65 import org.apache.commons.logging.Log; 66 import org.apache.commons.logging.LogFactory; 67 68 import org.exolab.core.foundation.HandleIfc; 69 import org.exolab.jms.authentication.User; 70 import org.exolab.jms.client.JmsDestination; 71 import org.exolab.jms.config.Configuration; 72 import org.exolab.jms.config.DatabaseConfiguration; 73 import org.exolab.jms.events.EventHandler; 74 import org.exolab.jms.message.MessageId; 75 import org.exolab.jms.message.MessageImpl; 76 import org.exolab.jms.messagemgr.PersistentMessageHandle; 77 78 79 /*** 80 * The batching RDBMS adapter is used to improve the performance of persistent 81 * messages by batching more instructions into a single transaction. 82 * <p> 83 * It encpasulates an {@link RDBMSAdapter} and delegates all the behaviour to 84 * it. In the interim it will only batch 'insert', 'update' and 'delete' 85 * statements. If it receives a 'select' or any query it will first commit the 86 * batched statements before satisfying the query. 87 * <p> 88 * This piece of code is still under development and could effect the integrity 89 * of your system if used. It could also improve the throughput of persistent 90 * messages if it works correctly. 91 * 92 * @version $Revision: 1.11 $ $Date: 2004/01/08 05:55:05 $ 93 * @author <a href="mailto:jima@intalio.org">Jim Alateras</a> 94 */ 95 public class BatchingRdbmsAdapter 96 extends PersistenceAdapter 97 implements EventHandler { 98 99 /*** 100 * This is the maximum number of statements that can be batched before 101 * actually committing the work to the database. This value defaults to 102 * 100 but it can be changed at runtime. 103 */ 104 private int _maxStatementsToBatch = 500; 105 106 /*** 107 * The directory where the log files are stored. This can be set by the 108 * client 109 */ 110 private String _logDirectory = "."; 111 112 /*** 113 * Holds a reference to the RDBMSAdapter, which it delegates all the 114 * work too. 115 */ 116 private RDBMSAdapter _rdbms = null; 117 118 /*** 119 * Holds the current batch of trnasactional objects. 120 */ 121 private LinkedList _batch = new LinkedList(); 122 123 /*** 124 * Holds a reference to message handles that have been batched up. 125 */ 126 private HashMap _handles = new HashMap(); 127 128 /*** 129 * Holds a reference to messages that have been batched up 130 */ 131 private HashMap _messages = new HashMap(); 132 133 /*** 134 * The logger 135 */ 136 private static final Log _log = 137 LogFactory.getLog(BatchingRdbmsAdapter.class); 138 139 140 /*** 141 * Connects to the given db. 142 * 143 * @param driver - the rdbms driver to use 144 * @param url - the url to the database 145 * @param userName - a valid user name for a connection to the database 146 * @param password - the password for the connection to the database 147 * @param batchSize - the number of requests it should batch 148 * @throws PersistenceException for any database error 149 */ 150 BatchingRdbmsAdapter(String driver, String url, String userName, 151 String password, int batchSize) 152 throws PersistenceException { 153 // create the rdbms adapter 154 _rdbms = new RDBMSAdapter(driver, url, userName, password); 155 _maxStatementsToBatch = batchSize; 156 } 157 158 /*** 159 * Close the database if open. 160 * 161 */ 162 public void close() { 163 if (_rdbms != null) { 164 try { 165 flush(); 166 } catch (PersistenceException exception) { 167 _log.error("Failed to flush statements", exception); 168 } 169 _rdbms.close(); 170 } 171 } 172 173 /*** 174 * Set the maximum number of SQL statements to batch 175 * 176 * @param count - number of statements to batch 177 */ 178 public void setMaxStatementsToBatch(int max) { 179 _maxStatementsToBatch = max; 180 } 181 182 /*** 183 * Return the maximum number of statements to batch 184 * 185 * @return int 186 */ 187 public int getMaxStatementsToBatch() { 188 return _maxStatementsToBatch; 189 } 190 191 // implementation of PersistenceAdapter.getLastId 192 public long getLastId(Connection connection) 193 throws PersistenceException { 194 return _rdbms.getLastId(connection); 195 } 196 197 // implementation of PersistenceAdapter.updateIds 198 public void updateIds(Connection connection, long id) 199 throws PersistenceException { 200 _rdbms.updateIds(connection, id); 201 } 202 203 // implementation of PersistenceMessage.addMessage 204 public void addMessage(Connection connection, MessageImpl message) 205 throws PersistenceException { 206 addToBatch(TransactionalObjectWrapper.ADD_MESSAGE, message); 207 } 208 209 // implementation of PersistenceMessage.addMessage 210 public void updateMessage(Connection connection, MessageImpl message) 211 throws PersistenceException { 212 addToBatch(TransactionalObjectWrapper.UPDATE_MESSAGE, message); 213 } 214 215 // implementation of PersistenceAdapter.getUnprocessedMessages 216 public Vector getUnprocessedMessages(Connection connection) 217 throws PersistenceException { 218 flush(); 219 return _rdbms.getUnprocessedMessages(connection); 220 } 221 222 223 // implementation of PersistenceAdapter.removeMessage 224 public void removeMessage(Connection connection, String id) 225 throws PersistenceException { 226 addToBatch(TransactionalObjectWrapper.DELETE_MESSAGE, id); 227 } 228 229 // implementation of PersistenceAdapter.getMessage 230 public MessageImpl getMessage(Connection connection, String id) 231 throws PersistenceException { 232 flush(); 233 return _rdbms.getMessage(connection, id); 234 } 235 236 // implementation of PersistenceAdapter.getMessages 237 public Vector getMessages(Connection connection, 238 PersistentMessageHandle handle) 239 throws PersistenceException { 240 flush(); 241 return _rdbms.getMessages(connection, handle); 242 } 243 244 // implementation of PersistenceAdapter.addMessageHandle 245 public void addMessageHandle(Connection connection, 246 PersistentMessageHandle handle) 247 throws PersistenceException { 248 addToBatch(TransactionalObjectWrapper.ADD_HANDLE, handle); 249 } 250 251 // implementation of PersistenceAdapter.updateMessageHandle 252 public void updateMessageHandle(Connection connection, 253 PersistentMessageHandle handle) 254 throws PersistenceException { 255 addToBatch(TransactionalObjectWrapper.UPDATE_HANDLE, handle); 256 } 257 258 // implementation of PersistenceAdapter.removeMessageHandle 259 public void removeMessageHandle(Connection connection, 260 PersistentMessageHandle handle) 261 throws PersistenceException { 262 addToBatch(TransactionalObjectWrapper.DELETE_HANDLE, handle); 263 } 264 265 // implementation of PersistenceAdapter.getMessageHandles 266 public Vector getMessageHandles(Connection connection, 267 JmsDestination destination, String name) 268 throws PersistenceException { 269 flush(); 270 return _rdbms.getMessageHandles(connection, destination, name); 271 } 272 273 // implementation of PersistenceAdapter.addDurableConsumer 274 public void addDurableConsumer(Connection connection, String topic, 275 String consumer) 276 throws PersistenceException { 277 flush(); 278 _rdbms.addDurableConsumer(connection, topic, consumer); 279 } 280 281 // implementation of PersistenceAdapter.removeDurableConsumer 282 public void removeDurableConsumer(Connection connection, String consumer) 283 throws PersistenceException { 284 flush(); 285 _rdbms.removeDurableConsumer(connection, consumer); 286 } 287 288 // implementation of PersistenceAdapter.getDurableConsumers 289 public Enumeration getDurableConsumers(Connection connection, 290 String topic) 291 throws PersistenceException { 292 flush(); 293 return _rdbms.getDurableConsumers(connection, topic); 294 } 295 296 // implementation of PersistenceAdapter.getAllDurableConsumers 297 public HashMap getAllDurableConsumers(Connection connection) 298 throws PersistenceException { 299 flush(); 300 return _rdbms.getAllDurableConsumers(connection); 301 } 302 303 // implementation of PersistenceAdapter.durableConsumerExists 304 public boolean durableConsumerExists(Connection connection, String name) 305 throws PersistenceException { 306 flush(); 307 return _rdbms.durableConsumerExists(connection, name); 308 } 309 310 // implementation of PersistenceAdapter.addDestination 311 public void addDestination(Connection connection, String name, 312 boolean queue) 313 throws PersistenceException { 314 flush(); 315 _rdbms.addDestination(connection, name, queue); 316 } 317 318 // implementation of PersistenceAdapter.removeDestination 319 public void removeDestination(Connection connection, String name) 320 throws PersistenceException { 321 flush(); 322 _rdbms.removeDestination(connection, name); 323 } 324 325 // implementation of PersistenceAdapter.getAllDestinations 326 public Enumeration getAllDestinations(Connection connection) 327 throws PersistenceException { 328 flush(); 329 return _rdbms.getAllDestinations(connection); 330 } 331 332 // implementation of PersistenceAdapter.checkDestination 333 public boolean checkDestination(Connection connection, String name) 334 throws PersistenceException { 335 flush(); 336 return _rdbms.checkDestination(connection, name); 337 } 338 339 // implementation of getQueueMessageCount 340 public int getQueueMessageCount(Connection connection, String name) 341 throws PersistenceException { 342 flush(); 343 return _rdbms.getQueueMessageCount(connection, name); 344 } 345 346 // implementation of PersistenceAdapter.getQueueMessageCount 347 public int getDurableConsumerMessageCount(Connection connection, 348 String destination, String name) 349 throws PersistenceException { 350 flush(); 351 return _rdbms.getDurableConsumerMessageCount(connection, destination, 352 name); 353 } 354 355 // implementation of PersistenceAdapter.getQueueMessageCount 356 public void removeExpiredMessages(Connection connection) 357 throws PersistenceException { 358 flush(); 359 _rdbms.removeExpiredMessages(connection); 360 } 361 362 // implementation of PersistenceAdapter.removeExpiredMessageHandles 363 public void removeExpiredMessageHandles(Connection connection, 364 String consumer) 365 throws PersistenceException { 366 flush(); 367 _rdbms.removeExpiredMessageHandles(connection, consumer); 368 } 369 370 // implementation of PersistenceAdapter.getQueueMessageCount 371 public Vector getNonExpiredMessages(Connection connection, 372 JmsDestination destination) 373 throws PersistenceException { 374 flush(); 375 return _rdbms.getNonExpiredMessages(connection, destination); 376 } 377 378 /*** 379 * Return a connection to the database from the pool of connections. It 380 * will throw an PersistenceException if it cannot retrieve a connection. 381 * The client should close the connection normally, since the pool is a 382 * connection event listener. 383 * 384 * @return Connection - a pooled connection or null 385 * @exception PersistenceException - if it cannot retrieve a connection 386 */ 387 public Connection getConnection() 388 throws PersistenceException { 389 390 return _rdbms.getConnection(); 391 } 392 393 /*** 394 * Purge all processed messages from the database. 395 * 396 * @return int - the number of messages deleted 397 */ 398 public synchronized int purgeMessages() { 399 try { 400 flush(); 401 } catch (PersistenceException exception) { 402 _log.error("Error in purgeMessages " + exception); 403 } 404 405 return _rdbms.purgeMessages(); 406 } 407 408 // implementation of EventHandler.handleEvent 409 public void handleEvent(int event, Object callback, long time) { 410 _rdbms.handleEvent(event, callback, time); 411 } 412 413 // implementation of EventHandler.getHandle 414 public HandleIfc getHandle() { 415 return null; 416 } 417 418 /*** 419 * Close the current piece of work and commit it to the database. This is 420 * called before a query or fetch is executed on the data. 421 * <p> 422 * It will grab a connection and commit the transactional objects before 423 * returning. If there is any problem it will throw a PersistenceException 424 * excpetion 425 * 426 * @throws PersistenceException 427 */ 428 private synchronized void flush() throws PersistenceException { 429 if (_batch.size() == 0) { 430 return; 431 } 432 433 // need to do this in a separate thread since the current thread is 434 // already associated with a Connection object 435 Thread thread = new Thread(new Runnable() { 436 437 public void run() { 438 Connection connection = null; 439 try { 440 connection = _rdbms.getConnection(); 441 442 Iterator iter = _batch.iterator(); 443 while (iter.hasNext()) { 444 TransactionalObjectWrapper wrapper = 445 (TransactionalObjectWrapper) iter.next(); 446 switch (wrapper._action) { 447 case TransactionalObjectWrapper.ADD_MESSAGE: 448 _rdbms.addMessage(connection, 449 (MessageImpl) wrapper._object); 450 break; 451 452 case TransactionalObjectWrapper.UPDATE_MESSAGE: 453 _rdbms.updateMessage(connection, 454 (MessageImpl) wrapper._object); 455 break; 456 457 case TransactionalObjectWrapper.DELETE_MESSAGE: 458 _rdbms.removeMessage(connection, 459 (String) wrapper._object); 460 break; 461 462 case TransactionalObjectWrapper.ADD_HANDLE: 463 _rdbms.addMessageHandle(connection, 464 (PersistentMessageHandle) wrapper._object); 465 break; 466 467 case TransactionalObjectWrapper.UPDATE_HANDLE: 468 _rdbms.updateMessageHandle(connection, 469 (PersistentMessageHandle) wrapper._object); 470 break; 471 472 case TransactionalObjectWrapper.DELETE_HANDLE: 473 _rdbms.removeMessageHandle(connection, 474 (PersistentMessageHandle) wrapper._object); 475 break; 476 } 477 } 478 connection.commit(); 479 480 // if the commit has worked then flush the batch list 481 _batch.clear(); 482 _messages.clear(); 483 _handles.clear(); 484 } catch (PersistenceException exception) { 485 SQLHelper.rollback(connection); 486 _log.error("Failure in flush()", exception); 487 } catch (Exception exception) { 488 _log.error("Failure in flush()", exception); 489 } finally { 490 if (connection != null) { 491 try { 492 connection.close(); 493 } catch (Exception nested) { 494 _log.error("Failure in flush()", nested); 495 } 496 } 497 } 498 } 499 }); 500 501 // start the thread. 502 thread.start(); 503 504 // wait for the thread to finish...sort of defeat the purpose here. 505 try { 506 thread.join(); 507 } catch (InterruptedException exception) { 508 // ignore the error 509 } 510 } 511 512 /*** 513 * Add this transactional object along with the associated action to the 514 * current batch job. 515 * 516 * @param action - the action to take 517 * @param object - the transactional object 518 * @throws PersistenceException 519 */ 520 private synchronized void addToBatch(int action, Object object) 521 throws PersistenceException { 522 if (_batch.size() >= _maxStatementsToBatch) { 523 flush(); 524 } 525 526 switch (action) { 527 case TransactionalObjectWrapper.ADD_MESSAGE: 528 { 529 TransactionalObjectWrapper txobj = 530 new TransactionalObjectWrapper(action, object); 531 MessageImpl message = (MessageImpl) object; 532 MessageId id = message.getMessageId(); 533 if (_messages.containsKey(id)) { 534 throw new PersistenceException("Inconsistency in cache " + 535 id + " is present when it shouldn't be."); 536 } 537 _messages.put(id, txobj); 538 _batch.addLast(txobj); 539 break; 540 } 541 542 case TransactionalObjectWrapper.UPDATE_MESSAGE: 543 { 544 MessageImpl message = (MessageImpl) object; 545 MessageId id = message.getMessageId(); 546 TransactionalObjectWrapper txobj = 547 (TransactionalObjectWrapper) _messages.get(id); 548 TransactionalObjectWrapper newtxobj = 549 new TransactionalObjectWrapper(action, object); 550 551 if (txobj != null) { 552 // if the batch already contains a entry for this 553 // message then remove it and add the new version 554 // with an 555 _batch.remove(txobj); 556 if (txobj._action == TransactionalObjectWrapper.ADD_MESSAGE) { 557 newtxobj._action = TransactionalObjectWrapper.ADD_MESSAGE; 558 _batch.addLast(newtxobj); 559 } else if (txobj._action == TransactionalObjectWrapper.UPDATE_MESSAGE) { 560 _batch.addLast(newtxobj); 561 } else { 562 // could only be a delete and should never happen 563 throw new PersistenceException("Inconsistency in cache." + 564 " Cannot update a deleted message."); 565 } 566 } else { 567 _batch.addLast(newtxobj); 568 } 569 _messages.put(id, newtxobj); 570 break; 571 } 572 573 case TransactionalObjectWrapper.DELETE_MESSAGE: 574 { 575 MessageImpl message = (MessageImpl) object; 576 MessageId id = message.getMessageId(); 577 TransactionalObjectWrapper txobj = 578 (TransactionalObjectWrapper) _messages.get(id); 579 TransactionalObjectWrapper newtxobj = 580 new TransactionalObjectWrapper(action, object); 581 582 if (txobj != null) { 583 // if the batch already contains a entry for this 584 // message then remove it and add the new version 585 // with an 586 _batch.remove(txobj); 587 if (txobj._action == TransactionalObjectWrapper.ADD_MESSAGE) { 588 // if an add and the delete happened in the same batch then 589 // we don't have to commit either of the transactions. 590 } else if (txobj._action == TransactionalObjectWrapper.UPDATE_MESSAGE) { 591 // if the update and the delete happened in the same batch then 592 // we need add the transaction 593 _batch.addLast(newtxobj); 594 } else { 595 // if the delete already exists then simple ignore it. 596 } 597 } else { 598 _batch.addLast(newtxobj); 599 } 600 _messages.put(id, newtxobj); 601 break; 602 } 603 604 case TransactionalObjectWrapper.ADD_HANDLE: 605 { 606 TransactionalObjectWrapper txobj = 607 new TransactionalObjectWrapper(action, object); 608 PersistentMessageHandle handle = (PersistentMessageHandle) object; 609 if (_handles.containsKey(handle)) { 610 throw new PersistenceException("Inconsistency in cache " + 611 handle + " is present when it shouldn't be."); 612 } 613 _handles.put(handle, txobj); 614 _batch.addLast(txobj); 615 break; 616 } 617 618 case TransactionalObjectWrapper.UPDATE_HANDLE: 619 { 620 PersistentMessageHandle handle = (PersistentMessageHandle) object; 621 TransactionalObjectWrapper txobj = 622 (TransactionalObjectWrapper) _handles.get(handle); 623 TransactionalObjectWrapper newtxobj = 624 new TransactionalObjectWrapper(action, object); 625 626 if (txobj != null) { 627 // if the batch already contains a entry for this 628 // handle then remove it and add the new version. 629 _batch.remove(txobj); 630 if (txobj._action == TransactionalObjectWrapper.ADD_HANDLE) { 631 newtxobj._action = TransactionalObjectWrapper.ADD_HANDLE; 632 _batch.addLast(newtxobj); 633 } else if (txobj._action == TransactionalObjectWrapper.UPDATE_HANDLE) { 634 _batch.addLast(newtxobj); 635 } else { 636 // could only be a delete and should never happen 637 throw new PersistenceException("Inconsistency in cache." + 638 " Cannot update a deleted handle."); 639 } 640 } else { 641 _batch.addLast(newtxobj); 642 } 643 _handles.put(handle, newtxobj); 644 break; 645 } 646 647 case TransactionalObjectWrapper.DELETE_HANDLE: 648 { 649 PersistentMessageHandle handle = (PersistentMessageHandle) object; 650 TransactionalObjectWrapper txobj = 651 (TransactionalObjectWrapper) _handles.get(handle); 652 TransactionalObjectWrapper newtxobj = 653 new TransactionalObjectWrapper(action, object); 654 655 if (txobj != null) { 656 // if the batch already contains a entry for this 657 // message then remove it and add the new version 658 // with an 659 _batch.remove(txobj); 660 if (txobj._action == TransactionalObjectWrapper.ADD_HANDLE) { 661 // if an add and the delete happenedd in the same batch then 662 // we don't have to commit either of the transactions. 663 } else if (txobj._action == TransactionalObjectWrapper.UPDATE_HANDLE) { 664 // if the update and the delete happened in the same batch then 665 // we need add the transaction 666 _batch.addLast(newtxobj); 667 } else { 668 // if the delete already exists then simple ignore it. 669 } 670 } else { 671 _batch.addLast(newtxobj); 672 } 673 _handles.put(handle, newtxobj); 674 break; 675 } 676 } 677 678 } 679 680 public void addUser(Connection connection, User user) 681 throws PersistenceException { 682 } 683 684 public Enumeration getAllUsers(Connection connection) 685 throws PersistenceException { 686 return null; 687 } 688 689 public User getUser(Connection connection, User user) 690 throws PersistenceException { 691 return null; 692 } 693 694 public void removeUser(Connection connection, User user) 695 throws PersistenceException { 696 } 697 698 public void updateUser(Connection connection, User user) 699 throws PersistenceException { 700 } 701 702 /*** 703 * Inner class that wraps up transactional objects so that they can 704 * correctly be processed in a batch transaction 705 */ 706 private class TransactionalObjectWrapper { 707 708 /*** 709 * This is the list of actions that the wrapper supports. 710 */ 711 public final static int ADD_MESSAGE = 1; 712 public final static int UPDATE_MESSAGE = 2; 713 public final static int DELETE_MESSAGE = 3; 714 public final static int ADD_HANDLE = 4; 715 public final static int UPDATE_HANDLE = 5; 716 public final static int DELETE_HANDLE = 6; 717 718 /*** 719 * Action associated with the transactional object 720 */ 721 public int _action; 722 723 /*** 724 * The transactional object 725 */ 726 public Object _object; 727 728 /*** 729 * Constructor using the action and object 730 */ 731 public TransactionalObjectWrapper(int action, Object object) { 732 _action = action; 733 _object = object; 734 } 735 } 736 }

This page was automatically generated by Maven