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}