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