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-2004 (C) Exoffice Technologies Inc. All Rights Reserved.
42 */
43
44 package org.exolab.jms.persistence;
45
46 import java.sql.Connection;
47 import java.sql.PreparedStatement;
48 import java.sql.ResultSet;
49 import java.sql.SQLException;
50 import java.util.Vector;
51
52 import org.apache.commons.logging.Log;
53 import org.apache.commons.logging.LogFactory;
54
55 import org.exolab.jms.client.JmsDestination;
56 import org.exolab.jms.client.JmsTopic;
57 import org.exolab.jms.message.MessageId;
58 import org.exolab.jms.messagemgr.PersistentMessageHandle;
59
60
61 /***
62 * This class provides persistency for PersistentMessageHandle objects
63 * in an RDBMS database
64 *
65 * @version $Revision: 1.30 $ $Date: 2004/01/08 05:55:07 $
66 * @author <a href="mailto:tma@netspace.net.au">Tim Anderson</a>
67 * @see org.exolab.jms.persistence.RDBMSAdapter
68 */
69 class MessageHandles {
70
71 /***
72 * prepared statement for inserting a message handle
73 */
74 private static final String INSERT_MSG_HANDLE_STMT =
75 "insert into message_handles values " + "(?,?,?,?,?,?,?,?)";
76
77 /***
78 * prepared statements for deleting message handle
79 */
80 private static final String DELETE_MSG_HANDLE_STMT1 =
81 "delete from message_handles where messageId=? and consumerId=?";
82 private static final String DELETE_MSG_HANDLE_STMT2 =
83 "delete from message_handles where messageId=? and destinationId=? " +
84 "and consumerId=?";
85
86 /***
87 * Delete all message handles with the specified message id
88 */
89 private static final String DELETE_MSG_HANDLES_STMT =
90 "delete from message_handles where messageId=?";
91
92 /***
93 * determine the number of handles with the specified message identity
94 */
95 private static final String GET_MSG_HANDLE_COUNT =
96 "select count(messageId) from message_handles where messageId=?";
97
98 /***
99 * Delete a message from the message table
100 */
101 private static final String DELETE_MESSAGE =
102 "delete from messages where messageId=?";
103
104 /***
105 * Update a row in the message handles table
106 */
107 private static final String UPDATE_MSG_HANDLE_STMT =
108 "update message_handles set delivered=? where messageId=? and " +
109 "destinationId=? and consumerId=?";
110
111 /***
112 * Delete all message handles for a destination
113 */
114 private static final String DELETE_MSG_HANDLES_FOR_DEST =
115 "delete from message_handles where destinationId=?";
116
117 /***
118 * Retrieve all message handles for a particular consumer
119 */
120 private static final String GET_MSG_HANDLES_FOR_DEST =
121 "select * from message_handles where consumerId=? order by " +
122 "acceptedTime asc";
123
124 /***
125 * Retrieve a range of message handles between the specified times
126 */
127 private static final String GET_MESSAGE_HANDLES_IN_RANGE =
128 "select distinct messageId from message_handles where " +
129 " acceptedTime >= ? and acceptedTime <=?";
130
131 /***
132 * Retrieve a handle with the specified id
133 */
134 private static final String GET_MESSAGE_HANDLE_WITH_ID =
135 "select distinct messageId from message_handles where messageId=?";
136
137 /***
138 * Return the number of messages and a specified destination and cousmer
139 */
140 private static final String GET_MSG_HANDLE_COUNT_FOR_DEST_AND_CONSUMER =
141 "select count(messageId) from message_handles where destinationId=? " +
142 "and consumerId=?";
143
144 /***
145 * Return the number of messages and a specified consumer
146 */
147 private static final String GET_MSG_HANDLE_COUNT_FOR_CONSUMER =
148 "select count(messageId) from message_handles where consumerId=?";
149
150 /***
151 * Delete all expired messages
152 */
153 private static final String DELETE_EXPIRED_MESSAGES =
154 "delete from message_handles where consumerId=? and expiryTime != 0 " +
155 "and expiryTime<?";
156
157 /***
158 * Singleton to this class
159 */
160 private static MessageHandles _instance;
161
162 /***
163 * Cache the transaction retry count
164 */
165 private static int _retryCount = 10;
166
167 /***
168 * Cache the retry interval in milliseconds
169 */
170 private static long _retryInterval = 50;
171
172 /***
173 * Used to ensure that only one thread initialises the class
174 */
175 private static final Object _block = new Object();
176
177 /***
178 * The logger
179 */
180 private static final Log _log = LogFactory.getLog(MessageHandles.class);
181
182
183 /***
184 * Returns the singleton instance.
185 *
186 * Note that initialise() must have been invoked first for this
187 * to return a valid instance.
188 *
189 * @return MessageHandles
190 */
191 public static MessageHandles instance() {
192 return _instance;
193 }
194
195 /***
196 * Constructor
197 */
198 protected MessageHandles() {
199 }
200
201 /***
202 * Initialise the singleton _instance
203 *
204 * @return MessageHandles
205 */
206 public static MessageHandles initialise() {
207 if (_instance == null) {
208 synchronized (_block) {
209 if (_instance == null) {
210 _instance = new MessageHandles();
211 }
212 }
213 }
214
215 return _instance;
216 }
217
218 /***
219 * Add the specified message handle to the database
220 *
221 * @param connection - the connection to use
222 * @param handle - message handle to add
223 * @throws PersistenceException - if add does not complete
224 */
225 public void addMessageHandle(Connection connection,
226 PersistentMessageHandle handle)
227 throws PersistenceException {
228
229 if (_log.isDebugEnabled()) {
230 _log.debug("addMessageHandle(handle=[consumer="
231 + handle.getConsumerName()
232 + ", destination=" + handle.getDestination()
233 + ", id=" + handle.getMessageId().getId() + "])");
234 }
235
236 PreparedStatement insert = null;
237 try {
238 // map the destination name to an actual identity
239 long destinationId = Destinations.instance().getId(
240 handle.getDestination().getName());
241 if (destinationId == 0) {
242 throw new PersistenceException(
243 "Cannot add message handle id=" + handle.getMessageId() +
244 " for destination=" + handle.getDestination().getName() +
245 " and consumer=" + handle.getConsumerName() +
246 " since the destination cannot be mapped to an id");
247 }
248
249 // map the consumer name ot an identity
250 long consumerId = Consumers.instance().getConsumerId(
251 handle.getConsumerName());
252 if (consumerId == 0) {
253 throw new PersistenceException(
254 "Cannot add message handle id=" + handle.getMessageId() +
255 " for destination=" + handle.getDestination().getName() +
256 " and consumer=" + handle.getConsumerName() +
257 " since the consumer cannot be mapped to an id");
258 }
259
260 insert = connection.prepareStatement(INSERT_MSG_HANDLE_STMT);
261 insert.setString(1, handle.getMessageId().getId());
262 insert.setLong(2, destinationId);
263 insert.setLong(3, consumerId);
264 insert.setInt(4, handle.getPriority());
265 insert.setLong(5, handle.getAcceptedTime());
266 insert.setLong(6, handle.getSequenceNumber());
267 insert.setLong(7, handle.getExpiryTime());
268 insert.setInt(8, (handle.getDelivered()) ? 1 : 0);
269
270 // execute the insert
271 if (insert.executeUpdate() != 1) {
272 _log.error(
273 "Failed to execute addMessageHandle for handle="
274 + handle.getMessageId().getId() + ", destination Id="
275 + destinationId);
276 }
277 } catch (SQLException exception) {
278 throw new PersistenceException("Failed to add message handle=" +
279 handle, exception);
280 } finally {
281 SQLHelper.close(insert);
282 }
283 }
284
285 /***
286 * Remove the specified message handle from the database. Once the handle
287 * has been removed check to see whether there are any more message handles
288 * referencing the same message. If there are not then remove the
289 * corresponding message from the messages tables.
290 *
291 * @param connection - the connection to use
292 * @param handle - the handle to remove
293 * @throws PersistenceException - sql releated exception
294 */
295 public void removeMessageHandle(Connection connection,
296 PersistentMessageHandle handle)
297 throws PersistenceException {
298
299 if (_log.isDebugEnabled()) {
300 _log.debug("removeMessageHandle(handle=[consumer="
301 + handle.getConsumerName()
302 + ", destination=" + handle.getDestination()
303 + ", id=" + handle.getMessageId().getId() + "])");
304 }
305
306 PreparedStatement delete = null;
307 PreparedStatement select = null;
308 ResultSet rs = null;
309
310 try {
311 // first check to see that the consumer exists and only
312 // proceed if it non-zero.
313 long consumerId = Consumers.instance().getConsumerId(
314 handle.getConsumerName());
315 if (consumerId != 0) {
316 // get the message id
317 String id = handle.getMessageId().getId();
318
319 // map the destination name to an actual identity. If it is
320 // null then the destination does not currently exist but we
321 // may need to delete orphaned handles
322 long destinationId = Destinations.instance().getId(
323 handle.getDestination().getName());
324
325 if (destinationId == 0) {
326 delete = connection.prepareStatement(
327 DELETE_MSG_HANDLE_STMT1);
328 delete.setString(1, id);
329 delete.setLong(2, consumerId);
330
331 } else {
332 delete = connection.prepareStatement(
333 DELETE_MSG_HANDLE_STMT2);
334 delete.setString(1, id);
335 delete.setLong(2, destinationId);
336 delete.setLong(3, consumerId);
337 }
338
339 // execute the delete
340 if (delete.executeUpdate() != 1 && !handle.hasExpired()) {
341 // only log if the message hasn't been garbage
342 // collected
343 _log.error("Failed to execute removeMessageHandle for "
344 + "handle=" + id + " destination id="
345 + destinationId + " consumer id=" + consumerId);
346 }
347
348 // if there are no more handles with the specified message id
349 // then delete the corresponding message from the message table
350 select = connection.prepareStatement(GET_MSG_HANDLE_COUNT);
351 select.setString(1, id);
352 rs = select.executeQuery();
353 if (rs.next() && (rs.getInt(1) == 0)) {
354 delete.close();
355 delete = connection.prepareStatement(DELETE_MESSAGE);
356 delete.setString(1, id);
357 if (delete.executeUpdate() != 1 && !handle.hasExpired()) {
358 // can get 2 durable consumers trying to do this
359 // simultaneously, so don't log an error
360 // See bug 819212.
361 if (_log.isDebugEnabled()) {
362 _log.debug(
363 "Failed to delete the message with id=" + id
364 + " in a call to removeMessageHandle.");
365 }
366 }
367 }
368 }
369 } catch (SQLException exception) {
370 throw new PersistenceException("Failed to remove message handle=" +
371 handle, exception);
372 } finally {
373 SQLHelper.close(rs);
374 SQLHelper.close(delete);
375 SQLHelper.close(select);
376 }
377 }
378
379 /***
380 * Update the specified message handle from the database
381 *
382 * @param connection - the connection to use
383 * @param handle - the handle to update
384 * @throws PersistenceException - sql releated exception
385 */
386 public void updateMessageHandle(Connection connection,
387 PersistentMessageHandle handle)
388 throws PersistenceException {
389 PreparedStatement update = null;
390
391 if (_log.isDebugEnabled()) {
392 _log.debug("updateMessageHandle(handle=[consumer="
393 + handle.getConsumerName()
394 + ", destination=" + handle.getDestination()
395 + ", id=" + handle.getMessageId().getId() + "])");
396 }
397
398 try {
399 // get the message id
400 String id = handle.getMessageId().getId();
401
402 // map the destination name to an actual identity
403 long destinationId = Destinations.instance().getId(
404 handle.getDestination().getName());
405 if (destinationId == 0) {
406 throw new PersistenceException(
407 "Cannot update message handle id=" +
408 handle.getMessageId() + " for destination=" +
409 handle.getDestination().getName() + " and consumer=" +
410 handle.getConsumerName() +
411 " since the destination cannot be mapped to an id");
412 }
413
414 // map the consumer name to an identity
415 long consumerId = Consumers.instance().getConsumerId(
416 handle.getConsumerName());
417 if (consumerId == 0) {
418 throw new PersistenceException(
419 "Cannot update message handle id=" +
420 handle.getMessageId() + " for destination=" +
421 handle.getDestination().getName() + " and consumer=" +
422 handle.getConsumerName() +
423 " since the consumer cannot be mapped to an id");
424 }
425
426 update = connection.prepareStatement(UPDATE_MSG_HANDLE_STMT);
427 update.setInt(1, handle.getDelivered() ? 1 : 0);
428 update.setString(2, id);
429 update.setLong(3, destinationId);
430 update.setLong(4, consumerId);
431
432 // execute the delete
433 if (update.executeUpdate() != 1 && !handle.hasExpired()) {
434 // only log if the message hasn't been garbage collected
435 _log.error(
436 "Failed to execute updateMessageHandle for handle=" +
437 id + ", destination id=" + destinationId +
438 ", consumer id=" + consumerId);
439 }
440 } catch (SQLException exception) {
441 throw new PersistenceException("Failed to update message handle=" +
442 handle, exception);
443 } finally {
444 SQLHelper.close(update);
445 }
446 }
447
448 /***
449 * Remove all the message handles associated with the specified destination
450 *
451 * @param connection - the connection to use
452 * @param String name of destination
453 * @throws PersistenceException - sql releated exception
454 */
455 public void removeMessageHandles(Connection connection, String destination)
456 throws PersistenceException {
457
458 PreparedStatement delete = null;
459
460 try {
461 // map the destination name to an actual identity
462 long destinationId = Destinations.instance().getId(destination);
463 if (destinationId == 0) {
464 throw new PersistenceException(
465 "Cannot remove message handles for destination=" +
466 destination + " since the destination cannot be " +
467 "mapped to an id");
468 }
469
470 delete = connection.prepareStatement(DELETE_MSG_HANDLES_FOR_DEST);
471 delete.setLong(1, destinationId);
472 delete.executeUpdate();
473 } catch (SQLException exception) {
474 throw new PersistenceException(
475 "Failed to remove message handles for destination=" +
476 destination, exception);
477 } finally {
478 SQLHelper.close(delete);
479 }
480 }
481
482 /***
483 * Remove all the message handles for the specified messageid
484 *
485 * @param connection - the connection to use
486 * @param id - message identity
487 * @throws PersistenceException - sql releated exception
488 */
489 public void removeMessageHandles(Connection connection, long messageId)
490 throws PersistenceException {
491
492 PreparedStatement delete = null;
493
494 try {
495 delete = connection.prepareStatement(DELETE_MSG_HANDLES_STMT);
496 delete.setLong(1, messageId);
497 delete.executeUpdate();
498 } catch (SQLException exception) {
499 throw new PersistenceException(
500 "Failed to remove message handles for message id=" + messageId,
501 exception);
502 } finally {
503 SQLHelper.close(delete);
504 }
505 }
506
507 /***
508 * Retrieve the message handle for the specified desitation and consumer
509 * name
510 *
511 * @param connection - the connection to use
512 * @param destination - destination name
513 * @param name - consumer name
514 * @return Vector - collection of PersistentMessageHandle objects
515 * @throws PersistenceException - sql releated exception
516 */
517 public Vector getMessageHandles(Connection connection, String destination,
518 String name)
519 throws PersistenceException {
520
521 Vector result = new Vector();
522 PreparedStatement select = null;
523 ResultSet set = null;
524
525 // if the consumer and/or destination cannot be mapped then
526 // return an empty vector
527 long destinationId = Destinations.instance().getId(destination);
528 long consumerId = Consumers.instance().getConsumerId(name);
529 if ((consumerId == 0) ||
530 (destinationId == 0)) {
531 return result;
532 }
533
534 // all preprequisites have been met so continue processing the
535 // request.
536 try {
537 select = connection.prepareStatement(GET_MSG_HANDLES_FOR_DEST);
538 select.setLong(1, consumerId);
539
540 // iterate through the result set and construct the corresponding
541 // PersistentMessageHandles
542 set = select.executeQuery();
543 while (set.next()) {
544 // Attempt to retrieve the corresponding destination
545 JmsDestination dest = Destinations.instance().get(
546 set.getLong(2));
547 if (dest == null) {
548 throw new PersistenceException(
549 "Cannot create persistent handle, because " +
550 "destination mapping failed for " + set.getLong(2));
551 }
552
553 String consumer = Consumers.instance().getConsumerName(
554 set.getLong(3));
555 if (name == null) {
556 throw new PersistenceException(
557 "Cannot create persistent handle because " +
558 "consumer mapping failed for " + set.getLong(3));
559 }
560
561 PersistentMessageHandle handle = new PersistentMessageHandle();
562 handle.setMessageId(new MessageId(set.getString(1)));
563 handle.setDestination(dest);
564 handle.setConsumerName(consumer);
565 handle.setPriority(set.getInt(4));
566 handle.setAcceptedTime(set.getLong(5));
567 handle.setSequenceNumber(set.getLong(6));
568 handle.setExpiryTime(set.getLong(7));
569 handle.setDelivered((set.getInt(8) == 0) ? false : true);
570 result.add(handle);
571 }
572 } catch (SQLException exception) {
573 throw new PersistenceException(
574 "Failed to get message handles for destination=" +
575 destination + ", consumer=" + name, exception);
576 } finally {
577 SQLHelper.close(set);
578 SQLHelper.close(select);
579 }
580
581 return result;
582 }
583
584 /***
585 * Retrieve a distint list of message ids, in this table, between the min
586 * and max times inclusive.
587 *
588 * @param connection - the connection to use
589 * @param min - the minimum time in milliseconds
590 * @param max - the maximum time in milliseconds
591 * @return Vector - collection of String objects
592 * @throws PersistenceException - sql related exception
593 */
594 public Vector getMessageIds(Connection connection, long min, long max)
595 throws PersistenceException {
596
597 Vector result = new Vector();
598 PreparedStatement select = null;
599 ResultSet set = null;
600
601 try {
602 select = connection.prepareStatement(GET_MESSAGE_HANDLES_IN_RANGE);
603 select.setLong(1, min);
604 select.setLong(2, max);
605
606 // iterate through the result set and construct the corresponding
607 // PersistentMessageHandles
608 set = select.executeQuery();
609 while (set.next()) {
610 result.add(set.getString(1));
611 }
612
613
614 } catch (SQLException exception) {
615 throw new PersistenceException("Failed to retrieve message ids",
616 exception);
617 } finally {
618 SQLHelper.close(set);
619 SQLHelper.close(select);
620 }
621
622 return result;
623 }
624
625 /***
626 * Check if a message with the specified messageId exists in the
627 * table
628 *
629 * @param connection - the connection to use
630 * @param id - message Id
631 * @return Vector - collection of PersistentMessageHandle objects
632 * @throws PersistenceException - sql releated exception
633 */
634 public boolean messageExists(Connection connection, long messageId)
635 throws PersistenceException {
636
637 boolean result = false;
638 PreparedStatement select = null;
639 ResultSet set = null;
640
641 try {
642 select = connection.prepareStatement(GET_MESSAGE_HANDLE_WITH_ID);
643 select.setLong(1, messageId);
644 set = select.executeQuery();
645
646 if (set.next()) {
647 result = true;
648 }
649
650 } catch (SQLException exception) {
651 throw new PersistenceException(
652 "Failed to determine if message exists, id=" + messageId,
653 exception);
654 } finally {
655 SQLHelper.close(set);
656 SQLHelper.close(select);
657 }
658 return result;
659 }
660
661 /***
662 * Returns the number of messages for the specified destination and
663 * consumer
664 *
665 * @param connection - the connection to use
666 * @param destination - destination name
667 * @param name - consumer name
668 * @return Vector - collection of PersistentMessageHandle objects
669 * @throws PersistenceException - sql releated exception
670 */
671 public int getMessageCount(Connection connection, String destination,
672 String name)
673 throws PersistenceException {
674
675 int result = -1;
676 boolean destinationIsWildCard = false;
677
678 // map the destination name to an actual identity
679 long destinationId = Destinations.instance().getId(destination);
680 if (destinationId == 0) {
681 if (JmsTopic.isWildCard(destination)) {
682 destinationIsWildCard = true;
683 } else {
684 throw new PersistenceException(
685 "Cannot get message handle count for destination=" +
686 destination + " and consumer=" + name +
687 " since the destination cannot be mapped to an id");
688 }
689 }
690
691 // map the consumer name to an identity
692 long consumerId = Consumers.instance().getConsumerId(name);
693 if (consumerId == 0) {
694 throw new PersistenceException(
695 "Cannot get message handle count for destination=" +
696 destination + " and consumer=" + name +
697 " since the consumer cannot be mapped to an id");
698 }
699
700 PreparedStatement select = null;
701 ResultSet set = null;
702
703 try {
704 if (destinationIsWildCard) {
705 select = connection.prepareStatement(
706 GET_MSG_HANDLE_COUNT_FOR_DEST_AND_CONSUMER);
707 select.setLong(1, destinationId);
708 select.setLong(2, consumerId);
709 } else {
710 select = connection.prepareStatement(
711 GET_MSG_HANDLE_COUNT_FOR_CONSUMER);
712 select.setLong(1, consumerId);
713 }
714
715 set = select.executeQuery();
716 if (set.next()) {
717 result = set.getInt(1);
718 }
719 } catch (SQLException exception) {
720 throw new PersistenceException(
721 "Failed to count messages for destination=" + destination +
722 ", consumer=" + name, exception);
723 } finally {
724 SQLHelper.close(set);
725 SQLHelper.close(select);
726 }
727
728 return result;
729 }
730
731 /***
732 * Remove all expired handles for the specified consumer
733 *
734 * @param connection - the connection to use
735 * @param consumer - consumer name
736 * @throws PersistenceException - sql releated exception
737 */
738 public void removeExpiredMessageHandles(Connection connection,
739 String consumer)
740 throws PersistenceException {
741
742 PreparedStatement delete = null;
743
744 // map the consumer name ot an identity
745 long consumerId = Consumers.instance().getConsumerId(consumer);
746 if (consumerId != 0) {
747 try {
748 delete = connection.prepareStatement(DELETE_EXPIRED_MESSAGES);
749 delete.setLong(1, consumerId);
750 delete.setLong(2, System.currentTimeMillis());
751 delete.executeUpdate();
752 } catch (SQLException exception) {
753 throw new PersistenceException(
754 "Failed to remove expired message handles",
755 exception);
756 } finally {
757 SQLHelper.close(delete);
758 }
759 }
760 }
761
762 /***
763 * Deallocates resources owned or referenced by the instance
764 */
765 public void close() {
766 _instance = null;
767 }
768
769 } //-- MessageHandles
This page was automatically generated by Maven