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