From 4aa00d1acdded1477911210c2aa7783e92bf1dca Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 24 Feb 2009 12:59:59 +0000 Subject: [PATCH] Added Form converter git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@679 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../util/LinkedMultiValueMap.java | 8 ++ .../converter/FormHttpMessageConverter.java | 113 ++++++++++++++++++ .../FormHttpMessageConverterTests.java | 78 ++++++++++++ 3 files changed, 199 insertions(+) create mode 100644 org.springframework.web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java create mode 100644 org.springframework.web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java diff --git a/org.springframework.core/src/main/java/org/springframework/util/LinkedMultiValueMap.java b/org.springframework.core/src/main/java/org/springframework/util/LinkedMultiValueMap.java index e237689ef61..f22286c2aff 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/LinkedMultiValueMap.java +++ b/org.springframework.core/src/main/java/org/springframework/util/LinkedMultiValueMap.java @@ -44,6 +44,14 @@ public class LinkedMultiValueMap implements MultiValueMap { this.targetMap = new LinkedHashMap>(); } + /** + * Create a new SimpleMultiValueMap that wraps a newly created {@link LinkedHashMap} with the given initial capacity. + * @param initialCapacity the initial capacity + */ + public LinkedMultiValueMap(int initialCapacity) { + this.targetMap = new LinkedHashMap>(initialCapacity); + } + /** * Create a new SimpleMultiValueMap that wraps the given target Map. *

Note: The given Map will be used as active underlying Map. diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java b/org.springframework.web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java new file mode 100644 index 00000000000..bd2ed2507b9 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java @@ -0,0 +1,113 @@ +/* + * Copyright 2002-2009 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.http.converter; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; + +/** + * Implementation of {@link HttpMessageConverter} that can read and write form data. + * + *

By default, this converter reads and writes the media type ({@code application/x-www-form-urlencoded}). This + * can be overridden by setting the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property. Form + * data is read from and written into a {@link MultiValueMap MultiValueMap<String, String>}. + * @author Arjen Poutsma + * @see MultiValueMap + * @since 3.0 + */ +public class FormHttpMessageConverter extends AbstractHttpMessageConverter> { + + public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1"); + + /** + * Creates a new instance of the {@code FormHttpMessageConverter}. + */ + public FormHttpMessageConverter() { + super(new MediaType("application", "x-www-form-urlencoded")); + } + + public boolean supports(Class> clazz) { + return MultiValueMap.class.isAssignableFrom(clazz); + } + + public MultiValueMap read(Class> clazz, HttpInputMessage inputMessage) + throws IOException { + MediaType contentType = inputMessage.getHeaders().getContentType(); + Charset charset = contentType.getCharSet() != null ? contentType.getCharSet() : DEFAULT_CHARSET; + String body = FileCopyUtils.copyToString(new InputStreamReader(inputMessage.getBody(), charset)); + + String[] pairs = StringUtils.tokenizeToStringArray(body, "&"); + + MultiValueMap result = new LinkedMultiValueMap(pairs.length); + + for (String pair : pairs) { + int idx = pair.indexOf('='); + if (idx == -1) { + result.add(URLDecoder.decode(pair, charset.name()), null); + } + else { + String name = URLDecoder.decode(pair.substring(0, idx), charset.name()); + String value = URLDecoder.decode(pair.substring(idx + 1), charset.name()); + result.add(name, value); + } + } + return result; + } + + @Override + protected void writeToInternal(MultiValueMap form, HttpOutputMessage outputMessage) + throws IOException { + MediaType contentType = getContentType(form); + Charset charset = contentType.getCharSet() != null ? contentType.getCharSet() : DEFAULT_CHARSET; + StringBuilder builder = new StringBuilder(); + for (Iterator>> entryIterator = form.entrySet().iterator(); + entryIterator.hasNext();) { + Map.Entry> entry = entryIterator.next(); + String name = entry.getKey(); + for (Iterator valueIterator = entry.getValue().iterator(); valueIterator.hasNext();) { + String value = valueIterator.next(); + builder.append(URLEncoder.encode(name, charset.name())); + if (value != null) { + builder.append('='); + builder.append(URLEncoder.encode(value, charset.name())); + if (valueIterator.hasNext()) { + builder.append('&'); + } + } + } + if (entryIterator.hasNext()) { + builder.append('&'); + } + } + FileCopyUtils.copy(builder.toString(), new OutputStreamWriter(outputMessage.getBody(), charset)); + } +} diff --git a/org.springframework.web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java b/org.springframework.web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java new file mode 100644 index 00000000000..6a790145aa9 --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2009 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.http.converter; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.List; + +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.http.MediaType; +import org.springframework.http.MockHttpInputMessage; +import org.springframework.http.MockHttpOutputMessage; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * @author Arjen Poutsma + */ +public class FormHttpMessageConverterTests { + + private FormHttpMessageConverter converter; + + @Before + public void setUp() { + converter = new FormHttpMessageConverter(); + } + + @SuppressWarnings("unchecked") + @Test + public void read() throws Exception { + String body = "name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3"; + Charset iso88591 = Charset.forName("ISO-8859-1"); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(iso88591)); + inputMessage.getHeaders().setContentType(new MediaType("application", "x-www-form-urlencoded", iso88591)); + MultiValueMap result = converter.read(null, inputMessage); + assertEquals("Invalid result", 3, result.size()); + assertEquals("Invalid result", "value 1", result.getFirst("name 1")); + List values = (List) result.get("name 2"); + assertEquals("Invalid result", 2, values.size()); + assertEquals("Invalid result", "value 2+1", values.get(0)); + assertEquals("Invalid result", "value 2+2", values.get(1)); + assertNull("Invalid result", result.getFirst("name 3")); + } + + @Test + public void write() throws IOException { + MultiValueMap body = new LinkedMultiValueMap(); + body.set("name 1", "value 1"); + body.add("name 2", "value 2+1"); + body.add("name 2", "value 2+2"); + body.add("name 3", null); + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + converter.write(body, outputMessage); + Charset iso88591 = Charset.forName("ISO-8859-1"); + assertEquals("Invalid result", "name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3", + outputMessage.getBodyAsString(iso88591)); + assertEquals("Invalid content-type", new MediaType("application", "x-www-form-urlencoded"), + outputMessage.getHeaders().getContentType()); + } + +}