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