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