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