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     */
017    package org.apache.camel.impl;
018    
019    import java.util.ArrayList;
020    import java.util.LinkedHashSet;
021    import java.util.List;
022    import java.util.Set;
023    import java.util.Stack;
024    
025    import org.apache.camel.Exchange;
026    import org.apache.camel.Message;
027    import org.apache.camel.Service;
028    import org.apache.camel.spi.RouteContext;
029    import org.apache.camel.spi.Synchronization;
030    import org.apache.camel.spi.TracedRouteNodes;
031    import org.apache.camel.spi.UnitOfWork;
032    import org.apache.camel.util.EventHelper;
033    import org.apache.camel.util.UuidGenerator;
034    import org.apache.commons.logging.Log;
035    import org.apache.commons.logging.LogFactory;
036    
037    /**
038     * The default implementation of {@link org.apache.camel.spi.UnitOfWork}
039     *
040     * @version $Revision: 899628 $
041     */
042    public class DefaultUnitOfWork implements UnitOfWork, Service {
043        private static final transient Log LOG = LogFactory.getLog(DefaultUnitOfWork.class);
044    
045        private String id;
046        private List<Synchronization> synchronizations;
047        private Message originalInMessage;
048        private final TracedRouteNodes tracedRouteNodes;
049        private Set<Object> transactedBy;
050        private final Stack<RouteContext> routeContextStack = new Stack<RouteContext>();
051    
052        public DefaultUnitOfWork(Exchange exchange) {
053            tracedRouteNodes = new DefaultTracedRouteNodes();
054    
055            // TODO: optimize to only copy original message if enabled to do so in the route
056            // special for JmsMessage as it can cause it to loose headers later.
057            // This will be resolved when we get the message facade with copy on write implemented
058            if (exchange.getIn().getClass().getSimpleName().equals("JmsMessage")) {
059                this.originalInMessage = new DefaultMessage();
060                this.originalInMessage.setBody(exchange.getIn().getBody());
061                // cannot copy headers with a JmsMessage as the underlying javax.jms.Message object goes nuts 
062            } else {
063                this.originalInMessage = exchange.getIn().copy();
064            }
065    
066            // fire event
067            EventHelper.notifyExchangeCreated(exchange.getContext(), exchange);
068    
069            // register to inflight registry
070            if (exchange.getContext() != null) {
071                exchange.getContext().getInflightRepository().add(exchange);
072            }
073        }
074    
075        public void start() throws Exception {
076            id = null;
077        }
078    
079        public void stop() throws Exception {
080            // need to clean up when we are stopping to not leak memory
081            if (synchronizations != null) {
082                synchronizations.clear();
083            }
084            if (tracedRouteNodes != null) {
085                tracedRouteNodes.clear();
086            }
087            if (transactedBy != null) {
088                transactedBy.clear();
089            }
090            originalInMessage = null;
091    
092            if (!routeContextStack.isEmpty()) {
093                routeContextStack.clear();
094            }
095        }
096    
097        public synchronized void addSynchronization(Synchronization synchronization) {
098            if (synchronizations == null) {
099                synchronizations = new ArrayList<Synchronization>();
100            }
101            synchronizations.add(synchronization);
102        }
103    
104        public synchronized void removeSynchronization(Synchronization synchronization) {
105            if (synchronizations != null) {
106                synchronizations.remove(synchronization);
107            }
108        }
109    
110        public void handoverSynchronization(Exchange target) {
111            if (synchronizations == null || synchronizations.isEmpty()) {
112                return;
113            }
114    
115            for (Synchronization synchronization : synchronizations) {
116                target.addOnCompletion(synchronization);
117            }
118    
119            // clear this list as its handed over to the other exchange
120            this.synchronizations.clear();
121        }
122    
123        public void done(Exchange exchange) {
124            boolean failed = exchange.isFailed();
125    
126            // fire event to signal the exchange is done
127            try {
128                if (failed) {
129                    EventHelper.notifyExchangeFailed(exchange.getContext(), exchange);
130                } else {
131                    EventHelper.notifyExchangeDone(exchange.getContext(), exchange);
132                }
133            } catch (Exception e) {
134                // must catch exceptions to ensure synchronizations is also invoked
135                LOG.warn("Exception occurred during event notification. This exception will be ignored.", e);
136            }
137    
138            if (synchronizations != null && !synchronizations.isEmpty()) {
139                // invoke synchronization callbacks
140                for (Synchronization synchronization : synchronizations) {
141                    try {
142                        if (failed) {
143                            synchronization.onFailure(exchange);
144                        } else {
145                            synchronization.onComplete(exchange);
146                        }
147                    } catch (Exception e) {
148                        // must catch exceptions to ensure all synchronizations have a chance to run
149                        LOG.warn("Exception occurred during onCompletion. This exception will be ignored.", e);
150                    }
151                }
152            }
153    
154            // unregister from inflight registry
155            if (exchange.getContext() != null) {
156                exchange.getContext().getInflightRepository().remove(exchange);
157            }
158        }
159    
160        public String getId() {
161            if (id == null) {
162                id = UuidGenerator.get().generateUuid();
163            }
164            return id;
165        }
166    
167        public Message getOriginalInMessage() {
168            return originalInMessage;
169        }
170    
171        public TracedRouteNodes getTracedRouteNodes() {
172            return tracedRouteNodes;
173        }
174    
175        public boolean isTransactedBy(Object transactionDefinition) {
176            return getTransactedBy().contains(transactionDefinition);
177        }
178    
179        public void beginTransactedBy(Object transactionDefinition) {
180            getTransactedBy().add(transactionDefinition);
181        }
182    
183        public void endTransactedBy(Object transactionDefinition) {
184            getTransactedBy().remove(transactionDefinition);
185        }
186    
187        public RouteContext getRouteContext() {
188            if (routeContextStack.isEmpty()) {
189                return null;
190            }
191            return routeContextStack.peek();
192        }
193    
194        public void pushRouteContext(RouteContext routeContext) {
195            routeContextStack.add(routeContext);
196        }
197    
198        public RouteContext popRouteContext() {
199            if (routeContextStack.isEmpty()) {
200                return null;
201            }
202            return routeContextStack.pop();
203        }
204    
205        private Set<Object> getTransactedBy() {
206            if (transactedBy == null) {
207                transactedBy = new LinkedHashSet<Object>();
208            }
209            return transactedBy;
210        }
211    }