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