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 }