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