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}