KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > test > jbossmq > MQBase


1 /*
2   * JBoss, Home of Professional Open Source
3   * Copyright 2005, JBoss Inc., and individual contributors as indicated
4   * by the @authors tag. See the copyright.txt in the distribution for a
5   * full listing of individual contributors.
6   *
7   * This is free software; you can redistribute it and/or modify it
8   * under the terms of the GNU Lesser General Public License as
9   * published by the Free Software Foundation; either version 2.1 of
10   * the License, or (at your option) any later version.
11   *
12   * This software is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15   * Lesser General Public License for more details.
16   *
17   * You should have received a copy of the GNU Lesser General Public
18   * License along with this software; if not, write to the Free
19   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21   */

22 package org.jboss.test.jbossmq;
23
24 import java.util.Enumeration JavaDoc;
25
26 import javax.jms.Connection JavaDoc;
27 import javax.jms.DeliveryMode JavaDoc;
28 import javax.jms.Destination JavaDoc;
29 import javax.jms.ExceptionListener JavaDoc;
30 import javax.jms.JMSException JavaDoc;
31 import javax.jms.Message JavaDoc;
32 import javax.jms.MessageConsumer JavaDoc;
33 import javax.jms.MessageListener JavaDoc;
34 import javax.jms.MessageProducer JavaDoc;
35 import javax.jms.Queue JavaDoc;
36 import javax.jms.QueueBrowser JavaDoc;
37 import javax.jms.QueueConnection JavaDoc;
38 import javax.jms.QueueConnectionFactory JavaDoc;
39 import javax.jms.QueueSender JavaDoc;
40 import javax.jms.QueueSession JavaDoc;
41 import javax.jms.Session JavaDoc;
42 import javax.jms.Topic JavaDoc;
43 import javax.jms.TopicConnection JavaDoc;
44 import javax.jms.TopicConnectionFactory JavaDoc;
45 import javax.jms.TopicPublisher JavaDoc;
46 import javax.jms.TopicSession JavaDoc;
47 import javax.naming.Context JavaDoc;
48 import javax.naming.NamingException JavaDoc;
49
50 import org.jboss.test.JBossTestCase;
51 import org.jboss.logging.Logger;
52
53 /**
54  * JMS tests base class.
55  *
56  * Your test extends this class, and can then use common methods. To do
57  * the tests you use TopicWorker or QueueWorker and the MessageCreator,
58  * MessageFilter and perhaps MessageQos classes, directly or by extending
59  * them.
60  *
61  * You can change the connection factories and destinations used by the
62  * properties: jbosstest.queuefactory, jbosstest.topicfactory,
63  * jbosstest.queue or jbosstest.topic.
64  *
65  *
66  * @author <a HREF="pra@tim.se">Peter Antman</a>
67  * @version $Revision: 58115 $
68  */

69 public class MQBase extends JBossTestCase
70 {
71    public static final int PUBLISHER = 0;
72    public static final int SUBSCRIBER = 1;
73    public static final int GETTER = 2;
74    public static final int CONNECTOR = 3;
75    public static final int FAILSAFE_SUBSCRIBER = 4;
76    public static final int TRANS_NONE = 0;
77    public static final int TRANS_INDIVIDUAL = 1;
78    public static final int TRANS_TOTAL = 2;
79    public static final String JavaDoc[] TRANS_DESC = {"NOT", "individually", "totally"};
80    public static final int DEFAULT_RUNSLEEP = 50;
81    public final Logger log = getLog();
82
83    // Provider specific
84
public String JavaDoc TOPIC_FACTORY = "ConnectionFactory";
85    public String JavaDoc QUEUE_FACTORY = "ConnectionFactory";
86
87    public String JavaDoc TEST_QUEUE = "queue/testQueue";
88    public String JavaDoc TEST_TOPIC = "topic/testTopic";
89
90    public Context JavaDoc context;
91    public QueueConnectionFactory JavaDoc queueFactory;
92    public TopicConnectionFactory JavaDoc topicFactory;
93
94    public MQBase(String JavaDoc name)
95    {
96       super(name);
97    }
98
99    public long getRunSleep()
100    {
101       log.info("run.sleep: " + System.getProperty("run.sleep"));
102       return 1000L * Integer.getInteger("run.sleep", DEFAULT_RUNSLEEP).intValue();
103    }
104
105    public void sleep(long sleep)
106    {
107       try
108       {
109          Thread.sleep(sleep);
110       }
111       catch (InterruptedException JavaDoc e)
112       {
113       }
114    }
115
116    public void drainTopic() throws JMSException JavaDoc
117    {
118       TopicWorker sub1 = new TopicWorker(GETTER,
119          TRANS_NONE,
120          null
121       );
122       sub1.connect();
123       sub1.get();
124       sub1.close();
125    }
126
127    public void drainQueue() throws JMSException JavaDoc
128    {
129       QueueWorker sub1 = new QueueWorker(GETTER,
130          TRANS_NONE,
131          null
132       );
133       sub1.connect();
134       sub1.get();
135       sub1.close();
136    }
137
138    /**
139     * The JUnit setup method
140     *
141     * @exception Exception Description of Exception
142     */

143    protected void setUp() throws Exception JavaDoc
144    {
145       // Reconfigure acording to props
146
QUEUE_FACTORY = System.getProperty("jbosstest.queuefactory", QUEUE_FACTORY);
147       TOPIC_FACTORY = System.getProperty("jbosstest.topicfactory", TOPIC_FACTORY);
148       TEST_QUEUE = System.getProperty("jbosstest.queue", TEST_QUEUE);
149       TEST_TOPIC = System.getProperty("jbosstest.topic", TEST_TOPIC);
150
151       if (context == null)
152       {
153
154          context = getInitialContext();
155
156          queueFactory = (QueueConnectionFactory JavaDoc) context.lookup(QUEUE_FACTORY);
157          topicFactory = (TopicConnectionFactory JavaDoc) context.lookup(TOPIC_FACTORY);
158
159          getLog().debug("Connection to JBossMQ established.");
160       }
161
162    }
163
164
165    public static void main(String JavaDoc[] args)
166    {
167
168    }
169
170    public abstract class JMSWorker implements Runnable JavaDoc, MessageListener JavaDoc, ExceptionListener JavaDoc
171    {
172
173       protected boolean stopRequested = false;
174       protected int messageHandled = 0;
175       protected Exception JavaDoc runEx = null;
176       protected MessageFilter filter;
177       protected MessageCreator creator;
178       protected int number = 1;
179       protected int type = -1;
180       protected int transacted;
181       protected QosConfig qosConfig = new QosConfig();
182       protected String JavaDoc userName;
183       protected String JavaDoc password;
184       protected String JavaDoc clientID;
185
186       // Generic ones, should be set by sublcasses
187
public Connection JavaDoc connection;
188       public Destination JavaDoc destination;
189       public Session JavaDoc session;
190       public MessageProducer JavaDoc producer;
191       public MessageConsumer JavaDoc consumer;
192
193       /**
194        * Create one without any settings, use mutators instead. Makes it easier to owerride.
195        */

196       public JMSWorker()
197       {
198       }
199
200       public JMSWorker(int type, int transacted, MessageFilter filter)
201       {
202          this.type = type;
203          this.transacted = transacted;
204          this.filter = filter;
205       }
206
207       public JMSWorker(int type,
208          int transacted,
209          MessageCreator creator,
210          int number
211          )
212       {
213          this.type = type;
214          this.transacted = transacted;
215          this.creator = creator;
216          this.number = number;
217       }
218
219       public void setSubscriberAttrs(int type, int transacted, MessageFilter filter)
220       {
221          this.type = type;
222          this.transacted = transacted;
223          this.filter = filter;
224       }
225
226       public void setPublisherAttrs(int type,
227          int transacted,
228          MessageCreator creator,
229          int number)
230       {
231          this.type = type;
232          this.transacted = transacted;
233          this.creator = creator;
234          this.number = number;
235       }
236
237       public void setUser(String JavaDoc userName, String JavaDoc password)
238       {
239          this.userName = userName;
240          this.password = password;
241       }
242
243       public void setClientID(String JavaDoc ID)
244       {
245          this.clientID = ID;
246       }
247
248       abstract public void publish() throws JMSException JavaDoc;
249
250       abstract public void publish(int nr) throws JMSException JavaDoc;
251
252       /**
253        * Subsribes, collects, checking any set filters. A messageComsumer must be created before calling this.
254        */

255       public void subscribe() throws JMSException JavaDoc
256       {
257          subscribe(false);
258       }
259
260       /**
261        * Subsribes, collects, checking any set filters. A messageComsumer must be created before calling this. If arg set to true, do a failsafe sub
262        */

263       public void subscribe(boolean failsafe) throws JMSException JavaDoc
264       {
265          if (consumer == null)
266             throw new JMSException JavaDoc("No messageConsumer created");
267
268          if (failsafe)
269             connection.setExceptionListener(this);
270
271          consumer.setMessageListener(this);
272
273       }
274
275       public void get() throws JMSException JavaDoc
276       {
277          Message JavaDoc msg = consumer.receive(2000);
278          while (msg != null)
279          {
280             if (filter != null)
281             {
282                if (filter.ok(msg))
283                   messageHandled++;
284             }
285             else
286             {
287                messageHandled++;
288             }
289             msg = consumer.receive(2000);
290          }
291       }
292
293       abstract public void connect() throws JMSException JavaDoc;
294
295       public void setQosConfig(QosConfig qosConfig)
296       {
297          this.qosConfig = qosConfig;
298       }
299
300       public void setStoped() throws JMSException JavaDoc
301       {
302          stopRequested = true;
303       }
304
305       public int getMessageHandled()
306       {
307          return messageHandled;
308       }
309
310       public Exception JavaDoc getException()
311       {
312          return runEx;
313       }
314
315       public void reset()
316       {
317          messageHandled = 0;
318          stopRequested = false;
319          runEx = null;
320       }
321
322       public void close()
323       {
324          try
325          {
326             if (consumer != null)
327                consumer.close();
328             if (producer != null)
329                producer.close();
330             if (session != null)
331                session.close();
332          }
333          catch (JMSException JavaDoc ex)
334          {
335          }
336          finally
337          {
338             if (connection != null)
339             {
340                try
341                {
342                   connection.close();
343                }
344                catch (JMSException JavaDoc ex)
345                {
346                }
347             }
348          }
349       }
350
351       public void onMessage(Message JavaDoc msg)
352       {
353          try
354          {
355             if (filter != null)
356             {
357                if (filter.ok(msg))
358                   messageHandled++;
359             }
360             else
361             {
362                messageHandled++;
363             }
364             if (session.getTransacted())
365                session.commit();
366          }
367          catch (Exception JavaDoc ex)
368          {
369             log.warn("Exception in on message: " + ex, ex);
370             runEx = ex;
371          }
372       }
373
374       /**
375        * onException handling is only for subscriber. Will try to to
376        * a connect followed by a subscribe
377        */

378       public void onException(JMSException JavaDoc ex)
379       {
380          log.error("Ex in connection: " + ex);
381
382          try
383          {
384             connection.setExceptionListener(null);
385             close();
386          }
387          catch (JMSException JavaDoc c)
388          {
389          }
390          
391          // Try reconnect, loops until success or shut down
392
try
393          {
394             boolean tryIt = true;
395             while (tryIt && !stopRequested)
396             {
397                log.info("Trying reconnect...");
398                try
399                {
400                   Thread.sleep(10000);
401                }
402                catch (InterruptedException JavaDoc ie)
403                {
404                }
405                try
406                {
407                   connect();
408                   subscribe(true);
409                   tryIt = false;
410                   log.info("Reconnect OK");
411                   //return;
412
}
413                catch (JMSException JavaDoc e)
414                {
415                   log.error("Error in reconnect: " + e);
416                }
417             }
418
419          }
420          catch (Exception JavaDoc je)
421          {
422             log.error("Strange error in failsafe handling" + je, je);
423          }
424       }
425
426       public void run()
427       {
428          try
429          {
430             switch (type)
431             {
432                case -1:
433                   log.info("Nothing to do for type " + type);
434                   break;
435                case PUBLISHER:
436                   connect();
437                   publish();
438                   break;
439                case SUBSCRIBER:
440                   connect();
441                   subscribe();
442                   break;
443                case GETTER:
444                   connect();
445                   get();
446                   break;
447                case CONNECTOR:
448                   connect();
449                   break;
450                case FAILSAFE_SUBSCRIBER:
451                   connect();
452                   subscribe(true);
453                   break;
454             }
455
456             //if the method does not hold an own thread, we do it here
457
while (!stopRequested)
458             {
459                try
460                {
461                   Thread.sleep(1000);
462                }
463                catch (InterruptedException JavaDoc ex)
464                {
465
466                }
467             }
468          }
469          catch (JMSException JavaDoc ex)
470          {
471             runEx = ex;
472             log.error("Could not run: " + ex, ex);
473          }
474       }
475    }
476
477    public interface MessageCreator
478    {
479       public void setSession(Session JavaDoc session);
480
481       public Message JavaDoc createMessage(int nr) throws JMSException JavaDoc;
482    }
483
484    public abstract class BaseMessageCreator implements MessageCreator
485    {
486       protected Session JavaDoc session;
487       protected String JavaDoc property;
488
489       public BaseMessageCreator(String JavaDoc property)
490       {
491          this.property = property;
492       }
493
494       public void setSession(Session JavaDoc session)
495       {
496          this.session = session;
497       }
498
499       abstract public Message JavaDoc createMessage(int nr) throws JMSException JavaDoc;
500    }
501
502
503    public class IntRangeMessageCreator extends BaseMessageCreator
504    {
505       int start = 0;
506
507       public IntRangeMessageCreator(String JavaDoc property)
508       {
509          super(property);
510       }
511
512       public IntRangeMessageCreator(String JavaDoc property, int start)
513       {
514          super(property);
515          this.start = start;
516       }
517
518       public Message JavaDoc createMessage(int nr) throws JMSException JavaDoc
519       {
520          if (session == null)
521             throw new JMSException JavaDoc("Session not allowed to be null");
522
523          Message JavaDoc msg = session.createMessage();
524          msg.setStringProperty(property, String.valueOf(start + nr));
525          return msg;
526       }
527    }
528
529    public interface MessageFilter
530    {
531       public boolean ok(Message JavaDoc msg) throws JMSException JavaDoc;
532    }
533
534    public class IntRangeMessageFilter implements MessageFilter
535    {
536       Class JavaDoc messageClass;
537       String JavaDoc className;
538       String JavaDoc property;
539       int low;
540       int max;
541       int counter = 0;
542       int report = 1000;
543
544       public IntRangeMessageFilter(Class JavaDoc messageClass, String JavaDoc property, int low, int max)
545       {
546          this.messageClass = messageClass;
547          this.property = property;
548          className = messageClass.getName();
549          this.low = low;
550          this.max = max;
551       }
552
553       private boolean validateClass(Message JavaDoc msg)
554       {
555          Class JavaDoc clazz = null;
556          if (msg instanceof javax.jms.TextMessage JavaDoc)
557             clazz = javax.jms.TextMessage JavaDoc.class;
558          else if (msg instanceof javax.jms.BytesMessage JavaDoc)
559             clazz = javax.jms.BytesMessage JavaDoc.class;
560          else if (msg instanceof javax.jms.MapMessage JavaDoc)
561             clazz = javax.jms.MapMessage JavaDoc.class;
562          else if (msg instanceof javax.jms.ObjectMessage JavaDoc)
563             clazz = javax.jms.ObjectMessage JavaDoc.class;
564          else if (msg instanceof javax.jms.StreamMessage JavaDoc)
565             clazz = javax.jms.StreamMessage JavaDoc.class;
566          else
567             clazz = javax.jms.Message JavaDoc.class;
568
569          return clazz.equals(messageClass);
570       }
571
572       public boolean ok(Message JavaDoc msg) throws JMSException JavaDoc
573       {
574          boolean res = false;
575          if (validateClass(msg))
576          {
577             if (msg.propertyExists(property))
578             {
579                String JavaDoc p = msg.getStringProperty(property);
580                try
581                {
582                   int i = Integer.parseInt(p);
583                   //log.debug("Received message " + property +"=" +i);
584
if (i >= low && i < max)
585                      res = true;
586                }
587                catch (NumberFormatException JavaDoc ex)
588                {
589                   throw new JMSException JavaDoc("Property " + property + " was not int: " + p);
590                }
591             }
592          }
593          counter++;
594          int mod = counter % report;
595          if (mod == 0)
596             log.debug("Have received " + counter + " messages");
597          return res;
598       }
599
600    }
601
602    /*
603    public class REMessageFilter implements MessageFilter {
604       Class messageClass;
605       String className;
606       String property;
607       RE re = null;
608       public REMessageFilter(Class messageClass, String property, String regexp) throws REException{
609          this.messageClass = messageClass;
610          this.property = property;
611          re = new RE(regexp);
612          className = messageClass.getName();
613       }
614       
615       public boolean ok(Message msg) throws JMSException{
616          boolean res = false;
617          if (className.equals(msg.getClass().getName())) {
618             if (msg.propertyExists(property)) {
619                String p = msg.getStringProperty(property);
620                if (re.getMatch(p)!=null)
621                   res = true;
622             }
623          }
624          return true;
625       }
626    }
627    */

628    /**
629     * Defines quality of service for message publishing. Defaults are the same
630     * ase defined in SpyMessage.
631     */

632    public class QosConfig
633    {
634       int deliveryMode = DeliveryMode.PERSISTENT;
635       int priority = 4;
636       long ttl = 0;
637    }
638
639    public class TopicWorker extends JMSWorker
640    {
641       String JavaDoc durableHandle;
642
643       /**
644        * If using this, use mutators to add attrs.
645        */

646       public TopicWorker()
647       {
648          super();
649       }
650
651       public TopicWorker(int type, int transacted, MessageFilter filter)
652       {
653          super(type, transacted, filter);
654       }
655
656       public TopicWorker(int type,
657          int transacted,
658          MessageCreator creator,
659          int number
660          )
661       {
662          super(type, transacted, creator, number);
663       }
664
665
666       public void publish() throws JMSException JavaDoc
667       {
668          publish(number);
669       }
670
671       public void publish(int nr) throws JMSException JavaDoc
672       {
673          if (producer == null)
674             producer = ((TopicSession JavaDoc) session).createPublisher((Topic JavaDoc) destination);
675          if (creator == null)
676             throw new JMSException JavaDoc("Publish must have a MessageCreator set");
677
678          creator.setSession(session);
679          log.debug("Publishing " + nr + " messages");
680          for (int i = 0; i < nr; i++)
681          {
682             if (qosConfig != null)
683             {
684                ((TopicPublisher JavaDoc) producer).publish(creator.createMessage(i),
685                   qosConfig.deliveryMode,
686                   qosConfig.priority,
687                   qosConfig.ttl);
688             }
689             else
690             {
691                ((TopicPublisher JavaDoc) producer).publish(creator.createMessage(i));
692             }
693
694             messageHandled++;
695          }
696          if (session.getTransacted())
697             session.commit();
698          log.debug("Finished publishing");
699       }
700
701       public void subscribe() throws JMSException JavaDoc
702       {
703          subscribe(false);
704       }
705
706       public void subscribe(boolean failsafe) throws JMSException JavaDoc
707       {
708          if (durableHandle != null)
709             consumer = ((TopicSession JavaDoc) session).createDurableSubscriber((Topic JavaDoc) destination, durableHandle);
710          else
711             consumer = ((TopicSession JavaDoc) session).createSubscriber((Topic JavaDoc) destination);
712          super.subscribe(failsafe);
713          connection.start();
714       }
715
716       public void get() throws JMSException JavaDoc
717       {
718          consumer = ((TopicSession JavaDoc) session).createSubscriber((Topic JavaDoc) destination);
719          super.subscribe();
720          connection.start();
721       }
722
723       public void connect() throws JMSException JavaDoc
724       {
725          log.debug("Connecting: " + this.toString());
726          if (userName != null)
727             connection = topicFactory.createTopicConnection(userName, password);
728          else
729             connection = topicFactory.createTopicConnection();
730
731          if (clientID != null)
732          {
733             log.debug("Setting clientID" + clientID);
734             connection.setClientID(clientID);
735          }
736
737          session = ((TopicConnection JavaDoc) connection).createTopicSession(transacted != TRANS_NONE, Session.AUTO_ACKNOWLEDGE);
738          try
739          {
740             destination = (Destination JavaDoc) context.lookup(TEST_TOPIC);
741          }
742          catch (NamingException JavaDoc ex)
743          {
744             throw new JMSException JavaDoc("Could not lookup topic " + ex);
745          }
746       }
747
748       // Topic specific stuff
749
public void setDurable(String JavaDoc userId, String JavaDoc pwd, String JavaDoc handle)
750       {
751          this.userName = userId;
752          this.password = pwd;
753          this.durableHandle = handle;
754       }
755
756       public void setDurable(String JavaDoc handle)
757       {
758          this.durableHandle = handle;
759       }
760
761       public void unsubscribe() throws JMSException JavaDoc
762       {
763          if (durableHandle != null)
764             ((TopicSession JavaDoc) session).unsubscribe(durableHandle);
765       }
766
767       public String JavaDoc toString()
768       {
769          return "(userId=" + userName + " pwd=" + password + " handle=" + durableHandle + ")";
770       }
771
772    }
773
774    public class QueueWorker extends JMSWorker
775    {
776       String JavaDoc userId;
777       String JavaDoc pwd;
778       String JavaDoc handle;
779
780       /**
781        * If using this, use mutators to add attrs.
782        */

783       public QueueWorker()
784       {
785          super();
786       }
787
788       public QueueWorker(int type, int transacted, MessageFilter filter)
789       {
790          super(type, transacted, filter);
791       }
792
793       public QueueWorker(int type,
794          int transacted,
795          MessageCreator creator,
796          int number
797          )
798       {
799          super(type, transacted, creator, number);
800       }
801
802
803       public void publish() throws JMSException JavaDoc
804       {
805          publish(number);
806       }
807
808       public void publish(int nr) throws JMSException JavaDoc
809       {
810          if (producer == null)
811             producer = ((QueueSession JavaDoc) session).createSender((Queue JavaDoc) destination);
812          if (creator == null)
813             throw new JMSException JavaDoc("Publish must have a MessageCreator set");
814
815          creator.setSession(session);
816          log.debug("Publishing " + nr + " messages");
817          for (int i = 0; i < nr; i++)
818          {
819             if (qosConfig != null)
820             {
821                ((QueueSender JavaDoc) producer).send(creator.createMessage(i),
822                   qosConfig.deliveryMode,
823                   qosConfig.priority,
824                   qosConfig.ttl);
825             }
826             else
827             {
828                ((QueueSender JavaDoc) producer).send(creator.createMessage(i));
829             }
830
831             messageHandled++;
832          }
833          if (session.getTransacted())
834             session.commit();
835          log.debug("Finished publishing");
836       }
837
838       public void subscribe() throws JMSException JavaDoc
839       {
840          subscribe(false);
841       }
842
843       public void subscribe(boolean failsafe) throws JMSException JavaDoc
844       {
845
846          consumer = ((QueueSession JavaDoc) session).createReceiver((Queue JavaDoc) destination);
847          super.subscribe(failsafe);
848          connection.start();
849       }
850
851       public void get() throws JMSException JavaDoc
852       {
853          consumer = ((QueueSession JavaDoc) session).createReceiver((Queue JavaDoc) destination);
854          super.subscribe();
855          connection.start();
856       }
857
858       public void connect() throws JMSException JavaDoc
859       {
860          log.debug("Connecting: " + this.toString());
861          if (userName != null)
862             connection = queueFactory.createQueueConnection(userName, password);
863          else
864             connection = queueFactory.createQueueConnection();
865
866          if (clientID != null)
867             connection.setClientID(clientID);
868
869          session = ((QueueConnection JavaDoc) connection).createQueueSession(transacted != TRANS_NONE, Session.AUTO_ACKNOWLEDGE);
870          try
871          {
872             destination = (Destination JavaDoc) context.lookup(TEST_QUEUE);
873          }
874          catch (NamingException JavaDoc ex)
875          {
876             throw new JMSException JavaDoc("Could not lookup topic " + ex);
877          }
878       }
879
880
881       // Queue specific
882
public Enumeration JavaDoc browse() throws JMSException JavaDoc
883       {
884          QueueBrowser JavaDoc b = ((QueueSession JavaDoc) session).createBrowser((Queue JavaDoc) destination);
885          return b.getEnumeration();
886       }
887    }
888 } // MQBase
889
Popular Tags