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