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