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