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                if (destination.isPattern()) {
163                    DestinationFilter filter = DestinationFilter.parseFilter(destination);
164                    if (filter.matches(destination2)) {
165                        return 1;
166                    }
167                }
168                if (destination2.isPattern()) {
169                    DestinationFilter filter = DestinationFilter.parseFilter(destination2);
170                    if (filter.matches(destination)) {
171                        return -1;
172                    }
173                }
174                return destination.getPhysicalName().compareTo(destination2.getPhysicalName());
175
176            } else {
177                return destination.isQueue() ? -1 : 1;
178            }
179        }
180    }
181
182    @Override
183    public int compareTo(Object that) {
184        if (that instanceof ActiveMQDestination) {
185            return compare(this, (ActiveMQDestination) that);
186        }
187        if (that == null) {
188            return 1;
189        } else {
190            return getClass().getName().compareTo(that.getClass().getName());
191        }
192    }
193
194    public boolean isComposite() {
195        return compositeDestinations != null;
196    }
197
198    public ActiveMQDestination[] getCompositeDestinations() {
199        return compositeDestinations;
200    }
201
202    public void setCompositeDestinations(ActiveMQDestination[] destinations) {
203        this.compositeDestinations = destinations;
204        this.destinationPaths = null;
205        this.hashValue = 0;
206        this.isPattern = false;
207
208        StringBuffer sb = new StringBuffer();
209        for (int i = 0; i < destinations.length; i++) {
210            if (i != 0) {
211                sb.append(COMPOSITE_SEPERATOR);
212            }
213            if (getDestinationType() == destinations[i].getDestinationType()) {
214                sb.append(destinations[i].getPhysicalName());
215            } else {
216                sb.append(destinations[i].getQualifiedName());
217            }
218        }
219        physicalName = sb.toString();
220    }
221
222    public String getQualifiedName() {
223        if (isComposite()) {
224            return physicalName;
225        }
226        return getQualifiedPrefix() + physicalName;
227    }
228
229    protected abstract String getQualifiedPrefix();
230
231    /**
232     * @openwire:property version=1
233     */
234    public String getPhysicalName() {
235        return physicalName;
236    }
237
238    public void setPhysicalName(String physicalName) {
239        physicalName = physicalName.trim();
240        final int length = physicalName.length();
241
242        if (physicalName.isEmpty()) {
243            throw new IllegalArgumentException("Invalid destination name: a non-empty name is required");
244        }
245
246        // options offset
247        int p = -1;
248        boolean composite = false;
249        for (int i = 0; i < length; i++) {
250            char c = physicalName.charAt(i);
251            if (c == '?') {
252                p = i;
253                break;
254            }
255            if (c == COMPOSITE_SEPERATOR) {
256                // won't be wild card
257                isPattern = false;
258                composite = true;
259            } else if (!composite && (c == '*' || c == '>')) {
260                isPattern = true;
261            }
262        }
263        // Strip off any options
264        if (p >= 0) {
265            String optstring = physicalName.substring(p + 1);
266            physicalName = physicalName.substring(0, p);
267            try {
268                options = URISupport.parseQuery(optstring);
269            } catch (URISyntaxException e) {
270                throw new IllegalArgumentException("Invalid destination name: " + physicalName + ", it's options are not encoded properly: " + e);
271            }
272        }
273        this.physicalName = physicalName;
274        this.destinationPaths = null;
275        this.hashValue = 0;
276        if (composite) {
277            // Check to see if it is a composite.
278            Set<String> l = new HashSet<String>();
279            StringTokenizer iter = new StringTokenizer(physicalName, "" + COMPOSITE_SEPERATOR);
280            while (iter.hasMoreTokens()) {
281                String name = iter.nextToken().trim();
282                if (name.length() == 0) {
283                    continue;
284                }
285                l.add(name);
286            }
287            compositeDestinations = new ActiveMQDestination[l.size()];
288            int counter = 0;
289            for (String dest : l) {
290                compositeDestinations[counter++] = createDestination(dest);
291            }
292        }
293    }
294
295    public ActiveMQDestination createDestination(String name) {
296        return createDestination(name, getDestinationType());
297    }
298
299    public String[] getDestinationPaths() {
300
301        if (destinationPaths != null) {
302            return destinationPaths;
303        }
304
305        List<String> l = new ArrayList<String>();
306        StringBuilder level = new StringBuilder();
307        final char separator = PATH_SEPERATOR.charAt(0);
308        for (char c : physicalName.toCharArray()) {
309            if (c == separator) {
310                l.add(level.toString());
311                level.delete(0, level.length());
312            } else {
313                level.append(c);
314            }
315        }
316        l.add(level.toString());
317
318        destinationPaths = new String[l.size()];
319        l.toArray(destinationPaths);
320        return destinationPaths;
321    }
322
323    public abstract byte getDestinationType();
324
325    public boolean isQueue() {
326        return false;
327    }
328
329    public boolean isTopic() {
330        return false;
331    }
332
333    public boolean isTemporary() {
334        return false;
335    }
336
337    @Override
338    public boolean equals(Object o) {
339        if (this == o) {
340            return true;
341        }
342        if (o == null || getClass() != o.getClass()) {
343            return false;
344        }
345
346        ActiveMQDestination d = (ActiveMQDestination) o;
347        return physicalName.equals(d.physicalName);
348    }
349
350    @Override
351    public int hashCode() {
352        if (hashValue == 0) {
353            hashValue = physicalName.hashCode();
354        }
355        return hashValue;
356    }
357
358    @Override
359    public String toString() {
360        return getQualifiedName();
361    }
362
363    @Override
364    public void writeExternal(ObjectOutput out) throws IOException {
365        out.writeUTF(this.getPhysicalName());
366        out.writeObject(options);
367    }
368
369    @Override
370    @SuppressWarnings("unchecked")
371    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
372        this.setPhysicalName(in.readUTF());
373        this.options = (Map<String, String>) in.readObject();
374    }
375
376    public String getDestinationTypeAsString() {
377        switch (getDestinationType()) {
378            case QUEUE_TYPE:
379                return "Queue";
380            case TOPIC_TYPE:
381                return "Topic";
382            case TEMP_QUEUE_TYPE:
383                return "TempQueue";
384            case TEMP_TOPIC_TYPE:
385                return "TempTopic";
386            default:
387                throw new IllegalArgumentException("Invalid destination type: " + getDestinationType());
388        }
389    }
390
391    public Map<String, String> getOptions() {
392        return options;
393    }
394
395    @Override
396    public boolean isMarshallAware() {
397        return false;
398    }
399
400    @Override
401    public void buildFromProperties(Properties properties) {
402        if (properties == null) {
403            properties = new Properties();
404        }
405
406        IntrospectionSupport.setProperties(this, properties);
407    }
408
409    @Override
410    public void populateProperties(Properties props) {
411        props.setProperty("physicalName", getPhysicalName());
412    }
413
414    public boolean isPattern() {
415        return isPattern;
416    }
417
418    public boolean isDLQ() {
419        return options != null && options.containsKey(IS_DLQ);
420    }
421
422    public void setDLQ() {
423        if (options == null) {
424            options = new HashMap<String, String>();
425        }
426        options.put(IS_DLQ, String.valueOf(true));
427    }
428
429    public static UnresolvedDestinationTransformer getUnresolvableDestinationTransformer() {
430        return unresolvableDestinationTransformer;
431    }
432
433    public static void setUnresolvableDestinationTransformer(UnresolvedDestinationTransformer unresolvableDestinationTransformer) {
434        ActiveMQDestination.unresolvableDestinationTransformer = unresolvableDestinationTransformer;
435    }
436}