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