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    }