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