First pass at inbound/outbound JSON mapping for INT-1000 and INT-1001. Needs complete Javadoc'ing and still has some TODO's to address.

This commit is contained in:
Jeremy Grelle
2010-03-05 22:38:58 +00:00
parent 0757df5b33
commit 4374a3da20
9 changed files with 585 additions and 1 deletions

View File

@@ -0,0 +1,102 @@
package org.springframework.integration.json;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.type.TypeFactory;
import org.codehaus.jackson.type.JavaType;
import org.codehaus.jackson.type.TypeReference;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageHeaders;
import org.springframework.integration.message.InboundMessageMapper;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.util.Assert;
/**
* {@link InboundMessageMapper} implementation that maps incoming JSON messages to a {@link Message} with the specified payload type.
*
* TODO - Need to figure out if we need to go as deep in mapping HeaderTypes...right now it wouldn't work if the header type was something like List<TestBean>
*
* @author Jeremy Grelle
*/
public class InboundJsonMessageMapper implements
InboundMessageMapper<String> {
private static final String MESSAGE_FORMAT_ERROR = "JSON message is invalid. Expected a message in the format of {\"headers\":{...},\"payload\":{...}} but was ";
private ObjectMapper objectMapper = new ObjectMapper();
private static Map<String, Class<?>> DEFAULT_HEADER_TYPES;
private Map<String, Class<?>> headerTypes = DEFAULT_HEADER_TYPES;
private boolean mapToPayload = false;
private JavaType payloadType;
static {
DEFAULT_HEADER_TYPES = new HashMap<String, Class<?>>();
DEFAULT_HEADER_TYPES.put(MessageHeaders.TIMESTAMP, Long.class);
DEFAULT_HEADER_TYPES.put(MessageHeaders.EXPIRATION_DATE, Long.class);
}
public InboundJsonMessageMapper(Class<?> payloadType) {
this.payloadType = TypeFactory.type(payloadType);
}
public InboundJsonMessageMapper(TypeReference<?> typeReference) {
this.payloadType = TypeFactory.type(typeReference);
}
public void setHeaderTypes(Map<String, Class<?>> headerTypes) {
this.headerTypes.putAll(headerTypes);
}
public void setMapToPayload(boolean mapToPayload) {
this.mapToPayload = mapToPayload;
}
public Message<?> toMessage(String jsonMessage) throws Exception {
JsonParser parser = new JsonFactory().createJsonParser(jsonMessage);
if (mapToPayload) {
try {
Object payload = objectMapper.readValue(parser, payloadType);
return MessageBuilder.withPayload(payload).build();
} catch (JsonMappingException ex) {
throw new IllegalArgumentException("Mapping of JSON message "+jsonMessage+" directly to payload of type "+payloadType.getRawClass().getName()+" failed.", ex);
}
} else {
String error = MESSAGE_FORMAT_ERROR + jsonMessage;
Assert.isTrue(parser.nextToken() == JsonToken.START_OBJECT, error);
Assert.isTrue(parser.nextToken() == JsonToken.FIELD_NAME, error);
Assert.isTrue(parser.getCurrentName().equals("headers"), error);
Assert.isTrue(parser.nextToken() == JsonToken.START_OBJECT, error);
Map<String, Object> headers = new LinkedHashMap<String, Object>();
while (parser.nextToken() != JsonToken.END_OBJECT) {
String headerName = parser.getCurrentName();
parser.nextToken();
Class<?> headerType = headerTypes.containsKey(headerName) ? headerTypes.get(headerName) : Object.class;
try {
headers.put(headerName, objectMapper.readValue(parser, headerType));
} catch (JsonMappingException ex) {
throw new IllegalArgumentException("Mapping header \""+headerName+"\" of JSON message "+jsonMessage+" to header type "+payloadType.getRawClass().getName()+" failed.", ex);
}
}
Assert.isTrue(parser.nextToken() == JsonToken.FIELD_NAME, error);
Assert.isTrue(parser.getCurrentName().equals("payload"), error);
parser.nextToken();
try {
Object payload = objectMapper.readValue(parser, payloadType);
return MessageBuilder.withPayload(payload).copyHeaders(headers).build();
} catch (JsonMappingException ex) {
throw new IllegalArgumentException("Mapping payload of JSON message "+jsonMessage+" to payload type "+payloadType.getRawClass().getName()+" failed.", ex);
}
}
}
}

View File

@@ -0,0 +1,35 @@
package org.springframework.integration.json;
import java.io.StringWriter;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.integration.core.Message;
import org.springframework.integration.message.OutboundMessageMapper;
/**
* {@link OutboundMessageMapper} implementation the converts a {@link Message} to a JSON string representation.
*
* TODO - We might need to add special handling for MessageHistory
*
* @author Jeremy Grelle
*/
public class OutboundJsonMessageMapper implements OutboundMessageMapper<String> {
private boolean shouldExtractPayload = false;
private ObjectMapper objectMapper = new ObjectMapper();
public String fromMessage(Message<?> message) throws Exception {
StringWriter writer = new StringWriter();
if (shouldExtractPayload) {
objectMapper.writeValue(writer, message.getPayload());
} else {
objectMapper.writeValue(writer, message);
}
return writer.toString();
}
public void setShouldExtractPayload(boolean shouldExtractPayload) {
this.shouldExtractPayload = shouldExtractPayload;
}
}

View File

@@ -0,0 +1,203 @@
package org.springframework.integration.json;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import org.junit.Test;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageHeaders;
import org.springframework.integration.message.MessageBuilder;
public class InboundJsonMessageMapperTests {
ObjectMapper mapper = new ObjectMapper();
@Test
public void testToMessageWithHeadersAndStringPayload() throws Exception {
String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"myUniqueId\"},\"payload\":\"myPayloadStuff\"}";
Message<String> expected = MessageBuilder.withPayload("myPayloadStuff").setHeader(MessageHeaders.TIMESTAMP, new Long(1)).setHeader(MessageHeaders.ID,"myUniqueId").build();
InboundJsonMessageMapper mapper = new InboundJsonMessageMapper(String.class);
Message<?> result = mapper.toMessage(jsonMessage);
assertEquals(expected, result);
}
@Test
public void testToMessageWithStringPayload() throws Exception {
String jsonMessage = "\"myPayloadStuff\"";
String expected = "myPayloadStuff";
InboundJsonMessageMapper mapper = new InboundJsonMessageMapper(String.class);
mapper.setMapToPayload(true);
Message<?> result = mapper.toMessage(jsonMessage);
assertEquals(expected, result.getPayload());
}
@Test
public void testToMessageWithHeadersAndBeanPayload() throws Exception {
TestBean bean = new TestBean();
String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"myUniqueId\"},\"payload\":"+getBeanAsJson(bean)+"}";
Message<TestBean> expected = MessageBuilder.withPayload(bean).setHeader(MessageHeaders.TIMESTAMP, new Long(1)).setHeader(MessageHeaders.ID,"myUniqueId").build();
InboundJsonMessageMapper mapper = new InboundJsonMessageMapper(TestBean.class);
Message<?> result = mapper.toMessage(jsonMessage);
assertEquals(expected, result);
}
@Test
public void testToMessageWithBeanPayload() throws Exception {
TestBean expected = new TestBean();
String jsonMessage = getBeanAsJson(expected);
InboundJsonMessageMapper mapper = new InboundJsonMessageMapper(TestBean.class);
mapper.setMapToPayload(true);
Message<?> result = mapper.toMessage(jsonMessage);
assertEquals(expected, result.getPayload());
}
@Test
public void testToMessageWithBeanHeaderAndStringPayload() throws Exception {
TestBean bean = new TestBean();
String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"myUniqueId\", \"myHeader\":"+getBeanAsJson(bean)+"},\"payload\":\"myPayloadStuff\"}";
Message<String> expected = MessageBuilder.withPayload("myPayloadStuff").
setHeader(MessageHeaders.TIMESTAMP, new Long(1)).setHeader(MessageHeaders.ID,"myUniqueId").setHeader("myHeader", bean).build();
InboundJsonMessageMapper mapper = new InboundJsonMessageMapper(String.class);
Map<String, Class<?>> headerTypes = new HashMap<String, Class<?>>();
headerTypes.put("myHeader", TestBean.class);
mapper.setHeaderTypes(headerTypes);
Message<?> result = mapper.toMessage(jsonMessage);
assertEquals(expected, result);
}
@Test
public void testToMessageWithHeadersAndListOfStringsPayload() throws Exception {
String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"myUniqueId\"},\"payload\":[\"myPayloadStuff1\",\"myPayloadStuff2\",\"myPayloadStuff3\"]}";
List<String> expectedList = Arrays.asList(new String[]{"myPayloadStuff1", "myPayloadStuff2", "myPayloadStuff3"});
Message<List<String>> expected = MessageBuilder.withPayload(expectedList).setHeader(MessageHeaders.TIMESTAMP, new Long(1)).setHeader(MessageHeaders.ID,"myUniqueId").build();
InboundJsonMessageMapper mapper = new InboundJsonMessageMapper(new TypeReference<List<String>>(){});
Message<?> result = mapper.toMessage(jsonMessage);
assertEquals(expected, result);
}
@Test
public void testToMessageWithHeadersAndListOfBeansPayload() throws Exception {
TestBean bean1 = new TestBean();
TestBean bean2 = new TestBean();
String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"myUniqueId\"},\"payload\":["+getBeanAsJson(bean1)+","+getBeanAsJson(bean2)+"]}";
List<TestBean> expectedList = Arrays.asList(new TestBean[]{bean1, bean2});
Message<List<TestBean>> expected = MessageBuilder.withPayload(expectedList).setHeader(MessageHeaders.TIMESTAMP, new Long(1)).setHeader(MessageHeaders.ID,"myUniqueId").build();
InboundJsonMessageMapper mapper = new InboundJsonMessageMapper(new TypeReference<List<TestBean>>(){});
Message<?> result = mapper.toMessage(jsonMessage);
assertEquals(expected, result);
}
@Test
public void testToMessageInvalidFormatPayloadAndHeadersReversed() throws Exception {
String jsonMessage = "{\"payload\":\"myPayloadStuff\",\"headers\":{\"$timestamp\":1,\"$id\":\"myUniqueId\"}}";
InboundJsonMessageMapper mapper = new InboundJsonMessageMapper(String.class);
try {
mapper.toMessage(jsonMessage);
fail();
} catch(IllegalArgumentException ex) {
//Expected
}
}
@Test
public void testToMessageInvalidFormatPayloadNoHeaders() throws Exception {
String jsonMessage = "{\"payload\":\"myPayloadStuff\"}";
InboundJsonMessageMapper mapper = new InboundJsonMessageMapper(String.class);
try {
mapper.toMessage(jsonMessage);
fail();
} catch(IllegalArgumentException ex) {
//Expected
}
}
@Test
public void testToMessageInvalidFormatHeadersNoPayload() throws Exception {
String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"myUniqueId\"}}";
InboundJsonMessageMapper mapper = new InboundJsonMessageMapper(String.class);
try {
mapper.toMessage(jsonMessage);
fail();
} catch(IllegalArgumentException ex) {
//Expected
}
}
@Test
public void testToMessageInvalidFormatHeadersAndStringPayloadWithMapToPayload() throws Exception {
String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"myUniqueId\"},\"payload\":\"myPayloadStuff\"}";
InboundJsonMessageMapper mapper = new InboundJsonMessageMapper(String.class);
mapper.setMapToPayload(true);
try {
mapper.toMessage(jsonMessage);
fail();
} catch(IllegalArgumentException ex) {
//Expected
}
}
@Test
public void testToMessageInvalidFormatHeadersAndBeanPayloadWithMapToPayload() throws Exception {
TestBean bean = new TestBean();
String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"myUniqueId\"},\"payload\":"+getBeanAsJson(bean)+"}";
InboundJsonMessageMapper mapper = new InboundJsonMessageMapper(TestBean.class);
mapper.setMapToPayload(true);
try {
mapper.toMessage(jsonMessage);
fail();
} catch(IllegalArgumentException ex) {
//Expected
}
}
@Test
public void testToMessageWithHeadersAndPayloadTypeMappingFailure() throws Exception {
TestBean bean = new TestBean();
String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"myUniqueId\"},\"payload\":"+getBeanAsJson(bean)+"}";
InboundJsonMessageMapper mapper = new InboundJsonMessageMapper(Long.class);
try {
mapper.toMessage(jsonMessage);
fail();
} catch(IllegalArgumentException ex) {
//Expected
}
}
@Test
public void testToMessageWithBeanHeaderTypeMappingFailure() throws Exception {
TestBean bean = new TestBean();
String jsonMessage = "{\"headers\":{\"$timestamp\":1,\"$id\":\"myUniqueId\",\"myHeader\":"+getBeanAsJson(bean)+"},\"payload\":\"myPayloadStuff\"}";
InboundJsonMessageMapper mapper = new InboundJsonMessageMapper(String.class);
Map<String, Class<?>> headerTypes = new HashMap<String, Class<?>>();
headerTypes.put("myHeader", Long.class);
mapper.setHeaderTypes(headerTypes);
try {
mapper.toMessage(jsonMessage);
fail();
} catch(IllegalArgumentException ex) {
//Expected
}
}
private String getBeanAsJson(TestBean bean) throws JsonGenerationException, JsonMappingException, IOException {
StringWriter writer = new StringWriter();
mapper.writeValue(writer, bean);
return writer.toString();
}
}

View File

@@ -0,0 +1,76 @@
package org.springframework.integration.json;
import static org.junit.Assert.*;
import java.io.IOException;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.Test;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageHeaders;
import org.springframework.integration.message.MessageBuilder;
public class OutboundJsonMessageMapperTests {
private JsonFactory jsonFactory = new JsonFactory();
private ObjectMapper objectMapper = new ObjectMapper();
@Test
public void testFromMessageWithHeadersAndStringPayload() throws Exception {
Message<String> testMessage = MessageBuilder.withPayload("myPayloadStuff").build();
String expected = "{\"headers\":{\"$timestamp\":"+testMessage.getHeaders().getTimestamp()+
",\"$history\":[],\"$id\":\""+testMessage.getHeaders().getId()+"\"},\"payload\":\"myPayloadStuff\"}";
OutboundJsonMessageMapper mapper = new OutboundJsonMessageMapper();
String result = mapper.fromMessage(testMessage);
assertEquals(expected, result);
}
@Test
public void testFromMessageExtractStringPayload() throws Exception {
Message<String> testMessage = MessageBuilder.withPayload("myPayloadStuff").build();
String expected = "\"myPayloadStuff\"";
OutboundJsonMessageMapper mapper = new OutboundJsonMessageMapper();
mapper.setShouldExtractPayload(true);
String result = mapper.fromMessage(testMessage);
assertEquals(expected, result);
}
@Test
public void testFromMessageWithHeadersAndBeanPayload() throws Exception {
TestBean payload = new TestBean();
Message<TestBean> testMessage = MessageBuilder.withPayload(payload).setHeader(MessageHeaders.TIMESTAMP, new Long(1234)).setHeader(MessageHeaders.ID, "myUniqueId").build();
String expectedHeaders = "\"headers\":{\"$timestamp\":"+testMessage.getHeaders().getTimestamp()+
",\"$history\":[],\"$id\":\""+testMessage.getHeaders().getId()+"\"";
OutboundJsonMessageMapper mapper = new OutboundJsonMessageMapper();
String result = mapper.fromMessage(testMessage);
assertTrue(result.contains(expectedHeaders));
TestBean parsedPayload = extractJsonPayloadToTestBean(result);
assertEquals(payload, parsedPayload);
}
@Test
public void testFromMessageExtractBeanPayload() throws Exception {
TestBean payload = new TestBean();
Message<TestBean> testMessage = MessageBuilder.withPayload(payload).build();
OutboundJsonMessageMapper mapper = new OutboundJsonMessageMapper();
mapper.setShouldExtractPayload(true);
String result = mapper.fromMessage(testMessage);
assertTrue(!result.contains("headers"));
TestBean parsedPayload = objectMapper.readValue(result, TestBean.class);
assertEquals(payload, parsedPayload);
}
private TestBean extractJsonPayloadToTestBean(String json) throws JsonParseException, IOException {
JsonParser parser = jsonFactory.createJsonParser(json);
do {
parser.nextToken();
} while(parser.getCurrentToken() != JsonToken.FIELD_NAME || !parser.getCurrentName().equals("payload"));
parser.nextToken();
return objectMapper.readValue(parser, TestBean.class);
}
}

View File

@@ -0,0 +1,93 @@
package org.springframework.integration.json;
import java.util.Date;
public class TestBean {
private String value = "foo";
private boolean test = false;
private long number = 42;
private Date now = new Date();
private TestChildBean child = new TestChildBean();
public String getValue() {
return value;
}
public boolean getTest() {
return test;
}
public long getNumber() {
return number;
}
public Date getNow() {
return this.now;
}
public TestChildBean getChild() {
return child;
}
public void setValue(String value) {
this.value = value;
}
public void setTest(boolean test) {
this.test = test;
}
public void setNumber(long number) {
this.number = number;
}
public void setChild(TestChildBean child) {
this.child = child;
}
public void setNow(Date now) {
this.now = now;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((child == null) ? 0 : child.hashCode());
result = prime * result + (int) (number ^ (number >>> 32));
result = prime * result + (test ? 1231 : 1237);
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TestBean other = (TestBean) obj;
if (child == null) {
if (other.child != null)
return false;
} else if (!child.equals(other.child))
return false;
if (number != other.number)
return false;
if (test != other.test)
return false;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
}

View File

@@ -0,0 +1,71 @@
package org.springframework.integration.json;
public class TestChildBean {
private String value = "bar";
private String baz = null;
private TestBean parent = null;
public String getValue() {
return value;
}
public String getBaz() {
return baz;
}
public TestBean getParent() {
return parent;
}
public void setParent(TestBean parent) {
this.parent = parent;
}
public void setValue(String value) {
this.value = value;
}
public void setBaz(String baz) {
this.baz = baz;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((baz == null) ? 0 : baz.hashCode());
result = prime * result
+ ((parent == null) ? 0 : parent.hashCode());
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TestChildBean other = (TestChildBean) obj;
if (baz == null) {
if (other.baz != null)
return false;
} else if (!baz.equals(other.baz))
return false;
if (parent == null) {
if (other.parent != null)
return false;
} else if (!parent.equals(other.parent))
return false;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
}