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