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 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 Message getMessage() {
354        return hasOut() ? getOut() : getIn();
355    }
356
357    public <T> T getMessage(Class<T> type) {
358        return hasOut() ? getOut(type) : getIn(type);
359    }
360
361    public void setMessage(Message message) {
362        if (hasOut()) {
363            setOut(message);
364        } else {
365            setIn(message);
366        }
367    }
368
369
370    public Exception getException() {
371        return exception;
372    }
373
374    public <T> T getException(Class<T> type) {
375        return ObjectHelper.getException(type, exception);
376    }
377
378    public void setException(Throwable t) {
379        if (t == null) {
380            this.exception = null;
381        } else if (t instanceof Exception) {
382            this.exception = (Exception) t;
383        } else {
384            // wrap throwable into an exception
385            this.exception = ObjectHelper.wrapCamelExecutionException(this, t);
386        }
387        if (t instanceof InterruptedException) {
388            // mark the exchange as interrupted due to the interrupt exception
389            setProperty(Exchange.INTERRUPTED, Boolean.TRUE);
390        }
391    }
392
393    public ExchangePattern getPattern() {
394        return pattern;
395    }
396
397    public void setPattern(ExchangePattern pattern) {
398        this.pattern = pattern;
399    }
400
401    public Endpoint getFromEndpoint() {
402        return fromEndpoint;
403    }
404
405    public void setFromEndpoint(Endpoint fromEndpoint) {
406        this.fromEndpoint = fromEndpoint;
407    }
408
409    public String getFromRouteId() {
410        return fromRouteId;
411    }
412
413    public void setFromRouteId(String fromRouteId) {
414        this.fromRouteId = fromRouteId;
415    }
416
417    public String getExchangeId() {
418        if (exchangeId == null) {
419            exchangeId = createExchangeId();
420        }
421        return exchangeId;
422    }
423
424    public void setExchangeId(String id) {
425        this.exchangeId = id;
426    }
427
428    public boolean isFailed() {
429        if (exception != null) {
430            return true;
431        }
432        return hasOut() ? getOut().isFault() : getIn().isFault();
433    }
434
435    public boolean isTransacted() {
436        UnitOfWork uow = getUnitOfWork();
437        if (uow != null) {
438            return uow.isTransacted();
439        } else {
440            return false;
441        }
442    }
443
444    public Boolean isExternalRedelivered() {
445        Boolean answer = null;
446
447        // check property first, as the implementation details to know if the message
448        // was externally redelivered is message specific, and thus the message implementation
449        // could potentially change during routing, and therefore later we may not know if the
450        // original message was externally redelivered or not, therefore we store this detail
451        // as a exchange property to keep it around for the lifecycle of the exchange
452        if (hasProperties()) {
453            answer = getProperty(Exchange.EXTERNAL_REDELIVERED, null, Boolean.class);
454        }
455        
456        if (answer == null) {
457            // lets avoid adding methods to the Message API, so we use the
458            // DefaultMessage to allow component specific messages to extend
459            // and implement the isExternalRedelivered method.
460            Message msg = getIn();
461            if (msg instanceof DefaultMessage) {
462                answer = ((DefaultMessage) msg).isTransactedRedelivered();
463            }
464        }
465
466        return answer;
467    }
468
469    public boolean isRollbackOnly() {
470        return Boolean.TRUE.equals(getProperty(Exchange.ROLLBACK_ONLY)) || Boolean.TRUE.equals(getProperty(Exchange.ROLLBACK_ONLY_LAST));
471    }
472
473    public UnitOfWork getUnitOfWork() {
474        return unitOfWork;
475    }
476
477    public void setUnitOfWork(UnitOfWork unitOfWork) {
478        this.unitOfWork = unitOfWork;
479        if (unitOfWork != null && onCompletions != null) {
480            // now an unit of work has been assigned so add the on completions
481            // we might have registered already
482            for (Synchronization onCompletion : onCompletions) {
483                unitOfWork.addSynchronization(onCompletion);
484            }
485            // cleanup the temporary on completion list as they now have been registered
486            // on the unit of work
487            onCompletions.clear();
488            onCompletions = null;
489        }
490    }
491
492    public void addOnCompletion(Synchronization onCompletion) {
493        if (unitOfWork == null) {
494            // unit of work not yet registered so we store the on completion temporary
495            // until the unit of work is assigned to this exchange by the unit of work
496            if (onCompletions == null) {
497                onCompletions = new ArrayList<>();
498            }
499            onCompletions.add(onCompletion);
500        } else {
501            getUnitOfWork().addSynchronization(onCompletion);
502        }
503    }
504
505    public boolean containsOnCompletion(Synchronization onCompletion) {
506        if (unitOfWork != null) {
507            // if there is an unit of work then the completions is moved there
508            return unitOfWork.containsSynchronization(onCompletion);
509        } else {
510            // check temporary completions if no unit of work yet
511            return onCompletions != null && onCompletions.contains(onCompletion);
512        }
513    }
514
515    public void handoverCompletions(Exchange target) {
516        if (onCompletions != null) {
517            for (Synchronization onCompletion : onCompletions) {
518                target.addOnCompletion(onCompletion);
519            }
520            // cleanup the temporary on completion list as they have been handed over
521            onCompletions.clear();
522            onCompletions = null;
523        } else if (unitOfWork != null) {
524            // let unit of work handover
525            unitOfWork.handoverSynchronization(target);
526        }
527    }
528
529    public List<Synchronization> handoverCompletions() {
530        List<Synchronization> answer = null;
531        if (onCompletions != null) {
532            answer = new ArrayList<>(onCompletions);
533            onCompletions.clear();
534            onCompletions = null;
535        }
536        return answer;
537    }
538
539    /**
540     * Configures the message after it has been set on the exchange
541     */
542    protected void configureMessage(Message message) {
543        if (message instanceof MessageSupport) {
544            MessageSupport messageSupport = (MessageSupport)message;
545            messageSupport.setExchange(this);
546            messageSupport.setCamelContext(getContext());
547        }
548    }
549
550    @SuppressWarnings("deprecation")
551    protected String createExchangeId() {
552        String answer = null;
553        if (in != null) {
554            answer = in.createExchangeId();
555        }
556        if (answer == null) {
557            answer = context.getUuidGenerator().generateUuid();
558        }
559        return answer;
560    }
561
562    protected Map<String, Object> createProperties() {
563        return new HashMap<>();
564    }
565
566    protected Map<String, Object> createProperties(Map<String, Object> properties) {
567        return new HashMap<>(properties);
568    }
569
570    private static boolean isExcludePatternMatch(String key, String... excludePatterns) {
571        for (String pattern : excludePatterns) {
572            if (EndpointHelper.matchPattern(key, pattern)) {
573                return true;
574            }
575        }
576        return false;
577    }
578}