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