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