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<Object> {
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        protected static UnresolvedDestinationTransformer unresolvableDestinationTransformer = new DefaultUnresolvedDestinationTransformer();
076    
077        public ActiveMQDestination() {
078        }
079    
080        protected ActiveMQDestination(String name) {
081            setPhysicalName(name);
082        }
083    
084        public ActiveMQDestination(ActiveMQDestination composites[]) {
085            setCompositeDestinations(composites);
086        }
087    
088    
089        // static helper methods for working with destinations
090        // -------------------------------------------------------------------------
091        public static ActiveMQDestination createDestination(String name, byte defaultType) {
092    
093            if (name.startsWith(QUEUE_QUALIFIED_PREFIX)) {
094                return new ActiveMQQueue(name.substring(QUEUE_QUALIFIED_PREFIX.length()));
095            } else if (name.startsWith(TOPIC_QUALIFIED_PREFIX)) {
096                return new ActiveMQTopic(name.substring(TOPIC_QUALIFIED_PREFIX.length()));
097            } else if (name.startsWith(TEMP_QUEUE_QUALIFED_PREFIX)) {
098                return new ActiveMQTempQueue(name.substring(TEMP_QUEUE_QUALIFED_PREFIX.length()));
099            } else if (name.startsWith(TEMP_TOPIC_QUALIFED_PREFIX)) {
100                return new ActiveMQTempTopic(name.substring(TEMP_TOPIC_QUALIFED_PREFIX.length()));
101            }
102    
103            switch (defaultType) {
104            case QUEUE_TYPE:
105                return new ActiveMQQueue(name);
106            case TOPIC_TYPE:
107                return new ActiveMQTopic(name);
108            case TEMP_QUEUE_TYPE:
109                return new ActiveMQTempQueue(name);
110            case TEMP_TOPIC_TYPE:
111                return new ActiveMQTempTopic(name);
112            default:
113                throw new IllegalArgumentException("Invalid default destination type: " + defaultType);
114            }
115        }
116    
117        public static ActiveMQDestination transform(Destination dest) throws JMSException {
118            if (dest == null) {
119                return null;
120            }
121            if (dest instanceof ActiveMQDestination) {
122                return (ActiveMQDestination)dest;
123            }
124    
125            if (dest instanceof Queue && dest instanceof Topic) {
126                String queueName = ((Queue) dest).getQueueName();
127                String topicName = ((Topic) dest).getTopicName();
128                if (queueName != null && topicName == null) {
129                    return new ActiveMQQueue(queueName);
130                } else if (queueName == null && topicName != null) {
131                    return new ActiveMQTopic(topicName);
132                } else {
133                    return unresolvableDestinationTransformer.transform(dest);
134                }
135            }
136            if (dest instanceof TemporaryQueue) {
137                return new ActiveMQTempQueue(((TemporaryQueue)dest).getQueueName());
138            }
139            if (dest instanceof TemporaryTopic) {
140                return new ActiveMQTempTopic(((TemporaryTopic)dest).getTopicName());
141            }
142            if (dest instanceof Queue) {
143                return new ActiveMQQueue(((Queue)dest).getQueueName());
144            }
145            if (dest instanceof Topic) {
146                return new ActiveMQTopic(((Topic)dest).getTopicName());
147            }
148            throw new JMSException("Could not transform the destination into a ActiveMQ destination: " + dest);
149        }
150    
151        public static int compare(ActiveMQDestination destination, ActiveMQDestination destination2) {
152            if (destination == destination2) {
153                return 0;
154            }
155            if (destination == null) {
156                return -1;
157            } else if (destination2 == null) {
158                return 1;
159            } else {
160                if (destination.isQueue() == destination2.isQueue()) {
161                    return destination.getPhysicalName().compareTo(destination2.getPhysicalName());
162                } else {
163                    return destination.isQueue() ? -1 : 1;
164                }
165            }
166        }
167    
168        @Override
169        public int compareTo(Object that) {
170            if (that instanceof ActiveMQDestination) {
171                return compare(this, (ActiveMQDestination)that);
172            }
173            if (that == null) {
174                return 1;
175            } else {
176                return getClass().getName().compareTo(that.getClass().getName());
177            }
178        }
179    
180        public boolean isComposite() {
181            return compositeDestinations != null;
182        }
183    
184        public ActiveMQDestination[] getCompositeDestinations() {
185            return compositeDestinations;
186        }
187    
188        public void setCompositeDestinations(ActiveMQDestination[] destinations) {
189            this.compositeDestinations = destinations;
190            this.destinationPaths = null;
191            this.hashValue = 0;
192            this.isPattern = false;
193    
194            StringBuffer sb = new StringBuffer();
195            for (int i = 0; i < destinations.length; i++) {
196                if (i != 0) {
197                    sb.append(COMPOSITE_SEPERATOR);
198                }
199                if (getDestinationType() == destinations[i].getDestinationType()) {
200                    sb.append(destinations[i].getPhysicalName());
201                } else {
202                    sb.append(destinations[i].getQualifiedName());
203                }
204            }
205            physicalName = sb.toString();
206        }
207    
208        public String getQualifiedName() {
209            if (isComposite()) {
210                return physicalName;
211            }
212            return getQualifiedPrefix() + physicalName;
213        }
214    
215        protected abstract String getQualifiedPrefix();
216    
217        /**
218         * @openwire:property version=1
219         */
220        public String getPhysicalName() {
221            return physicalName;
222        }
223    
224        public void setPhysicalName(String physicalName) {
225            physicalName = physicalName.trim();
226            final int len = physicalName.length();
227            // options offset
228            int p = -1;
229            boolean composite = false;
230            for (int i = 0; i < len; i++) {
231                char c = physicalName.charAt(i);
232                if (c == '?') {
233                    p = i;
234                    break;
235                }
236                if (c == COMPOSITE_SEPERATOR) {
237                    // won't be wild card
238                    isPattern = false;
239                    composite = true;
240                } else if (!composite && (c == '*' || c == '>')) {
241                    isPattern = true;
242                }
243            }
244            // Strip off any options
245            if (p >= 0) {
246                String optstring = physicalName.substring(p + 1);
247                physicalName = physicalName.substring(0, p);
248                try {
249                    options = URISupport.parseQuery(optstring);
250                } catch (URISyntaxException e) {
251                    throw new IllegalArgumentException("Invalid destination name: " + physicalName + ", it's options are not encoded properly: " + e);
252                }
253            }
254            this.physicalName = physicalName;
255            this.destinationPaths = null;
256            this.hashValue = 0;
257            if (composite) {
258                // Check to see if it is a composite.
259                Set<String> l = new HashSet<String>();
260                StringTokenizer iter = new StringTokenizer(physicalName, "" + COMPOSITE_SEPERATOR);
261                while (iter.hasMoreTokens()) {
262                    String name = iter.nextToken().trim();
263                    if (name.length() == 0) {
264                        continue;
265                    }
266                    l.add(name);
267                }
268                compositeDestinations = new ActiveMQDestination[l.size()];
269                int counter = 0;
270                for (String dest : l) {
271                    compositeDestinations[counter++] = createDestination(dest);
272                }
273            }
274        }
275    
276        public ActiveMQDestination createDestination(String name) {
277            return createDestination(name, getDestinationType());
278        }
279    
280        public String[] getDestinationPaths() {
281    
282            if (destinationPaths != null) {
283                return destinationPaths;
284            }
285    
286            List<String> l = new ArrayList<String>();
287            StringTokenizer iter = new StringTokenizer(physicalName, PATH_SEPERATOR);
288            while (iter.hasMoreTokens()) {
289                String name = iter.nextToken().trim();
290                if (name.length() == 0) {
291                    continue;
292                }
293                l.add(name);
294            }
295    
296            destinationPaths = new String[l.size()];
297            l.toArray(destinationPaths);
298            return destinationPaths;
299        }
300    
301        public abstract byte getDestinationType();
302    
303        public boolean isQueue() {
304            return false;
305        }
306    
307        public boolean isTopic() {
308            return false;
309        }
310    
311        public boolean isTemporary() {
312            return false;
313        }
314    
315        public boolean equals(Object o) {
316            if (this == o) {
317                return true;
318            }
319            if (o == null || getClass() != o.getClass()) {
320                return false;
321            }
322    
323            ActiveMQDestination d = (ActiveMQDestination)o;
324            return physicalName.equals(d.physicalName);
325        }
326    
327        public int hashCode() {
328            if (hashValue == 0) {
329                hashValue = physicalName.hashCode();
330            }
331            return hashValue;
332        }
333    
334        public String toString() {
335            return getQualifiedName();
336        }
337    
338        public void writeExternal(ObjectOutput out) throws IOException {
339            out.writeUTF(this.getPhysicalName());
340            out.writeObject(options);
341        }
342    
343        @SuppressWarnings("unchecked")
344        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
345            this.setPhysicalName(in.readUTF());
346            this.options = (Map<String, String>)in.readObject();
347        }
348    
349        public String getDestinationTypeAsString() {
350            switch (getDestinationType()) {
351            case QUEUE_TYPE:
352                return "Queue";
353            case TOPIC_TYPE:
354                return "Topic";
355            case TEMP_QUEUE_TYPE:
356                return "TempQueue";
357            case TEMP_TOPIC_TYPE:
358                return "TempTopic";
359            default:
360                throw new IllegalArgumentException("Invalid destination type: " + getDestinationType());
361            }
362        }
363    
364        public Map<String, String> getOptions() {
365            return options;
366        }
367    
368        public boolean isMarshallAware() {
369            return false;
370        }
371    
372        public void buildFromProperties(Properties properties) {
373            if (properties == null) {
374                properties = new Properties();
375            }
376    
377            IntrospectionSupport.setProperties(this, properties);
378        }
379    
380        public void populateProperties(Properties props) {
381            props.setProperty("physicalName", getPhysicalName());
382        }
383    
384        public boolean isPattern() {
385            return isPattern;
386        }
387    
388        public static UnresolvedDestinationTransformer getUnresolvableDestinationTransformer() {
389            return unresolvableDestinationTransformer;
390        }
391    
392        public static void setUnresolvableDestinationTransformer(UnresolvedDestinationTransformer unresolvableDestinationTransformer) {
393            ActiveMQDestination.unresolvableDestinationTransformer = unresolvableDestinationTransformer;
394        }
395    }