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 */
017package org.apache.activemq.command;
018
019import java.io.Externalizable;
020import java.io.IOException;
021import java.io.ObjectInput;
022import java.io.ObjectOutput;
023import java.net.URISyntaxException;
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Map;
029import java.util.Properties;
030import java.util.Set;
031import java.util.StringTokenizer;
032
033import javax.jms.Destination;
034import javax.jms.JMSException;
035import javax.jms.Queue;
036import javax.jms.TemporaryQueue;
037import javax.jms.TemporaryTopic;
038import javax.jms.Topic;
039
040import org.apache.activemq.jndi.JNDIBaseStorable;
041import org.apache.activemq.util.IntrospectionSupport;
042import org.apache.activemq.util.URISupport;
043
044/**
045 * @openwire:marshaller
046 */
047public 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    public static final String IS_DLQ = "isDLQ";
063
064    public static final String TEMP_DESTINATION_NAME_PREFIX = "ID:";
065
066    private static final long serialVersionUID = -3885260014960795889L;
067
068    protected String physicalName;
069
070    protected transient ActiveMQDestination[] compositeDestinations;
071    protected transient String[] destinationPaths;
072    protected transient boolean isPattern;
073    protected transient int hashValue;
074    protected Map<String, String> options;
075
076    protected static UnresolvedDestinationTransformer unresolvableDestinationTransformer = new DefaultUnresolvedDestinationTransformer();
077
078    public ActiveMQDestination() {
079    }
080
081    protected ActiveMQDestination(String name) {
082        setPhysicalName(name);
083    }
084
085    public ActiveMQDestination(ActiveMQDestination composites[]) {
086        setCompositeDestinations(composites);
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 length = physicalName.length();
226
227        if (physicalName.isEmpty()) {
228            throw new IllegalArgumentException("Invalid destination name: a non-empty name is required");
229        }
230
231        // options offset
232        int p = -1;
233        boolean composite = false;
234        for (int i = 0; i < length; i++) {
235            char c = physicalName.charAt(i);
236            if (c == '?') {
237                p = i;
238                break;
239            }
240            if (c == COMPOSITE_SEPERATOR) {
241                // won't be wild card
242                isPattern = false;
243                composite = true;
244            } else if (!composite && (c == '*' || c == '>')) {
245                isPattern = true;
246            }
247        }
248        // Strip off any options
249        if (p >= 0) {
250            String optstring = physicalName.substring(p + 1);
251            physicalName = physicalName.substring(0, p);
252            try {
253                options = URISupport.parseQuery(optstring);
254            } catch (URISyntaxException e) {
255                throw new IllegalArgumentException("Invalid destination name: " + physicalName + ", it's options are not encoded properly: " + e);
256            }
257        }
258        this.physicalName = physicalName;
259        this.destinationPaths = null;
260        this.hashValue = 0;
261        if (composite) {
262            // Check to see if it is a composite.
263            Set<String> l = new HashSet<String>();
264            StringTokenizer iter = new StringTokenizer(physicalName, "" + COMPOSITE_SEPERATOR);
265            while (iter.hasMoreTokens()) {
266                String name = iter.nextToken().trim();
267                if (name.length() == 0) {
268                    continue;
269                }
270                l.add(name);
271            }
272            compositeDestinations = new ActiveMQDestination[l.size()];
273            int counter = 0;
274            for (String dest : l) {
275                compositeDestinations[counter++] = createDestination(dest);
276            }
277        }
278    }
279
280    public ActiveMQDestination createDestination(String name) {
281        return createDestination(name, getDestinationType());
282    }
283
284    public String[] getDestinationPaths() {
285
286        if (destinationPaths != null) {
287            return destinationPaths;
288        }
289
290        List<String> l = new ArrayList<String>();
291        StringBuilder level = new StringBuilder();
292        final char separator = PATH_SEPERATOR.charAt(0);
293        for (char c : physicalName.toCharArray()) {
294            if (c == separator) {
295                l.add(level.toString());
296                level.delete(0, level.length());
297            } else {
298                level.append(c);
299            }
300        }
301        l.add(level.toString());
302
303        destinationPaths = new String[l.size()];
304        l.toArray(destinationPaths);
305        return destinationPaths;
306    }
307
308    public abstract byte getDestinationType();
309
310    public boolean isQueue() {
311        return false;
312    }
313
314    public boolean isTopic() {
315        return false;
316    }
317
318    public boolean isTemporary() {
319        return false;
320    }
321
322    @Override
323    public boolean equals(Object o) {
324        if (this == o) {
325            return true;
326        }
327        if (o == null || getClass() != o.getClass()) {
328            return false;
329        }
330
331        ActiveMQDestination d = (ActiveMQDestination) o;
332        return physicalName.equals(d.physicalName);
333    }
334
335    @Override
336    public int hashCode() {
337        if (hashValue == 0) {
338            hashValue = physicalName.hashCode();
339        }
340        return hashValue;
341    }
342
343    @Override
344    public String toString() {
345        return getQualifiedName();
346    }
347
348    @Override
349    public void writeExternal(ObjectOutput out) throws IOException {
350        out.writeUTF(this.getPhysicalName());
351        out.writeObject(options);
352    }
353
354    @Override
355    @SuppressWarnings("unchecked")
356    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
357        this.setPhysicalName(in.readUTF());
358        this.options = (Map<String, String>) in.readObject();
359    }
360
361    public String getDestinationTypeAsString() {
362        switch (getDestinationType()) {
363            case QUEUE_TYPE:
364                return "Queue";
365            case TOPIC_TYPE:
366                return "Topic";
367            case TEMP_QUEUE_TYPE:
368                return "TempQueue";
369            case TEMP_TOPIC_TYPE:
370                return "TempTopic";
371            default:
372                throw new IllegalArgumentException("Invalid destination type: " + getDestinationType());
373        }
374    }
375
376    public Map<String, String> getOptions() {
377        return options;
378    }
379
380    @Override
381    public boolean isMarshallAware() {
382        return false;
383    }
384
385    @Override
386    public void buildFromProperties(Properties properties) {
387        if (properties == null) {
388            properties = new Properties();
389        }
390
391        IntrospectionSupport.setProperties(this, properties);
392    }
393
394    @Override
395    public void populateProperties(Properties props) {
396        props.setProperty("physicalName", getPhysicalName());
397    }
398
399    public boolean isPattern() {
400        return isPattern;
401    }
402
403    public boolean isDLQ() {
404        return options != null && options.containsKey(IS_DLQ);
405    }
406
407    public void setDLQ() {
408        if (options == null) {
409            options = new HashMap<String, String>();
410        }
411        options.put(IS_DLQ, String.valueOf(true));
412    }
413
414    public static UnresolvedDestinationTransformer getUnresolvableDestinationTransformer() {
415        return unresolvableDestinationTransformer;
416    }
417
418    public static void setUnresolvableDestinationTransformer(UnresolvedDestinationTransformer unresolvableDestinationTransformer) {
419        ActiveMQDestination.unresolvableDestinationTransformer = unresolvableDestinationTransformer;
420    }
421}