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.camel.impl; 018 019import java.util.ArrayList; 020import java.util.List; 021import java.util.Map; 022import java.util.concurrent.ConcurrentHashMap; 023 024import org.apache.camel.CamelContext; 025import org.apache.camel.Endpoint; 026import org.apache.camel.Exchange; 027import org.apache.camel.ExchangePattern; 028import org.apache.camel.Message; 029import org.apache.camel.MessageHistory; 030import org.apache.camel.spi.Synchronization; 031import org.apache.camel.spi.UnitOfWork; 032import org.apache.camel.util.EndpointHelper; 033import org.apache.camel.util.ExchangeHelper; 034import org.apache.camel.util.ObjectHelper; 035 036/** 037 * A default implementation of {@link Exchange} 038 * 039 * @version 040 */ 041public final class DefaultExchange implements Exchange { 042 043 protected final CamelContext context; 044 private Map<String, Object> properties; 045 private Message in; 046 private Message out; 047 private Exception exception; 048 private String exchangeId; 049 private UnitOfWork unitOfWork; 050 private ExchangePattern pattern; 051 private Endpoint fromEndpoint; 052 private String fromRouteId; 053 private List<Synchronization> onCompletions; 054 055 public DefaultExchange(CamelContext context) { 056 this(context, ExchangePattern.InOnly); 057 } 058 059 public DefaultExchange(CamelContext context, ExchangePattern pattern) { 060 this.context = context; 061 this.pattern = pattern; 062 } 063 064 public DefaultExchange(Exchange parent) { 065 this(parent.getContext(), parent.getPattern()); 066 this.fromEndpoint = parent.getFromEndpoint(); 067 this.fromRouteId = parent.getFromRouteId(); 068 this.unitOfWork = parent.getUnitOfWork(); 069 } 070 071 public DefaultExchange(Endpoint fromEndpoint) { 072 this(fromEndpoint, ExchangePattern.InOnly); 073 } 074 075 public DefaultExchange(Endpoint fromEndpoint, ExchangePattern pattern) { 076 this(fromEndpoint.getCamelContext(), pattern); 077 this.fromEndpoint = fromEndpoint; 078 } 079 080 @Override 081 public String toString() { 082 return "Exchange[" + (out == null ? in : out) + "]"; 083 } 084 085 public Exchange copy() { 086 DefaultExchange exchange = new DefaultExchange(this); 087 088 if (hasProperties()) { 089 exchange.setProperties(safeCopy(getProperties())); 090 } 091 092 exchange.setIn(getIn().copy()); 093 if (hasOut()) { 094 exchange.setOut(getOut().copy()); 095 } 096 exchange.setException(getException()); 097 return exchange; 098 } 099 100 @SuppressWarnings("unchecked") 101 private static Map<String, Object> safeCopy(Map<String, Object> properties) { 102 if (properties == null) { 103 return null; 104 } 105 106 Map<String, Object> answer = new ConcurrentHashMap<String, Object>(properties); 107 108 // safe copy message history using a defensive copy 109 List<MessageHistory> history = (List<MessageHistory>) answer.remove(Exchange.MESSAGE_HISTORY); 110 if (history != null) { 111 answer.put(Exchange.MESSAGE_HISTORY, new ArrayList<MessageHistory>(history)); 112 } 113 114 return answer; 115 } 116 117 public CamelContext getContext() { 118 return context; 119 } 120 121 public Object getProperty(String name) { 122 if (properties != null) { 123 return properties.get(name); 124 } 125 return null; 126 } 127 128 public Object getProperty(String name, Object defaultValue) { 129 Object answer = getProperty(name); 130 return answer != null ? answer : defaultValue; 131 } 132 133 @SuppressWarnings("unchecked") 134 public <T> T getProperty(String name, Class<T> type) { 135 Object value = getProperty(name); 136 if (value == null) { 137 // lets avoid NullPointerException when converting to boolean for null values 138 if (boolean.class.isAssignableFrom(type)) { 139 return (T) Boolean.FALSE; 140 } 141 return null; 142 } 143 144 // eager same instance type test to avoid the overhead of invoking the type converter 145 // if already same type 146 if (type.isInstance(value)) { 147 return type.cast(value); 148 } 149 150 return ExchangeHelper.convertToType(this, type, value); 151 } 152 153 @SuppressWarnings("unchecked") 154 public <T> T getProperty(String name, Object defaultValue, Class<T> type) { 155 Object value = getProperty(name, defaultValue); 156 if (value == null) { 157 // lets avoid NullPointerException when converting to boolean for null values 158 if (boolean.class.isAssignableFrom(type)) { 159 return (T) Boolean.FALSE; 160 } 161 return null; 162 } 163 164 // eager same instance type test to avoid the overhead of invoking the type converter 165 // if already same type 166 if (type.isInstance(value)) { 167 return type.cast(value); 168 } 169 170 return ExchangeHelper.convertToType(this, type, value); 171 } 172 173 public void setProperty(String name, Object value) { 174 if (value != null) { 175 // avoid the NullPointException 176 getProperties().put(name, value); 177 } else { 178 // if the value is null, we just remove the key from the map 179 if (name != null) { 180 getProperties().remove(name); 181 } 182 } 183 } 184 185 public Object removeProperty(String name) { 186 if (!hasProperties()) { 187 return null; 188 } 189 return getProperties().remove(name); 190 } 191 192 public boolean removeProperties(String pattern) { 193 return removeProperties(pattern, (String[]) null); 194 } 195 196 public boolean removeProperties(String pattern, String... excludePatterns) { 197 if (!hasProperties()) { 198 return false; 199 } 200 201 boolean matches = false; 202 for (Map.Entry<String, Object> entry : properties.entrySet()) { 203 String key = entry.getKey(); 204 if (EndpointHelper.matchPattern(key, pattern)) { 205 if (excludePatterns != null && isExcludePatternMatch(key, excludePatterns)) { 206 continue; 207 } 208 matches = true; 209 properties.remove(entry.getKey()); 210 } 211 212 } 213 return matches; 214 } 215 216 public Map<String, Object> getProperties() { 217 if (properties == null) { 218 properties = new ConcurrentHashMap<String, Object>(); 219 } 220 return properties; 221 } 222 223 public boolean hasProperties() { 224 return properties != null && !properties.isEmpty(); 225 } 226 227 public void setProperties(Map<String, Object> properties) { 228 this.properties = properties; 229 } 230 231 public Message getIn() { 232 if (in == null) { 233 in = new DefaultMessage(); 234 configureMessage(in); 235 } 236 return in; 237 } 238 239 public <T> T getIn(Class<T> type) { 240 Message in = getIn(); 241 242 // eager same instance type test to avoid the overhead of invoking the type converter 243 // if already same type 244 if (type.isInstance(in)) { 245 return type.cast(in); 246 } 247 248 // fallback to use type converter 249 return context.getTypeConverter().convertTo(type, this, in); 250 } 251 252 public void setIn(Message in) { 253 this.in = in; 254 configureMessage(in); 255 } 256 257 public Message getOut() { 258 // lazy create 259 if (out == null) { 260 out = (in != null && in instanceof MessageSupport) 261 ? ((MessageSupport)in).newInstance() : new DefaultMessage(); 262 configureMessage(out); 263 } 264 return out; 265 } 266 267 public <T> T getOut(Class<T> type) { 268 if (!hasOut()) { 269 return null; 270 } 271 272 Message out = getOut(); 273 274 // eager same instance type test to avoid the overhead of invoking the type converter 275 // if already same type 276 if (type.isInstance(out)) { 277 return type.cast(out); 278 } 279 280 // fallback to use type converter 281 return context.getTypeConverter().convertTo(type, this, out); 282 } 283 284 public boolean hasOut() { 285 return out != null; 286 } 287 288 public void setOut(Message out) { 289 this.out = out; 290 configureMessage(out); 291 } 292 293 public Exception getException() { 294 return exception; 295 } 296 297 public <T> T getException(Class<T> type) { 298 return ObjectHelper.getException(type, exception); 299 } 300 301 public void setException(Throwable t) { 302 if (t == null) { 303 this.exception = null; 304 } else if (t instanceof Exception) { 305 this.exception = (Exception) t; 306 } else { 307 // wrap throwable into an exception 308 this.exception = ObjectHelper.wrapCamelExecutionException(this, t); 309 } 310 } 311 312 public ExchangePattern getPattern() { 313 return pattern; 314 } 315 316 public void setPattern(ExchangePattern pattern) { 317 this.pattern = pattern; 318 } 319 320 public Endpoint getFromEndpoint() { 321 return fromEndpoint; 322 } 323 324 public void setFromEndpoint(Endpoint fromEndpoint) { 325 this.fromEndpoint = fromEndpoint; 326 } 327 328 public String getFromRouteId() { 329 return fromRouteId; 330 } 331 332 public void setFromRouteId(String fromRouteId) { 333 this.fromRouteId = fromRouteId; 334 } 335 336 public String getExchangeId() { 337 if (exchangeId == null) { 338 exchangeId = createExchangeId(); 339 } 340 return exchangeId; 341 } 342 343 public void setExchangeId(String id) { 344 this.exchangeId = id; 345 } 346 347 public boolean isFailed() { 348 if (exception != null) { 349 return true; 350 } 351 return hasOut() ? getOut().isFault() : getIn().isFault(); 352 } 353 354 public boolean isTransacted() { 355 UnitOfWork uow = getUnitOfWork(); 356 if (uow != null) { 357 return uow.isTransacted(); 358 } else { 359 return false; 360 } 361 } 362 363 public Boolean isExternalRedelivered() { 364 Boolean answer = null; 365 366 // check property first, as the implementation details to know if the message 367 // was externally redelivered is message specific, and thus the message implementation 368 // could potentially change during routing, and therefore later we may not know if the 369 // original message was externally redelivered or not, therefore we store this detail 370 // as a exchange property to keep it around for the lifecycle of the exchange 371 if (hasProperties()) { 372 answer = getProperty(Exchange.EXTERNAL_REDELIVERED, null, Boolean.class); 373 } 374 375 if (answer == null) { 376 // lets avoid adding methods to the Message API, so we use the 377 // DefaultMessage to allow component specific messages to extend 378 // and implement the isExternalRedelivered method. 379 DefaultMessage msg = getIn(DefaultMessage.class); 380 if (msg != null) { 381 answer = msg.isTransactedRedelivered(); 382 // store as property to keep around 383 setProperty(Exchange.EXTERNAL_REDELIVERED, answer); 384 } 385 } 386 387 return answer; 388 } 389 390 public boolean isRollbackOnly() { 391 return Boolean.TRUE.equals(getProperty(Exchange.ROLLBACK_ONLY)) || Boolean.TRUE.equals(getProperty(Exchange.ROLLBACK_ONLY_LAST)); 392 } 393 394 public UnitOfWork getUnitOfWork() { 395 return unitOfWork; 396 } 397 398 public void setUnitOfWork(UnitOfWork unitOfWork) { 399 this.unitOfWork = unitOfWork; 400 if (unitOfWork != null && onCompletions != null) { 401 // now an unit of work has been assigned so add the on completions 402 // we might have registered already 403 for (Synchronization onCompletion : onCompletions) { 404 unitOfWork.addSynchronization(onCompletion); 405 } 406 // cleanup the temporary on completion list as they now have been registered 407 // on the unit of work 408 onCompletions.clear(); 409 onCompletions = null; 410 } 411 } 412 413 public void addOnCompletion(Synchronization onCompletion) { 414 if (unitOfWork == null) { 415 // unit of work not yet registered so we store the on completion temporary 416 // until the unit of work is assigned to this exchange by the unit of work 417 if (onCompletions == null) { 418 onCompletions = new ArrayList<Synchronization>(); 419 } 420 onCompletions.add(onCompletion); 421 } else { 422 getUnitOfWork().addSynchronization(onCompletion); 423 } 424 } 425 426 public boolean containsOnCompletion(Synchronization onCompletion) { 427 if (unitOfWork != null) { 428 // if there is an unit of work then the completions is moved there 429 return unitOfWork.containsSynchronization(onCompletion); 430 } else { 431 // check temporary completions if no unit of work yet 432 return onCompletions != null && onCompletions.contains(onCompletion); 433 } 434 } 435 436 public void handoverCompletions(Exchange target) { 437 if (onCompletions != null) { 438 for (Synchronization onCompletion : onCompletions) { 439 target.addOnCompletion(onCompletion); 440 } 441 // cleanup the temporary on completion list as they have been handed over 442 onCompletions.clear(); 443 onCompletions = null; 444 } else if (unitOfWork != null) { 445 // let unit of work handover 446 unitOfWork.handoverSynchronization(target); 447 } 448 } 449 450 public List<Synchronization> handoverCompletions() { 451 List<Synchronization> answer = null; 452 if (onCompletions != null) { 453 answer = new ArrayList<Synchronization>(onCompletions); 454 onCompletions.clear(); 455 onCompletions = null; 456 } 457 return answer; 458 } 459 460 /** 461 * Configures the message after it has been set on the exchange 462 */ 463 protected void configureMessage(Message message) { 464 if (message instanceof MessageSupport) { 465 MessageSupport messageSupport = (MessageSupport)message; 466 messageSupport.setExchange(this); 467 } 468 } 469 470 @SuppressWarnings("deprecation") 471 protected String createExchangeId() { 472 String answer = null; 473 if (in != null) { 474 answer = in.createExchangeId(); 475 } 476 if (answer == null) { 477 answer = context.getUuidGenerator().generateUuid(); 478 } 479 return answer; 480 } 481 482 private static boolean isExcludePatternMatch(String key, String... excludePatterns) { 483 for (String pattern : excludePatterns) { 484 if (EndpointHelper.matchPattern(key, pattern)) { 485 return true; 486 } 487 } 488 return false; 489 } 490}