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