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