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 != null && 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 Exception getException() { 354 return exception; 355 } 356 357 public <T> T getException(Class<T> type) { 358 return ObjectHelper.getException(type, exception); 359 } 360 361 public void setException(Throwable t) { 362 if (t == null) { 363 this.exception = null; 364 } else if (t instanceof Exception) { 365 this.exception = (Exception) t; 366 } else { 367 // wrap throwable into an exception 368 this.exception = ObjectHelper.wrapCamelExecutionException(this, t); 369 } 370 if (t instanceof InterruptedException) { 371 // mark the exchange as interrupted due to the interrupt exception 372 setProperty(Exchange.INTERRUPTED, Boolean.TRUE); 373 } 374 } 375 376 public ExchangePattern getPattern() { 377 return pattern; 378 } 379 380 public void setPattern(ExchangePattern pattern) { 381 this.pattern = pattern; 382 } 383 384 public Endpoint getFromEndpoint() { 385 return fromEndpoint; 386 } 387 388 public void setFromEndpoint(Endpoint fromEndpoint) { 389 this.fromEndpoint = fromEndpoint; 390 } 391 392 public String getFromRouteId() { 393 return fromRouteId; 394 } 395 396 public void setFromRouteId(String fromRouteId) { 397 this.fromRouteId = fromRouteId; 398 } 399 400 public String getExchangeId() { 401 if (exchangeId == null) { 402 exchangeId = createExchangeId(); 403 } 404 return exchangeId; 405 } 406 407 public void setExchangeId(String id) { 408 this.exchangeId = id; 409 } 410 411 public boolean isFailed() { 412 if (exception != null) { 413 return true; 414 } 415 return hasOut() ? getOut().isFault() : getIn().isFault(); 416 } 417 418 public boolean isTransacted() { 419 UnitOfWork uow = getUnitOfWork(); 420 if (uow != null) { 421 return uow.isTransacted(); 422 } else { 423 return false; 424 } 425 } 426 427 public Boolean isExternalRedelivered() { 428 Boolean answer = null; 429 430 // check property first, as the implementation details to know if the message 431 // was externally redelivered is message specific, and thus the message implementation 432 // could potentially change during routing, and therefore later we may not know if the 433 // original message was externally redelivered or not, therefore we store this detail 434 // as a exchange property to keep it around for the lifecycle of the exchange 435 if (hasProperties()) { 436 answer = getProperty(Exchange.EXTERNAL_REDELIVERED, null, Boolean.class); 437 } 438 439 if (answer == null) { 440 // lets avoid adding methods to the Message API, so we use the 441 // DefaultMessage to allow component specific messages to extend 442 // and implement the isExternalRedelivered method. 443 Message msg = getIn(); 444 if (msg instanceof DefaultMessage) { 445 answer = ((DefaultMessage) msg).isTransactedRedelivered(); 446 } 447 } 448 449 return answer; 450 } 451 452 public boolean isRollbackOnly() { 453 return Boolean.TRUE.equals(getProperty(Exchange.ROLLBACK_ONLY)) || Boolean.TRUE.equals(getProperty(Exchange.ROLLBACK_ONLY_LAST)); 454 } 455 456 public UnitOfWork getUnitOfWork() { 457 return unitOfWork; 458 } 459 460 public void setUnitOfWork(UnitOfWork unitOfWork) { 461 this.unitOfWork = unitOfWork; 462 if (unitOfWork != null && onCompletions != null) { 463 // now an unit of work has been assigned so add the on completions 464 // we might have registered already 465 for (Synchronization onCompletion : onCompletions) { 466 unitOfWork.addSynchronization(onCompletion); 467 } 468 // cleanup the temporary on completion list as they now have been registered 469 // on the unit of work 470 onCompletions.clear(); 471 onCompletions = null; 472 } 473 } 474 475 public void addOnCompletion(Synchronization onCompletion) { 476 if (unitOfWork == null) { 477 // unit of work not yet registered so we store the on completion temporary 478 // until the unit of work is assigned to this exchange by the unit of work 479 if (onCompletions == null) { 480 onCompletions = new ArrayList<Synchronization>(); 481 } 482 onCompletions.add(onCompletion); 483 } else { 484 getUnitOfWork().addSynchronization(onCompletion); 485 } 486 } 487 488 public boolean containsOnCompletion(Synchronization onCompletion) { 489 if (unitOfWork != null) { 490 // if there is an unit of work then the completions is moved there 491 return unitOfWork.containsSynchronization(onCompletion); 492 } else { 493 // check temporary completions if no unit of work yet 494 return onCompletions != null && onCompletions.contains(onCompletion); 495 } 496 } 497 498 public void handoverCompletions(Exchange target) { 499 if (onCompletions != null) { 500 for (Synchronization onCompletion : onCompletions) { 501 target.addOnCompletion(onCompletion); 502 } 503 // cleanup the temporary on completion list as they have been handed over 504 onCompletions.clear(); 505 onCompletions = null; 506 } else if (unitOfWork != null) { 507 // let unit of work handover 508 unitOfWork.handoverSynchronization(target); 509 } 510 } 511 512 public List<Synchronization> handoverCompletions() { 513 List<Synchronization> answer = null; 514 if (onCompletions != null) { 515 answer = new ArrayList<Synchronization>(onCompletions); 516 onCompletions.clear(); 517 onCompletions = null; 518 } 519 return answer; 520 } 521 522 /** 523 * Configures the message after it has been set on the exchange 524 */ 525 protected void configureMessage(Message message) { 526 if (message instanceof MessageSupport) { 527 MessageSupport messageSupport = (MessageSupport)message; 528 messageSupport.setExchange(this); 529 messageSupport.setCamelContext(getContext()); 530 } 531 } 532 533 @SuppressWarnings("deprecation") 534 protected String createExchangeId() { 535 String answer = null; 536 if (in != null) { 537 answer = in.createExchangeId(); 538 } 539 if (answer == null) { 540 answer = context.getUuidGenerator().generateUuid(); 541 } 542 return answer; 543 } 544 545 protected Map<String, Object> createProperties() { 546 return new HashMap<>(); 547 } 548 549 protected Map<String, Object> createProperties(Map<String, Object> properties) { 550 return new HashMap<>(properties); 551 } 552 553 private static boolean isExcludePatternMatch(String key, String... excludePatterns) { 554 for (String pattern : excludePatterns) { 555 if (EndpointHelper.matchPattern(key, pattern)) { 556 return true; 557 } 558 } 559 return false; 560 } 561}