View Javadoc

1   /*** 
2    * 
3    * Copyright 2004 Protique Ltd
4    * 
5    * Licensed under the Apache License, Version 2.0 (the "License"); 
6    * you may not use this file except in compliance with the License. 
7    * You may obtain a copy of the License at 
8    * 
9    * http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, 
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
14   * See the License for the specific language governing permissions and 
15   * limitations under the License. 
16   * 
17   **/
18  
19  package org.codehaus.activemq.message;
20  
21  import org.codehaus.activemq.filter.DestinationFilter;
22  import org.codehaus.activemq.filter.DestinationPath;
23  import org.codehaus.activemq.jndi.JNDIBaseStorable;
24  import org.codehaus.activemq.management.JMSDestinationStats;
25  
26  import javax.jms.Destination;
27  import javax.jms.JMSException;
28  import javax.jms.Queue;
29  import javax.jms.TemporaryQueue;
30  import javax.jms.TemporaryTopic;
31  import javax.jms.Topic;
32  import java.io.DataInput;
33  import java.io.DataOutput;
34  import java.io.IOException;
35  import java.io.Serializable;
36  import java.util.ArrayList;
37  import java.util.List;
38  import java.util.Properties;
39  import java.util.StringTokenizer;
40  
41  /***
42   * A <CODE>Destination</CODE> object encapsulates a provider-specific
43   * address.
44   * The JMS API does not define a standard address syntax. Although a standard
45   * address syntax was considered, it was decided that the differences in
46   * address semantics between existing message-oriented middleware (MOM)
47   * products were too wide to bridge with a single syntax.
48   * <p/>
49   * <P>Since <CODE>Destination</CODE> is an administered object, it may
50   * contain
51   * provider-specific configuration information in addition to its address.
52   * <p/>
53   * <P>The JMS API also supports a client's use of provider-specific address
54   * names.
55   * <p/>
56   * <P><CODE>Destination</CODE> objects support concurrent use.
57   * <p/>
58   * <P>A <CODE>Destination</CODE> object is a JMS administered object.
59   * <p/>
60   * <P>JMS administered objects are objects containing configuration
61   * information that are created by an administrator and later used by
62   * JMS clients. They make it practical to administer the JMS API in the
63   * enterprise.
64   * <p/>
65   * <P>Although the interfaces for administered objects do not explicitly
66   * depend on the Java Naming and Directory Interface (JNDI) API, the JMS API
67   * establishes the convention that JMS clients find administered objects by
68   * looking them up in a JNDI namespace.
69   * <p/>
70   * <P>An administrator can place an administered object anywhere in a
71   * namespace. The JMS API does not define a naming policy.
72   * <p/>
73   * <P>It is expected that JMS providers will provide the tools an
74   * administrator needs to create and configure administered objects in a
75   * JNDI namespace. JMS provider implementations of administered objects
76   * should implement the <CODE>javax.naming.Referenceable</CODE> and
77   * <CODE>java.io.Serializable</CODE> interfaces so that they can be stored in
78   * all JNDI naming contexts. In addition, it is recommended that these
79   * implementations follow the JavaBeans<SUP><FONT SIZE="-2">TM</FONT></SUP>
80   * design patterns.
81   * <p/>
82   * <P>This strategy provides several benefits:
83   * <p/>
84   * <UL>
85   * <LI>It hides provider-specific details from JMS clients.
86   * <LI>It abstracts JMS administrative information into objects in the Java
87   * programming language ("Java objects")
88   * that are easily organized and administered from a common
89   * management console.
90   * <LI>Since there will be JNDI providers for all popular naming
91   * services, JMS providers can deliver one implementation
92   * of administered objects that will run everywhere.
93   * </UL>
94   * <p/>
95   * <P>An administered object should not hold on to any remote resources.
96   * Its lookup should not use remote resources other than those used by the
97   * JNDI API itself.
98   * <p/>
99   * <P>Clients should think of administered objects as local Java objects.
100  * Looking them up should not have any hidden side effects or use surprising
101  * amounts of local resources.
102  */
103 
104 public abstract class ActiveMQDestination extends JNDIBaseStorable implements Destination, Comparable, Serializable {
105 
106     /***
107      * Topic Destination object
108      */
109     public static final int ACTIVEMQ_TOPIC = 1;
110     /***
111      * Temporary Topic Destination object
112      */
113     public static final int ACTIVEMQ_TEMPORARY_TOPIC = 2;
114 
115     /***
116      * Queue Destination object
117      */
118     public static final int ACTIVEMQ_QUEUE = 3;
119     /***
120      * Temporary Queue Destination object
121      */
122     public static final int ACTIVEMQ_TEMPORARY_QUEUE = 4;
123 
124     private static final int NULL_DESTINATION = 10;
125 
126     private static final String TEMP_PREFIX = "{TD{";
127     private static final String TEMP_POSTFIX = "}TD}";
128     private static final String COMPOSITE_SEPARATOR = ",";
129     private static final String QUEUE_PREFIX = "queue://";
130     private static final String TOPIC_PREFIX = "topic://";
131 
132     private String physicalName;
133 
134     // Cached transient data
135     private transient DestinationFilter filter;
136     private transient JMSDestinationStats stats;
137     private transient String[] paths;
138 
139     /***
140      * A helper method to return a descriptive string for the topic or queue
141      *
142      * @return a descriptive string for this queue or topic
143      */
144     public static String inspect(Destination destination) {
145         if (destination instanceof Topic) {
146             return "Topic(" + destination.toString() + ")";
147         }
148         else {
149             return "Queue(" + destination.toString() + ")";
150         }
151     }
152 
153     /***
154      * @param destination
155      * @return @throws JMSException
156      * @throws javax.jms.JMSException
157      */
158     public static ActiveMQDestination transformDestination(Destination destination) throws JMSException {
159         ActiveMQDestination result = null;
160         if (destination != null) {
161             if (destination instanceof ActiveMQDestination) {
162                 result = (ActiveMQDestination) destination;
163             }
164             else {
165                 if (destination instanceof TemporaryQueue) {
166                     result = new ActiveMQTemporaryQueue(((Queue) destination).getQueueName());
167                 }
168                 else if (destination instanceof TemporaryTopic) {
169                     result = new ActiveMQTemporaryTopic(((Topic) destination).getTopicName());
170                 }
171                 else if (destination instanceof Queue) {
172                     result = new ActiveMQTemporaryQueue(((Queue) destination).getQueueName());
173                 }
174                 else if (destination instanceof Topic) {
175                     result = new ActiveMQTemporaryTopic(((Topic) destination).getTopicName());
176                 }
177             }
178         }
179         return result;
180     }
181 
182     /***
183      * Write an ActiveMQDestination to a Stream
184      *
185      * @param destination
186      * @param dataOut
187      * @throws IOException
188      */
189 
190     public static void writeToStream(ActiveMQDestination destination, DataOutput dataOut) throws IOException {
191         if (destination != null) {
192             dataOut.write(destination.getDestinationType());
193             dataOut.writeUTF(destination == null ? "" : destination.getPhysicalName());
194         }
195         else {
196             dataOut.write(NULL_DESTINATION);
197         }
198     }
199 
200     /***
201      * Read an ActiveMQDestination  from a Stream
202      *
203      * @param dataIn
204      * @return the ActiveMQDestination
205      * @throws IOException
206      */
207 
208     public static ActiveMQDestination readFromStream(DataInput dataIn) throws IOException {
209 
210         int type = dataIn.readByte();
211         if (type == NULL_DESTINATION) {
212             return null;
213         }
214         ActiveMQDestination result = null;
215         if (type == ACTIVEMQ_TOPIC) {
216             result = new ActiveMQTopic();
217         }
218         else if (type == ACTIVEMQ_TEMPORARY_TOPIC) {
219             result = new ActiveMQTemporaryTopic();
220         }
221         else if (type == ACTIVEMQ_QUEUE) {
222             result = new ActiveMQQueue();
223         }
224         else {
225             result = new ActiveMQTemporaryQueue();
226         }
227         result.setPhysicalName(dataIn.readUTF());
228         return result;
229 
230     }
231 
232     /***
233      * Create a temporary name from the clientId
234      *
235      * @param clientId
236      * @return
237      */
238     public static String createTemporaryName(String clientId) {
239         return TEMP_PREFIX + clientId + TEMP_POSTFIX;
240     }
241 
242     /***
243      * From a temporary destination find the clientId of the Connection that created it
244      *
245      * @param destination
246      * @return the clientId or null if not a temporary destination
247      */
248     public static String getClientId(ActiveMQDestination destination) {
249         String answer = null;
250         if (destination != null && destination.isTemporary()) {
251             String name = destination.getPhysicalName();
252             int start = name.indexOf(TEMP_PREFIX);
253             if (start >= 0) {
254                 start += TEMP_PREFIX.length();
255                 int stop = name.lastIndexOf(TEMP_POSTFIX);
256                 if (stop > start && stop < name.length()) {
257                     answer = name.substring(start, stop);
258                 }
259             }
260         }
261         return answer;
262     }
263 
264 
265     /***
266      * The Default Constructor
267      */
268     protected ActiveMQDestination() {
269     }
270 
271     /***
272      * Construct the ActiveMQDestination with a defined physical name;
273      *
274      * @param name
275      */
276 
277     protected ActiveMQDestination(String name) {
278         this.physicalName = name;
279     }
280 
281     /***
282      * @param o object to compare
283      * @return 1 if this > o else 0 if they are equal or -1 if this < o
284      */
285     public int compareTo(Object o) {
286         if (o instanceof ActiveMQDestination) {
287             return compareTo((ActiveMQDestination) o);
288         }
289         return -1;
290     }
291 
292     /***
293      * Lets sort by name first then lets sort topics greater than queues
294      *
295      * @param that another destination to compare against
296      * @return 1 if this > that else 0 if they are equal or -1 if this < that
297      */
298     public int compareTo(ActiveMQDestination that) {
299         int answer = 0;
300         if (physicalName != that.physicalName) {
301             if (physicalName == null) {
302                 return -1;
303             }
304             else if (that.physicalName == null) {
305                 return 1;
306             }
307             answer = physicalName.compareTo(that.physicalName);
308         }
309         if (answer == 0) {
310             if (isTopic()) {
311                 if (that.isQueue()) {
312                     return 1;
313                 }
314             }
315             else {
316                 if (that.isTopic()) {
317                     return -1;
318                 }
319             }
320         }
321         return answer;
322     }
323 
324 
325     /***
326      * @return Returns the Destination type
327      */
328 
329     public abstract int getDestinationType();
330 
331 
332     /***
333      * @return Returns the physicalName.
334      */
335     public String getPhysicalName() {
336         return this.physicalName;
337     }
338 
339     /***
340      * @param newPhysicalName The physicalName to set.
341      */
342     public void setPhysicalName(String newPhysicalName) {
343         this.physicalName = newPhysicalName;
344     }
345 
346     /***
347      * Returns true if a temporary Destination
348      *
349      * @return true/false
350      */
351 
352     public boolean isTemporary() {
353         return getDestinationType() == ACTIVEMQ_TEMPORARY_TOPIC ||
354                 getDestinationType() == ACTIVEMQ_TEMPORARY_QUEUE;
355     }
356 
357     /***
358      * Returns true if a Topic Destination
359      *
360      * @return true/false
361      */
362 
363     public boolean isTopic() {
364         return getDestinationType() == ACTIVEMQ_TOPIC ||
365                 getDestinationType() == ACTIVEMQ_TEMPORARY_TOPIC;
366     }
367 
368     /***
369      * Returns true if a Queue Destination
370      *
371      * @return true/false
372      */
373     public boolean isQueue() {
374         return !isTopic();
375     }
376 
377     /***
378      * Returns true if this destination represents a collection of
379      * destinations; allowing a set of destinations to be published to or subscribed
380      * from in one JMS operation.
381      * <p/>
382      * If this destination is a composite then you can call {@link #getChildDestinations()}
383      * to return the list of child destinations.
384      *
385      * @return true if this destination represents a collection of child destinations.
386      */
387     public boolean isComposite() {
388         return physicalName.indexOf(COMPOSITE_SEPARATOR) > 0;
389     }
390 
391     /***
392      * Returns a list of child destinations if this destination represents a composite
393      * destination.
394      *
395      * @return
396      */
397     public List getChildDestinations() {
398         List answer = new ArrayList();
399         StringTokenizer iter = new StringTokenizer(physicalName, COMPOSITE_SEPARATOR);
400         while (iter.hasMoreTokens()) {
401             String name = iter.nextToken();
402             Destination child = null;
403             if (name.startsWith(QUEUE_PREFIX)) {
404                 child = new ActiveMQQueue(name.substring(QUEUE_PREFIX.length()));
405             }
406             else if (name.startsWith(TOPIC_PREFIX)) {
407                 child = new ActiveMQTopic(name.substring(TOPIC_PREFIX.length()));
408             }
409             else {
410                 child = createDestination(name);
411             }
412             answer.add(child);
413         }
414         if (answer.size() == 1) {
415             // lets put ourselves inside the collection
416             // as we are not really a composite destination
417             answer.set(0, this);
418         }
419         return answer;
420     }
421 
422     /***
423      * @return string representation of this instance
424      */
425 
426     public String toString() {
427         return this.physicalName;
428     }
429 
430     /***
431      * @return hashCode for this instance
432      */
433 
434     public int hashCode() {
435         int answer = 0xcafebabe;
436 
437         if (this.physicalName != null) {
438             answer = physicalName.hashCode();
439         }
440         if (isTopic()) {
441             answer ^= 0xfabfab;
442         }
443         return answer;
444     }
445 
446     /***
447      * if the object passed in is equivalent, return true
448      *
449      * @param obj the object to compare
450      * @return true if this instance and obj are equivalent
451      */
452 
453     public boolean equals(Object obj) {
454         boolean result = this == obj;
455         if (!result && obj != null && obj instanceof ActiveMQDestination) {
456             ActiveMQDestination other = (ActiveMQDestination) obj;
457             result = this.getDestinationType() == other.getDestinationType() &&
458                     this.physicalName.equals(other.physicalName);
459         }
460         return result;
461     }
462 
463 
464     /***
465      * @return true if the destination matches multiple possible destinations
466      */
467     public boolean isWildcard() {
468         if (physicalName != null) {
469             return physicalName.indexOf(DestinationFilter.ANY_CHILD) >= 0
470                     || physicalName.indexOf(DestinationFilter.ANY_DESCENDENT) >= 0;
471         }
472         return false;
473     }
474 
475     /***
476      * Returns true if the given destination matches this destination; including wildcards
477      */
478     public boolean matches(ActiveMQDestination destination) {
479         if (isWildcard()) {
480             return getDestinationFilter().matches(destination);
481         }
482         else {
483             return equals(destination);
484         }
485     }
486 
487     public DestinationFilter getDestinationFilter() {
488         if (filter == null) {
489             filter = DestinationFilter.parseFilter(this);
490         }
491         return filter;
492     }
493 
494     public String[] getDestinationPaths() {
495         if (paths == null) {
496             paths = DestinationPath.getDestinationPaths(physicalName);
497         }
498         return paths;
499     }
500 
501     public JMSDestinationStats getStats() {
502         if (stats == null) {
503             stats = createDestinationStats();
504         }
505         return stats;
506     }
507 
508 
509     public void setStats(JMSDestinationStats stats) {
510         this.stats = stats;
511     }
512 
513     // Implementation methods
514     //-------------------------------------------------------------------------
515 
516 
517     /***
518      * Factory method to create a child destination if this destination is a composite
519      */
520     protected abstract Destination createDestination(String name);
521 
522     /***
523      * Factory method to create a statistics counter object
524      *
525      * @return
526      */
527     protected abstract JMSDestinationStats createDestinationStats();
528 
529     /***
530      * Set the properties that will represent the instance in JNDI
531      *
532      * @param props
533      */
534     protected void buildFromProperties(Properties props) {
535         this.physicalName = props.getProperty("physicalName", this.physicalName);
536 
537     }
538 
539     /***
540      * Initialize the instance from properties stored in JNDI
541      *
542      * @param props
543      */
544 
545     protected void populateProperties(Properties props) {
546         props.put("physicalName", this.physicalName);
547     }
548 
549 }