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, 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, 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, value);
300 if (xml != null) {
301 // must always xml encode
302 sb.append(StringHelper.xmlEncode(xml));
303 }
304 } catch (Exception e) {
305 // ignore as the body is for logging purpose
306 }
307 }
308
309 sb.append("</header>\n");
310 }
311 sb.append("</headers>\n");
312 }
313
314 if (includeBody) {
315 sb.append("<body");
316 String type = ObjectHelper.classCanonicalName(message.getBody());
317 if (type != null) {
318 sb.append(" type=\"" + type + "\"");
319 }
320 sb.append(">");
321
322 // dump body value as XML, use Camel type converter to convert to
323 // String
324 // do not allow streams, but allow files, and clip very big message
325 // bodies (128kb)
326 String xml = extractBodyForLogging(message, "", false, true, 128 * 1024);
327 if (xml != null) {
328 // must always xml encode
329 sb.append(StringHelper.xmlEncode(xml));
330 }
331
332 sb.append("</body>\n");
333 }
334
335 sb.append("</message>");
336 return sb.toString();
337 }
338
339 /**
340 * Copies the headers from the source to the target message.
341 *
342 * @param source the source message
343 * @param target the target message
344 * @param override whether to override existing headers
345 */
346 public static void copyHeaders(Message source, Message target, boolean override) {
347 if (!source.hasHeaders()) {
348 return;
349 }
350
351 for (Map.Entry<String, Object> entry : source.getHeaders().entrySet()) {
352 String key = entry.getKey();
353 Object value = entry.getValue();
354
355 if (target.getHeader(key) == null || override) {
356 target.setHeader(key, value);
357 }
358 }
359 }
360
361 }