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.util; 018 019 import java.io.File; 020 import java.io.InputStream; 021 import java.io.OutputStream; 022 import java.io.Reader; 023 import java.io.Writer; 024 import java.util.Map; 025 import java.util.TreeMap; 026 import javax.xml.transform.stream.StreamSource; 027 028 import org.apache.camel.BytesSource; 029 import org.apache.camel.Exchange; 030 import org.apache.camel.Message; 031 import org.apache.camel.StreamCache; 032 import org.apache.camel.StringSource; 033 import org.apache.camel.WrappedFile; 034 035 /** 036 * Some helper methods when working with {@link org.apache.camel.Message}. 037 * 038 * @version 039 */ 040 public final class MessageHelper { 041 042 /** 043 * Utility classes should not have a public constructor. 044 */ 045 private MessageHelper() { 046 } 047 048 /** 049 * Extracts the given body and returns it as a String, that can be used for 050 * logging etc. 051 * <p/> 052 * Will handle stream based bodies wrapped in StreamCache. 053 * 054 * @param message the message with the body 055 * @return the body as String, can return <tt>null</null> if no body 056 */ 057 public static String extractBodyAsString(Message message) { 058 if (message == null) { 059 return null; 060 } 061 062 StreamCache newBody = message.getBody(StreamCache.class); 063 if (newBody != null) { 064 message.setBody(newBody); 065 } 066 067 Object answer = message.getBody(String.class); 068 if (answer == null) { 069 answer = message.getBody(); 070 } 071 072 if (newBody != null) { 073 // Reset the InputStreamCache 074 newBody.reset(); 075 } 076 077 return answer != null ? answer.toString() : null; 078 } 079 080 /** 081 * Gets the given body class type name as a String. 082 * <p/> 083 * Will skip java.lang. for the build in Java types. 084 * 085 * @param message the message with the body 086 * @return the body typename as String, can return 087 * <tt>null</null> if no body 088 */ 089 public static String getBodyTypeName(Message message) { 090 if (message == null) { 091 return null; 092 } 093 String answer = ObjectHelper.classCanonicalName(message.getBody()); 094 if (answer != null && answer.startsWith("java.lang.")) { 095 return answer.substring(10); 096 } 097 return answer; 098 } 099 100 /** 101 * If the message body contains a {@link StreamCache} instance, reset the 102 * cache to enable reading from it again. 103 * 104 * @param message the message for which to reset the body 105 */ 106 public static void resetStreamCache(Message message) { 107 if (message == null) { 108 return; 109 } 110 if (message.getBody() instanceof StreamCache) { 111 ((StreamCache)message.getBody()).reset(); 112 } 113 } 114 115 /** 116 * Returns the MIME content type on the message or <tt>null</tt> if none 117 * defined 118 */ 119 public static String getContentType(Message message) { 120 return message.getHeader(Exchange.CONTENT_TYPE, String.class); 121 } 122 123 /** 124 * Returns the MIME content encoding on the message or <tt>null</tt> if none 125 * defined 126 */ 127 public static String getContentEncoding(Message message) { 128 return message.getHeader(Exchange.CONTENT_ENCODING, String.class); 129 } 130 131 /** 132 * Extracts the body for logging purpose. 133 * <p/> 134 * Will clip the body if its too big for logging. Will prepend the message 135 * with <tt>Message: </tt> 136 * 137 * @see org.apache.camel.Exchange#LOG_DEBUG_BODY_STREAMS 138 * @see org.apache.camel.Exchange#LOG_DEBUG_BODY_MAX_CHARS 139 * @param message the message 140 * @return the logging message 141 */ 142 public static String extractBodyForLogging(Message message) { 143 return extractBodyForLogging(message, "Message: "); 144 } 145 146 /** 147 * Extracts the body for logging purpose. 148 * <p/> 149 * Will clip the body if its too big for logging. 150 * 151 * @see org.apache.camel.Exchange#LOG_DEBUG_BODY_STREAMS 152 * @see org.apache.camel.Exchange#LOG_DEBUG_BODY_MAX_CHARS 153 * @param message the message 154 * @param prepend a message to prepend 155 * @return the logging message 156 */ 157 public static String extractBodyForLogging(Message message, String prepend) { 158 boolean streams = false; 159 if (message.getExchange() != null) { 160 String property = message.getExchange().getContext().getProperties().get(Exchange.LOG_DEBUG_BODY_STREAMS); 161 if (property != null) { 162 streams = message.getExchange().getContext().getTypeConverter().convertTo(Boolean.class, message.getExchange(), property); 163 } 164 } 165 166 // default to 1000 chars 167 int maxChars = 1000; 168 169 if (message.getExchange() != null) { 170 String property = message.getExchange().getContext().getProperties().get(Exchange.LOG_DEBUG_BODY_MAX_CHARS); 171 if (property != null) { 172 maxChars = message.getExchange().getContext().getTypeConverter().convertTo(Integer.class, property); 173 } 174 } 175 176 return extractBodyForLogging(message, prepend, streams, false, maxChars); 177 } 178 179 /** 180 * Extracts the body for logging purpose. 181 * <p/> 182 * Will clip the body if its too big for logging. 183 * 184 * @see org.apache.camel.Exchange#LOG_DEBUG_BODY_MAX_CHARS 185 * @param message the message 186 * @param prepend a message to prepend 187 * @param allowStreams whether or not streams is allowed 188 * @param allowFiles whether or not files is allowed 189 * @param maxChars limit to maximum number of chars. Use 0 or negative value 190 * to not limit at all. 191 * @return the logging message 192 */ 193 public static String extractBodyForLogging(Message message, String prepend, boolean allowStreams, boolean allowFiles, int maxChars) { 194 Object obj = message.getBody(); 195 if (obj == null) { 196 return prepend + "[Body is null]"; 197 } 198 199 if (!allowStreams) { 200 if (obj instanceof StreamSource && !(obj instanceof StringSource || obj instanceof BytesSource)) { 201 /* 202 * Generally do not log StreamSources but as StringSource and 203 * ByteSoure are memory based they are ok 204 */ 205 return prepend + "[Body is instance of java.xml.transform.StreamSource]"; 206 } else if (obj instanceof StreamCache) { 207 return prepend + "[Body is instance of org.apache.camel.StreamCache]"; 208 } else if (obj instanceof InputStream) { 209 return prepend + "[Body is instance of java.io.InputStream]"; 210 } else if (obj instanceof OutputStream) { 211 return prepend + "[Body is instance of java.io.OutputStream]"; 212 } else if (obj instanceof Reader) { 213 return prepend + "[Body is instance of java.io.Reader]"; 214 } else if (obj instanceof Writer) { 215 return prepend + "[Body is instance of java.io.Writer]"; 216 } else if (obj instanceof WrappedFile || obj instanceof File) { 217 return prepend + "[Body is file based: " + obj + "]"; 218 } 219 } 220 221 // is the body a stream cache 222 StreamCache cache; 223 if (obj instanceof StreamCache) { 224 cache = (StreamCache)obj; 225 } else { 226 cache = null; 227 } 228 229 // grab the message body as a string 230 String body = null; 231 if (message.getExchange() != null) { 232 try { 233 body = message.getExchange().getContext().getTypeConverter().convertTo(String.class, message.getExchange(), obj); 234 } catch (Exception e) { 235 // ignore as the body is for logging purpose 236 } 237 } 238 if (body == null) { 239 body = obj.toString(); 240 } 241 242 // reset stream cache after use 243 if (cache != null) { 244 cache.reset(); 245 } 246 247 if (body == null) { 248 return prepend + "[Body is null]"; 249 } 250 251 // clip body if length enabled and the body is too big 252 if (maxChars > 0 && body.length() > maxChars) { 253 body = body.substring(0, maxChars) + "... [Body clipped after " + maxChars + " chars, total length is " + body.length() + "]"; 254 } 255 256 return prepend + body; 257 } 258 259 /** 260 * Dumps the message as a generic XML structure. 261 * 262 * @param message the message 263 * @return the XML 264 */ 265 public static String dumpAsXml(Message message) { 266 return dumpAsXml(message, true); 267 } 268 269 /** 270 * Dumps the message as a generic XML structure. 271 * 272 * @param message the message 273 * @param includeBody whether or not to include the message body 274 * @return the XML 275 */ 276 public static String dumpAsXml(Message message, boolean includeBody) { 277 StringBuilder sb = new StringBuilder(); 278 // include exchangeId as attribute on the <message> tag 279 sb.append("<message exchangeId=\"").append(message.getExchange().getExchangeId()).append("\">\n"); 280 281 // headers 282 if (message.hasHeaders()) { 283 sb.append("<headers>\n"); 284 // sort the headers so they are listed A..Z 285 Map<String, Object> headers = new TreeMap<String, Object>(message.getHeaders()); 286 for (Map.Entry<String, Object> entry : headers.entrySet()) { 287 Object value = entry.getValue(); 288 String type = ObjectHelper.classCanonicalName(value); 289 sb.append("<header key=\"" + entry.getKey() + "\""); 290 if (type != null) { 291 sb.append(" type=\"" + type + "\""); 292 } 293 sb.append(">"); 294 295 // dump header value as XML, use Camel type converter to convert 296 // to String 297 if (value != null) { 298 try { 299 String xml = message.getExchange().getContext().getTypeConverter().convertTo(String.class, 300 message.getExchange(), value); 301 if (xml != null) { 302 // must always xml encode 303 sb.append(StringHelper.xmlEncode(xml)); 304 } 305 } catch (Exception e) { 306 // ignore as the body is for logging purpose 307 } 308 } 309 310 sb.append("</header>\n"); 311 } 312 sb.append("</headers>\n"); 313 } 314 315 if (includeBody) { 316 sb.append("<body"); 317 String type = ObjectHelper.classCanonicalName(message.getBody()); 318 if (type != null) { 319 sb.append(" type=\"" + type + "\""); 320 } 321 sb.append(">"); 322 323 // dump body value as XML, use Camel type converter to convert to 324 // String 325 // do not allow streams, but allow files, and clip very big message 326 // bodies (128kb) 327 String xml = extractBodyForLogging(message, "", false, true, 128 * 1024); 328 if (xml != null) { 329 // must always xml encode 330 sb.append(StringHelper.xmlEncode(xml)); 331 } 332 333 sb.append("</body>\n"); 334 } 335 336 sb.append("</message>"); 337 return sb.toString(); 338 } 339 340 /** 341 * Copies the headers from the source to the target message. 342 * 343 * @param source the source message 344 * @param target the target message 345 * @param override whether to override existing headers 346 */ 347 public static void copyHeaders(Message source, Message target, boolean override) { 348 if (!source.hasHeaders()) { 349 return; 350 } 351 352 for (Map.Entry<String, Object> entry : source.getHeaders().entrySet()) { 353 String key = entry.getKey(); 354 Object value = entry.getValue(); 355 356 if (target.getHeader(key) == null || override) { 357 target.setHeader(key, value); 358 } 359 } 360 } 361 362 }