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