/*
* 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;
}
}
|