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.io.ByteArrayInputStream;
47 import java.io.ByteArrayOutputStream;
48 import java.io.ObjectInputStream;
49 import java.io.ObjectOutputStream;
50 import java.sql.Connection;
51 import java.sql.PreparedStatement;
52 import java.sql.ResultSet;
53 import java.sql.SQLException;
54 import java.util.HashMap;
55 import java.util.Vector;
56
57 import javax.jms.Destination;
58 import javax.jms.JMSException;
59 import javax.sql.DataSource;
60
61 import org.apache.commons.logging.Log;
62 import org.apache.commons.logging.LogFactory;
63
64 import org.exolab.jms.client.JmsDestination;
65 import org.exolab.jms.client.JmsTopic;
66 import org.exolab.jms.message.MessageId;
67 import org.exolab.jms.message.MessageImpl;
68 import org.exolab.jms.messagemgr.PersistentMessageHandle;
69
70
71 /***
72 * This class manages the persistent of message objects. It maintains
73 * all the SQL for achieving this.
74 *
75 * @version $Revision: 1.24 $ $Date: 2004/01/08 05:55:07 $
76 * @author <a href="mailto:tma@netspace.net.au">Tim Anderson</a>
77 * @see org.exolab.jms.message.MessageImpl
78 * @see org.exolab.jms.persistence.RDBMSAdapter
79 */
80 public class Messages {
81
82 /***
83 * Caches the singleton instance of this class
84 */
85 private static Messages _instance;
86
87 /***
88 * Cache the transaction retry count
89 */
90 private static int _retryCount = 10;
91
92 /***
93 * Cache the retry interval in milliseconds
94 */
95 private static long _retryInterval = 50;
96
97 /***
98 * Monitor used to synchronize the initialization of this
99 * class
100 */
101 private static final Object _block = new Object();
102
103 /***
104 * The logger
105 */
106 private static final Log _log = LogFactory.getLog(Messages.class);
107
108
109 /***
110 * Returns a reference to the singleton instance.
111 * <p>
112 * Note that initialise() must have been invoked first for this
113 * to return a valid instance.
114 *
115 * @return Messages the singleton instance
116 */
117 public static Messages instance() {
118 return _instance;
119 }
120
121 /***
122 * Create an initialise the singleton istance of this class.
123 *
124 * @return Messages the singleton instance
125 */
126 public static Messages initialise() {
127 if (_instance == null) {
128 synchronized (_block) {
129 if (_instance == null) {
130 _instance = new Messages();
131 }
132 }
133 }
134 return _instance;
135 }
136
137 /***
138 * Add a message to the database, in the context of the specified
139 * transaction and connection.
140 *
141 * @param connection - execute on this connection
142 * @param message - the message to add
143 * @throws PersistenceException - an sql related error
144 */
145 public void add(Connection connection, MessageImpl message)
146 throws PersistenceException {
147
148 PreparedStatement insert = null;
149
150 // extract the identity of the message
151 String messageId = message.getMessageId().getId();
152
153 // check that the destination is actually registered
154 // and map the name to the corresponding id
155 String name;
156 try {
157 name = ((JmsDestination) message.getJMSDestination()).getName();
158 } catch (JMSException exception) {
159 throw new PersistenceException(
160 "Failed to get destination for message=" +
161 message.getMessageId(), exception);
162 }
163
164 long destinationId = Destinations.instance().getId(name);
165 if (destinationId == 0) {
166 throw new PersistenceException(
167 "Cannot add message=" + message.getMessageId() +
168 ", destination=" + name + " (" + destinationId +
169 "): destination does not exist");
170 }
171
172 try {
173 // create, populate and execute the insert
174 insert = connection.prepareStatement(
175 "insert into messages values " + "(?,?,?,?,?,?,?)");
176 insert.setString(1, messageId);
177 insert.setLong(2, destinationId);
178 insert.setInt(3, message.getJMSPriority());
179 insert.setLong(4, message.getAcceptedTime());
180 insert.setLong(5, message.getJMSExpiration());
181 insert.setInt(6, (message.getProcessed()) ? 1 : 0);
182
183 // serialize the message
184 byte[] bytes = serialize(message);
185 insert.setBinaryStream(7, new ByteArrayInputStream(bytes),
186 bytes.length);
187 //insert.setBytes(8, bytes);
188
189 // execute the insert
190 if (insert.executeUpdate() != 1) {
191 throw new PersistenceException(
192 "Failed to add message=" + message.getMessageId() +
193 ", destination=" + name + " (" + destinationId + ")");
194 }
195 } catch (PersistenceException exception) {
196 throw exception;
197 } catch (Exception exception) {
198 throw new PersistenceException(
199 "Failed to add message=" + message.getMessageId() +
200 ", destination=" + name + " (" + destinationId + ")",
201 exception);
202 } finally {
203 SQLHelper.close(insert);
204 }
205 }
206
207 /***
208 * Update the message state in the database. This will be called to set
209 * the message state to processed by the provider
210 *
211 * @param connection - execute on this connection
212 * @param message - the message to update
213 * @throws PersistenceException - an sql related error
214 */
215 public void update(Connection connection, MessageImpl message)
216 throws PersistenceException {
217
218 PreparedStatement update = null;
219
220 // extract the identity of the message
221 String messageId = message.getMessageId().getId();
222
223 try {
224 update = connection.prepareStatement(
225 "update messages set processed=? where messageId=?");
226 update.setInt(1, message.getProcessed() ? 1 : 0);
227 update.setString(2, messageId);
228
229 // execute the update
230 if (update.executeUpdate() != 1) {
231 _log.error("Cannot update message=" + messageId);
232 }
233 } catch (SQLException exception) {
234 throw new PersistenceException(
235 "Failed to update message, id=" + messageId, exception);
236 } finally {
237 SQLHelper.close(update);
238 }
239 }
240
241 /***
242 * Remove a message with the specified identity from the database
243 *
244 * @param connection - execute on this connection
245 * @param messageId - the message id of the message to remove
246 * @throws PersistenceException - an sql related error
247 */
248 public void remove(Connection connection, String messageId)
249 throws PersistenceException {
250
251 PreparedStatement delete = null;
252 try {
253 delete = connection.prepareStatement(
254 "delete from messages where messageId=?");
255 delete.setString(1, messageId);
256
257 // execute the delete
258 if (delete.executeUpdate() != 1) {
259 _log.error("Cannot remove message=" + messageId);
260 }
261 } catch (SQLException exception) {
262 throw new PersistenceException(
263 "Failed to remove message, id=" + messageId, exception);
264 } finally {
265 SQLHelper.close(delete);
266 }
267 }
268
269 /***
270 * Return the message identified by the message Id
271 *
272 * @param connection - execute on this connection
273 * @param messageId - id of message to retrieve
274 * @return MessageImpl - the associated message
275 * @throws PersistenceException - an sql related error
276 */
277 public MessageImpl get(Connection connection, String messageId)
278 throws PersistenceException {
279
280 MessageImpl result = null;
281 PreparedStatement select = null;
282 ResultSet set = null;
283 try {
284 select = connection.prepareStatement(
285 "select messageBlob, processed from messages where messageId=?");
286
287 select.setString(1, messageId);
288 set = select.executeQuery();
289 if (set.next()) {
290 result = deserialize(set.getBytes(1));
291 result.setProcessed((set.getInt(2) == 1 ? true : false));
292 }
293 } catch (SQLException exception) {
294 throw new PersistenceException(
295 "Failed to retrieve message, id=" + messageId, exception);
296 } finally {
297 SQLHelper.close(set);
298 SQLHelper.close(select);
299 }
300
301 return result;
302 }
303
304 /***
305 * Delete all messages for the given destination
306 *
307 * @param connection - execute on this connection
308 * @param messageId - id of message to retrieve
309 * @return int - the number of messages purged
310 * @throws PersistenceException - an sql related error
311 */
312 public int removeMessages(Connection connection, String destination)
313 throws PersistenceException {
314
315 int result = 0;
316 PreparedStatement delete = null;
317
318 // map the destination name to an id
319 long destinationId = Destinations.instance().getId(destination);
320 if (destinationId == 0) {
321 throw new PersistenceException("Cannot delete messages for " +
322 "destination=" + destination +
323 ": destination does not exist");
324 }
325
326 try {
327 delete = connection.prepareStatement(
328 "delete from messages where destinationId = ?");
329 delete.setLong(1, destinationId);
330 result = delete.executeUpdate();
331 } catch (SQLException exception) {
332 throw new PersistenceException(
333 "Failed to remove messages for destination=" + destination,
334 exception);
335 } finally {
336 SQLHelper.close(delete);
337 }
338
339 return result;
340 }
341
342 /***
343 * Retrieve the next set of messages for the specified destination with
344 * an acceptance time greater or equal to that specified. It will retrieve
345 * around 200 or so messages depending on what is available.
346 *
347 * @param connection - execute on this connection
348 * @param destination - the destination
349 * @param priority - the priority of the messages
350 * @param time - with timestamp greater or equal to this
351 * @return Vector - one or more MessageImpl objects
352 * @throws PersistenceException - if an SQL error occurs
353 */
354 public Vector getMessages(Connection connection, String destination,
355 int priority, long time)
356 throws PersistenceException {
357
358 PreparedStatement select = null;
359 ResultSet set = null;
360 Vector messages = new Vector();
361
362 try {
363 JmsDestination dest = Destinations.instance().get(destination);
364 if (dest == null) {
365 throw new PersistenceException(
366 "Cannot getMessages for destination=" + destination
367 + ": destination does not exist");
368 }
369
370 long destinationId = Destinations.instance().getId(destination);
371 if (destinationId == 0) {
372 throw new PersistenceException(
373 "Cannot getMessages for destination=" + destination
374 + ": destination does not exist");
375 }
376
377 if ((dest instanceof JmsTopic) &&
378 (((JmsTopic) dest).isWildCard())) {
379 // if the destination is a wildcard then we can't only select
380 // on timestamp. This will fault in any message greater than
381 // or equal to the specified timestamp.
382 select = connection.prepareStatement(
383 "select * from messages where priority=? and createTime>=? order by createTime asc");
384 select.setInt(1, priority);
385 select.setLong(2, time);
386 } else {
387 // if the destination is more specific then we can execute a
388 // more specialized query and fault in other messages for
389 // the same destination.
390 select = connection.prepareStatement(
391 "select * from messages where destinationId=? and priority=? and createTime>=? order by createTime asc");
392 select.setLong(1, destinationId);
393 select.setInt(2, priority);
394 select.setLong(3, time);
395 }
396 set = select.executeQuery();
397
398 // now iterate through the result set
399 int count = 0;
400 long lastTimeStamp = time;
401 while (set.next()) {
402 MessageImpl m = deserialize(set.getBytes("messageBlob"));
403 m.setProcessed((set.getInt("processed") == 1 ? true : false));
404 messages.add(m);
405 if (++count > 200) {
406 // if there are more than two hundred rows then exist
407 // the loop after 200 messages have been retrieved
408 // and the timestamp has changed.
409 if (set.getLong("createTime") > lastTimeStamp) {
410 break;
411 }
412 } else {
413 lastTimeStamp = set.getLong("createTime");
414 }
415 }
416 } catch (SQLException exception) {
417 throw new PersistenceException(
418 "Failed to retrieve messages", exception);
419 } finally {
420 SQLHelper.close(set);
421 SQLHelper.close(select);
422 }
423
424 return messages;
425 }
426
427 /***
428 * Retrieve the specified number of message ids from the database with a
429 * time greater than that specified. The number of items to retrieve
430 * is only a hint and does not reflect the number of messages actually
431 * returned.
432 *
433 * @param connection - execute on this connection
434 * @param time - with timestamp greater than
435 * @param hint - an indication of the number of messages to return.
436 * @return a map of messageId Strings to their creation time
437 * @throws PersistenceException - if an SQL error occurs
438 */
439 public HashMap getMessageIds(Connection connection, long time, int hint)
440 throws PersistenceException {
441
442 PreparedStatement select = null;
443 ResultSet set = null;
444 HashMap messages = new HashMap();
445
446 try {
447 select = connection.prepareStatement(
448 "select messageId,createTime from messages where createTime>? order by createTime asc");
449 select.setLong(1, time);
450 set = select.executeQuery();
451
452 // now iterate through the result set
453 int count = 0;
454 long lastTimeStamp = time;
455 while (set.next()) {
456 messages.put(set.getString("messageId"),
457 new Long(set.getLong("createTime")));
458 if (++count > hint) {
459 if (set.getLong("createTime") > lastTimeStamp) {
460 break;
461 }
462 } else {
463 lastTimeStamp = set.getLong("createTime");
464 }
465
466 }
467 } catch (SQLException exception) {
468 throw new PersistenceException(
469 "Failed to retrieve message identifiers", exception);
470 } finally {
471 SQLHelper.close(set);
472 SQLHelper.close(select);
473 }
474
475 return messages;
476 }
477
478 /***
479 * Retrieve a list of unprocessed messages and return them to the client.
480 * An unprocessed message has been accepted by the system but not
481 * processed.
482 *
483 * @param connection - execute on this connection
484 * @return Vector - one or more MessageImpl objects
485 * @throws PersistenceException - if an SQL error occurs
486 */
487 public Vector getUnprocessedMessages(Connection connection)
488 throws PersistenceException {
489
490 PreparedStatement select = null;
491 ResultSet set = null;
492 Vector messages = new Vector();
493
494 try {
495 select = connection.prepareStatement(
496 "select * from messages where processed=0");
497 set = select.executeQuery();
498 // now iterate through the result set
499 while (set.next()) {
500 MessageImpl m = deserialize(set.getBytes("messageBlob"));
501 m.setProcessed(false);
502 messages.add(m);
503 }
504 } catch (SQLException exception) {
505 throw new PersistenceException(
506 "Failed to retrieve unprocessed messages", exception);
507 } finally {
508 SQLHelper.close(set);
509 SQLHelper.close(select);
510 }
511
512 return messages;
513 }
514
515 /***
516 * Retrieve the message handle for all unexpired messages
517 *
518 * @param connection - execute on this connection
519 * @param destination - the destination in question
520 * @return Vector - collection of PersistentMessageHandle objects
521 * @throws PersistenceException - sql releated exception
522 */
523 public Vector getNonExpiredMessages(Connection connection,
524 JmsDestination destination)
525 throws PersistenceException {
526
527 Vector result = new Vector();
528 PreparedStatement select = null;
529 ResultSet set = null;
530
531 try {
532 long destinationId = Destinations.instance().getId(
533 destination.getName());
534
535 if (destinationId == 0) {
536 throw new PersistenceException(
537 "Cannot getMessages for destination=" + destination
538 + ": destination does not exist");
539 }
540
541 select = connection.prepareStatement(
542 "select messageId,destinationId,priority,createTime,expiryTime" +
543 " from messages where expiryTime>0 and destinationId=? order by expiryTime asc");
544 select.setLong(1, destinationId);
545 set = select.executeQuery();
546
547 while (set.next()) {
548 PersistentMessageHandle handle = new PersistentMessageHandle();
549 handle.setMessageId(new MessageId(set.getString(1)));
550 handle.setDestination(destination);
551 handle.setPriority(set.getInt(3));
552 handle.setAcceptedTime(set.getLong(4));
553 handle.setExpiryTime(set.getLong(5));
554 result.add(handle);
555 }
556 } catch (SQLException exception) {
557 throw new PersistenceException(
558 "Failed to retrieve non-expired messages", exception);
559 } finally {
560 SQLHelper.close(set);
561 SQLHelper.close(select);
562 }
563
564 return result;
565 }
566
567 /***
568 * Delete all expired messages and associated message handles.
569 *
570 * @param connection - execute on this connection
571 * @throws PersistenceException - if an SQL error occurs
572 */
573 public void removeExpiredMessages(Connection connection)
574 throws PersistenceException {
575
576 PreparedStatement delete = null;
577 try {
578 long time = System.currentTimeMillis();
579
580 // delete from the messages
581 delete = connection.prepareStatement(
582 "delete from messages where expiryTime > 0 and expiryTime < ?");
583 delete.setLong(1, time);
584 delete.executeUpdate();
585 delete.close();
586
587 // delete the message handles
588 delete = connection.prepareStatement(
589 "delete from message_handles where expiryTime > 0 and expiryTime < ?");
590 delete.setLong(1, time);
591 delete.executeUpdate();
592 } catch (SQLException exception) {
593 throw new PersistenceException(
594 "Failed to remove expired messages", exception);
595 } finally {
596 SQLHelper.close(delete);
597 }
598 }
599
600 /***
601 * Reset the instance. We need to deprecate this method since this
602 * class does not contain state information
603 */
604 public void close() {
605 _instance = null;
606 }
607
608 /***
609 * Default constructor does nothing at the moment.
610 */
611 protected Messages() {
612 }
613
614 /***
615 * Get the message as a serialized blob
616 *
617 * @param message the message to serialize
618 * @return byte[] the serialized message
619 */
620 public byte[] serialize(MessageImpl message)
621 throws PersistenceException {
622
623 byte[] result = null;
624 try {
625 ByteArrayOutputStream bstream = new ByteArrayOutputStream();
626 ObjectOutputStream ostream = new ObjectOutputStream(bstream);
627 ostream.writeObject(message);
628 ostream.close();
629 result = bstream.toByteArray();
630 } catch (Exception exception) {
631 throw new PersistenceException("Failed to serialize message",
632 exception);
633 }
634
635 return result;
636 }
637
638 /***
639 * Set the message from a serialized blob
640 *
641 * @param blob the serialized message
642 * @return the re-constructed message
643 */
644 public MessageImpl deserialize(byte[] blob) throws PersistenceException {
645 MessageImpl message = null;
646
647 if (blob != null) {
648 try {
649 ByteArrayInputStream bstream = new ByteArrayInputStream(blob);
650 ObjectInputStream istream = new ObjectInputStream(bstream);
651 message = (MessageImpl) istream.readObject();
652 istream.close();
653 } catch (Exception exception) {
654 throw new PersistenceException(
655 "Failed to de-serialize message", exception);
656 }
657 } else {
658 throw new PersistenceException(
659 "Cannot de-serialize null message blob");
660 }
661
662 return message;
663 }
664
665 } //-- Messages
This page was automatically generated by Maven