MessagePayloadProxy.java :  » JBoss » jbossesb-4.7 » org » jboss » soa » esb » message » Java Open Source

Java Open Source » JBoss » jbossesb 4.7 
jbossesb 4.7 » org » jboss » soa » esb » message » MessagePayloadProxy.java
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2006, JBoss Inc., and others contributors as indicated
 * by the @authors tag. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 *
 * (C) 2005-2006, JBoss Inc.
 */
package org.jboss.soa.esb.message;

import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.common.ModulePropertyManager;
import org.jboss.soa.esb.listeners.message.MessageDeliverException;
import org.jboss.soa.esb.message.body.content.BytesBody;
import org.jboss.internal.soa.esb.assertion.AssertArgument;
import org.apache.log4j.Logger;

import java.util.Arrays;
import java.util.List;
import java.io.UnsupportedEncodingException;

/**
 * Utility class to help make accessing the message payload a little more deterministic.
 * <p/>
 * This class can be used by actions/listeners to manage access to the payload on a
 * Message instance.  The class is constructed from the configuration of the
 * component (listener, actions etc) using the proxy instance.  It checks the
 * configuration for "get-payload-location" and "set-payload-location" configuration
 * properties, defaulting both to the Default Body Location
 * ({@link Body#DEFAULT_LOCATION}).
 * <p/>
 * Prior to the introduction of this class, there was no standardised pattern for
 * components exchanging data through the ESB Message.  It was adhoc in nature,
 * with components needing to have knowledge of where other components set
 * the data in the message.  This functionality is still supported through the
 * "get-payload-location" and "set-payload-location" configuration
 * properties, but all ESB now (by default) get and set their data on the
 * {@link Body#DEFAULT_LOCATION}.
 * <p/>
 * Code writen to work against ESB version up to and including version 4.2GA
 * can still use the old adhoc exchange patterns by setting the
 * "core:use.legacy.message.payload.exchange.patterns" config property in the
 * jbossesb-properties.xml file. 
 *
 * @author <a href="mailto:tom.fennelly@jboss.com">tom.fennelly@jboss.com</a>
 */
public class MessagePayloadProxy {

    /**
     * jbossesb-properties.xml config key for switching on and off legacy (adhoc) message payload
     * location getting/setting.
     */
    public static final String USE_LEGACY_EXCHANGE_PATTERNS_CONFIG = "use.legacy.message.payload.exchange.patterns";
    /**
     * Component property for the message location used in the {@link #getPayload(Message)} method.
     * Defaults to {@link org.jboss.soa.esb.message.Body#DEFAULT_LOCATION}. 
     */
    public static final String GET_PAYLOAD_LOCATION = "get-payload-location";
    /**
     * Component property for the message location used in the {@link #setPayload(Message, Object)} method.
     * Defaults to {@link org.jboss.soa.esb.message.Body#DEFAULT_LOCATION}.
     */
    public static final String SET_PAYLOAD_LOCATION = "set-payload-location";
    /**
     * Mime type message property key.
     */
    public static final String MIME_TYPE = "mime-type";

    public static enum NullPayloadHandling {
        NONE, // Do nothing
        LOG, // Create an INFO log.
        WARN, // Create a WARN log.
        EXCEPTION, // Throw a MessageDeliverException
    }

    private static Logger logger = Logger.getLogger(MessagePayloadProxy.class);
    private static boolean USE_LEGACY_EXCHANGE_LOCATIONS;
    static {
        setUseLegacyPatterns(ModulePropertyManager.getPropertyManager(ModulePropertyManager.CORE_MODULE).getProperty(USE_LEGACY_EXCHANGE_PATTERNS_CONFIG, "false").equals("true"));
    }
    
    private List<String> getPayloadLocations;
    private List<String> setPayloadLocations;
    private NullPayloadHandling nullGetPayloadHandling = NullPayloadHandling.EXCEPTION;
    private NullPayloadHandling nullSetPayloadHandling = NullPayloadHandling.NONE;

    /**
     * Public constructor.
     *
     * @param config The component configuration.
     * @param legacyGetPayloadLocations The message input locations as defined in the 4.2.x codebase.
     * @param legacySetPayloadLocations The message output locations as defined in the 4.2.x codebase.
     * @deprecated Use the {@link #MessagePayloadProxy(org.jboss.soa.esb.helpers.ConfigTree) non-legacy constructor}.
     * This method is here simply to support code that is dependent on the
     * 4.2.x message payload exchange patterns an will be removed in a subsequent release.  New code should use the
     * {@link #MessagePayloadProxy(org.jboss.soa.esb.helpers.ConfigTree)} constructor.
     */
    public MessagePayloadProxy(ConfigTree config, String[] legacyGetPayloadLocations, String[] legacySetPayloadLocations) {
        this(config);
        if(USE_LEGACY_EXCHANGE_LOCATIONS) {
            AssertArgument.isNotNullAndNotEmpty(legacyGetPayloadLocations, "legacyGetPayloadLocations");
            AssertArgument.isNotNullAndNotEmpty(legacySetPayloadLocations, "legacySetPayloadLocations");
            setDataLocations(legacyGetPayloadLocations, legacySetPayloadLocations);
        }
    }

    /**
     * Public constructor.
     *
     * @param config The component configuration.
     */
    public MessagePayloadProxy(ConfigTree config) {
        AssertArgument.isNotNull(config, "config");
        setDataLocations(new String[] {config.getAttribute(GET_PAYLOAD_LOCATION, Body.DEFAULT_LOCATION)},
                         new String[] {config.getAttribute(SET_PAYLOAD_LOCATION, Body.DEFAULT_LOCATION)});
    }

    /**
     * Public constructor.
     *
     * @param config The component configuration.
     */
    public MessagePayloadProxy(String getPayloadLocation, String setPayloadLocation) {
        if (getPayloadLocation == null)
        {
            getPayloadLocation = Body.DEFAULT_LOCATION ;
        }
        if (setPayloadLocation == null)
        {
            setPayloadLocation = Body.DEFAULT_LOCATION ;
        }
        
        setDataLocations(new String[] {getPayloadLocation}, new String[] {setPayloadLocation}) ;
    }

    private void setDataLocations(String[] getPayloadLocations, String[] setPayloadLocations) {
        this.getPayloadLocations = Arrays.asList(getPayloadLocations);
        this.setPayloadLocations = Arrays.asList(setPayloadLocations);
    }

    /**
     * Get the primary message payload from the supplied message.
     * @param message The Message instance.
     * @return The primary message payload.
     * @throws MessageDeliverException See {@link #setNullGetPayloadHandling(org.jboss.soa.esb.message.MessagePayloadProxy.NullPayloadHandling)}.
     */
    public Object getPayload(Message message) throws MessageDeliverException {
        AssertArgument.isNotNull(message, "message");
        for(String getPayloadLocation: getPayloadLocations) {
            Object object = message.getBody().get(getPayloadLocation);
            if(object != null) {
                if(USE_LEGACY_EXCHANGE_LOCATIONS && object instanceof byte[] && getPayloadLocation.equals(BytesBody.BYTES_LOCATION)) {
                    return legacyDecodeBinaryPayload((byte[])object, (String)message.getProperties().getProperty(MIME_TYPE));
                } else {
                    return object;
                }
            }
        }

        // Null get handling...
        if(nullGetPayloadHandling == NullPayloadHandling.NONE) {
            // Do nothing
        } else if(nullGetPayloadHandling == NullPayloadHandling.LOG) {
            logger.info("Null data found in message location(s): " + getPayloadLocations);
        } else if(nullGetPayloadHandling == NullPayloadHandling.WARN) {
            logger.warn("Null data found in message location(s): " + getPayloadLocations);
        } else if(nullGetPayloadHandling == NullPayloadHandling.EXCEPTION) {
            throw new MessageDeliverException("Null data found in message location(s): " + getPayloadLocations);
        }
        
        return null;
    }

    /**
     * Set the primary message payload on the supplied message.
     * @param message The message instance.
     * @param payload The message primary payload.
     * @throws MessageDeliverException See {@link #setNullSetPayloadHandling(org.jboss.soa.esb.message.MessagePayloadProxy.NullPayloadHandling)}.
     */
    public void setPayload(Message message, Object payload) throws MessageDeliverException {
        AssertArgument.isNotNull(message, "message");
        if(payload != null) {
            for(String setPayloadLocation : setPayloadLocations) {
                if(USE_LEGACY_EXCHANGE_LOCATIONS && setPayloadLocation.equals(BytesBody.BYTES_LOCATION)) {
                    legacyEncodeBinaryPayload(payload, message);
                } else {
                    setPayload(message, setPayloadLocation, payload);
                }
            }
        } else {
            // Null set handling...
            if(nullSetPayloadHandling == NullPayloadHandling.NONE) {
                // Just fall through and clear everything...
            } else if(nullSetPayloadHandling == NullPayloadHandling.LOG) {
                // Log... fall through and clear everything...
                logger.info("Setting null data in message location(s): " + setPayloadLocations);
            } else if(nullSetPayloadHandling == NullPayloadHandling.WARN) {
                // Warn... fall through and clear everything...
                logger.warn("Setting null data in message location(s): " + setPayloadLocations);
            } else if(nullSetPayloadHandling == NullPayloadHandling.EXCEPTION) {
                throw new MessageDeliverException("Setting null data in message location(s): " + setPayloadLocations);
            }

            // Clear everything...
            for(String getPayloadLocation : getPayloadLocations) {
                message.getBody().remove(getPayloadLocation);
            }
            for(String setPayloadLocation : setPayloadLocations) {
                message.getBody().remove(setPayloadLocation);
            }
        }
    }

    private void setPayload(Message message, String setPayloadLocation, Object payload) {
        message.getBody().add(setPayloadLocation, payload);

        // Attach a stack trace to the message, allowing trackback on
        // where data is being set on the message from.  Useful for debugging. 
        if(logger.isDebugEnabled()) {
            message.getBody().add(setPayloadLocation + "-set-stack", new Exception("setPayload stack trace for '" + setPayloadLocation + "'."));
        }
    }

    /**
     * Perform a legacy style Object to byte[] encoding.
     * <p/>
     * A number of places in the code (e.g. {@link org.jboss.soa.esb.listeners.gateway.PackageJmsMessageContents})
     * had a somewhat dodgy idea about Object to byte[] serialization whereby Objects being set in the
     * {@link BytesBody#BYTES_LOCATION} were getting serialized by doing xxx.toString().getBytes(), which is
     * clearly not accurate.  This method provides backward compatibility for this functionality.
     * <p/>
     * We're adding a half-baked MIME type property on the message such that the receiver has some idea
     * of what it's receiving.
     *
     * @param payload The payload object.
     * @param message The ESB Message.
     *
     * @see #legacyDecodeBinaryPayload(byte[], String) 
     */
    private void legacyEncodeBinaryPayload(Object payload, Message message) {
        if(payload instanceof byte[]) {
            // Raw bytes... intentionally not setting the mime type...
            setPayload(message, BytesBody.BYTES_LOCATION, payload);
        } else {
            // This is the more dodgy stuff :-)  Also... the caller should be encoding the message
            // to bytes themselves if they want to set on BytesBody.BYTES_LOCATION.
            if(payload instanceof String) {
                // We can work text safely enough because we can clearly mark its mime type for the receiver...
                try {
                    setPayload(message, BytesBody.BYTES_LOCATION, payload.toString().getBytes("UTF-8"));
                    message.getProperties().setProperty(MIME_TYPE, "text/plain");
                } catch (UnsupportedEncodingException e) {
                    throw new IllegalStateException("Unexpected environmental exception.  UTF-8 character encoding not supported.");
                }
            } else {
                // General catch all... doing toString on some objects may make no sense, but we need to
                // keep this in for backward compatibility...
                try {
                    setPayload(message, BytesBody.BYTES_LOCATION, payload.toString().getBytes("UTF-8"));
                    message.getProperties().setProperty(MIME_TYPE, "java/" + payload.getClass().getName());
                } catch (UnsupportedEncodingException e) {
                    throw new IllegalStateException("Unexpected environmental exception.  UTF-8 character encoding not supported.");
                }
            }
        }
    }

    /**
     * Perform a legacy style byte[] to Object decoding.
     * <p/>
     * We're not offering full decoding capability here because the old code didn't, but also
     * because it's simply the wrong place/mechanism.  We'll support text/plain, but after that
     * we're simply returning the byte[] undecoded.
     *
     * @param bytes The message payload bytes.
     * @param mimeType The binary data mime type.
     * @return The "decoded" byte[] ;-)
     *
     * @see #legacyEncodeBinaryPayload(Object, Message) 
     */
    private Object legacyDecodeBinaryPayload(byte[] bytes, String mimeType) {
        if(mimeType != null && mimeType.trim().equals("text/plain")) {
            try {
                return new String(bytes, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException("Unexpected environmental exception.  UTF-8 character encoding not supported.");
            }
        }

        return bytes;
    }

    /**
     * Get the primary message input Location as configured on the component config ("get-payload-location").
     * @return The message input location.
     */
    public String getGetPayloadLocation() {
        return getPayloadLocations.get(0);
    }

    /**
     * Get the primary message output Location as configured on the component config ("set-payload-location").
     * @return The message output location.
     */
    public String getSetPayloadLocation() {
        return setPayloadLocations.get(0);
    }

    /**
     * Get the null set-payload handling config.
     * @return Null set-payload Handling config.
     */
    public NullPayloadHandling getNullGetPayloadHandling() {
        return nullGetPayloadHandling;
    }

    /**
     * Set the null set-payload handling config.
     * <p/>
     * If not set, defaults to {@link org.jboss.soa.esb.message.MessagePayloadProxy.NullPayloadHandling#EXCEPTION}.
     *
     * @param nullGetPayloadHandling Null set-payload Handling config.
     */
    public void setNullGetPayloadHandling(NullPayloadHandling nullGetPayloadHandling) {
        this.nullGetPayloadHandling = (nullGetPayloadHandling != null ? nullGetPayloadHandling : NullPayloadHandling.EXCEPTION);
    }

    /**
     * Get the null get-payload handling config.
     * @return Null get-payload Handling config.
     */
    public NullPayloadHandling getNullSetPayloadHandling() {
        return nullSetPayloadHandling;
    }

    /**
     * Set the null get-payload handling config.
     * <p/>
     * If not set, defaults to {@link org.jboss.soa.esb.message.MessagePayloadProxy.NullPayloadHandling#NONE}.
     *
     * @param nullSetPayloadHandling Null get-payload Handling config.
     */
    public void setNullSetPayloadHandling(NullPayloadHandling nullSetPayloadHandling) {
        this.nullSetPayloadHandling = (nullSetPayloadHandling != null ? nullSetPayloadHandling : NullPayloadHandling.NONE);
    }

    public static void setUseLegacyPatterns(boolean useLegacyPatterns) {
        MessagePayloadProxy.USE_LEGACY_EXCHANGE_LOCATIONS = useLegacyPatterns;
        if(USE_LEGACY_EXCHANGE_LOCATIONS) {
            logger.warn("Using the legacy payload-to-message exchange patterns.  This is not recommended.  Please change to use the default message location, or the 'input-location' and 'output-location' properties.");
        } else {
            logger.info("Using the non-legacy payload-to-message exchange pattern.  To switch back to the legacy exchange patterns, use the '" + ModulePropertyManager.CORE_MODULE + ":" + USE_LEGACY_EXCHANGE_PATTERNS_CONFIG + "' property in jbossesb-properties.xml.");
        }
    }

    public static boolean isUsingLegacyPatterns() {
        return MessagePayloadProxy.USE_LEGACY_EXCHANGE_LOCATIONS;
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.