001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.activemq.command;
018    
019    import java.io.Externalizable;
020    import java.io.IOException;
021    import java.io.ObjectInput;
022    import java.io.ObjectOutput;
023    import java.net.URISyntaxException;
024    import java.util.ArrayList;
025    import java.util.List;
026    import java.util.Map;
027    import java.util.Properties;
028    import java.util.StringTokenizer;
029    
030    import javax.jms.Destination;
031    import javax.jms.JMSException;
032    import javax.jms.Queue;
033    import javax.jms.TemporaryQueue;
034    import javax.jms.TemporaryTopic;
035    import javax.jms.Topic;
036    
037    import org.apache.activemq.jndi.JNDIBaseStorable;
038    import org.apache.activemq.util.IntrospectionSupport;
039    import org.apache.activemq.util.URISupport;
040    
041    /**
042     * @openwire:marshaller
043     * @version $Revision: 1.10 $
044     */
045    public abstract class ActiveMQDestination extends JNDIBaseStorable implements DataStructure, Destination, Externalizable, Comparable {
046    
047        public static final String PATH_SEPERATOR = ".";
048        public static final char COMPOSITE_SEPERATOR = ',';
049    
050        public static final byte QUEUE_TYPE = 0x01;
051        public static final byte TOPIC_TYPE = 0x02;
052        public static final byte TEMP_MASK = 0x04;
053        public static final byte TEMP_TOPIC_TYPE = TOPIC_TYPE | TEMP_MASK;
054        public static final byte TEMP_QUEUE_TYPE = QUEUE_TYPE | TEMP_MASK;
055    
056        public static final String QUEUE_QUALIFIED_PREFIX = "queue://";
057        public static final String TOPIC_QUALIFIED_PREFIX = "topic://";
058        public static final String TEMP_QUEUE_QUALIFED_PREFIX = "temp-queue://";
059        public static final String TEMP_TOPIC_QUALIFED_PREFIX = "temp-topic://";
060    
061        public static final String TEMP_DESTINATION_NAME_PREFIX = "ID:";
062    
063        private static final long serialVersionUID = -3885260014960795889L;
064    
065        protected String physicalName;
066    
067        protected transient ActiveMQDestination[] compositeDestinations;
068        protected transient String[] destinationPaths;
069        protected transient boolean isPattern;
070        protected transient int hashValue;
071        protected Map<String, String> options;
072        
073        public ActiveMQDestination() {
074        }
075    
076        protected ActiveMQDestination(String name) {
077            setPhysicalName(name);
078        }
079    
080        public ActiveMQDestination(ActiveMQDestination composites[]) {
081            setCompositeDestinations(composites);
082        }
083    
084    
085        // static helper methods for working with destinations
086        // -------------------------------------------------------------------------
087        public static ActiveMQDestination createDestination(String name, byte defaultType) {
088    
089            if (name.startsWith(QUEUE_QUALIFIED_PREFIX)) {
090                return new ActiveMQQueue(name.substring(QUEUE_QUALIFIED_PREFIX.length()));
091            } else if (name.startsWith(TOPIC_QUALIFIED_PREFIX)) {
092                return new ActiveMQTopic(name.substring(TOPIC_QUALIFIED_PREFIX.length()));
093            } else if (name.startsWith(TEMP_QUEUE_QUALIFED_PREFIX)) {
094                return new ActiveMQTempQueue(name.substring(TEMP_QUEUE_QUALIFED_PREFIX.length()));
095            } else if (name.startsWith(TEMP_TOPIC_QUALIFED_PREFIX)) {
096                return new ActiveMQTempTopic(name.substring(TEMP_TOPIC_QUALIFED_PREFIX.length()));
097            }
098    
099            switch (defaultType) {
100            case QUEUE_TYPE:
101                return new ActiveMQQueue(name);
102            case TOPIC_TYPE:
103                return new ActiveMQTopic(name);
104            case TEMP_QUEUE_TYPE:
105                return new ActiveMQTempQueue(name);
106            case TEMP_TOPIC_TYPE:
107                return new ActiveMQTempTopic(name);
108            default:
109                throw new IllegalArgumentException("Invalid default destination type: " + defaultType);
110            }
111        }
112    
113        public static ActiveMQDestination transform(Destination dest) throws JMSException {
114            if (dest == null) {
115                return null;
116            }
117            if (dest instanceof ActiveMQDestination) {
118                return (ActiveMQDestination)dest;
119            }
120            
121            if (dest instanceof Queue && dest instanceof Topic) {
122                String queueName = ((Queue) dest).getQueueName();
123                String topicName = ((Topic) dest).getTopicName();
124                if (queueName != null && topicName == null) {
125                    return new ActiveMQQueue(queueName);
126                } else if (queueName == null && topicName != null) {
127                    return new ActiveMQTopic(topicName);
128                }
129                throw new JMSException("Could no disambiguate on queue|Topic-name totransform pollymorphic destination into a ActiveMQ destination: " + dest);
130            }
131            if (dest instanceof TemporaryQueue) {
132                return new ActiveMQTempQueue(((TemporaryQueue)dest).getQueueName());
133            }
134            if (dest instanceof TemporaryTopic) {
135                return new ActiveMQTempTopic(((TemporaryTopic)dest).getTopicName());
136            }
137            if (dest instanceof Queue) {
138                return new ActiveMQQueue(((Queue)dest).getQueueName());
139            }
140            if (dest instanceof Topic) {
141                return new ActiveMQTopic(((Topic)dest).getTopicName());
142            }
143            throw new JMSException("Could not transform the destination into a ActiveMQ destination: " + dest);
144        }
145    
146        public static int compare(ActiveMQDestination destination, ActiveMQDestination destination2) {
147            if (destination == destination2) {
148                return 0;
149            }
150            if (destination == null) {
151                return -1;
152            } else if (destination2 == null) {
153                return 1;
154            } else {
155                if (destination.isQueue() == destination2.isQueue()) {
156                    return destination.getPhysicalName().compareTo(destination2.getPhysicalName());
157                } else {
158                    return destination.isQueue() ? -1 : 1;
159                }
160            }
161        }
162    
163        public int compareTo(Object that) {
164            if (that instanceof ActiveMQDestination) {
165                return compare(this, (ActiveMQDestination)that);
166            }
167            if (that == null) {
168                return 1;
169            } else {
170                return getClass().getName().compareTo(that.getClass().getName());
171            }
172        }
173    
174        public boolean isComposite() {
175            return compositeDestinations != null;
176        }
177    
178        public ActiveMQDestination[] getCompositeDestinations() {
179            return compositeDestinations;
180        }
181    
182        public void setCompositeDestinations(ActiveMQDestination[] destinations) {
183            this.compositeDestinations = destinations;
184            this.destinationPaths = null;
185            this.hashValue = 0;
186            this.isPattern = false;
187    
188            StringBuffer sb = new StringBuffer();
189            for (int i = 0; i < destinations.length; i++) {
190                if (i != 0) {
191                    sb.append(COMPOSITE_SEPERATOR);
192                }
193                if (getDestinationType() == destinations[i].getDestinationType()) {
194                    sb.append(destinations[i].getPhysicalName());
195                } else {
196                    sb.append(destinations[i].getQualifiedName());
197                }
198            }
199            physicalName = sb.toString();
200        }
201    
202        public String getQualifiedName() {
203            if (isComposite()) {
204                return physicalName;
205            }
206            return getQualifiedPrefix() + physicalName;
207        }
208    
209        protected abstract String getQualifiedPrefix();
210    
211        /**
212         * @openwire:property version=1
213         */
214        public String getPhysicalName() {
215            return physicalName;
216        }
217    
218        public void setPhysicalName(String physicalName) {
219            final int len = physicalName.length();
220            // options offset
221            int p = -1;
222            boolean composite = false;
223            for (int i = 0; i < len; i++) {
224                char c = physicalName.charAt(i);
225                if (c == '?') {
226                    p = i;
227                    break;
228                }
229                if (c == COMPOSITE_SEPERATOR) {
230                    // won't be wild card
231                    isPattern = false;
232                    composite = true;
233                } else if (!composite && (c == '*' || c == '>')) {
234                    isPattern = true;
235                }
236            }
237            // Strip off any options
238            if (p >= 0) {
239                String optstring = physicalName.substring(p + 1);
240                physicalName = physicalName.substring(0, p);
241                try {
242                    options = URISupport.parseQuery(optstring);
243                } catch (URISyntaxException e) {
244                    throw new IllegalArgumentException("Invalid destination name: " + physicalName + ", it's options are not encoded properly: " + e);
245                }
246            }
247            this.physicalName = physicalName;
248            this.destinationPaths = null;
249            this.hashValue = 0;
250            if (composite) {
251                // Check to see if it is a composite.
252                List<String> l = new ArrayList<String>();
253                StringTokenizer iter = new StringTokenizer(physicalName, "" + COMPOSITE_SEPERATOR);
254                while (iter.hasMoreTokens()) {
255                    String name = iter.nextToken().trim();
256                    if (name.length() == 0) {
257                        continue;
258                    }
259                    l.add(name);
260                }
261                if (l.size() > 1) {
262                    compositeDestinations = new ActiveMQDestination[l.size()];
263                    int counter = 0;
264                    for (String dest : l) {
265                        compositeDestinations[counter++] = createDestination(dest);
266                    }
267                }
268            }
269        }
270    
271        public ActiveMQDestination createDestination(String name) {
272            return createDestination(name, getDestinationType());
273        }
274    
275        public String[] getDestinationPaths() {
276    
277            if (destinationPaths != null) {
278                return destinationPaths;
279            }
280    
281            List<String> l = new ArrayList<String>();
282            StringTokenizer iter = new StringTokenizer(physicalName, PATH_SEPERATOR);
283            while (iter.hasMoreTokens()) {
284                String name = iter.nextToken().trim();
285                if (name.length() == 0) {
286                    continue;
287                }
288                l.add(name);
289            }
290    
291            destinationPaths = new String[l.size()];
292            l.toArray(destinationPaths);
293            return destinationPaths;
294        }
295    
296        public abstract byte getDestinationType();
297    
298        public boolean isQueue() {
299            return false;
300        }
301    
302        public boolean isTopic() {
303            return false;
304        }
305    
306        public boolean isTemporary() {
307            return false;
308        }
309    
310        public boolean equals(Object o) {
311            if (this == o) {
312                return true;
313            }
314            if (o == null || getClass() != o.getClass()) {
315                return false;
316            }
317    
318            ActiveMQDestination d = (ActiveMQDestination)o;
319            return physicalName.equals(d.physicalName);
320        }
321    
322        public int hashCode() {
323            if (hashValue == 0) {
324                hashValue = physicalName.hashCode();
325            }
326            return hashValue;
327        }
328    
329        public String toString() {
330            return getQualifiedName();
331        }
332    
333        public void writeExternal(ObjectOutput out) throws IOException {
334            out.writeUTF(this.getPhysicalName());
335            out.writeObject(options);
336        }
337    
338        @SuppressWarnings("unchecked")
339        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
340            this.setPhysicalName(in.readUTF());
341            this.options = (Map<String, String>)in.readObject();
342        }
343    
344        public String getDestinationTypeAsString() {
345            switch (getDestinationType()) {
346            case QUEUE_TYPE:
347                return "Queue";
348            case TOPIC_TYPE:
349                return "Topic";
350            case TEMP_QUEUE_TYPE:
351                return "TempQueue";
352            case TEMP_TOPIC_TYPE:
353                return "TempTopic";
354            default:
355                throw new IllegalArgumentException("Invalid destination type: " + getDestinationType());
356            }
357        }
358    
359        public Map<String, String> getOptions() {
360            return options;
361        }
362    
363        public boolean isMarshallAware() {
364            return false;
365        }
366    
367        public void buildFromProperties(Properties properties) {
368            if (properties == null) {
369                properties = new Properties();
370            }
371    
372            IntrospectionSupport.setProperties(this, properties);
373        }
374    
375        public void populateProperties(Properties props) {
376            props.setProperty("physicalName", getPhysicalName());
377        }
378    
379        public boolean isPattern() {
380            return isPattern;
381        }
382    }