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
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
416
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
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 }