All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.camel.util.MessageHelper Maven / Gradle / Ivy

There is a newer version: 4.6.0
Show newest version
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.xml.transform.Source;

import org.apache.camel.BytesSource;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.MessageHistory;
import org.apache.camel.StreamCache;
import org.apache.camel.StringSource;
import org.apache.camel.WrappedFile;
import org.apache.camel.spi.ExchangeFormatter;
import org.apache.camel.spi.HeaderFilterStrategy;

/**
 * Some helper methods when working with {@link org.apache.camel.Message}.
 * 
 * @version
 */
public final class MessageHelper {

    private static final String MESSAGE_HISTORY_HEADER = "%-20s %-20s %-80s %-12s";
    private static final String MESSAGE_HISTORY_OUTPUT = "[%-18.18s] [%-18.18s] [%-78.78s] [%10.10s]";

    /**
     * Utility classes should not have a public constructor.
     */
    private MessageHelper() {
    }

    /**
     * Extracts the given body and returns it as a String, that can be used for
     * logging etc.
     * 

* Will handle stream based bodies wrapped in StreamCache. * * @param message the message with the body * @return the body as String, can return null if no body */ public static String extractBodyAsString(Message message) { if (message == null) { return null; } // optimize if the body is a String type already Object body = message.getBody(); if (body instanceof String) { return (String) body; } // we need to favor using stream cache so the body can be re-read later StreamCache newBody = message.getBody(StreamCache.class); if (newBody != null) { message.setBody(newBody); } Object answer = message.getBody(String.class); if (answer == null) { answer = message.getBody(); } if (newBody != null) { // Reset the InputStreamCache newBody.reset(); } return answer != null ? answer.toString() : null; } /** * Gets the given body class type name as a String. *

* Will skip java.lang. for the build in Java types. * * @param message the message with the body * @return the body type name as String, can return * null if no body */ public static String getBodyTypeName(Message message) { if (message == null) { return null; } String answer = ObjectHelper.classCanonicalName(message.getBody()); if (answer != null && answer.startsWith("java.lang.")) { return answer.substring(10); } return answer; } /** * If the message body contains a {@link StreamCache} instance, reset the * cache to enable reading from it again. * * @param message the message for which to reset the body */ public static void resetStreamCache(Message message) { if (message == null) { return; } Object body = message.getBody(); if (body != null && body instanceof StreamCache) { ((StreamCache) body).reset(); } } /** * Returns the MIME content type on the message or null if none * defined */ public static String getContentType(Message message) { return message.getHeader(Exchange.CONTENT_TYPE, String.class); } /** * Returns the MIME content encoding on the message or null if none * defined */ public static String getContentEncoding(Message message) { return message.getHeader(Exchange.CONTENT_ENCODING, String.class); } /** * Extracts the body for logging purpose. *

* Will clip the body if its too big for logging. Will prepend the message * with Message: * * @see org.apache.camel.Exchange#LOG_DEBUG_BODY_STREAMS * @see org.apache.camel.Exchange#LOG_DEBUG_BODY_MAX_CHARS * @param message the message * @return the logging message */ public static String extractBodyForLogging(Message message) { return extractBodyForLogging(message, "Message: "); } /** * Extracts the value for logging purpose. *

* Will clip the value if its too big for logging. * * @see org.apache.camel.Exchange#LOG_DEBUG_BODY_STREAMS * @see org.apache.camel.Exchange#LOG_DEBUG_BODY_MAX_CHARS * @param value the value * @param message the message * @return the logging message */ public static String extractValueForLogging(Object value, Message message) { boolean streams = false; if (message.getExchange() != null) { String globalOption = message.getExchange().getContext().getGlobalOption(Exchange.LOG_DEBUG_BODY_STREAMS); if (globalOption != null) { streams = message.getExchange().getContext().getTypeConverter().convertTo(Boolean.class, message.getExchange(), globalOption); } } // default to 1000 chars int maxChars = 1000; if (message.getExchange() != null) { String property = message.getExchange().getContext().getGlobalOption(Exchange.LOG_DEBUG_BODY_MAX_CHARS); if (property != null) { maxChars = message.getExchange().getContext().getTypeConverter().convertTo(Integer.class, property); } } return extractValueForLogging(value, message, "", streams, false, maxChars); } /** * Extracts the body for logging purpose. *

* Will clip the body if its too big for logging. * * @see org.apache.camel.Exchange#LOG_DEBUG_BODY_STREAMS * @see org.apache.camel.Exchange#LOG_DEBUG_BODY_MAX_CHARS * @param message the message * @param prepend a message to prepend * @return the logging message */ public static String extractBodyForLogging(Message message, String prepend) { boolean streams = false; if (message.getExchange() != null) { String globalOption = message.getExchange().getContext().getGlobalOption(Exchange.LOG_DEBUG_BODY_STREAMS); if (globalOption != null) { streams = message.getExchange().getContext().getTypeConverter().convertTo(Boolean.class, message.getExchange(), globalOption); } } return extractBodyForLogging(message, prepend, streams, false); } /** * Extracts the body for logging purpose. *

* Will clip the body if its too big for logging. * * @see org.apache.camel.Exchange#LOG_DEBUG_BODY_STREAMS * @see org.apache.camel.Exchange#LOG_DEBUG_BODY_MAX_CHARS * @param message the message * @param prepend a message to prepend * @param allowStreams whether or not streams is allowed * @param allowFiles whether or not files is allowed (currently not in use) * @return the logging message */ public static String extractBodyForLogging(Message message, String prepend, boolean allowStreams, boolean allowFiles) { // default to 1000 chars int maxChars = 1000; if (message.getExchange() != null) { String globalOption = message.getExchange().getContext().getGlobalOption(Exchange.LOG_DEBUG_BODY_MAX_CHARS); if (globalOption != null) { maxChars = message.getExchange().getContext().getTypeConverter().convertTo(Integer.class, globalOption); } } return extractBodyForLogging(message, prepend, allowStreams, allowFiles, maxChars); } /** * Extracts the body for logging purpose. *

* Will clip the body if its too big for logging. * * @see org.apache.camel.Exchange#LOG_DEBUG_BODY_MAX_CHARS * @param message the message * @param prepend a message to prepend * @param allowStreams whether or not streams is allowed * @param allowFiles whether or not files is allowed (currently not in use) * @param maxChars limit to maximum number of chars. Use 0 for not limit, and -1 for turning logging message body off. * @return the logging message */ public static String extractBodyForLogging(Message message, String prepend, boolean allowStreams, boolean allowFiles, int maxChars) { return extractValueForLogging(message.getBody(), message, prepend, allowStreams, allowFiles, maxChars); } /** * Extracts the value for logging purpose. *

* Will clip the value if its too big for logging. * * @see org.apache.camel.Exchange#LOG_DEBUG_BODY_MAX_CHARS * @param obj the value * @param message the message * @param prepend a message to prepend * @param allowStreams whether or not streams is allowed * @param allowFiles whether or not files is allowed (currently not in use) * @param maxChars limit to maximum number of chars. Use 0 for not limit, and -1 for turning logging message body off. * @return the logging message */ public static String extractValueForLogging(Object obj, Message message, String prepend, boolean allowStreams, boolean allowFiles, int maxChars) { if (maxChars < 0) { return prepend + "[Body is not logged]"; } if (obj == null) { return prepend + "[Body is null]"; } if (!allowStreams) { if (obj instanceof Source && !(obj instanceof StringSource || obj instanceof BytesSource)) { // for Source its only StringSource or BytesSource that is okay as they are memory based // all other kinds we should not touch the body return prepend + "[Body is instance of java.xml.transform.Source]"; } else if (obj instanceof StreamCache) { return prepend + "[Body is instance of org.apache.camel.StreamCache]"; } else if (obj instanceof InputStream) { return prepend + "[Body is instance of java.io.InputStream]"; } else if (obj instanceof OutputStream) { return prepend + "[Body is instance of java.io.OutputStream]"; } else if (obj instanceof Reader) { return prepend + "[Body is instance of java.io.Reader]"; } else if (obj instanceof Writer) { return prepend + "[Body is instance of java.io.Writer]"; } else if (obj instanceof WrappedFile || obj instanceof File) { if (!allowFiles) { return prepend + "[Body is file based: " + obj + "]"; } } } if (!allowFiles) { if (obj instanceof WrappedFile || obj instanceof File) { return prepend + "[Body is file based: " + obj + "]"; } } // is the body a stream cache or input stream StreamCache cache = null; InputStream is = null; if (obj instanceof StreamCache) { cache = (StreamCache)obj; is = null; } else if (obj instanceof InputStream) { cache = null; is = (InputStream) obj; } // grab the message body as a string String body = null; if (message.getExchange() != null) { try { body = message.getExchange().getContext().getTypeConverter().tryConvertTo(String.class, message.getExchange(), obj); } catch (Throwable e) { // ignore as the body is for logging purpose } } if (body == null) { try { body = obj.toString(); } catch (Throwable e) { // ignore as the body is for logging purpose } } // reset stream cache after use if (cache != null) { cache.reset(); } else if (is != null && is.markSupported()) { try { is.reset(); } catch (IOException e) { // ignore } } if (body == null) { return prepend + "[Body is null]"; } // clip body if length enabled and the body is too big if (maxChars > 0 && body.length() > maxChars) { body = body.substring(0, maxChars) + "... [Body clipped after " + maxChars + " chars, total length is " + body.length() + "]"; } return prepend + body; } /** * Dumps the message as a generic XML structure. * * @param message the message * @return the XML */ public static String dumpAsXml(Message message) { return dumpAsXml(message, true); } /** * Dumps the message as a generic XML structure. * * @param message the message * @param includeBody whether or not to include the message body * @return the XML */ public static String dumpAsXml(Message message, boolean includeBody) { return dumpAsXml(message, includeBody, 0); } /** * Dumps the message as a generic XML structure. * * @param message the message * @param includeBody whether or not to include the message body * @param indent number of spaces to indent * @return the XML */ public static String dumpAsXml(Message message, boolean includeBody, int indent) { return dumpAsXml(message, includeBody, indent, false, true, 128 * 1024); } /** * Dumps the message as a generic XML structure. * * @param message the message * @param includeBody whether or not to include the message body * @param indent number of spaces to indent * @param allowStreams whether to include message body if they are stream based * @param allowFiles whether to include message body if they are file based * @param maxChars clip body after maximum chars (to avoid very big messages). Use 0 or negative value to not limit at all. * @return the XML */ public static String dumpAsXml(Message message, boolean includeBody, int indent, boolean allowStreams, boolean allowFiles, int maxChars) { StringBuilder sb = new StringBuilder(); StringBuilder prefix = new StringBuilder(); for (int i = 0; i < indent; i++) { prefix.append(" "); } // include exchangeId as attribute on the tag sb.append(prefix); sb.append("\n"); // headers if (message.hasHeaders()) { sb.append(prefix); sb.append(" \n"); // sort the headers so they are listed A..Z Map headers = new TreeMap(message.getHeaders()); for (Map.Entry entry : headers.entrySet()) { Object value = entry.getValue(); String type = ObjectHelper.classCanonicalName(value); sb.append(prefix); sb.append("

"); // dump header value as XML, use Camel type converter to convert // to String if (value != null) { try { String xml = message.getExchange().getContext().getTypeConverter().tryConvertTo(String.class, message.getExchange(), value); if (xml != null) { // must always xml encode sb.append(StringHelper.xmlEncode(xml)); } } catch (Throwable e) { // ignore as the body is for logging purpose } } sb.append("
\n"); } sb.append(prefix); sb.append(" \n"); } if (includeBody) { sb.append(prefix); sb.append(" "); String xml = extractBodyForLogging(message, "", allowStreams, allowFiles, maxChars); if (xml != null) { // must always xml encode sb.append(StringHelper.xmlEncode(xml)); } sb.append("\n"); } sb.append(prefix); sb.append(""); return sb.toString(); } /** * Copies the headers from the source to the target message. * * @param source the source message * @param target the target message * @param override whether to override existing headers */ public static void copyHeaders(Message source, Message target, boolean override) { copyHeaders(source, target, null, override); } /** * Copies the headers from the source to the target message. * * @param source the source message * @param target the target message * @param strategy the header filter strategy which could help us to filter the protocol message headers * @param override whether to override existing headers */ public static void copyHeaders(Message source, Message target, HeaderFilterStrategy strategy, boolean override) { if (!source.hasHeaders()) { return; } for (Map.Entry entry : source.getHeaders().entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); if (target.getHeader(key) == null || override) { if (strategy == null) { target.setHeader(key, value); } else if (!strategy.applyFilterToExternalHeaders(key, value, target.getExchange())) { // Just make sure we don't copy the protocol headers to target target.setHeader(key, value); } } } } /** * Dumps the {@link MessageHistory} from the {@link Exchange} in a human readable format. * * @param exchange the exchange * @param exchangeFormatter if provided then information about the exchange is included in the dump * @param logStackTrace whether to include a header for the stacktrace, to be added (not included in this dump). * @return a human readable message history as a table */ public static String dumpMessageHistoryStacktrace(Exchange exchange, ExchangeFormatter exchangeFormatter, boolean logStackTrace) { // must not cause new exceptions so run this in a try catch block try { return doDumpMessageHistoryStacktrace(exchange, exchangeFormatter, logStackTrace); } catch (Throwable e) { // ignore as the body is for logging purpose return ""; } } @SuppressWarnings("unchecked") public static String doDumpMessageHistoryStacktrace(Exchange exchange, ExchangeFormatter exchangeFormatter, boolean logStackTrace) { List list = exchange.getProperty(Exchange.MESSAGE_HISTORY, List.class); if (list == null || list.isEmpty()) { return null; } StringBuilder sb = new StringBuilder(); sb.append("\n"); sb.append("Message History\n"); sb.append("---------------------------------------------------------------------------------------------------------------------------------------\n"); String goMessageHistoryHeaeder = exchange.getContext().getGlobalOption(Exchange.MESSAGE_HISTORY_HEADER_FORMAT); sb.append(String.format( goMessageHistoryHeaeder == null ? MESSAGE_HISTORY_HEADER : goMessageHistoryHeaeder, "RouteId", "ProcessorId", "Processor", "Elapsed (ms)")); sb.append("\n"); // add incoming origin of message on the top String routeId = exchange.getFromRouteId(); String id = routeId; String label = ""; if (exchange.getFromEndpoint() != null) { label = URISupport.sanitizeUri(exchange.getFromEndpoint().getEndpointUri()); } long elapsed = 0; Date created = exchange.getProperty(Exchange.CREATED_TIMESTAMP, Date.class); if (created != null) { elapsed = new StopWatch(created).stop(); } String goMessageHistoryOutput = exchange.getContext().getGlobalOption(Exchange.MESSAGE_HISTORY_OUTPUT_FORMAT); sb.append(String.format( goMessageHistoryOutput == null ? MESSAGE_HISTORY_OUTPUT : goMessageHistoryOutput, routeId, id, label, elapsed)); sb.append("\n"); // and then each history for (MessageHistory history : list) { routeId = history.getRouteId() != null ? history.getRouteId() : ""; id = history.getNode().getId(); // we need to avoid leak the sensible information here // the sanitizeUri takes a very long time for very long string and the format cuts this to // 78 characters, anyway. Cut this to 100 characters. This will give enough space for removing // characters in the sanitizeUri method and will be reasonably fast label = URISupport.sanitizeUri(StringHelper.limitLength(history.getNode().getLabel(), 100)); elapsed = history.getElapsed(); sb.append(String.format(MESSAGE_HISTORY_OUTPUT, routeId, id, label, elapsed)); sb.append("\n"); } if (exchangeFormatter != null) { sb.append("\nExchange\n"); sb.append("---------------------------------------------------------------------------------------------------------------------------------------\n"); sb.append(exchangeFormatter.format(exchange)); sb.append("\n"); } if (logStackTrace) { sb.append("\nStacktrace\n"); sb.append("---------------------------------------------------------------------------------------------------------------------------------------"); } return sb.toString(); } }