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