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