### Eclipse Workspace Patch 1.0 #P resteasy Index: resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/PrefixedMultivaluedMap.java =================================================================== --- resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/PrefixedMultivaluedMap.java (revision 0) +++ resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/PrefixedMultivaluedMap.java (revision 0) @@ -0,0 +1,32 @@ +package org.jboss.resteasy.util; + +import java.util.List; + +import javax.ws.rs.core.MultivaluedMap; + +/** + * {@link MultivaluedMap} implementation that wraps another instance and only returns values that are prefixed with the given {@link #prefix}. + * @param The type of the keys in the map. + * @param The type of the values in the lists in the map. + */ +public class PrefixedMultivaluedMap extends DelegatingMultivaludMap { + + private final String prefix; + + /** + * Constructor setting the prefix and the delegate. + */ + public PrefixedMultivaluedMap(String prefix, MultivaluedMap delegate) { + super(delegate); + this.prefix = prefix; + } + + /** + * Returns the value assigned to "prefix.key" implicitly converts the key to {@link String} + */ + @Override + public List get(Object key) { + return super.get(prefix + '.' + key); + } + +} Index: resteasy-jaxrs/src/main/java/org/jboss/resteasy/core/InjectorFactoryImpl.java =================================================================== --- resteasy-jaxrs/src/main/java/org/jboss/resteasy/core/InjectorFactoryImpl.java (revision 1326) +++ resteasy-jaxrs/src/main/java/org/jboss/resteasy/core/InjectorFactoryImpl.java (working copy) @@ -72,12 +72,13 @@ HeaderParam header; MatrixParam matrix; PathParam uriParam; + Form form; CookieParam cookie; FormParam formParam; Suspend suspend; - if ((query = findAnnotation(annotations, QueryParam.class)) != null) + if ((query = findAnnotation(annotations, QueryParam.class)) != null) { return new QueryParamInjector(type, genericType, injectTarget, query.value(), defaultVal, encode, annotations, providerFactory); } @@ -97,9 +98,12 @@ { return new PathParamInjector(type, genericType, injectTarget, uriParam.value(), defaultVal, encode, annotations, providerFactory); } - else if (findAnnotation(annotations, Form.class) != null) + else if ((form = findAnnotation(annotations, Form.class)) != null) { - return new FormInjector(type, providerFactory); + if (form.prefix().length()> 0) { + return new PrefixedFormInjector(type, form.prefix(), providerFactory); + } + return new FormInjector(type, providerFactory); } else if ((matrix = findAnnotation(annotations, MatrixParam.class)) != null) { Index: resteasy-jaxrs/src/test/java/org/jboss/resteasy/test/form/ComplexFormTest.java =================================================================== --- resteasy-jaxrs/src/test/java/org/jboss/resteasy/test/form/ComplexFormTest.java (revision 0) +++ resteasy-jaxrs/src/test/java/org/jboss/resteasy/test/form/ComplexFormTest.java (revision 0) @@ -0,0 +1,73 @@ +package org.jboss.resteasy.test.form; + +import static junit.framework.Assert.*; + +import javax.ws.rs.Consumes; +import javax.ws.rs.FormParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.jboss.resteasy.annotations.Form; +import org.jboss.resteasy.mock.MockHttpRequest; +import org.jboss.resteasy.mock.MockHttpResponse; +import org.jboss.resteasy.test.BaseResourceTest; +import org.junit.Before; +import org.junit.Test; + +public class ComplexFormTest extends BaseResourceTest { + + public static class Person { + + @FormParam("name") + private String name; + + @Form(prefix="invoice") + private Address invoice; + + @Form(prefix="shipping") + private Address shipping; + + @Override + public String toString() { + return new StringBuilder("name:'").append(name).append("', invoice:'").append(invoice.street).append("', shipping:'").append(shipping.street).append("'").toString(); + } + } + + public static class Address { + + @FormParam("street") + private String street; + } + + @Path("person") + public static class MyResource { + + @POST + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public String post (@Form Person p) { + return p.toString(); + } + } + + @Before + public void register () { + deployment.getRegistry(). + + addPerRequestResource(MyResource.class); + } + + @Test + public void shouldSupportNestedForm() throws Exception { + MockHttpResponse response = new MockHttpResponse(); + MockHttpRequest request = MockHttpRequest.post("person").accept(MediaType.TEXT_PLAIN).contentType(MediaType.APPLICATION_FORM_URLENCODED); + request.addFormHeader("name", "John Doe"); + request.addFormHeader("invoice.street", "Main Street"); + request.addFormHeader("shipping.street", "Station Street"); + dispatcher.invoke(request, response); + + assertEquals("name:'John Doe', invoice:'Main Street', shipping:'Station Street'", response.getContentAsString()); + } +} Index: resteasy-jaxrs/src/main/java/org/jboss/resteasy/core/PrefixedFormInjector.java =================================================================== --- resteasy-jaxrs/src/main/java/org/jboss/resteasy/core/PrefixedFormInjector.java (revision 0) +++ resteasy-jaxrs/src/main/java/org/jboss/resteasy/core/PrefixedFormInjector.java (revision 0) @@ -0,0 +1,58 @@ +package org.jboss.resteasy.core; + +import java.util.List; + +import javax.ws.rs.core.MultivaluedMap; + +import org.jboss.resteasy.spi.HttpRequest; +import org.jboss.resteasy.spi.HttpResponse; +import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.jboss.resteasy.util.PrefixedFormFieldsHttpRequest; + +/** + * Extension of {@link FormInjector} that handles prefixes for associated classes. + */ +public class PrefixedFormInjector extends FormInjector { + + private final String prefix; + + /** + * Constructor setting the prefix. + */ + public PrefixedFormInjector(Class type, String prefix, ResteasyProviderFactory factory) { + super(type, factory); + this.prefix = prefix; + } + + /** + * {@inheritDoc} Wraps the request in a + */ + @Override + public Object inject(HttpRequest request, HttpResponse response) { + if (!containsPrefixedFormFieldsWithValue(request.getDecodedFormParameters())) { + return null; + } + return super.inject(new PrefixedFormFieldsHttpRequest(prefix, request), response); + } + + /** + * Checks to see if the decodedParameters contains any form fields starting with the prefix. Also checks if the value is not empty. + */ + private boolean containsPrefixedFormFieldsWithValue(MultivaluedMap decodedFormParameters) { + for (String parameterName : decodedFormParameters.keySet()) { + if (parameterName.startsWith(prefix)) { + if (hasValue(decodedFormParameters.get(parameterName))) { + return true; + } + } + } + return false; + } + + /** + * Checks that the list has an non empty value. + */ + private boolean hasValue(List list) { + return !list.isEmpty() && list.get(0).length() > 0; + } +} Index: resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/DelegatingMultivaludMap.java =================================================================== --- resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/DelegatingMultivaludMap.java (revision 0) +++ resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/DelegatingMultivaludMap.java (revision 0) @@ -0,0 +1,161 @@ +package org.jboss.resteasy.util; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.core.MultivaluedMap; + +/** + * {@link MultivaluedMap} implementation that delegates to another instance. + * Convenience class for {@link MultivaluedMap} enhancements that don't want to implement all methods. + * + * @param The type of keys in the map. + * @param The type of values in the lists in the map. + */ +public class DelegatingMultivaludMap implements MultivaluedMap { + + private final MultivaluedMap delegate; + + public DelegatingMultivaludMap(MultivaluedMap delegate) { + this.delegate = delegate; + } + + /** + * @see javax.ws.rs.core.MultivaluedMap#putSingle(java.lang.Object, java.lang.Object) + */ + @Override + public void putSingle(K key, V value) { + delegate.putSingle(key, value); + } + + /** + * @see javax.ws.rs.core.MultivaluedMap#add(java.lang.Object, java.lang.Object) + */ + @Override + public void add(K key, V value) { + delegate.add(key, value); + } + + /** + * @see javax.ws.rs.core.MultivaluedMap#getFirst(java.lang.Object) + */ + @Override + public V getFirst(K key) { + return delegate.getFirst(key); + } + + /** + * @see java.util.Map#size() + */ + @Override + public int size() { + return delegate.size(); + } + + /** + * @see java.util.Map#isEmpty() + */ + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + /** + * @see java.util.Map#containsKey(java.lang.Object) + */ + @Override + public boolean containsKey(Object key) { + return delegate.containsKey(key); + } + + /** + * @see java.util.Map#containsValue(java.lang.Object) + */ + @Override + public boolean containsValue(Object value) { + return delegate.containsValue(value); + } + + /** + * @see java.util.Map#get(java.lang.Object) + */ + @Override + public List get(Object key) { + return delegate.get(key); + } + + /** + * @see java.util.Map#put(java.lang.Object, java.lang.Object) + */ + @Override + public List put(K key, List value) { + return delegate.put(key, value); + } + + /** + * @see java.util.Map#remove(java.lang.Object) + */ + @Override + public List remove(Object key) { + return delegate.remove(key); + } + + /** + * @see java.util.Map#putAll(java.util.Map) + */ + @Override + public void putAll(Map> m) { + delegate.putAll(m); + } + + /** + * @see java.util.Map#clear() + */ + @Override + public void clear() { + delegate.clear(); + } + + /** + * @see java.util.Map#keySet() + */ + @Override + public Set keySet() { + return delegate.keySet(); + } + + /** + * @see java.util.Map#values() + */ + @Override + public Collection> values() { + return delegate.values(); + } + + /** + * @see java.util.Map#entrySet() + */ + @Override + public Set>> entrySet() { + return delegate.entrySet(); + } + + /** + * @see java.util.Map#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + + /** + * @see java.util.Map#hashCode() + */ + @Override + public int hashCode() { + return delegate.hashCode(); + } + +} Index: resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/PrefixedFormFieldsHttpRequest.java =================================================================== --- resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/PrefixedFormFieldsHttpRequest.java (revision 0) +++ resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/PrefixedFormFieldsHttpRequest.java (revision 0) @@ -0,0 +1,21 @@ +package org.jboss.resteasy.util; + +import javax.ws.rs.core.MultivaluedMap; + +import org.jboss.resteasy.spi.HttpRequest; + +public class PrefixedFormFieldsHttpRequest extends DelegatingHttpRequest { + + private final String prefix; + + public PrefixedFormFieldsHttpRequest(String prefix, HttpRequest request) { + super(request); + this.prefix = prefix; + } + + @Override + public MultivaluedMap getDecodedFormParameters() { + return new PrefixedMultivaluedMap(prefix, super.getDecodedFormParameters()); + } + +} Index: resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/DelegatingHttpRequest.java =================================================================== --- resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/DelegatingHttpRequest.java (revision 0) +++ resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/DelegatingHttpRequest.java (revision 0) @@ -0,0 +1,163 @@ +package org.jboss.resteasy.util; + +import java.io.InputStream; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.UriInfo; + +import org.jboss.resteasy.spi.AsynchronousResponse; +import org.jboss.resteasy.spi.HttpRequest; + +/** + * {@link HttpRequest} implementation that wraps another {@link HttpRequest} and delegates all calls to the wrapped instance. + * Convenience class for other implementations that need to extend the functionality of {@link HttpRequest} without having to implement all methods. + */ +public class DelegatingHttpRequest implements HttpRequest { + + private final HttpRequest delegate; + + /** + * Constructor setting the delegate. + */ + public DelegatingHttpRequest(HttpRequest delegate) { + this.delegate = delegate; + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#getHttpHeaders() + */ + @Override + public HttpHeaders getHttpHeaders() { + return delegate.getHttpHeaders(); + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#getInputStream() + */ + @Override + public InputStream getInputStream() { + return delegate.getInputStream(); + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#setInputStream(java.io.InputStream) + */ + @Override + public void setInputStream(InputStream stream) { + delegate.setInputStream(stream); + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#getUri() + */ + @Override + public UriInfo getUri() { + return delegate.getUri(); + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#getHttpMethod() + */ + @Override + public String getHttpMethod() { + return delegate.getHttpMethod(); + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#getPreprocessedPath() + */ + @Override + public String getPreprocessedPath() { + return delegate.getPreprocessedPath(); + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#setPreprocessedPath(java.lang.String) + */ + @Override + public void setPreprocessedPath(String path) { + delegate.setPreprocessedPath(path); + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#getFormParameters() + */ + @Override + public MultivaluedMap getFormParameters() { + return delegate.getFormParameters(); + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#getDecodedFormParameters() + */ + @Override + public MultivaluedMap getDecodedFormParameters() { + return delegate.getDecodedFormParameters(); + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#getAttribute(java.lang.String) + */ + @Override + public Object getAttribute(String attribute) { + return delegate.getAttribute(attribute); + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#setAttribute(java.lang.String, java.lang.Object) + */ + @Override + public void setAttribute(String name, Object value) { + delegate.setAttribute(name, value); + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#removeAttribute(java.lang.String) + */ + @Override + public void removeAttribute(String name) { + delegate.removeAttribute(name); + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#isInitial() + */ + @Override + public boolean isInitial() { + return delegate.isInitial(); + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#isSuspended() + */ + @Override + public boolean isSuspended() { + return delegate.isSuspended(); + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#createAsynchronousResponse(long) + */ + @Override + public AsynchronousResponse createAsynchronousResponse(long suspendTimeout) { + return delegate.createAsynchronousResponse(suspendTimeout); + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#getAsynchronousResponse() + */ + @Override + public AsynchronousResponse getAsynchronousResponse() { + return delegate.getAsynchronousResponse(); + } + + /** + * @see org.jboss.resteasy.spi.HttpRequest#initialRequestThreadFinished() + */ + @Override + public void initialRequestThreadFinished() { + delegate.initialRequestThreadFinished(); + } + +} Index: resteasy-jaxrs/src/main/java/org/jboss/resteasy/annotations/Form.java =================================================================== --- resteasy-jaxrs/src/main/java/org/jboss/resteasy/annotations/Form.java (revision 1326) +++ resteasy-jaxrs/src/main/java/org/jboss/resteasy/annotations/Form.java (working copy) @@ -33,4 +33,6 @@ public @interface Form { + String prefix() default ""; + }