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; 026import java.util.function.Predicate; 027 028import org.apache.camel.AsyncCallback; 029import org.apache.camel.CamelContext; 030import org.apache.camel.CamelUnitOfWorkException; 031import org.apache.camel.Exchange; 032import org.apache.camel.Message; 033import org.apache.camel.Processor; 034import org.apache.camel.Route; 035import org.apache.camel.Service; 036import org.apache.camel.spi.RouteContext; 037import org.apache.camel.spi.SubUnitOfWork; 038import org.apache.camel.spi.SubUnitOfWorkCallback; 039import org.apache.camel.spi.Synchronization; 040import org.apache.camel.spi.SynchronizationVetoable; 041import org.apache.camel.spi.TracedRouteNodes; 042import org.apache.camel.spi.UnitOfWork; 043import org.apache.camel.util.EventHelper; 044import org.apache.camel.util.UnitOfWorkHelper; 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048/** 049 * The default implementation of {@link org.apache.camel.spi.UnitOfWork} 050 */ 051public class DefaultUnitOfWork implements UnitOfWork, Service { 052 private static final Logger LOG = LoggerFactory.getLogger(DefaultUnitOfWork.class); 053 054 // TODO: This implementation seems to have transformed itself into a to broad concern 055 // where unit of work is doing a bit more work than the transactional aspect that ties 056 // to its name. Maybe this implementation should be named ExchangeContext and we can 057 // introduce a simpler UnitOfWork concept. This would also allow us to refactor the 058 // SubUnitOfWork into a general parent/child unit of work concept. However this 059 // requires API changes and thus is best kept for Camel 3.0 060 061 private UnitOfWork parent; 062 private String id; 063 private CamelContext context; 064 private List<Synchronization> synchronizations; 065 private Message originalInMessage; 066 private final TracedRouteNodes tracedRouteNodes; 067 private Set<Object> transactedBy; 068 private final Stack<RouteContext> routeContextStack = new Stack<RouteContext>(); 069 private Stack<DefaultSubUnitOfWork> subUnitOfWorks; 070 private final transient Logger log; 071 072 public DefaultUnitOfWork(Exchange exchange) { 073 this(exchange, LOG); 074 } 075 076 protected DefaultUnitOfWork(Exchange exchange, Logger logger) { 077 log = logger; 078 if (log.isTraceEnabled()) { 079 log.trace("UnitOfWork created for ExchangeId: {} with {}", exchange.getExchangeId(), exchange); 080 } 081 tracedRouteNodes = new DefaultTracedRouteNodes(); 082 context = exchange.getContext(); 083 084 if (context.isAllowUseOriginalMessage()) { 085 // special for JmsMessage as it can cause it to loose headers later. 086 if (exchange.getIn().getClass().getName().equals("org.apache.camel.component.jms.JmsMessage")) { 087 this.originalInMessage = new DefaultMessage(); 088 this.originalInMessage.setBody(exchange.getIn().getBody()); 089 this.originalInMessage.getHeaders().putAll(exchange.getIn().getHeaders()); 090 } else { 091 this.originalInMessage = exchange.getIn().copy(); 092 } 093 // must preserve exchange on the original in message 094 if (this.originalInMessage instanceof MessageSupport) { 095 ((MessageSupport) this.originalInMessage).setExchange(exchange); 096 } 097 } 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 handoverSynchronization(target, null); 199 } 200 201 @Override 202 public void handoverSynchronization(Exchange target, Predicate<Synchronization> filter) { 203 if (synchronizations == null || synchronizations.isEmpty()) { 204 return; 205 } 206 207 Iterator<Synchronization> it = synchronizations.iterator(); 208 while (it.hasNext()) { 209 Synchronization synchronization = it.next(); 210 211 boolean handover = true; 212 if (synchronization instanceof SynchronizationVetoable) { 213 SynchronizationVetoable veto = (SynchronizationVetoable) synchronization; 214 handover = veto.allowHandover(); 215 } 216 217 if (handover && (filter == null || filter.test(synchronization))) { 218 log.trace("Handover synchronization {} to: {}", synchronization, target); 219 target.addOnCompletion(synchronization); 220 // remove it if its handed over 221 it.remove(); 222 } else { 223 log.trace("Handover not allow for synchronization {}", synchronization); 224 } 225 } 226 } 227 228 public void done(Exchange exchange) { 229 log.trace("UnitOfWork done for ExchangeId: {} with {}", exchange.getExchangeId(), exchange); 230 231 boolean failed = exchange.isFailed(); 232 233 // at first done the synchronizations 234 UnitOfWorkHelper.doneSynchronizations(exchange, synchronizations, log); 235 236 // notify uow callback if in use 237 try { 238 SubUnitOfWorkCallback uowCallback = getSubUnitOfWorkCallback(); 239 if (uowCallback != null) { 240 uowCallback.onDone(exchange); 241 } 242 } catch (Throwable e) { 243 // must catch exceptions to ensure synchronizations is also invoked 244 log.warn("Exception occurred during savepoint onDone. This exception will be ignored.", e); 245 } 246 247 // unregister from inflight registry, before signalling we are done 248 if (exchange.getContext() != null) { 249 exchange.getContext().getInflightRepository().remove(exchange); 250 } 251 252 // then fire event to signal the exchange is done 253 try { 254 if (failed) { 255 EventHelper.notifyExchangeFailed(exchange.getContext(), exchange); 256 } else { 257 EventHelper.notifyExchangeDone(exchange.getContext(), exchange); 258 } 259 } catch (Throwable e) { 260 // must catch exceptions to ensure synchronizations is also invoked 261 log.warn("Exception occurred during event notification. This exception will be ignored.", e); 262 } 263 } 264 265 @Override 266 public void beforeRoute(Exchange exchange, Route route) { 267 if (log.isTraceEnabled()) { 268 log.trace("UnitOfWork beforeRoute: {} for ExchangeId: {} with {}", new Object[]{route.getId(), exchange.getExchangeId(), exchange}); 269 } 270 UnitOfWorkHelper.beforeRouteSynchronizations(route, exchange, synchronizations, log); 271 } 272 273 @Override 274 public void afterRoute(Exchange exchange, Route route) { 275 if (log.isTraceEnabled()) { 276 log.trace("UnitOfWork afterRoute: {} for ExchangeId: {} with {}", new Object[]{route.getId(), exchange.getExchangeId(), exchange}); 277 } 278 UnitOfWorkHelper.afterRouteSynchronizations(route, exchange, synchronizations, log); 279 } 280 281 public String getId() { 282 if (id == null) { 283 id = context.getUuidGenerator().generateUuid(); 284 } 285 return id; 286 } 287 288 public Message getOriginalInMessage() { 289 if (originalInMessage == null && !context.isAllowUseOriginalMessage()) { 290 throw new IllegalStateException("AllowUseOriginalMessage is disabled. Cannot access the original message."); 291 } 292 return originalInMessage; 293 } 294 295 public TracedRouteNodes getTracedRouteNodes() { 296 return tracedRouteNodes; 297 } 298 299 public boolean isTransacted() { 300 return transactedBy != null && !transactedBy.isEmpty(); 301 } 302 303 public boolean isTransactedBy(Object key) { 304 return getTransactedBy().contains(key); 305 } 306 307 public void beginTransactedBy(Object key) { 308 getTransactedBy().add(key); 309 } 310 311 public void endTransactedBy(Object key) { 312 getTransactedBy().remove(key); 313 } 314 315 public RouteContext getRouteContext() { 316 synchronized (routeContextStack) { 317 if (routeContextStack.isEmpty()) { 318 return null; 319 } 320 return routeContextStack.peek(); 321 } 322 } 323 324 public void pushRouteContext(RouteContext routeContext) { 325 synchronized (routeContextStack) { 326 routeContextStack.add(routeContext); 327 } 328 } 329 330 public RouteContext popRouteContext() { 331 synchronized (routeContextStack) { 332 if (routeContextStack.isEmpty()) { 333 return null; 334 } 335 return routeContextStack.pop(); 336 } 337 } 338 339 public AsyncCallback beforeProcess(Processor processor, Exchange exchange, AsyncCallback callback) { 340 // no wrapping needed 341 return callback; 342 } 343 344 public void afterProcess(Processor processor, Exchange exchange, AsyncCallback callback, boolean doneSync) { 345 } 346 347 @Override 348 public void beginSubUnitOfWork(Exchange exchange) { 349 if (log.isTraceEnabled()) { 350 log.trace("beginSubUnitOfWork exchangeId: {}", exchange.getExchangeId()); 351 } 352 353 if (subUnitOfWorks == null) { 354 subUnitOfWorks = new Stack<DefaultSubUnitOfWork>(); 355 } 356 subUnitOfWorks.push(new DefaultSubUnitOfWork()); 357 } 358 359 @Override 360 public void endSubUnitOfWork(Exchange exchange) { 361 if (log.isTraceEnabled()) { 362 log.trace("endSubUnitOfWork exchangeId: {}", exchange.getExchangeId()); 363 } 364 365 if (subUnitOfWorks == null || subUnitOfWorks.isEmpty()) { 366 return; 367 } 368 369 // pop last sub unit of work as its now ended 370 SubUnitOfWork subUoW = subUnitOfWorks.pop(); 371 if (subUoW.isFailed()) { 372 // the sub unit of work failed so set an exception containing all the caused exceptions 373 // and mark the exchange for rollback only 374 375 // if there are multiple exceptions then wrap those into another exception with them all 376 Exception cause; 377 List<Exception> list = subUoW.getExceptions(); 378 if (list != null) { 379 if (list.size() == 1) { 380 cause = list.get(0); 381 } else { 382 cause = new CamelUnitOfWorkException(exchange, list); 383 } 384 exchange.setException(cause); 385 } 386 // mark it as rollback and that the unit of work is exhausted. This ensures that we do not try 387 // to redeliver this exception (again) 388 exchange.setProperty(Exchange.ROLLBACK_ONLY, true); 389 exchange.setProperty(Exchange.UNIT_OF_WORK_EXHAUSTED, true); 390 // and remove any indications of error handled which will prevent this exception to be noticed 391 // by the error handler which we want to react with the result of the sub unit of work 392 exchange.setProperty(Exchange.ERRORHANDLER_HANDLED, null); 393 exchange.setProperty(Exchange.FAILURE_HANDLED, null); 394 if (log.isTraceEnabled()) { 395 log.trace("endSubUnitOfWork exchangeId: {} with {} caused exceptions.", exchange.getExchangeId(), list != null ? list.size() : 0); 396 } 397 } 398 } 399 400 @Override 401 public SubUnitOfWorkCallback getSubUnitOfWorkCallback() { 402 // if there is a parent-child relationship between unit of works 403 // then we should use the callback strategies from the parent 404 if (parent != null) { 405 return parent.getSubUnitOfWorkCallback(); 406 } 407 408 if (subUnitOfWorks == null || subUnitOfWorks.isEmpty()) { 409 return null; 410 } 411 return subUnitOfWorks.peek(); 412 } 413 414 private Set<Object> getTransactedBy() { 415 if (transactedBy == null) { 416 transactedBy = new LinkedHashSet<Object>(); 417 } 418 return transactedBy; 419 } 420 421 @Override 422 public String toString() { 423 return "DefaultUnitOfWork"; 424 } 425}