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 }