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