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.List;
021import java.util.Map;
022import java.util.concurrent.ConcurrentHashMap;
023
024import org.apache.camel.CamelContext;
025import org.apache.camel.Endpoint;
026import org.apache.camel.Exchange;
027import org.apache.camel.ExchangePattern;
028import org.apache.camel.Message;
029import org.apache.camel.MessageHistory;
030import org.apache.camel.spi.Synchronization;
031import org.apache.camel.spi.UnitOfWork;
032import org.apache.camel.util.ExchangeHelper;
033import org.apache.camel.util.ObjectHelper;
034
035/**
036 * A default implementation of {@link Exchange}
037 *
038 * @version 
039 */
040public final class DefaultExchange implements Exchange {
041
042    protected final CamelContext context;
043    private Map<String, Object> properties;
044    private Message in;
045    private Message out;
046    private Exception exception;
047    private String exchangeId;
048    private UnitOfWork unitOfWork;
049    private ExchangePattern pattern;
050    private Endpoint fromEndpoint;
051    private String fromRouteId;
052    private List<Synchronization> onCompletions;
053
054    public DefaultExchange(CamelContext context) {
055        this(context, ExchangePattern.InOnly);
056    }
057
058    public DefaultExchange(CamelContext context, ExchangePattern pattern) {
059        this.context = context;
060        this.pattern = pattern;
061    }
062
063    public DefaultExchange(Exchange parent) {
064        this(parent.getContext(), parent.getPattern());
065        this.fromEndpoint = parent.getFromEndpoint();
066        this.fromRouteId = parent.getFromRouteId();
067        this.unitOfWork = parent.getUnitOfWork();
068    }
069
070    public DefaultExchange(Endpoint fromEndpoint) {
071        this(fromEndpoint, ExchangePattern.InOnly);
072    }
073
074    public DefaultExchange(Endpoint fromEndpoint, ExchangePattern pattern) {
075        this(fromEndpoint.getCamelContext(), pattern);
076        this.fromEndpoint = fromEndpoint;
077    }
078
079    @Override
080    public String toString() {
081        return "Exchange[" + (out == null ? in : out) + "]";
082    }
083
084    public Exchange copy() {
085        DefaultExchange exchange = new DefaultExchange(this);
086
087        if (hasProperties()) {
088            exchange.setProperties(safeCopy(getProperties()));
089        }
090        
091        exchange.setIn(getIn().copy());
092        if (hasOut()) {
093            exchange.setOut(getOut().copy());
094        }
095        exchange.setException(getException());
096        return exchange;
097    }
098
099    @SuppressWarnings("unchecked")
100    private static Map<String, Object> safeCopy(Map<String, Object> properties) {
101        if (properties == null) {
102            return null;
103        }
104
105        Map<String, Object> answer = new ConcurrentHashMap<String, Object>(properties);
106
107        // safe copy message history using a defensive copy
108        List<MessageHistory> history = (List<MessageHistory>) answer.remove(Exchange.MESSAGE_HISTORY);
109        if (history != null) {
110            answer.put(Exchange.MESSAGE_HISTORY, new ArrayList<MessageHistory>(history));
111        }
112
113        return answer;
114    }
115
116    public CamelContext getContext() {
117        return context;
118    }
119
120    public Object getProperty(String name) {
121        if (properties != null) {
122            return properties.get(name);
123        }
124        return null;
125    }
126
127    public Object getProperty(String name, Object defaultValue) {
128        Object answer = getProperty(name);
129        return answer != null ? answer : defaultValue;
130    }
131
132    @SuppressWarnings("unchecked")
133    public <T> T getProperty(String name, Class<T> type) {
134        Object value = getProperty(name);
135        if (value == null) {
136            // lets avoid NullPointerException when converting to boolean for null values
137            if (boolean.class.isAssignableFrom(type)) {
138                return (T) Boolean.FALSE;
139            }
140            return null;
141        }
142
143        // eager same instance type test to avoid the overhead of invoking the type converter
144        // if already same type
145        if (type.isInstance(value)) {
146            return type.cast(value);
147        }
148
149        return ExchangeHelper.convertToType(this, type, value);
150    }
151
152    @SuppressWarnings("unchecked")
153    public <T> T getProperty(String name, Object defaultValue, Class<T> type) {
154        Object value = getProperty(name, defaultValue);
155        if (value == null) {
156            // lets avoid NullPointerException when converting to boolean for null values
157            if (boolean.class.isAssignableFrom(type)) {
158                return (T) Boolean.FALSE;
159            }
160            return null;
161        }
162
163        // eager same instance type test to avoid the overhead of invoking the type converter
164        // if already same type
165        if (type.isInstance(value)) {
166            return type.cast(value);
167        }
168
169        return ExchangeHelper.convertToType(this, type, value);
170    }
171
172    public void setProperty(String name, Object value) {
173        if (value != null) {
174            // avoid the NullPointException
175            getProperties().put(name, value);
176        } else {
177            // if the value is null, we just remove the key from the map
178            if (name != null) {
179                getProperties().remove(name);
180            }
181        }
182    }
183
184    public Object removeProperty(String name) {
185        if (!hasProperties()) {
186            return null;
187        }
188        return getProperties().remove(name);
189    }
190
191    public Map<String, Object> getProperties() {
192        if (properties == null) {
193            properties = new ConcurrentHashMap<String, Object>();
194        }
195        return properties;
196    }
197
198    public boolean hasProperties() {
199        return properties != null && !properties.isEmpty();
200    }
201
202    public void setProperties(Map<String, Object> properties) {
203        this.properties = properties;
204    }
205
206    public Message getIn() {
207        if (in == null) {
208            in = new DefaultMessage();
209            configureMessage(in);
210        }
211        return in;
212    }
213
214    public <T> T getIn(Class<T> type) {
215        Message in = getIn();
216
217        // eager same instance type test to avoid the overhead of invoking the type converter
218        // if already same type
219        if (type.isInstance(in)) {
220            return type.cast(in);
221        }
222
223        // fallback to use type converter
224        return context.getTypeConverter().convertTo(type, this, in);
225    }
226
227    public void setIn(Message in) {
228        this.in = in;
229        configureMessage(in);
230    }
231
232    public Message getOut() {
233        // lazy create
234        if (out == null) {
235            out = (in != null && in instanceof MessageSupport)
236                ? ((MessageSupport)in).newInstance() : new DefaultMessage();
237            configureMessage(out);
238        }
239        return out;
240    }
241
242    public <T> T getOut(Class<T> type) {
243        if (!hasOut()) {
244            return null;
245        }
246
247        Message out = getOut();
248
249        // eager same instance type test to avoid the overhead of invoking the type converter
250        // if already same type
251        if (type.isInstance(out)) {
252            return type.cast(out);
253        }
254
255        // fallback to use type converter
256        return context.getTypeConverter().convertTo(type, this, out);
257    }
258
259    public boolean hasOut() {
260        return out != null;
261    }
262
263    public void setOut(Message out) {
264        this.out = out;
265        configureMessage(out);
266    }
267
268    public Exception getException() {
269        return exception;
270    }
271
272    public <T> T getException(Class<T> type) {
273        return ObjectHelper.getException(type, exception);
274    }
275
276    public void setException(Throwable t) {
277        if (t == null) {
278            this.exception = null;
279        } else if (t instanceof Exception) {
280            this.exception = (Exception) t;
281        } else {
282            // wrap throwable into an exception
283            this.exception = ObjectHelper.wrapCamelExecutionException(this, t);
284        }
285    }
286
287    public ExchangePattern getPattern() {
288        return pattern;
289    }
290
291    public void setPattern(ExchangePattern pattern) {
292        this.pattern = pattern;
293    }
294
295    public Endpoint getFromEndpoint() {
296        return fromEndpoint;
297    }
298
299    public void setFromEndpoint(Endpoint fromEndpoint) {
300        this.fromEndpoint = fromEndpoint;
301    }
302
303    public String getFromRouteId() {
304        return fromRouteId;
305    }
306
307    public void setFromRouteId(String fromRouteId) {
308        this.fromRouteId = fromRouteId;
309    }
310
311    public String getExchangeId() {
312        if (exchangeId == null) {
313            exchangeId = createExchangeId();
314        }
315        return exchangeId;
316    }
317
318    public void setExchangeId(String id) {
319        this.exchangeId = id;
320    }
321
322    public boolean isFailed() {
323        return (hasOut() && getOut().isFault()) || getException() != null;
324    }
325
326    public boolean isTransacted() {
327        UnitOfWork uow = getUnitOfWork();
328        if (uow != null) {
329            return uow.isTransacted();
330        } else {
331            return false;
332        }
333    }
334
335    public Boolean isExternalRedelivered() {
336        Boolean answer = null;
337
338        // check property first, as the implementation details to know if the message
339        // was externally redelivered is message specific, and thus the message implementation
340        // could potentially change during routing, and therefore later we may not know if the
341        // original message was externally redelivered or not, therefore we store this detail
342        // as a exchange property to keep it around for the lifecycle of the exchange
343        if (hasProperties()) {
344            answer = getProperty(Exchange.EXTERNAL_REDELIVERED, null, Boolean.class);
345        }
346        
347        if (answer == null) {
348            // lets avoid adding methods to the Message API, so we use the
349            // DefaultMessage to allow component specific messages to extend
350            // and implement the isExternalRedelivered method.
351            DefaultMessage msg = getIn(DefaultMessage.class);
352            if (msg != null) {
353                answer = msg.isTransactedRedelivered();
354                // store as property to keep around
355                setProperty(Exchange.EXTERNAL_REDELIVERED, answer);
356            }
357        }
358
359        return answer;
360    }
361
362    public boolean isRollbackOnly() {
363        return Boolean.TRUE.equals(getProperty(Exchange.ROLLBACK_ONLY)) || Boolean.TRUE.equals(getProperty(Exchange.ROLLBACK_ONLY_LAST));
364    }
365
366    public UnitOfWork getUnitOfWork() {
367        return unitOfWork;
368    }
369
370    public void setUnitOfWork(UnitOfWork unitOfWork) {
371        this.unitOfWork = unitOfWork;
372        if (unitOfWork != null && onCompletions != null) {
373            // now an unit of work has been assigned so add the on completions
374            // we might have registered already
375            for (Synchronization onCompletion : onCompletions) {
376                unitOfWork.addSynchronization(onCompletion);
377            }
378            // cleanup the temporary on completion list as they now have been registered
379            // on the unit of work
380            onCompletions.clear();
381            onCompletions = null;
382        }
383    }
384
385    public void addOnCompletion(Synchronization onCompletion) {
386        if (unitOfWork == null) {
387            // unit of work not yet registered so we store the on completion temporary
388            // until the unit of work is assigned to this exchange by the unit of work
389            if (onCompletions == null) {
390                onCompletions = new ArrayList<Synchronization>();
391            }
392            onCompletions.add(onCompletion);
393        } else {
394            getUnitOfWork().addSynchronization(onCompletion);
395        }
396    }
397
398    public boolean containsOnCompletion(Synchronization onCompletion) {
399        if (unitOfWork != null) {
400            // if there is an unit of work then the completions is moved there
401            return unitOfWork.containsSynchronization(onCompletion);
402        } else {
403            // check temporary completions if no unit of work yet
404            return onCompletions != null && onCompletions.contains(onCompletion);
405        }
406    }
407
408    public void handoverCompletions(Exchange target) {
409        if (onCompletions != null) {
410            for (Synchronization onCompletion : onCompletions) {
411                target.addOnCompletion(onCompletion);
412            }
413            // cleanup the temporary on completion list as they have been handed over
414            onCompletions.clear();
415            onCompletions = null;
416        } else if (unitOfWork != null) {
417            // let unit of work handover
418            unitOfWork.handoverSynchronization(target);
419        }
420    }
421
422    public List<Synchronization> handoverCompletions() {
423        List<Synchronization> answer = null;
424        if (onCompletions != null) {
425            answer = new ArrayList<Synchronization>(onCompletions);
426            onCompletions.clear();
427            onCompletions = null;
428        }
429        return answer;
430    }
431
432    /**
433     * Configures the message after it has been set on the exchange
434     */
435    protected void configureMessage(Message message) {
436        if (message instanceof MessageSupport) {
437            MessageSupport messageSupport = (MessageSupport)message;
438            messageSupport.setExchange(this);
439        }
440    }
441
442    @SuppressWarnings("deprecation")
443    protected String createExchangeId() {
444        String answer = null;
445        if (in != null) {
446            answer = in.createExchangeId();
447        }
448        if (answer == null) {
449            answer = context.getUuidGenerator().generateUuid();
450        }
451        return answer;
452    }
453}