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