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.HashSet;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Properties;
029    import java.util.Set;
030    import java.util.StringTokenizer;
031    
032    import javax.jms.Destination;
033    import javax.jms.JMSException;
034    import javax.jms.Queue;
035    import javax.jms.TemporaryQueue;
036    import javax.jms.TemporaryTopic;
037    import javax.jms.Topic;
038    
039    import org.apache.activemq.jndi.JNDIBaseStorable;
040    import org.apache.activemq.util.IntrospectionSupport;
041    import org.apache.activemq.util.URISupport;
042    
043    /**
044     * @openwire:marshaller
045     * 
046     */
047    public abstract class ActiveMQDestination extends JNDIBaseStorable implements DataStructure, Destination, Externalizable, Comparable {
048    
049        public static final String PATH_SEPERATOR = ".";
050        public static final char COMPOSITE_SEPERATOR = ',';
051    
052        public static final byte QUEUE_TYPE = 0x01;
053        public static final byte TOPIC_TYPE = 0x02;
054        public static final byte TEMP_MASK = 0x04;
055        public static final byte TEMP_TOPIC_TYPE = TOPIC_TYPE | TEMP_MASK;
056        public static final byte TEMP_QUEUE_TYPE = QUEUE_TYPE | TEMP_MASK;
057    
058        public static final String QUEUE_QUALIFIED_PREFIX = "queue://";
059        public static final String TOPIC_QUALIFIED_PREFIX = "topic://";
060        public static final String TEMP_QUEUE_QUALIFED_PREFIX = "temp-queue://";
061        public static final String TEMP_TOPIC_QUALIFED_PREFIX = "temp-topic://";
062    
063        public static final String TEMP_DESTINATION_NAME_PREFIX = "ID:";
064    
065        private static final long serialVersionUID = -3885260014960795889L;
066    
067        protected String physicalName;
068    
069        protected transient ActiveMQDestination[] compositeDestinations;
070        protected transient String[] destinationPaths;
071        protected transient boolean isPattern;
072        protected transient int hashValue;
073        protected Map<String, String> options;
074        
075        public ActiveMQDestination() {
076        }
077    
078        protected ActiveMQDestination(String name) {
079            setPhysicalName(name);
080        }
081    
082        public ActiveMQDestination(ActiveMQDestination composites[]) {
083            setCompositeDestinations(composites);
084        }
085    
086    
087        // static helper methods for working with destinations
088        // -------------------------------------------------------------------------
089        public static ActiveMQDestination createDestination(String name, byte defaultType) {
090    
091            if (name.startsWith(QUEUE_QUALIFIED_PREFIX)) {
092                return new ActiveMQQueue(name.substring(QUEUE_QUALIFIED_PREFIX.length()));
093            } else if (name.startsWith(TOPIC_QUALIFIED_PREFIX)) {
094                return new ActiveMQTopic(name.substring(TOPIC_QUALIFIED_PREFIX.length()));
095            } else if (name.startsWith(TEMP_QUEUE_QUALIFED_PREFIX)) {
096                return new ActiveMQTempQueue(name.substring(TEMP_QUEUE_QUALIFED_PREFIX.length()));
097            } else if (name.startsWith(TEMP_TOPIC_QUALIFED_PREFIX)) {
098                return new ActiveMQTempTopic(name.substring(TEMP_TOPIC_QUALIFED_PREFIX.length()));
099            }
100    
101            switch (defaultType) {
102            case QUEUE_TYPE:
103                return new ActiveMQQueue(name);
104            case TOPIC_TYPE:
105                return new ActiveMQTopic(name);
106            case TEMP_QUEUE_TYPE:
107                return new ActiveMQTempQueue(name);
108            case TEMP_TOPIC_TYPE:
109                return new ActiveMQTempTopic(name);
110            default:
111                throw new IllegalArgumentException("Invalid default destination type: " + defaultType);
112            }
113        }
114    
115        public static ActiveMQDestination transform(Destination dest) throws JMSException {
116            if (dest == null) {
117                return null;
118            }
119            if (dest instanceof ActiveMQDestination) {
120                return (ActiveMQDestination)dest;
121            }
122            
123            if (dest instanceof Queue && dest instanceof Topic) {
124                String queueName = ((Queue) dest).getQueueName();
125                String topicName = ((Topic) dest).getTopicName();
126                if (queueName != null && topicName == null) {
127                    return new ActiveMQQueue(queueName);
128                } else if (queueName == null && topicName != null) {
129                    return new ActiveMQTopic(topicName);
130                }
131                throw new JMSException("Could no disambiguate on queue|Topic-name totransform pollymorphic destination into a ActiveMQ destination: " + dest);
132            }
133            if (dest instanceof TemporaryQueue) {
134                return new ActiveMQTempQueue(((TemporaryQueue)dest).getQueueName());
135            }
136            if (dest instanceof TemporaryTopic) {
137                return new ActiveMQTempTopic(((TemporaryTopic)dest).getTopicName());
138            }
139            if (dest instanceof Queue) {
140                return new ActiveMQQueue(((Queue)dest).getQueueName());
141            }
142            if (dest instanceof Topic) {
143                return new ActiveMQTopic(((Topic)dest).getTopicName());
144            }
145            throw new JMSException("Could not transform the destination into a ActiveMQ destination: " + dest);
146        }
147    
148        public static int compare(ActiveMQDestination destination, ActiveMQDestination destination2) {
149            if (destination == destination2) {
150                return 0;
151            }
152            if (destination == null) {
153                return -1;
154            } else if (destination2 == null) {
155                return 1;
156            } else {
157                if (destination.isQueue() == destination2.isQueue()) {
158                    return destination.getPhysicalName().compareTo(destination2.getPhysicalName());
159                } else {
160                    return destination.isQueue() ? -1 : 1;
161                }
162            }
163        }
164    
165        public int compareTo(Object that) {
166            if (that instanceof ActiveMQDestination) {
167                return compare(this, (ActiveMQDestination)that);
168            }
169            if (that == null) {
170                return 1;
171            } else {
172                return getClass().getName().compareTo(that.getClass().getName());
173            }
174        }
175    
176        public boolean isComposite() {
177            return compositeDestinations != null;
178        }
179    
180        public ActiveMQDestination[] getCompositeDestinations() {
181            return compositeDestinations;
182        }
183    
184        public void setCompositeDestinations(ActiveMQDestination[] destinations) {
185            this.compositeDestinations = destinations;
186            this.destinationPaths = null;
187            this.hashValue = 0;
188            this.isPattern = false;
189    
190            StringBuffer sb = new StringBuffer();
191            for (int i = 0; i < destinations.length; i++) {
192                if (i != 0) {
193                    sb.append(COMPOSITE_SEPERATOR);
194                }
195                if (getDestinationType() == destinations[i].getDestinationType()) {
196                    sb.append(destinations[i].getPhysicalName());
197                } else {
198                    sb.append(destinations[i].getQualifiedName());
199                }
200            }
201            physicalName = sb.toString();
202        }
203    
204        public String getQualifiedName() {
205            if (isComposite()) {
206                return physicalName;
207            }
208            return getQualifiedPrefix() + physicalName;
209        }
210    
211        protected abstract String getQualifiedPrefix();
212    
213        /**
214         * @openwire:property version=1
215         */
216        public String getPhysicalName() {
217            return physicalName;
218        }
219    
220        public void setPhysicalName(String physicalName) {
221            final int len = physicalName.length();
222            // options offset
223            int p = -1;
224            boolean composite = false;
225            for (int i = 0; i < len; i++) {
226                char c = physicalName.charAt(i);
227                if (c == '?') {
228                    p = i;
229                    break;
230                }
231                if (c == COMPOSITE_SEPERATOR) {
232                    // won't be wild card
233                    isPattern = false;
234                    composite = true;
235                } else if (!composite && (c == '*' || c == '>')) {
236                    isPattern = true;
237                }
238            }
239            // Strip off any options
240            if (p >= 0) {
241                String optstring = physicalName.substring(p + 1);
242                physicalName = physicalName.substring(0, p);
243                try {
244                    options = URISupport.parseQuery(optstring);
245                } catch (URISyntaxException e) {
246                    throw new IllegalArgumentException("Invalid destination name: " + physicalName + ", it's options are not encoded properly: " + e);
247                }
248            }
249            this.physicalName = physicalName;
250            this.destinationPaths = null;
251            this.hashValue = 0;
252            if (composite) {
253                // Check to see if it is a composite.
254                Set<String> l = new HashSet<String>();
255                StringTokenizer iter = new StringTokenizer(physicalName, "" + COMPOSITE_SEPERATOR);
256                while (iter.hasMoreTokens()) {
257                    String name = iter.nextToken().trim();
258                    if (name.length() == 0) {
259                        continue;
260                    }
261                    l.add(name);
262                }
263                compositeDestinations = new ActiveMQDestination[l.size()];
264                int counter = 0;
265                for (String dest : l) {
266                    compositeDestinations[counter++] = createDestination(dest);
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    }