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}