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