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.Iterator; 022import java.util.LinkedHashSet; 023import java.util.List; 024import java.util.Set; 025import java.util.Stack; 026 027import org.apache.camel.AsyncCallback; 028import org.apache.camel.CamelContext; 029import org.apache.camel.CamelUnitOfWorkException; 030import org.apache.camel.Exchange; 031import org.apache.camel.Message; 032import org.apache.camel.Processor; 033import org.apache.camel.Route; 034import org.apache.camel.Service; 035import org.apache.camel.spi.RouteContext; 036import org.apache.camel.spi.SubUnitOfWork; 037import org.apache.camel.spi.SubUnitOfWorkCallback; 038import org.apache.camel.spi.Synchronization; 039import org.apache.camel.spi.SynchronizationVetoable; 040import org.apache.camel.spi.TracedRouteNodes; 041import org.apache.camel.spi.UnitOfWork; 042import org.apache.camel.util.EventHelper; 043import org.apache.camel.util.UnitOfWorkHelper; 044import org.slf4j.Logger; 045import org.slf4j.LoggerFactory; 046 047/** 048 * The default implementation of {@link org.apache.camel.spi.UnitOfWork} 049 */ 050public class DefaultUnitOfWork implements UnitOfWork, Service { 051 private static final Logger LOG = LoggerFactory.getLogger(DefaultUnitOfWork.class); 052 053 // TODO: This implementation seems to have transformed itself into a to broad concern 054 // where unit of work is doing a bit more work than the transactional aspect that ties 055 // to its name. Maybe this implementation should be named ExchangeContext and we can 056 // introduce a simpler UnitOfWork concept. This would also allow us to refactor the 057 // SubUnitOfWork into a general parent/child unit of work concept. However this 058 // requires API changes and thus is best kept for Camel 3.0 059 060 private UnitOfWork parent; 061 private String id; 062 private CamelContext context; 063 private List<Synchronization> synchronizations; 064 private Message originalInMessage; 065 private final TracedRouteNodes tracedRouteNodes; 066 private Set<Object> transactedBy; 067 private final Stack<RouteContext> routeContextStack = new Stack<RouteContext>(); 068 private Stack<DefaultSubUnitOfWork> subUnitOfWorks; 069 private final transient Logger log; 070 071 public DefaultUnitOfWork(Exchange exchange) { 072 this(exchange, LOG); 073 } 074 075 protected DefaultUnitOfWork(Exchange exchange, Logger logger) { 076 log = logger; 077 if (log.isTraceEnabled()) { 078 log.trace("UnitOfWork created for ExchangeId: {} with {}", exchange.getExchangeId(), exchange); 079 } 080 tracedRouteNodes = new DefaultTracedRouteNodes(); 081 context = exchange.getContext(); 082 083 if (context.isAllowUseOriginalMessage()) { 084 // TODO: Camel 3.0: the copy on facade strategy will help us here in the future 085 // TODO: optimize to only copy original message if enabled to do so in the route 086 // special for JmsMessage as it can cause it to loose headers later. 087 // This will be resolved when we get the message facade with copy on write implemented 088 if (exchange.getIn().getClass().getName().equals("org.apache.camel.component.jms.JmsMessage")) { 089 this.originalInMessage = new DefaultMessage(); 090 this.originalInMessage.setBody(exchange.getIn().getBody()); 091 this.originalInMessage.getHeaders().putAll(exchange.getIn().getHeaders()); 092 } else { 093 this.originalInMessage = exchange.getIn().copy(); 094 } 095 } 096 097 // TODO: Optimize to only copy if useOriginalMessage has been enabled 098 099 // mark the creation time when this Exchange was created 100 if (exchange.getProperty(Exchange.CREATED_TIMESTAMP) == null) { 101 exchange.setProperty(Exchange.CREATED_TIMESTAMP, new Date()); 102 } 103 104 // inject breadcrumb header if enabled 105 if (exchange.getContext().isUseBreadcrumb()) { 106 // create or use existing breadcrumb 107 String breadcrumbId = exchange.getIn().getHeader(Exchange.BREADCRUMB_ID, String.class); 108 if (breadcrumbId == null) { 109 // no existing breadcrumb, so create a new one based on the message id 110 breadcrumbId = exchange.getIn().getMessageId(); 111 exchange.getIn().setHeader(Exchange.BREADCRUMB_ID, breadcrumbId); 112 } 113 } 114 115 // setup whether the exchange is externally redelivered or not (if not initialized before) 116 // store as property so we know that the origin exchange was redelivered 117 if (exchange.getProperty(Exchange.EXTERNAL_REDELIVERED) == null) { 118 exchange.setProperty(Exchange.EXTERNAL_REDELIVERED, exchange.isExternalRedelivered()); 119 } 120 121 // fire event 122 try { 123 EventHelper.notifyExchangeCreated(exchange.getContext(), exchange); 124 } catch (Throwable e) { 125 // must catch exceptions to ensure the exchange is not failing due to notification event failed 126 log.warn("Exception occurred during event notification. This exception will be ignored.", e); 127 } 128 129 // register to inflight registry 130 if (exchange.getContext() != null) { 131 exchange.getContext().getInflightRepository().add(exchange); 132 } 133 } 134 135 UnitOfWork newInstance(Exchange exchange) { 136 return new DefaultUnitOfWork(exchange); 137 } 138 139 @Override 140 public void setParentUnitOfWork(UnitOfWork parentUnitOfWork) { 141 this.parent = parentUnitOfWork; 142 } 143 144 public UnitOfWork createChildUnitOfWork(Exchange childExchange) { 145 // create a new child unit of work, and mark me as its parent 146 UnitOfWork answer = newInstance(childExchange); 147 answer.setParentUnitOfWork(this); 148 return answer; 149 } 150 151 public void start() throws Exception { 152 id = null; 153 } 154 155 public void stop() throws Exception { 156 // need to clean up when we are stopping to not leak memory 157 if (synchronizations != null) { 158 synchronizations.clear(); 159 } 160 if (tracedRouteNodes != null) { 161 tracedRouteNodes.clear(); 162 } 163 if (transactedBy != null) { 164 transactedBy.clear(); 165 } 166 synchronized (routeContextStack) { 167 if (!routeContextStack.isEmpty()) { 168 routeContextStack.clear(); 169 } 170 } 171 if (subUnitOfWorks != null) { 172 subUnitOfWorks.clear(); 173 } 174 originalInMessage = null; 175 parent = null; 176 id = null; 177 } 178 179 public synchronized void addSynchronization(Synchronization synchronization) { 180 if (synchronizations == null) { 181 synchronizations = new ArrayList<Synchronization>(); 182 } 183 log.trace("Adding synchronization {}", synchronization); 184 synchronizations.add(synchronization); 185 } 186 187 public synchronized void removeSynchronization(Synchronization synchronization) { 188 if (synchronizations != null) { 189 synchronizations.remove(synchronization); 190 } 191 } 192 193 public synchronized boolean containsSynchronization(Synchronization synchronization) { 194 return synchronizations != null && synchronizations.contains(synchronization); 195 } 196 197 public void handoverSynchronization(Exchange target) { 198 if (synchronizations == null || synchronizations.isEmpty()) { 199 return; 200 } 201 202 Iterator<Synchronization> it = synchronizations.iterator(); 203 while (it.hasNext()) { 204 Synchronization synchronization = it.next(); 205 206 boolean handover = true; 207 if (synchronization instanceof SynchronizationVetoable) { 208 SynchronizationVetoable veto = (SynchronizationVetoable) synchronization; 209 handover = veto.allowHandover(); 210 } 211 212 if (handover) { 213 log.trace("Handover synchronization {} to: {}", synchronization, target); 214 target.addOnCompletion(synchronization); 215 // remove it if its handed over 216 it.remove(); 217 } else { 218 log.trace("Handover not allow for synchronization {}", synchronization); 219 } 220 } 221 } 222 223 public void done(Exchange exchange) { 224 log.trace("UnitOfWork done for ExchangeId: {} with {}", exchange.getExchangeId(), exchange); 225 226 boolean failed = exchange.isFailed(); 227 228 // at first done the synchronizations 229 UnitOfWorkHelper.doneSynchronizations(exchange, synchronizations, log); 230 231 // notify uow callback if in use 232 try { 233 SubUnitOfWorkCallback uowCallback = getSubUnitOfWorkCallback(); 234 if (uowCallback != null) { 235 uowCallback.onDone(exchange); 236 } 237 } catch (Throwable e) { 238 // must catch exceptions to ensure synchronizations is also invoked 239 log.warn("Exception occurred during savepoint onDone. This exception will be ignored.", e); 240 } 241 242 // unregister from inflight registry, before signalling we are done 243 if (exchange.getContext() != null) { 244 exchange.getContext().getInflightRepository().remove(exchange); 245 } 246 247 // then fire event to signal the exchange is done 248 try { 249 if (failed) { 250 EventHelper.notifyExchangeFailed(exchange.getContext(), exchange); 251 } else { 252 EventHelper.notifyExchangeDone(exchange.getContext(), exchange); 253 } 254 } catch (Throwable e) { 255 // must catch exceptions to ensure synchronizations is also invoked 256 log.warn("Exception occurred during event notification. This exception will be ignored.", e); 257 } 258 } 259 260 @Override 261 public void beforeRoute(Exchange exchange, Route route) { 262 if (log.isTraceEnabled()) { 263 log.trace("UnitOfWork beforeRoute: {} for ExchangeId: {} with {}", new Object[]{route.getId(), exchange.getExchangeId(), exchange}); 264 } 265 UnitOfWorkHelper.beforeRouteSynchronizations(route, exchange, synchronizations, log); 266 } 267 268 @Override 269 public void afterRoute(Exchange exchange, Route route) { 270 if (log.isTraceEnabled()) { 271 log.trace("UnitOfWork afterRouteL: {} for ExchangeId: {} with {}", new Object[]{route.getId(), exchange.getExchangeId(), exchange}); 272 } 273 UnitOfWorkHelper.afterRouteSynchronizations(route, exchange, synchronizations, log); 274 } 275 276 public String getId() { 277 if (id == null) { 278 id = context.getUuidGenerator().generateUuid(); 279 } 280 return id; 281 } 282 283 public Message getOriginalInMessage() { 284 return originalInMessage; 285 } 286 287 public TracedRouteNodes getTracedRouteNodes() { 288 return tracedRouteNodes; 289 } 290 291 public boolean isTransacted() { 292 return transactedBy != null && !transactedBy.isEmpty(); 293 } 294 295 public boolean isTransactedBy(Object key) { 296 return getTransactedBy().contains(key); 297 } 298 299 public void beginTransactedBy(Object key) { 300 getTransactedBy().add(key); 301 } 302 303 public void endTransactedBy(Object key) { 304 getTransactedBy().remove(key); 305 } 306 307 public RouteContext getRouteContext() { 308 synchronized (routeContextStack) { 309 if (routeContextStack.isEmpty()) { 310 return null; 311 } 312 return routeContextStack.peek(); 313 } 314 } 315 316 public void pushRouteContext(RouteContext routeContext) { 317 synchronized (routeContextStack) { 318 routeContextStack.add(routeContext); 319 } 320 } 321 322 public RouteContext popRouteContext() { 323 synchronized (routeContextStack) { 324 if (routeContextStack.isEmpty()) { 325 return null; 326 } 327 return routeContextStack.pop(); 328 } 329 } 330 331 public AsyncCallback beforeProcess(Processor processor, Exchange exchange, AsyncCallback callback) { 332 // no wrapping needed 333 return callback; 334 } 335 336 public void afterProcess(Processor processor, Exchange exchange, AsyncCallback callback, boolean doneSync) { 337 } 338 339 @Override 340 public void beginSubUnitOfWork(Exchange exchange) { 341 if (log.isTraceEnabled()) { 342 log.trace("beginSubUnitOfWork exchangeId: {}", exchange.getExchangeId()); 343 } 344 345 if (subUnitOfWorks == null) { 346 subUnitOfWorks = new Stack<DefaultSubUnitOfWork>(); 347 } 348 subUnitOfWorks.push(new DefaultSubUnitOfWork()); 349 } 350 351 @Override 352 public void endSubUnitOfWork(Exchange exchange) { 353 if (log.isTraceEnabled()) { 354 log.trace("endSubUnitOfWork exchangeId: {}", exchange.getExchangeId()); 355 } 356 357 if (subUnitOfWorks == null || subUnitOfWorks.isEmpty()) { 358 return; 359 } 360 361 // pop last sub unit of work as its now ended 362 SubUnitOfWork subUoW = subUnitOfWorks.pop(); 363 if (subUoW.isFailed()) { 364 // the sub unit of work failed so set an exception containing all the caused exceptions 365 // and mark the exchange for rollback only 366 367 // if there are multiple exceptions then wrap those into another exception with them all 368 Exception cause; 369 List<Exception> list = subUoW.getExceptions(); 370 if (list != null) { 371 if (list.size() == 1) { 372 cause = list.get(0); 373 } else { 374 cause = new CamelUnitOfWorkException(exchange, list); 375 } 376 exchange.setException(cause); 377 } 378 // mark it as rollback and that the unit of work is exhausted. This ensures that we do not try 379 // to redeliver this exception (again) 380 exchange.setProperty(Exchange.ROLLBACK_ONLY, true); 381 exchange.setProperty(Exchange.UNIT_OF_WORK_EXHAUSTED, true); 382 // and remove any indications of error handled which will prevent this exception to be noticed 383 // by the error handler which we want to react with the result of the sub unit of work 384 exchange.setProperty(Exchange.ERRORHANDLER_HANDLED, null); 385 exchange.setProperty(Exchange.FAILURE_HANDLED, null); 386 if (log.isTraceEnabled()) { 387 log.trace("endSubUnitOfWork exchangeId: {} with {} caused exceptions.", exchange.getExchangeId(), list != null ? list.size() : 0); 388 } 389 } 390 } 391 392 @Override 393 public SubUnitOfWorkCallback getSubUnitOfWorkCallback() { 394 // if there is a parent-child relationship between unit of works 395 // then we should use the callback strategies from the parent 396 if (parent != null) { 397 return parent.getSubUnitOfWorkCallback(); 398 } 399 400 if (subUnitOfWorks == null || subUnitOfWorks.isEmpty()) { 401 return null; 402 } 403 return subUnitOfWorks.peek(); 404 } 405 406 private Set<Object> getTransactedBy() { 407 if (transactedBy == null) { 408 transactedBy = new LinkedHashSet<Object>(); 409 } 410 return transactedBy; 411 } 412 413 @Override 414 public String toString() { 415 return "DefaultUnitOfWork"; 416 } 417}