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     */
017    package org.apache.camel.impl;
018    
019    import java.io.Serializable;
020    import java.util.Collection;
021    import java.util.LinkedHashMap;
022    import java.util.Map;
023    
024    import org.apache.camel.Exchange;
025    import org.apache.camel.util.ObjectHelper;
026    import org.slf4j.Logger;
027    import org.slf4j.LoggerFactory;
028    
029    /**
030     * Holder object for sending an exchange over a remote wire as a serialized object.
031     * This is usually configured using the <tt>transferExchange=true</tt> option on the endpoint.
032     * <p/>
033     * As opposed to normal usage where only the body part of the exchange is transferred over the wire,
034     * this holder object serializes the following fields over the wire:
035     * <ul>
036     * <li>exchangeId</li>
037     * <li>in body</li>
038     * <li>out body</li>
039     * <li>in headers</li>
040     * <li>out headers</li>
041     * <li>fault body </li>
042     * <li>fault headers</li>
043     * <li>exchange properties</li>
044     * <li>exception</li>
045     * </ul>
046     * Any object that is not serializable will be skipped and Camel will log this at WARN level.
047     *
048     * @version 
049     */
050    public class DefaultExchangeHolder implements Serializable {
051    
052        private static final long serialVersionUID = 2L;
053        private static final transient Logger LOG = LoggerFactory.getLogger(DefaultExchangeHolder.class);
054    
055        private String exchangeId;
056        private Object inBody;
057        private Object outBody;
058        private Boolean outFaultFlag = Boolean.FALSE;
059        private Map<String, Object> inHeaders;
060        private Map<String, Object> outHeaders;
061        private Map<String, Object> properties;
062        private Exception exception;
063    
064        /**
065         * Creates a payload object with the information from the given exchange.
066         *
067         * @param exchange the exchange
068         * @return the holder object with information copied form the exchange
069         */
070        public static DefaultExchangeHolder marshal(Exchange exchange) {
071            return marshal(exchange, true);
072        }
073    
074        /**
075         * Creates a payload object with the information from the given exchange.
076         *
077         * @param exchange the exchange
078         * @param includeProperties whether or not to include exchange properties
079         * @return the holder object with information copied form the exchange
080         */
081        public static DefaultExchangeHolder marshal(Exchange exchange, boolean includeProperties) {
082            DefaultExchangeHolder payload = new DefaultExchangeHolder();
083    
084            payload.exchangeId = exchange.getExchangeId();
085            payload.inBody = checkSerializableBody("in body", exchange, exchange.getIn().getBody());
086            payload.safeSetInHeaders(exchange);
087            if (exchange.hasOut()) {
088                payload.outBody = checkSerializableBody("out body", exchange, exchange.getOut().getBody());
089                payload.outFaultFlag = exchange.getOut().isFault();
090                payload.safeSetOutHeaders(exchange);
091            }
092            if (includeProperties) {
093                payload.safeSetProperties(exchange);
094            }
095            payload.exception = exchange.getException();
096    
097            return payload;
098        }
099    
100        /**
101         * Transfers the information from the payload to the exchange.
102         *
103         * @param exchange the exchange to set values from the payload
104         * @param payload  the payload with the values
105         */
106        public static void unmarshal(Exchange exchange, DefaultExchangeHolder payload) {
107            exchange.setExchangeId(payload.exchangeId);
108            exchange.getIn().setBody(payload.inBody);
109            if (payload.inHeaders != null) {
110                exchange.getIn().setHeaders(payload.inHeaders);
111            }
112            if (payload.outBody != null) {
113                exchange.getOut().setBody(payload.outBody);
114                if (payload.outHeaders != null) {
115                    exchange.getOut().setHeaders(payload.outHeaders);
116                }
117                exchange.getOut().setFault(payload.outFaultFlag.booleanValue());
118            }
119            if (payload.properties != null) {
120                for (String key : payload.properties.keySet()) {
121                    exchange.setProperty(key, payload.properties.get(key));
122                }
123            }
124            exchange.setException(payload.exception);
125        }
126    
127        /**
128         * Adds a property to the payload.
129         * <p/>
130         * This can be done in special situations where additional information must be added which was not provided
131         * from the source.
132         *
133         * @param payload the serialized payload
134         * @param key the property key to add
135         * @param property the property value to add
136         */
137        public static void addProperty(DefaultExchangeHolder payload, String key, Serializable property) {
138            if (key == null || property == null) {
139                return;
140            }
141            if (payload.properties == null) {
142                payload.properties = new LinkedHashMap<String, Object>();
143            }
144            payload.properties.put(key, property);
145        }
146    
147        public String toString() {
148            StringBuilder sb = new StringBuilder("DefaultExchangeHolder[exchangeId=").append(exchangeId);
149            sb.append("inBody=").append(inBody).append(", outBody=").append(outBody);
150            sb.append(", inHeaders=").append(inHeaders).append(", outHeaders=").append(outHeaders);
151            sb.append(", properties=").append(properties).append(", exception=").append(exception);
152            return sb.append(']').toString();
153        }
154    
155        private Map<String, Object> safeSetInHeaders(Exchange exchange) {
156            if (exchange.getIn().hasHeaders()) {
157                Map<String, Object> map = checkMapSerializableObjects("in headers", exchange, exchange.getIn().getHeaders());
158                if (map != null && !map.isEmpty()) {
159                    inHeaders = new LinkedHashMap<String, Object>(map);
160                }
161            }
162            return null;
163        }
164    
165        private Map<String, Object> safeSetOutHeaders(Exchange exchange) {
166            if (exchange.hasOut() && exchange.getOut().hasHeaders()) {
167                Map<String, Object> map = checkMapSerializableObjects("out headers", exchange, exchange.getOut().getHeaders());
168                if (map != null && !map.isEmpty()) {
169                    outHeaders = new LinkedHashMap<String, Object>(map);
170                }
171            }
172            return null;
173        }
174    
175        private Map<String, Object> safeSetProperties(Exchange exchange) {
176            if (exchange.hasProperties()) {
177                Map<String, Object> map = checkMapSerializableObjects("properties", exchange, exchange.getProperties());
178                if (map != null && !map.isEmpty()) {
179                    properties = new LinkedHashMap<String, Object>(map);
180                }
181            }
182            return null;
183        }
184    
185        private static Object checkSerializableBody(String type, Exchange exchange, Object object) {
186            if (object == null) {
187                return null;
188            }
189    
190            Serializable converted = exchange.getContext().getTypeConverter().convertTo(Serializable.class, exchange, object);
191            if (converted != null) {
192                return converted;
193            } else {
194                LOG.warn("Exchange " + type + " containing object: " + object + " of type: " + object.getClass().getCanonicalName() + " cannot be serialized, it will be excluded by the holder.");
195                return null;
196            }
197        }
198    
199        private static Map<String, Object> checkMapSerializableObjects(String type, Exchange exchange, Map<String, Object> map) {
200            if (map == null) {
201                return null;
202            }
203    
204            Map<String, Object> result = new LinkedHashMap<String, Object>();
205            for (Map.Entry<String, Object> entry : map.entrySet()) {
206    
207                // silently skip any values which is null
208                if (entry.getValue() != null) {
209                    Serializable converted = exchange.getContext().getTypeConverter().convertTo(Serializable.class, exchange, entry.getValue());
210    
211                    // if the converter is a map/collection we need to check its content as well
212                    if (converted instanceof Collection) {
213                        Collection<?> valueCol = (Collection<?>) converted;
214                        if (!collectionContainsAllSerializableObjects(valueCol, exchange)) {
215                            logCannotSerializeObject(type, entry.getKey(), entry.getValue());
216                            continue;
217                        }
218                    } else if (converted instanceof Map) {
219                        Map<?, ?> valueMap = (Map<?, ?>) converted;
220                        if (!mapContainsAllSerializableObjects(valueMap, exchange)) {
221                            logCannotSerializeObject(type, entry.getKey(), entry.getValue());
222                            continue;
223                        }
224                    }
225    
226                    if (converted != null) {
227                        result.put(entry.getKey(), converted);
228                    } else {
229                        logCannotSerializeObject(type, entry.getKey(), entry.getValue());
230                    }
231                }
232            }
233    
234            return result;
235        }
236    
237        private static void logCannotSerializeObject(String type, String key, Object value) {
238            if (key.startsWith("Camel")) {
239                // log Camel at DEBUG level
240                if (LOG.isDebugEnabled()) {
241                    LOG.debug("Exchange {} containing key: {} with object: {} of type: {} cannot be serialized, it will be excluded by the holder."
242                              , new Object[]{type, key, value, ObjectHelper.classCanonicalName(value)});
243                }
244            } else {
245                // log regular at WARN level
246                LOG.warn("Exchange {} containing key: {} with object: {} of type: {} cannot be serialized, it will be excluded by the holder."
247                         , new Object[]{type, key, value, ObjectHelper.classCanonicalName(value)});
248            }
249        }
250    
251        private static boolean collectionContainsAllSerializableObjects(Collection<?> col, Exchange exchange) {
252            for (Object value : col) {
253                if (value != null) {
254                    Serializable converted = exchange.getContext().getTypeConverter().convertTo(Serializable.class, exchange, value);
255                    if (converted == null) {
256                        return false;
257                    }
258                }
259            }
260            return true;
261        }
262    
263        private static boolean mapContainsAllSerializableObjects(Map<?, ?> map, Exchange exchange) {
264            for (Object value : map.values()) {
265                if (value != null) {
266                    Serializable converted = exchange.getContext().getTypeConverter().convertTo(Serializable.class, exchange, value);
267                    if (converted == null) {
268                        return false;
269                    }
270                }
271            }
272            return true;
273        }
274    
275    }