diff --git a/org.springframework.integration.http/src/main/java/org/springframework/integration/http/DefaultOutboundRequestMapper.java b/org.springframework.integration.http/src/main/java/org/springframework/integration/http/DefaultOutboundRequestMapper.java index fb02b44040..6eba71d2be 100644 --- a/org.springframework.integration.http/src/main/java/org/springframework/integration/http/DefaultOutboundRequestMapper.java +++ b/org.springframework.integration.http/src/main/java/org/springframework/integration/http/DefaultOutboundRequestMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,12 +20,18 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLEncoder; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; +import java.util.List; import java.util.Map; import org.springframework.integration.core.Message; @@ -107,6 +113,7 @@ public class DefaultOutboundRequestMapper implements OutboundRequestMapper { return this.createRequestFromMessage(message, url, requestMethod); } + @SuppressWarnings("unchecked") private HttpRequest createRequestFromPayload(Object payload, URL url, String requestMethod) throws Exception { ByteArrayOutputStream requestBody = new ByteArrayOutputStream(); String contentType = null; @@ -160,6 +167,7 @@ public class DefaultOutboundRequestMapper implements OutboundRequestMapper { return parameterMap; } + @SuppressWarnings("unchecked") private String writeToRequestBody(Object object, ByteArrayOutputStream byteStream) throws Exception { String contentType = null; if (object instanceof byte[]) { @@ -170,17 +178,89 @@ public class DefaultOutboundRequestMapper implements OutboundRequestMapper { byteStream.write(((String) object).getBytes(this.charset)); contentType = "text/plain; charset=" + this.charset; } - else if (object instanceof Serializable) { - byteStream.write(this.serializeObject((Serializable) object)); - contentType = "application/x-java-serialized-object"; - } else { + if (object instanceof Map && isFormData((Map) object)) { + byte[] data = this.formDataAsBytes((Map) object); + if (data != null) { + byteStream.write(data); + contentType = "application/x-www-form-urlencoded"; + } + } + if (contentType == null && object instanceof Serializable) { + byteStream.write(this.serializeObject((Serializable) object)); + contentType = "application/x-java-serialized-object"; + } + } + if (contentType == null) { throw new IllegalArgumentException("payload must be a byte array, " + - "String, or Serializable object for a 'POST' or 'PUT' request"); + "String, Map, or Serializable object for a 'POST' or 'PUT' request"); } return contentType; } + /** + * If all keys are Strings, we'll consider the Map to be form data. + */ + @SuppressWarnings("unchecked") + private boolean isFormData(Map map) { + for (Object key : map.keySet()) { + if (!(key instanceof String)) { + return false; + } + } + return true; + } + + @SuppressWarnings("unchecked") + private byte[] formDataAsBytes(Map form) throws UnsupportedEncodingException { + StringBuilder builder = new StringBuilder(); + Iterator nameIterator = form.keySet().iterator(); + while (nameIterator.hasNext()) { + Object next = nameIterator.next(); + Assert.isTrue(next instanceof String, "Form map keys must be Strings."); + String name = (String) next; + Object value = form.get(name); + if (value == null) { + builder.append(URLEncoder.encode(name, this.charset)); + } + else { + List values = null; + if (value instanceof String) { + values = Collections.singletonList((String) value); + } + else if (value instanceof String[]) { + values = Arrays.asList((String[]) value); + } + else { + if (!(value instanceof Iterable)) { + return null; + } + Iterator iterator = ((Iterable) value).iterator(); + values = new ArrayList(); + while (iterator.hasNext()) { + Object nextValue = iterator.next(); + if (!(nextValue instanceof String)) { + return null; + } + values.add((String) nextValue); + } + } + Iterator valueIterator = values.iterator(); + builder.append(URLEncoder.encode(name, this.charset)); + while (valueIterator.hasNext()) { + builder.append('=' + URLEncoder.encode(valueIterator.next(), this.charset)); + if (valueIterator.hasNext()) { + builder.append('&' + URLEncoder.encode(name, this.charset)); + } + } + } + if (nameIterator.hasNext()) { + builder.append('&'); + } + } + return builder.toString().getBytes(this.charset); + } + private byte[] serializeObject(Serializable object) throws IOException { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); ObjectOutputStream objectStream = new ObjectOutputStream(byteStream); diff --git a/org.springframework.integration.http/src/test/java/org/springframework/integration/http/DefaultOutboundRequestMapperTests.java b/org.springframework.integration.http/src/test/java/org/springframework/integration/http/DefaultOutboundRequestMapperTests.java new file mode 100644 index 0000000000..f852442fcc --- /dev/null +++ b/org.springframework.integration.http/src/test/java/org/springframework/integration/http/DefaultOutboundRequestMapperTests.java @@ -0,0 +1,142 @@ +/* + * Copyright 2002-2010 the original author or authors. + * + * Licensed 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.springframework.integration.http; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import org.springframework.integration.core.Message; +import org.springframework.integration.message.MessageBuilder; + +/** + * @author Mark Fisher + */ +public class DefaultOutboundRequestMapperTests { + + @Test + public void simpleStringValueFormData() throws Exception { + DefaultOutboundRequestMapper mapper = new DefaultOutboundRequestMapper(new URL("http://example.org")); + Map form = new LinkedHashMap(); + form.put("a", "1"); + form.put("b", "2"); + form.put("c", "3"); + Message message = MessageBuilder.withPayload(form).build(); + HttpRequest request = mapper.fromMessage(message); + String bodyText = request.getBody().toString("UTF-8"); + assertEquals("a=1&b=2&c=3", bodyText); + assertEquals("application/x-www-form-urlencoded", request.getContentType()); + } + + @Test + @SuppressWarnings("unchecked") + public void stringArrayValueFormData() throws Exception { + DefaultOutboundRequestMapper mapper = new DefaultOutboundRequestMapper(new URL("http://example.org")); + Map form = new LinkedHashMap(); + form.put("a", new String[] { "1", "2", "3" }); + form.put("b", "4"); + form.put("c", new String[] { "5" }); + form.put("d", "6"); + Message message = MessageBuilder.withPayload(form).build(); + HttpRequest request = mapper.fromMessage(message); + String bodyText = request.getBody().toString("UTF-8"); + assertEquals("a=1&a=2&a=3&b=4&c=5&d=6", bodyText); + assertEquals("application/x-www-form-urlencoded", request.getContentType()); + } + + @Test + @SuppressWarnings("unchecked") + public void stringListValueFormData() throws Exception { + DefaultOutboundRequestMapper mapper = new DefaultOutboundRequestMapper(new URL("http://example.org")); + Map form = new LinkedHashMap(); + List listA = new ArrayList(); + listA.add("1"); + listA.add("2"); + form.put("a", listA); + form.put("b", Collections.EMPTY_LIST); + form.put("c", Collections.singletonList("3")); + Message message = MessageBuilder.withPayload(form).build(); + HttpRequest request = mapper.fromMessage(message); + String bodyText = request.getBody().toString("UTF-8"); + assertEquals("a=1&a=2&b&c=3", bodyText); + assertEquals("application/x-www-form-urlencoded", request.getContentType()); + } + + @Test + @SuppressWarnings("unchecked") + public void nameOnlyWithNullValues() throws Exception { + DefaultOutboundRequestMapper mapper = new DefaultOutboundRequestMapper(new URL("http://example.org")); + Map form = new LinkedHashMap(); + form.put("a", null); + form.put("b", "foo"); + form.put("c", null); + Message message = MessageBuilder.withPayload(form).build(); + HttpRequest request = mapper.fromMessage(message); + String bodyText = request.getBody().toString("UTF-8"); + assertEquals("a&b=foo&c", bodyText); + assertEquals("application/x-www-form-urlencoded", request.getContentType()); + } + + @Test + public void encodedFormData() throws Exception { + DefaultOutboundRequestMapper mapper = new DefaultOutboundRequestMapper(new URL("http://example.org")); + Map form = new LinkedHashMap(); + form.put("a", "1 + 2 + 3"); + form.put("b", "4+5"); + form.put("c", "97%"); + Message message = MessageBuilder.withPayload(form).build(); + HttpRequest request = mapper.fromMessage(message); + String bodyText = request.getBody().toString("UTF-8"); + assertEquals("a=1+%2B+2+%2B+3&b=4%2B5&c=97%25", bodyText); + assertEquals("application/x-www-form-urlencoded", request.getContentType()); + } + + @Test + @SuppressWarnings("unchecked") + public void nonFormDataInMap() throws Exception { + DefaultOutboundRequestMapper mapper = new DefaultOutboundRequestMapper(new URL("http://example.org")); + Map form = new LinkedHashMap(); + form.put("A", new TestBean()); + form.put("B", new TestBean()); + Message message = MessageBuilder.withPayload(form).build(); + HttpRequest request = mapper.fromMessage(message); + byte[] body = request.getBody().toByteArray(); + ByteArrayInputStream byteStream = new ByteArrayInputStream(body); + Object result = new ObjectInputStream(byteStream).readObject(); + assertEquals(LinkedHashMap.class, result.getClass()); + Map resultMap = (Map) result; + assertEquals(2, resultMap.size()); + assertEquals(TestBean.class, resultMap.get("A").getClass()); + assertEquals(TestBean.class, resultMap.get("B").getClass()); + } + + + @SuppressWarnings("serial") + private static class TestBean implements Serializable { + } + +}