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