Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

please add JMS JsonMessageConverter [SPR-7404] #12062

Closed
spring-projects-issues opened this issue Jul 28, 2010 · 5 comments
Closed

please add JMS JsonMessageConverter [SPR-7404] #12062

spring-projects-issues opened this issue Jul 28, 2010 · 5 comments
Labels
in: messaging Issues in messaging modules (jms, messaging) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Jul 28, 2010

John Thoms opened SPR-7404 and commented

org.springframework.jms.support.converter.JsonMessageConverter would handle marshalling of JMS payloads similiar to MappingJacksonHttpMessageConverter for http and
spring-amqp/org.springframework.amqp.support.converter.JsonMessageConverter


Reference URL: http://forum.springsource.org/showthread.php?p=311501

Issue Links:

Referenced from: commits 1adf825, 7ec9292

1 votes, 1 watchers

@spring-projects-issues
Copy link
Collaborator Author

James Hoare commented

I based the following version on the amqp one, it just uses a Map rather than the class mapper strategy and also uses the JmsType header (helpful if your using AMQ's admin web app for testing).

Would be great to get something like this in Spring JMS.

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.MessageConversionException;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Map;
 
/**
 * JSON converter that uses the Jackson Json library.
 */
public class JsonMessageConverter implements MessageConverter {


    private static Log log = LogFactory.getLog(JsonMessageConverter.class);

    private ObjectMapper jsonObjectMapper = new ObjectMapper();

    private Map<String, Class> idClassMapping;



    public JsonMessageConverter() {
        initializeJsonObjectMapper();
    }

    /**
     * Map of key message type to value FQN
     * @param idClassMapping
     */
    public void setIdClassMapping(Map<String, Class> idClassMapping) {
        this.idClassMapping = idClassMapping;
    }

    /**
     * A simple check against the map of class simple name key to the FQN name of the class
     *
     * @param classId a string value that should be a property of the message header
     * @return Class to convert to
     */
    public Class toClass(String classId) {
        if (classId == null) {
            throw new MessageConversionException(
                    "failed to convert json-based Message content. Could not resolve JmsType in header, value was null");
        }
        if (this.idClassMapping.containsKey(classId)) {
            return idClassMapping.get(classId);
        }
        throw new MessageConversionException(
                    "failed to convert json-based Message content. Could not resolve JmsType in header, value was null");
    }




    /**
     * Subclass and override to customize.
     */
    protected void initializeJsonObjectMapper() {
        jsonObjectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
        jsonObjectMapper.configure(SerializationConfig.Feature.AUTO_DETECT_FIELDS, true);
        jsonObjectMapper.configure(SerializationConfig.Feature.USE_ANNOTATIONS, true);
        jsonObjectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
        jsonObjectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, true);
        //iso-8601 format
        jsonObjectMapper.getDeserializationConfig().setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"));
    }


    /**
     * Convert a Java object to a JMS Message using the supplied session
     * to create the message object.
     *
     * @param object  the object to convert
     * @param session the Session to use for creating a JMS Message
     * @return the JMS Message
     * @throws javax.jms.JMSException if thrown by JMS API methods
     * @throws org.springframework.jms.support.converter.MessageConversionException
     *                                in case of conversion failure
     */
    @Override
    public Message toMessage(Object object, Session session) throws JMSException, MessageConversionException {
        Message message = null;
        try {
            String json = jsonObjectMapper.writeValueAsString(object);
            log.info(object + " converted to " + json);
            message =  session.createTextMessage(json);
            message.setJMSType(object.getClass().getSimpleName());
            return message;
        } catch (JsonParseException e) {
            throw new MessageConversionException("Failed to convert Message content", e);
        } catch (JsonMappingException e) {
            throw new MessageConversionException("Failed to convert Message content", e);
        } catch (Exception e) {
            throw new MessageConversionException("Failed to convert Message content", e);
        }
    }

    /**
     * Convert from a JMS Message to a Java object.
     *
     * @param message the message to convert
     * @return the converted Java object
     * @throws javax.jms.JMSException if thrown by JMS API methods
     * @throws org.springframework.jms.support.converter.MessageConversionException
     *                                in case of conversion failure
     */
    @Override
    public Object fromMessage(Message message) throws JMSException, MessageConversionException {
        if (message instanceof TextMessage) {
            TextMessage textMessage = (TextMessage) message;
            Class clazzToConvertTo = toClass(message.getJMSType());
            try {
                Object converted = jsonObjectMapper.readValue(textMessage.getText(), clazzToConvertTo);
                log.info("Jms Message " + textMessage.getText() + " converted to " + converted);
                return converted;
            } catch (JsonParseException e) {
                throw new MessageConversionException("Mapping of JSON message " + textMessage.getText() + " directly to payload of type " + clazzToConvertTo.getName() + " failed.", e);
            } catch (JsonMappingException e) {
                throw new MessageConversionException("Mapping of JSON message " + textMessage.getText() + " directly to payload of type " + clazzToConvertTo.getName() + " failed.", e);
            } catch (IOException e) {
                throw new MessageConversionException("Mapping  of JSON message " + textMessage.getText() + " directly to payload of type " + clazzToConvertTo.getName() + " failed.", e);
            }

        } else {
            throw new MessageConversionException("Only support JMSTextMessage, received: " + message);
        }
    }


}

@spring-projects-issues
Copy link
Collaborator Author

Kyrill Alyoshin commented

Does this thing even work? It seems SI in "synchronous" interaction overrides outbound JMSType header on a with the original inbound JMSType header, which effectively makes it impossible to correctly determine the the Java type to convert to. It seems converters have to be specified for each gateway where Java type is explicitly defined.

@spring-projects-issues
Copy link
Collaborator Author

Michael Pilone commented

I stumbled on this feature request while searching for a good way to build a JMS solution using JSON message payloads. While the JsonMessageConverter would be nice, it is pretty easy to write one by hand. I think the bigger issue here is Kyrill's comment "It seems converters have to be specified for each gateway where Java type is explicitly defined."

Even if we had a JsonMessageConverter, having to instantiate a different converter for each target type isn't great. It may be that in combination with a json converter, a better adapter is needed which passes the target Object type to the converter so adapting the message to a method call can take the parameters into account during unmarshalling. This is important for any format that doesn't have the type information encoded in the payload (like XML might). For example, if you wanted to use ProtocolBuffers as the format.

@spring-projects-issues
Copy link
Collaborator Author

Dave Syer commented

I can't comment directly on the behaviour of Spring Integration and overriding JMS headers (and it doesn't seem relevant here, so maybe an INT-* issue would be useful to track that separate discussion?). The JsonMessageConverter in Spring AMQP is used pretty successfully by a lot of projects already, so the approach in general seems to be valid. It's really just a question of determining for each message what Java type the boy needs to be converted to - that could be a complex decision, which is why the AMQP case uses a strategy with a sensible default implementation. I think I would recommend the same design here (extending James's suggestion, or copying Spring AMQP depending which way you look at it). As you say, it is pretty trivial to implement, so there is hardly any barrier to trying it to see if it works, as I expect James already has.

@spring-projects-issues
Copy link
Collaborator Author

Dave Syer commented

Added a converter based on Spring AMQP with Java types specified in custom headers (message properties)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: messaging Issues in messaging modules (jms, messaging) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

1 participant