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