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