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.math.BigDecimal; 022import java.math.BigInteger; 023import java.util.Date; 024import java.util.LinkedHashMap; 025import java.util.Map; 026 027import org.apache.camel.Exchange; 028import org.apache.camel.RuntimeExchangeException; 029import org.apache.camel.WrappedFile; 030import org.apache.camel.util.ObjectHelper; 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034/** 035 * Holder object for sending an exchange over a remote wire as a serialized object. 036 * This is usually configured using the <tt>transferExchange=true</tt> option on the endpoint. 037 * <br/> 038 * <b>Note:</b> Message body of type {@link File} or {@link WrappedFile} is <b>not</b> supported and 039 * a {@link RuntimeExchangeException} is thrown. 040 * <br/> 041 * As opposed to normal usage where only the body part of the exchange is transferred over the wire, 042 * this holder object serializes the following fields over the wire: 043 * <ul> 044 * <li>exchangeId</li> 045 * <li>in body</li> 046 * <li>out body</li> 047 * <li>fault body </li> 048 * <li>exception</li> 049 * </ul> 050 * <br/> 051 * The exchange properties are not propagated by default. However you can specify they should be included 052 * by the {@link DefaultExchangeHolder#marshal(Exchange, boolean)} method. 053 * <br/> 054 * And the following headers is transferred if their values are of primitive types, String or Number based. 055 * <ul> 056 * <li>in headers</li> 057 * <li>out headers</li> 058 * <li>fault headers</li> 059 * </ul> 060 * The body is serialized and stored as serialized bytes. The header and exchange properties only include 061 * primitive, String, and Number types (and Exception types for exchange properties). Any other type is skipped. 062 * Any message body object that is not serializable will be skipped and Camel will log this at <tt>WARN</tt> level. 063 * And any message header values that is not a primitive value will be skipped and Camel will log this at <tt>DEBUG</tt> level. 064 * 065 * @version 066 */ 067public class DefaultExchangeHolder implements Serializable { 068 069 private static final long serialVersionUID = 2L; 070 private static final Logger LOG = LoggerFactory.getLogger(DefaultExchangeHolder.class); 071 072 private String exchangeId; 073 private Object inBody; 074 private Object outBody; 075 private Boolean inFaultFlag = Boolean.FALSE; 076 private Boolean outFaultFlag = Boolean.FALSE; 077 private Map<String, Object> inHeaders; 078 private Map<String, Object> outHeaders; 079 private Map<String, Object> properties; 080 private Exception exception; 081 082 /** 083 * Creates a payload object with the information from the given exchange. 084 * 085 * @param exchange the exchange, must <b>not</b> be <tt>null</tt> 086 * @return the holder object with information copied form the exchange 087 */ 088 public static DefaultExchangeHolder marshal(Exchange exchange) { 089 return marshal(exchange, true, false); 090 } 091 092 /** 093 * Creates a payload object with the information from the given exchange. 094 * 095 * @param exchange the exchange, must <b>not</b> be <tt>null</tt> 096 * @param includeProperties whether or not to include exchange properties 097 * @return the holder object with information copied form the exchange 098 */ 099 public static DefaultExchangeHolder marshal(Exchange exchange, boolean includeProperties) { 100 ObjectHelper.notNull(exchange, "exchange"); 101 102 // we do not support files 103 Object body = exchange.getIn().getBody(); 104 if (body instanceof WrappedFile || body instanceof File) { 105 throw new RuntimeExchangeException("Message body of type " + body.getClass().getCanonicalName() + " is not supported by this marshaller.", exchange); 106 } 107 108 DefaultExchangeHolder payload = new DefaultExchangeHolder(); 109 110 payload.exchangeId = exchange.getExchangeId(); 111 payload.inBody = checkSerializableBody("in body", exchange, exchange.getIn().getBody()); 112 payload.safeSetInHeaders(exchange, false); 113 if (exchange.hasOut()) { 114 payload.outBody = checkSerializableBody("out body", exchange, exchange.getOut().getBody()); 115 payload.outFaultFlag = exchange.getOut().isFault(); 116 payload.safeSetOutHeaders(exchange, false); 117 } else { 118 payload.inFaultFlag = exchange.getIn().isFault(); 119 } 120 if (includeProperties) { 121 payload.safeSetProperties(exchange, false); 122 } 123 payload.exception = exchange.getException(); 124 125 return payload; 126 } 127 128 /** 129 * Creates a payload object with the information from the given exchange. 130 * 131 * @param exchange the exchange, must <b>not</b> be <tt>null</tt> 132 * @param includeProperties whether or not to include exchange properties 133 * @param allowSerializedHeaders whether or not to include serialized headers 134 * @return the holder object with information copied form the exchange 135 */ 136 public static DefaultExchangeHolder marshal(Exchange exchange, boolean includeProperties, boolean allowSerializedHeaders) { 137 ObjectHelper.notNull(exchange, "exchange"); 138 139 // we do not support files 140 Object body = exchange.getIn().getBody(); 141 if (body instanceof WrappedFile || body instanceof File) { 142 throw new RuntimeExchangeException("Message body of type " + body.getClass().getCanonicalName() + " is not supported by this marshaller.", exchange); 143 } 144 145 DefaultExchangeHolder payload = new DefaultExchangeHolder(); 146 147 payload.exchangeId = exchange.getExchangeId(); 148 payload.inBody = checkSerializableBody("in body", exchange, exchange.getIn().getBody()); 149 payload.safeSetInHeaders(exchange, allowSerializedHeaders); 150 if (exchange.hasOut()) { 151 payload.outBody = checkSerializableBody("out body", exchange, exchange.getOut().getBody()); 152 payload.outFaultFlag = exchange.getOut().isFault(); 153 payload.safeSetOutHeaders(exchange, allowSerializedHeaders); 154 } else { 155 payload.inFaultFlag = exchange.getIn().isFault(); 156 } 157 if (includeProperties) { 158 payload.safeSetProperties(exchange, allowSerializedHeaders); 159 } 160 payload.exception = exchange.getException(); 161 162 return payload; 163 } 164 165 /** 166 * Transfers the information from the payload to the exchange. 167 * 168 * @param exchange the exchange to set values from the payload, must <b>not</b> be <tt>null</tt> 169 * @param payload the payload with the values, must <b>not</b> be <tt>null</tt> 170 */ 171 public static void unmarshal(Exchange exchange, DefaultExchangeHolder payload) { 172 ObjectHelper.notNull(exchange, "exchange"); 173 ObjectHelper.notNull(payload, "payload"); 174 175 exchange.setExchangeId(payload.exchangeId); 176 exchange.getIn().setBody(payload.inBody); 177 if (payload.inHeaders != null) { 178 exchange.getIn().setHeaders(payload.inHeaders); 179 } 180 if (payload.inFaultFlag != null) { 181 exchange.getIn().setFault(payload.inFaultFlag); 182 } 183 if (payload.outBody != null) { 184 exchange.getOut().setBody(payload.outBody); 185 if (payload.outHeaders != null) { 186 exchange.getOut().setHeaders(payload.outHeaders); 187 } 188 if (payload.outFaultFlag != null) { 189 exchange.getOut().setFault(payload.outFaultFlag); 190 } 191 } 192 if (payload.properties != null) { 193 for (String key : payload.properties.keySet()) { 194 exchange.setProperty(key, payload.properties.get(key)); 195 } 196 } 197 exchange.setException(payload.exception); 198 } 199 200 /** 201 * Adds a property to the payload. 202 * <p/> 203 * This can be done in special situations where additional information must be added which was not provided 204 * from the source. 205 * 206 * @param payload the serialized payload 207 * @param key the property key to add 208 * @param property the property value to add 209 */ 210 public static void addProperty(DefaultExchangeHolder payload, String key, Serializable property) { 211 if (key == null || property == null) { 212 return; 213 } 214 if (payload.properties == null) { 215 payload.properties = new LinkedHashMap<>(); 216 } 217 payload.properties.put(key, property); 218 } 219 220 public String toString() { 221 StringBuilder sb = new StringBuilder("DefaultExchangeHolder[exchangeId=").append(exchangeId); 222 sb.append("inBody=").append(inBody).append(", outBody=").append(outBody); 223 sb.append(", inHeaders=").append(inHeaders).append(", outHeaders=").append(outHeaders); 224 sb.append(", properties=").append(properties).append(", exception=").append(exception); 225 return sb.append(']').toString(); 226 } 227 228 private Map<String, Object> safeSetInHeaders(Exchange exchange, boolean allowSerializedHeaders) { 229 if (exchange.getIn().hasHeaders()) { 230 Map<String, Object> map = checkValidHeaderObjects("in headers", exchange, exchange.getIn().getHeaders(), allowSerializedHeaders); 231 if (map != null && !map.isEmpty()) { 232 inHeaders = new LinkedHashMap<>(map); 233 } 234 } 235 return null; 236 } 237 238 private Map<String, Object> safeSetOutHeaders(Exchange exchange, boolean allowSerializedHeaders) { 239 if (exchange.hasOut() && exchange.getOut().hasHeaders()) { 240 Map<String, Object> map = checkValidHeaderObjects("out headers", exchange, exchange.getOut().getHeaders(), allowSerializedHeaders); 241 if (map != null && !map.isEmpty()) { 242 outHeaders = new LinkedHashMap<>(map); 243 } 244 } 245 return null; 246 } 247 248 private Map<String, Object> safeSetProperties(Exchange exchange, boolean allowSerializedHeaders) { 249 if (exchange.hasProperties()) { 250 Map<String, Object> map = checkValidExchangePropertyObjects("properties", exchange, exchange.getProperties(), allowSerializedHeaders); 251 if (map != null && !map.isEmpty()) { 252 properties = new LinkedHashMap<>(map); 253 } 254 } 255 return null; 256 } 257 258 private static Object checkSerializableBody(String type, Exchange exchange, Object object) { 259 if (object == null) { 260 return null; 261 } 262 263 Serializable converted = exchange.getContext().getTypeConverter().convertTo(Serializable.class, exchange, object); 264 if (converted != null) { 265 return converted; 266 } else { 267 LOG.warn("Exchange {} containing object: {} of type: {} cannot be serialized, it will be excluded by the holder.", type, object, object.getClass().getCanonicalName()); 268 return null; 269 } 270 } 271 272 private static Map<String, Object> checkValidHeaderObjects(String type, Exchange exchange, Map<String, Object> map, boolean allowSerializedHeaders) { 273 if (map == null) { 274 return null; 275 } 276 277 Map<String, Object> result = new LinkedHashMap<>(); 278 for (Map.Entry<String, Object> entry : map.entrySet()) { 279 280 // silently skip any values which is null 281 if (entry.getValue() == null) { 282 continue; 283 } 284 285 Object value = getValidHeaderValue(entry.getKey(), entry.getValue(), allowSerializedHeaders); 286 if (value != null) { 287 Serializable converted = exchange.getContext().getTypeConverter().convertTo(Serializable.class, exchange, value); 288 if (converted != null) { 289 result.put(entry.getKey(), converted); 290 } else { 291 logCannotSerializeObject(type, entry.getKey(), entry.getValue()); 292 } 293 } else { 294 logInvalidHeaderValue(type, entry.getKey(), entry.getValue()); 295 } 296 } 297 298 return result; 299 } 300 301 private static Map<String, Object> checkValidExchangePropertyObjects(String type, Exchange exchange, Map<String, Object> map, boolean allowSerializedHeaders) { 302 if (map == null) { 303 return null; 304 } 305 306 Map<String, Object> result = new LinkedHashMap<>(); 307 for (Map.Entry<String, Object> entry : map.entrySet()) { 308 309 // silently skip any values which is null 310 if (entry.getValue() == null) { 311 continue; 312 } 313 314 Object value = getValidExchangePropertyValue(entry.getKey(), entry.getValue(), allowSerializedHeaders); 315 if (value != null) { 316 Serializable converted = exchange.getContext().getTypeConverter().convertTo(Serializable.class, exchange, value); 317 if (converted != null) { 318 result.put(entry.getKey(), converted); 319 } else { 320 logCannotSerializeObject(type, entry.getKey(), entry.getValue()); 321 } 322 } else { 323 logInvalidExchangePropertyValue(type, entry.getKey(), entry.getValue()); 324 } 325 } 326 327 return result; 328 } 329 330 /** 331 * We only want to store header values of primitive and String related types. 332 * <p/> 333 * This default implementation will allow: 334 * <ul> 335 * <li>any primitives and their counter Objects (Integer, Double etc.)</li> 336 * <li>String and any other literals, Character, CharSequence</li> 337 * <li>Boolean</li> 338 * <li>Number</li> 339 * <li>java.util.Date</li> 340 * </ul> 341 * 342 * We make possible store serialized headers by the boolean field allowSerializedHeaders 343 * 344 * @param headerName the header name 345 * @param headerValue the header value 346 * @param allowSerializedHeaders the header value 347 * @return the value to use, <tt>null</tt> to ignore this header 348 */ 349 protected static Object getValidHeaderValue(String headerName, Object headerValue, boolean allowSerializedHeaders) { 350 if (headerValue instanceof String) { 351 return headerValue; 352 } else if (headerValue instanceof BigInteger) { 353 return headerValue; 354 } else if (headerValue instanceof BigDecimal) { 355 return headerValue; 356 } else if (headerValue instanceof Number) { 357 return headerValue; 358 } else if (headerValue instanceof Character) { 359 return headerValue; 360 } else if (headerValue instanceof CharSequence) { 361 return headerValue.toString(); 362 } else if (headerValue instanceof Boolean) { 363 return headerValue; 364 } else if (headerValue instanceof Date) { 365 return headerValue; 366 } else if (allowSerializedHeaders) { 367 if (headerValue instanceof Serializable) { 368 return headerValue; 369 } 370 } 371 return null; 372 } 373 374 /** 375 * We only want to store exchange property values of primitive and String related types, and 376 * as well any caught exception that Camel routing engine has caught. 377 * <p/> 378 * This default implementation will allow the same values as {@link #getValidHeaderValue(String, Object, boolean)} 379 * and in addition any value of type {@link Throwable}. 380 * 381 * @param propertyName the property name 382 * @param propertyValue the property value 383 * @return the value to use, <tt>null</tt> to ignore this header 384 */ 385 protected static Object getValidExchangePropertyValue(String propertyName, Object propertyValue, boolean allowSerializedHeaders) { 386 // for exchange properties we also allow exception to be transferred so people can store caught exception 387 if (propertyValue instanceof Throwable) { 388 return propertyValue; 389 } 390 return getValidHeaderValue(propertyName, propertyValue, allowSerializedHeaders); 391 } 392 393 private static void logCannotSerializeObject(String type, String key, Object value) { 394 if (key.startsWith("Camel")) { 395 // log Camel at DEBUG level 396 if (LOG.isDebugEnabled()) { 397 LOG.debug("Exchange {} containing key: {} with object: {} of type: {} cannot be serialized, it will be excluded by the holder.", 398 new Object[]{type, key, value, ObjectHelper.classCanonicalName(value)}); 399 } 400 } else { 401 // log regular at WARN level 402 LOG.warn("Exchange {} containing key: {} with object: {} of type: {} cannot be serialized, it will be excluded by the holder.", 403 new Object[]{type, key, value, ObjectHelper.classCanonicalName(value)}); 404 } 405 } 406 407 private static void logInvalidHeaderValue(String type, String key, Object value) { 408 if (LOG.isDebugEnabled()) { 409 LOG.debug("Exchange {} containing key: {} with object: {} of type: {} is not valid header type, it will be excluded by the holder.", 410 new Object[]{type, key, value, ObjectHelper.classCanonicalName(value)}); 411 } 412 } 413 414 private static void logInvalidExchangePropertyValue(String type, String key, Object value) { 415 if (LOG.isDebugEnabled()) { 416 LOG.debug("Exchange {} containing key: {} with object: {} of type: {} is not valid exchange property type, it will be excluded by the holder.", 417 new Object[]{type, key, value, ObjectHelper.classCanonicalName(value)}); 418 } 419 } 420 421}