diff --git a/CHANGELOG.md b/CHANGELOG.md index aa2bd3a9..61f3ae94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ### Version 8.0 * Removes Dagger 1.x Dependency * Removes support for parameters annotated with `javax.inject.@Named`. Use `feign.@Param` instead. +* Makes body parameter type explicit. ### Version 7.1 * Introduces feign.@Param to annotate template parameters. Users must migrate from `javax.inject.@Named` to `feign.@Param` before updating to Feign 8.0. diff --git a/core/src/main/java/feign/MethodMetadata.java b/core/src/main/java/feign/MethodMetadata.java index bca3678c..61bbc38a 100644 --- a/core/src/main/java/feign/MethodMetadata.java +++ b/core/src/main/java/feign/MethodMetadata.java @@ -79,6 +79,7 @@ public final class MethodMetadata implements Serializable { return this; } + /** Type corresponding to {@link #bodyIndex()}. */ public Type bodyType() { return bodyType; } diff --git a/core/src/main/java/feign/ReflectiveFeign.java b/core/src/main/java/feign/ReflectiveFeign.java index 541bd5f6..bbb1ac0d 100644 --- a/core/src/main/java/feign/ReflectiveFeign.java +++ b/core/src/main/java/feign/ReflectiveFeign.java @@ -201,7 +201,7 @@ public class ReflectiveFeign extends Feign { formVariables.put(entry.getKey(), entry.getValue()); } try { - encoder.encode(formVariables, mutable); + encoder.encode(formVariables, Types.MAP_STRING_WILDCARD, mutable); } catch (EncodeException e) { throw e; } catch (RuntimeException e) { @@ -224,7 +224,7 @@ public class ReflectiveFeign extends Feign { Object body = argv[metadata.bodyIndex()]; checkArgument(body != null, "Body parameter %s was null", metadata.bodyIndex()); try { - encoder.encode(body, mutable); + encoder.encode(body, metadata.bodyType(), mutable); } catch (EncodeException e) { throw e; } catch (RuntimeException e) { diff --git a/core/src/main/java/feign/Types.java b/core/src/main/java/feign/Types.java index bfdc00fd..39755775 100644 --- a/core/src/main/java/feign/Types.java +++ b/core/src/main/java/feign/Types.java @@ -23,6 +23,7 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.Arrays; +import java.util.Map; import java.util.NoSuchElementException; /** @@ -32,6 +33,10 @@ import java.util.NoSuchElementException; * @author Jesse Wilson */ final class Types { + /** Type literal for {@code Map}. */ + static final Type MAP_STRING_WILDCARD = new ParameterizedTypeImpl(null, Map.class, String.class, + new WildcardTypeImpl(new Type[] { Object.class }, new Type[] { })); + private static final Type[] EMPTY_TYPE_ARRAY = new Type[0]; private Types() { diff --git a/core/src/main/java/feign/codec/Encoder.java b/core/src/main/java/feign/codec/Encoder.java index f9ba93fe..b34c5524 100644 --- a/core/src/main/java/feign/codec/Encoder.java +++ b/core/src/main/java/feign/codec/Encoder.java @@ -16,6 +16,7 @@ package feign.codec; import feign.RequestTemplate; +import java.lang.reflect.Type; import static java.lang.String.format; @@ -40,8 +41,8 @@ import static java.lang.String.format; * } * * @Override - * public void encode(Object object, RequestTemplate template) { - * template.body(gson.toJson(object)); + * public void encode(Object object, Type bodyType, RequestTemplate template) { + * template.body(gson.toJson(object, bodyType)); * } * } * @@ -59,24 +60,25 @@ import static java.lang.String.format; * */ public interface Encoder { + /** * Converts objects to an appropriate representation in the template. * * @param object what to encode as the request body. + * @param bodyType the type the object should be encoded as. {@code Map}, if form encoding. * @param template the request template to populate. * @throws EncodeException when encoding failed due to a checked exception. */ - void encode(Object object, RequestTemplate template) throws EncodeException; + void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException; /** * Default implementation of {@code Encoder}. */ class Default implements Encoder { - @Override - public void encode(Object object, RequestTemplate template) throws EncodeException { - if (object instanceof String) { + @Override public void encode(Object object, Type bodyType, RequestTemplate template) { + if (bodyType == String.class) { template.body(object.toString()); - } else if (object instanceof byte[]) { + } else if (bodyType == byte[].class) { template.body((byte[]) object, null); } else if (object != null) { throw new EncodeException(format("%s is not a type supported by this encoder.", object.getClass())); diff --git a/core/src/test/java/feign/DefaultContractTest.java b/core/src/test/java/feign/DefaultContractTest.java index 739d9884..12e7bba0 100644 --- a/core/src/test/java/feign/DefaultContractTest.java +++ b/core/src/test/java/feign/DefaultContractTest.java @@ -231,6 +231,14 @@ public class DefaultContractTest { ); } + /** Body type is only for the body param. */ + @Test public void formParamsDoesNotSetBodyType() throws Exception { + MethodMetadata md = contract.parseAndValidatateMetadata(FormParams.class.getDeclaredMethod("login", String.class, + String.class, String.class)); + + assertThat(md.bodyType()).isNull(); + } + interface HeaderParams { @RequestLine("POST /") @Headers({"Auth-Token: {Auth-Token}", "Auth-Token: Foo"}) diff --git a/core/src/test/java/feign/FeignBuilderTest.java b/core/src/test/java/feign/FeignBuilderTest.java index 5020e2b2..63d452ea 100644 --- a/core/src/test/java/feign/FeignBuilderTest.java +++ b/core/src/test/java/feign/FeignBuilderTest.java @@ -18,10 +18,7 @@ package feign; import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule; import feign.codec.Decoder; -import feign.codec.EncodeException; import feign.codec.Encoder; -import org.junit.Rule; - import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Type; @@ -29,6 +26,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Rule; import org.junit.Test; import static feign.assertj.MockWebServerAssertions.assertThat; @@ -63,8 +61,7 @@ public class FeignBuilderTest { String url = "http://localhost:" + server.getPort(); Encoder encoder = new Encoder() { - @Override - public void encode(Object object, RequestTemplate template) throws EncodeException { + @Override public void encode(Object object, Type bodyType, RequestTemplate template) { template.body(object.toString()); } }; diff --git a/core/src/test/java/feign/FeignTest.java b/core/src/test/java/feign/FeignTest.java index 939190a5..47e5e091 100644 --- a/core/src/test/java/feign/FeignTest.java +++ b/core/src/test/java/feign/FeignTest.java @@ -16,11 +16,13 @@ package feign; import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.SocketPolicy; import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule; import feign.Target.HardCodedTarget; import feign.codec.Decoder; +import feign.codec.EncodeException; import feign.codec.Encoder; import feign.codec.ErrorDecoder; import feign.codec.StringDecoder; @@ -31,6 +33,7 @@ import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -137,6 +140,26 @@ public class FeignTest { .hasBody("[netflix, denominator, password]"); } + /** The type of a parameter value may not be the desired type to encode as. Prefer the interface type. */ + @Test public void bodyTypeCorrespondsWithParameterType() throws IOException, InterruptedException { + server.enqueue(new MockResponse().setBody("foo")); + + final AtomicReference encodedType = new AtomicReference(); + TestInterface api = new TestInterfaceBuilder() + .encoder(new Encoder.Default() { + @Override public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { + encodedType.set(bodyType); + } + }) + .target("http://localhost:" + server.getPort()); + + api.body(Arrays.asList("netflix", "denominator", "password")); + + server.takeRequest(); + + assertThat(encodedType.get()).isEqualTo(new TypeToken>(){}.getType()); + } + @Test public void postGZIPEncodedBodyParam() throws IOException, InterruptedException { server.enqueue(new MockResponse().setBody("foo")); @@ -348,7 +371,7 @@ public class FeignTest { private final Feign.Builder delegate = new Feign.Builder() .decoder(new Decoder.Default()) .encoder(new Encoder() { - @Override public void encode(Object object, RequestTemplate template) { + @Override public void encode(Object object, Type bodyType, RequestTemplate template) { if (object instanceof Map) { template.body(new Gson().toJson(object)); } else { @@ -362,8 +385,8 @@ public class FeignTest { return this; } - TestInterfaceBuilder client(Client client) { - delegate.client(client); + TestInterfaceBuilder encoder(Encoder encoder) { + delegate.encoder(encoder); return this; } diff --git a/core/src/test/java/feign/assertj/RequestTemplateAssert.java b/core/src/test/java/feign/assertj/RequestTemplateAssert.java index 82832220..b2145ae7 100644 --- a/core/src/test/java/feign/assertj/RequestTemplateAssert.java +++ b/core/src/test/java/feign/assertj/RequestTemplateAssert.java @@ -46,7 +46,12 @@ public final class RequestTemplateAssert extends AbstractAssert", actual.bodyTemplate()); + } + objects.assertEqual(info, new String(actual.body(), UTF_8), utf8Expected); + return this; } public RequestTemplateAssert hasBody(byte[] expected) { diff --git a/core/src/test/java/feign/codec/DefaultEncoderTest.java b/core/src/test/java/feign/codec/DefaultEncoderTest.java index 1b643aa9..71d33674 100644 --- a/core/src/test/java/feign/codec/DefaultEncoderTest.java +++ b/core/src/test/java/feign/codec/DefaultEncoderTest.java @@ -34,14 +34,14 @@ public class DefaultEncoderTest { @Test public void testEncodesStrings() throws Exception { String content = "This is my content"; RequestTemplate template = new RequestTemplate(); - encoder.encode(content, template); + encoder.encode(content, String.class, template); assertEquals(content, new String(template.body(), UTF_8)); } @Test public void testEncodesByteArray() throws Exception { byte[] content = {12, 34, 56}; RequestTemplate template = new RequestTemplate(); - encoder.encode(content, template); + encoder.encode(content, byte[].class, template); assertTrue(Arrays.equals(content, template.body())); } @@ -49,6 +49,6 @@ public class DefaultEncoderTest { thrown.expect(EncodeException.class); thrown.expectMessage("is not a type supported by this encoder."); - encoder.encode(new Date(), new RequestTemplate()); + encoder.encode(new Date(), Date.class, new RequestTemplate()); } } diff --git a/gson/src/main/java/feign/gson/GsonEncoder.java b/gson/src/main/java/feign/gson/GsonEncoder.java index 57e1b54c..a01772b6 100644 --- a/gson/src/main/java/feign/gson/GsonEncoder.java +++ b/gson/src/main/java/feign/gson/GsonEncoder.java @@ -19,6 +19,7 @@ import com.google.gson.Gson; import com.google.gson.TypeAdapter; import feign.RequestTemplate; import feign.codec.Encoder; +import java.lang.reflect.Type; import java.util.Collections; public class GsonEncoder implements Encoder { @@ -36,7 +37,7 @@ public class GsonEncoder implements Encoder { this.gson = gson; } - @Override public void encode(Object object, RequestTemplate template) { - template.body(gson.toJson(object)); + @Override public void encode(Object object, Type bodyType, RequestTemplate template) { + template.body(gson.toJson(object, bodyType)); } } diff --git a/gson/src/test/java/feign/gson/GsonCodecTest.java b/gson/src/test/java/feign/gson/GsonCodecTest.java index dab2824d..c3b1ba3b 100644 --- a/gson/src/test/java/feign/gson/GsonCodecTest.java +++ b/gson/src/test/java/feign/gson/GsonCodecTest.java @@ -43,7 +43,7 @@ public class GsonCodecTest { map.put("foo", 1); RequestTemplate template = new RequestTemplate(); - new GsonEncoder().encode(map, template); + new GsonEncoder().encode(map, map.getClass(), template); assertThat(template).hasBody("" // + "{\n" // @@ -68,7 +68,7 @@ public class GsonCodecTest { form.put("bar", Arrays.asList(2, 3)); RequestTemplate template = new RequestTemplate(); - new GsonEncoder().encode(form, template); + new GsonEncoder().encode(form, new TypeToken>(){}.getType(), template); assertThat(template).hasBody("" // + "{\n" // @@ -129,7 +129,11 @@ public class GsonCodecTest { final TypeAdapter upperZone = new TypeAdapter() { @Override public void write(JsonWriter out, Zone value) throws IOException { - throw new IllegalArgumentException(); + out.beginObject(); + for(Map.Entry entry : value.entrySet()) { + out.name(entry.getKey()).value(entry.getValue().toString().toUpperCase()); + } + out.endObject(); } @Override public Zone read(JsonReader in) throws IOException { @@ -155,4 +159,26 @@ public class GsonCodecTest { assertEquals(zones, decoder.decode(response, new TypeToken>() { }.getType())); } + + @Test public void customEncoder() throws Exception { + GsonEncoder encoder = new GsonEncoder(Arrays.>asList(upperZone)); + + List zones = new LinkedList(); + zones.add(new Zone("denominator.io.")); + zones.add(new Zone("denominator.io.", "abcd")); + + RequestTemplate template = new RequestTemplate(); + encoder.encode(zones, new TypeToken>(){}.getType(), template); + + assertThat(template).hasBody("" // + + "[\n" // + + " {\n" // + + " \"name\": \"DENOMINATOR.IO.\"\n" // + + " },\n" // + + " {\n" // + + " \"name\": \"DENOMINATOR.IO.\",\n" // + + " \"id\": \"ABCD\"\n" // + + " }\n" // + + "]"); + } } diff --git a/jackson/src/main/java/feign/jackson/JacksonEncoder.java b/jackson/src/main/java/feign/jackson/JacksonEncoder.java index 2d0353f9..1b8db303 100644 --- a/jackson/src/main/java/feign/jackson/JacksonEncoder.java +++ b/jackson/src/main/java/feign/jackson/JacksonEncoder.java @@ -17,12 +17,14 @@ package feign.jackson; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import feign.RequestTemplate; import feign.codec.EncodeException; import feign.codec.Encoder; +import java.lang.reflect.Type; import java.util.Collections; public class JacksonEncoder implements Encoder { @@ -43,9 +45,10 @@ public class JacksonEncoder implements Encoder { this.mapper = mapper; } - @Override public void encode(Object object, RequestTemplate template) throws EncodeException { + @Override public void encode(Object object, Type bodyType, RequestTemplate template) { try { - template.body(mapper.writeValueAsString(object)); + JavaType javaType = mapper.getTypeFactory().constructType(bodyType); + template.body(mapper.writerWithType(javaType).writeValueAsString(object)); } catch (JsonProcessingException e) { throw new EncodeException(e.getMessage(), e); } diff --git a/jackson/src/test/java/feign/jackson/JacksonCodecTest.java b/jackson/src/test/java/feign/jackson/JacksonCodecTest.java index f59a7bfa..3bcaaf06 100644 --- a/jackson/src/test/java/feign/jackson/JacksonCodecTest.java +++ b/jackson/src/test/java/feign/jackson/JacksonCodecTest.java @@ -1,12 +1,15 @@ package feign.jackson; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; import feign.RequestTemplate; import feign.Response; import java.io.IOException; @@ -31,7 +34,7 @@ public class JacksonCodecTest { map.put("foo", 1); RequestTemplate template = new RequestTemplate(); - new JacksonEncoder().encode(map, template); + new JacksonEncoder().encode(map, map.getClass(), template); assertThat(template).hasBody(""// + "{\n" // @@ -45,7 +48,8 @@ public class JacksonCodecTest { form.put("bar", Arrays.asList(2, 3)); RequestTemplate template = new RequestTemplate(); - new JacksonEncoder().encode(form, template); + new JacksonEncoder().encode(form, new TypeReference>() { + }.getType(), template); assertThat(template).hasBody(""// + "{\n" // @@ -120,14 +124,9 @@ public class JacksonCodecTest { } } - static class ZoneModule extends SimpleModule { - public ZoneModule() { - addDeserializer(Zone.class, new ZoneDeserializer()); - } - } - @Test public void customDecoder() throws Exception { - JacksonDecoder decoder = new JacksonDecoder(Arrays.asList(new ZoneModule())); + JacksonDecoder decoder = new JacksonDecoder( + Arrays.asList(new SimpleModule().addDeserializer(Zone.class, new ZoneDeserializer()))); List zones = new LinkedList(); zones.add(new Zone("DENOMINATOR.IO.")); @@ -135,7 +134,42 @@ public class JacksonCodecTest { Response response = Response.create(200, "OK", Collections.>emptyMap(), zonesJson, UTF_8); - assertEquals(zones, decoder.decode(response, new TypeReference>() { - }.getType())); + assertEquals(zones, decoder.decode(response, new TypeReference>(){}.getType())); + } + + static class ZoneSerializer extends StdSerializer { + public ZoneSerializer() { + super(Zone.class); + } + + @Override public void serialize(Zone value, JsonGenerator jgen, SerializerProvider provider) + throws IOException { + jgen.writeStartObject(); + for(Map.Entry entry : value.entrySet()) { + jgen.writeFieldName(entry.getKey()); + jgen.writeString(entry.getValue().toString().toUpperCase()); + } + jgen.writeEndObject(); + } + } + + @Test public void customEncoder() throws Exception { + JacksonEncoder encoder = new JacksonEncoder( + Arrays.asList(new SimpleModule().addSerializer(Zone.class, new ZoneSerializer()))); + + List zones = new LinkedList(); + zones.add(new Zone("denominator.io.")); + zones.add(new Zone("denominator.io.", "abcd")); + + RequestTemplate template = new RequestTemplate(); + encoder.encode(zones, new TypeReference>(){}.getType(), template); + + assertThat(template).hasBody("" // + + "[ {\n" + + " \"name\" : \"DENOMINATOR.IO.\"\n" + + "}, {\n" + + " \"name\" : \"DENOMINATOR.IO.\",\n" + + " \"id\" : \"ABCD\"\n" + + "} ]"); } } diff --git a/jaxb/src/main/java/feign/jaxb/JAXBContextFactory.java b/jaxb/src/main/java/feign/jaxb/JAXBContextFactory.java index 39293259..b12ca555 100644 --- a/jaxb/src/main/java/feign/jaxb/JAXBContextFactory.java +++ b/jaxb/src/main/java/feign/jaxb/JAXBContextFactory.java @@ -29,100 +29,100 @@ import java.util.concurrent.ConcurrentHashMap; * Creates and caches JAXB contexts as well as creates Marshallers and Unmarshallers for each context. */ public final class JAXBContextFactory { - private final ConcurrentHashMap jaxbContexts = new ConcurrentHashMap(64); - private final Map properties; + private final ConcurrentHashMap jaxbContexts = new ConcurrentHashMap(64); + private final Map properties; - private JAXBContextFactory(Map properties) { - this.properties = properties; + private JAXBContextFactory(Map properties) { + this.properties = properties; + } + + /** + * Creates a new {@link javax.xml.bind.Unmarshaller} that handles the supplied class. + */ + public Unmarshaller createUnmarshaller(Class clazz) throws JAXBException { + JAXBContext ctx = getContext(clazz); + return ctx.createUnmarshaller(); + } + + /** + * Creates a new {@link javax.xml.bind.Marshaller} that handles the supplied class. + */ + public Marshaller createMarshaller(Class clazz) throws JAXBException { + JAXBContext ctx = getContext(clazz); + Marshaller marshaller = ctx.createMarshaller(); + setMarshallerProperties(marshaller); + return marshaller; + } + + private void setMarshallerProperties(Marshaller marshaller) throws PropertyException { + Iterator keys = properties.keySet().iterator(); + + while (keys.hasNext()) { + String key = keys.next(); + marshaller.setProperty(key, properties.get(key)); + } + } + + private JAXBContext getContext(Class clazz) throws JAXBException { + JAXBContext jaxbContext = this.jaxbContexts.get(clazz); + if (jaxbContext == null) { + jaxbContext = JAXBContext.newInstance(clazz); + this.jaxbContexts.putIfAbsent(clazz, jaxbContext); } + return jaxbContext; + } + + /** + * Creates instances of {@link feign.jaxb.JAXBContextFactory} + */ + public static class Builder { + private final Map properties = new HashMap(5); /** - * Creates a new {@link javax.xml.bind.Unmarshaller} that handles the supplied class. + * Sets the jaxb.encoding property of any Marshaller created by this factory. */ - public Unmarshaller createUnmarshaller(Class clazz) throws JAXBException { - JAXBContext ctx = getContext(clazz); - return ctx.createUnmarshaller(); + public Builder withMarshallerJAXBEncoding(String value) { + properties.put(Marshaller.JAXB_ENCODING, value); + return this; } /** - * Creates a new {@link javax.xml.bind.Marshaller} that handles the supplied class. + * Sets the jaxb.schemaLocation property of any Marshaller created by this factory. */ - public Marshaller createMarshaller(Class clazz) throws JAXBException { - JAXBContext ctx = getContext(clazz); - Marshaller marshaller = ctx.createMarshaller(); - setMarshallerProperties(marshaller); - return marshaller; + public Builder withMarshallerSchemaLocation(String value) { + properties.put(Marshaller.JAXB_SCHEMA_LOCATION, value); + return this; } - private void setMarshallerProperties(Marshaller marshaller) throws PropertyException { - Iterator keys = properties.keySet().iterator(); - - while(keys.hasNext()) { - String key = keys.next(); - marshaller.setProperty(key, properties.get(key)); - } + /** + * Sets the jaxb.noNamespaceSchemaLocation property of any Marshaller created by this factory. + */ + public Builder withMarshallerNoNamespaceSchemaLocation(String value) { + properties.put(Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION, value); + return this; } - private JAXBContext getContext(Class clazz) throws JAXBException { - JAXBContext jaxbContext = this.jaxbContexts.get(clazz); - if (jaxbContext == null) { - jaxbContext = JAXBContext.newInstance(clazz); - this.jaxbContexts.putIfAbsent(clazz, jaxbContext); - } - return jaxbContext; + /** + * Sets the jaxb.formatted.output property of any Marshaller created by this factory. + */ + public Builder withMarshallerFormattedOutput(Boolean value) { + properties.put(Marshaller.JAXB_FORMATTED_OUTPUT, value); + return this; } /** - * Creates instances of {@link feign.jaxb.JAXBContextFactory} + * Sets the jaxb.fragment property of any Marshaller created by this factory. */ - public static class Builder { - private final Map properties = new HashMap(5); - - /** - * Sets the jaxb.encoding property of any Marshaller created by this factory. - */ - public Builder withMarshallerJAXBEncoding(String value) { - properties.put(Marshaller.JAXB_ENCODING, value); - return this; - } - - /** - * Sets the jaxb.schemaLocation property of any Marshaller created by this factory. - */ - public Builder withMarshallerSchemaLocation(String value) { - properties.put(Marshaller.JAXB_SCHEMA_LOCATION, value); - return this; - } - - /** - * Sets the jaxb.noNamespaceSchemaLocation property of any Marshaller created by this factory. - */ - public Builder withMarshallerNoNamespaceSchemaLocation(String value) { - properties.put(Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION, value); - return this; - } - - /** - * Sets the jaxb.formatted.output property of any Marshaller created by this factory. - */ - public Builder withMarshallerFormattedOutput(Boolean value) { - properties.put(Marshaller.JAXB_FORMATTED_OUTPUT, value); - return this; - } - - /** - * Sets the jaxb.fragment property of any Marshaller created by this factory. - */ - public Builder withMarshallerFragment(Boolean value) { - properties.put(Marshaller.JAXB_FRAGMENT, value); - return this; - } + public Builder withMarshallerFragment(Boolean value) { + properties.put(Marshaller.JAXB_FRAGMENT, value); + return this; + } - /** - * Creates a new {@link feign.jaxb.JAXBContextFactory} instance. - */ - public JAXBContextFactory build() { - return new JAXBContextFactory(properties); - } + /** + * Creates a new {@link feign.jaxb.JAXBContextFactory} instance. + */ + public JAXBContextFactory build() { + return new JAXBContextFactory(properties); } + } } diff --git a/jaxb/src/main/java/feign/jaxb/JAXBDecoder.java b/jaxb/src/main/java/feign/jaxb/JAXBDecoder.java index 2cbc3cdb..51775f9f 100644 --- a/jaxb/src/main/java/feign/jaxb/JAXBDecoder.java +++ b/jaxb/src/main/java/feign/jaxb/JAXBDecoder.java @@ -15,7 +15,6 @@ */ package feign.jaxb; -import feign.FeignException; import feign.Response; import feign.codec.DecodeException; import feign.codec.Decoder; @@ -45,23 +44,25 @@ import javax.xml.bind.Unmarshaller; *

*/ public class JAXBDecoder implements Decoder { - private final JAXBContextFactory jaxbContextFactory; + private final JAXBContextFactory jaxbContextFactory; - public JAXBDecoder(JAXBContextFactory jaxbContextFactory) { - this.jaxbContextFactory = jaxbContextFactory; - } + public JAXBDecoder(JAXBContextFactory jaxbContextFactory) { + this.jaxbContextFactory = jaxbContextFactory; + } - @Override - public Object decode(Response response, Type type) throws IOException, FeignException { - try { - Unmarshaller unmarshaller = jaxbContextFactory.createUnmarshaller((Class) type); - return unmarshaller.unmarshal(response.body().asInputStream()); - } catch (JAXBException e) { - throw new DecodeException(e.toString(), e); - } finally { - if(response.body() != null) { - response.body().close(); - } - } + @Override public Object decode(Response response, Type type) throws IOException { + if (!(type instanceof Class)) { + throw new UnsupportedOperationException("JAXB only supports decoding raw types. Found " + type); + } + try { + Unmarshaller unmarshaller = jaxbContextFactory.createUnmarshaller((Class) type); + return unmarshaller.unmarshal(response.body().asInputStream()); + } catch (JAXBException e) { + throw new DecodeException(e.toString(), e); + } finally { + if (response.body() != null) { + response.body().close(); + } } + } } diff --git a/jaxb/src/main/java/feign/jaxb/JAXBEncoder.java b/jaxb/src/main/java/feign/jaxb/JAXBEncoder.java index 4b7801cb..79c546ef 100644 --- a/jaxb/src/main/java/feign/jaxb/JAXBEncoder.java +++ b/jaxb/src/main/java/feign/jaxb/JAXBEncoder.java @@ -19,6 +19,8 @@ import feign.RequestTemplate; import feign.codec.EncodeException; import feign.codec.Encoder; import java.io.StringWriter; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; @@ -43,21 +45,23 @@ import javax.xml.bind.Marshaller; *

*/ public class JAXBEncoder implements Encoder { - private final JAXBContextFactory jaxbContextFactory; + private final JAXBContextFactory jaxbContextFactory; - public JAXBEncoder(JAXBContextFactory jaxbContextFactory) { - this.jaxbContextFactory = jaxbContextFactory; - } + public JAXBEncoder(JAXBContextFactory jaxbContextFactory) { + this.jaxbContextFactory = jaxbContextFactory; + } - @Override - public void encode(Object object, RequestTemplate template) throws EncodeException { - try { - Marshaller marshaller = jaxbContextFactory.createMarshaller(object.getClass()); - StringWriter stringWriter = new StringWriter(); - marshaller.marshal(object, stringWriter); - template.body(stringWriter.toString()); - } catch (JAXBException e) { - throw new EncodeException(e.toString(), e); - } + @Override public void encode(Object object, Type bodyType, RequestTemplate template) { + if (!(bodyType instanceof Class)) { + throw new UnsupportedOperationException("JAXB only supports encoding raw types. Found " + bodyType); + } + try { + Marshaller marshaller = jaxbContextFactory.createMarshaller((Class) bodyType); + StringWriter stringWriter = new StringWriter(); + marshaller.marshal(object, stringWriter); + template.body(stringWriter.toString()); + } catch (JAXBException e) { + throw new EncodeException(e.toString(), e); } + } } diff --git a/jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java b/jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java index 30bb99ff..051e644f 100644 --- a/jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java +++ b/jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java @@ -18,148 +18,170 @@ package feign.jaxb; import feign.RequestTemplate; import feign.Response; import feign.codec.Encoder; +import java.lang.reflect.Type; import java.util.Collection; import java.util.Collections; +import java.util.Map; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import static feign.Util.UTF_8; import static feign.assertj.FeignAssertions.assertThat; import static org.junit.Assert.assertEquals; public class JAXBCodecTest { + @Rule public final ExpectedException thrown = ExpectedException.none(); - @XmlRootElement - @XmlAccessorType(XmlAccessType.FIELD) - static class MockObject { - - @XmlElement - private String value; - - @Override - public boolean equals(Object obj) { - if (obj instanceof MockObject) { - MockObject other = (MockObject) obj; - return value.equals(other.value); - } - return false; - } - - @Override - public int hashCode() { - return value != null ? value.hashCode() : 0; - } - } + @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) static class MockObject { - @Test - public void encodesXml() throws Exception { - MockObject mock = new MockObject(); - mock.value = "Test"; + @XmlElement private String value; - RequestTemplate template = new RequestTemplate(); - new JAXBEncoder(new JAXBContextFactory.Builder().build()).encode(mock, template); + @Override public boolean equals(Object obj) { + if (obj instanceof MockObject) { + MockObject other = (MockObject) obj; + return value.equals(other.value); + } + return false; + } - assertThat(template).hasBody( - "Test"); + @Override public int hashCode() { + return value != null ? value.hashCode() : 0; } + } - @Test - public void encodesXmlWithCustomJAXBEncoding() throws Exception { - JAXBContextFactory jaxbContextFactory = new JAXBContextFactory.Builder() - .withMarshallerJAXBEncoding("UTF-16") - .build(); + @Test public void encodesXml() throws Exception { + MockObject mock = new MockObject(); + mock.value = "Test"; - Encoder encoder = new JAXBEncoder(jaxbContextFactory); + RequestTemplate template = new RequestTemplate(); + new JAXBEncoder(new JAXBContextFactory.Builder().build()).encode(mock, MockObject.class, template); - MockObject mock = new MockObject(); - mock.value = "Test"; + assertThat(template).hasBody( + "Test"); + } - RequestTemplate template = new RequestTemplate(); - encoder.encode(mock, template); + @Test public void doesntEncodeParameterizedTypes() throws Exception { + thrown.expect(UnsupportedOperationException.class); + thrown.expectMessage("JAXB only supports encoding raw types. Found java.util.Map"); - assertThat(template).hasBody("Test"); + class ParameterizedHolder { + Map field; } + Type parameterized = ParameterizedHolder.class.getDeclaredField("field").getGenericType(); - @Test - public void encodesXmlWithCustomJAXBSchemaLocation() throws Exception { - JAXBContextFactory jaxbContextFactory = new JAXBContextFactory.Builder() - .withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd") - .build(); + RequestTemplate template = new RequestTemplate(); + new JAXBEncoder(new JAXBContextFactory.Builder().build()).encode(Collections.emptyMap(), parameterized, template); + } - Encoder encoder = new JAXBEncoder(jaxbContextFactory); + @Test public void encodesXmlWithCustomJAXBEncoding() throws Exception { + JAXBContextFactory jaxbContextFactory = + new JAXBContextFactory.Builder().withMarshallerJAXBEncoding("UTF-16").build(); - MockObject mock = new MockObject(); - mock.value = "Test"; + Encoder encoder = new JAXBEncoder(jaxbContextFactory); - RequestTemplate template = new RequestTemplate(); - encoder.encode(mock, template); + MockObject mock = new MockObject(); + mock.value = "Test"; - assertThat(template).hasBody("" + - "Test"); - } + RequestTemplate template = new RequestTemplate(); + encoder.encode(mock, MockObject.class, template); - @Test - public void encodesXmlWithCustomJAXBNoNamespaceSchemaLocation() throws Exception { - JAXBContextFactory jaxbContextFactory = new JAXBContextFactory.Builder() - .withMarshallerNoNamespaceSchemaLocation("http://apihost/schema.xsd") - .build(); + assertThat(template).hasBody("Test"); + } - Encoder encoder = new JAXBEncoder(jaxbContextFactory); + @Test public void encodesXmlWithCustomJAXBSchemaLocation() throws Exception { + JAXBContextFactory jaxbContextFactory = + new JAXBContextFactory.Builder().withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd") + .build(); - MockObject mock = new MockObject(); - mock.value = "Test"; + Encoder encoder = new JAXBEncoder(jaxbContextFactory); - RequestTemplate template = new RequestTemplate(); - encoder.encode(mock, template); + MockObject mock = new MockObject(); + mock.value = "Test"; - assertThat(template).hasBody("" + - "Test"); - } + RequestTemplate template = new RequestTemplate(); + encoder.encode(mock, MockObject.class, template); - @Test - public void encodesXmlWithCustomJAXBFormattedOutput() { - JAXBContextFactory jaxbContextFactory = new JAXBContextFactory.Builder() - .withMarshallerFormattedOutput(true) - .build(); + assertThat(template).hasBody("" + + "Test"); + } - Encoder encoder = new JAXBEncoder(jaxbContextFactory); + @Test public void encodesXmlWithCustomJAXBNoNamespaceSchemaLocation() throws Exception { + JAXBContextFactory jaxbContextFactory = + new JAXBContextFactory.Builder().withMarshallerNoNamespaceSchemaLocation("http://apihost/schema.xsd").build(); - MockObject mock = new MockObject(); - mock.value = "Test"; + Encoder encoder = new JAXBEncoder(jaxbContextFactory); - RequestTemplate template = new RequestTemplate(); - encoder.encode(mock, template); + MockObject mock = new MockObject(); + mock.value = "Test"; - String NEWLINE = System.getProperty("line.separator"); + RequestTemplate template = new RequestTemplate(); + encoder.encode(mock, MockObject.class, template); - assertThat(template).hasBody(new StringBuilder() - .append("").append(NEWLINE) - .append("").append(NEWLINE) - .append(" Test").append(NEWLINE) - .append("").append(NEWLINE).toString()); - } + assertThat(template).hasBody("" + + "Test"); + } + + @Test public void encodesXmlWithCustomJAXBFormattedOutput() { + JAXBContextFactory jaxbContextFactory = + new JAXBContextFactory.Builder().withMarshallerFormattedOutput(true).build(); + + Encoder encoder = new JAXBEncoder(jaxbContextFactory); + + MockObject mock = new MockObject(); + mock.value = "Test"; - @Test - public void decodesXml() throws Exception { - MockObject mock = new MockObject(); - mock.value = "Test"; + RequestTemplate template = new RequestTemplate(); + encoder.encode(mock, MockObject.class, template); - String mockXml = "" + - "Test"; + String NEWLINE = System.getProperty("line.separator"); - Response response = - Response.create(200, "OK", Collections.>emptyMap(), mockXml, UTF_8); + assertThat(template).hasBody( + new StringBuilder().append("") + .append(NEWLINE) + .append("") + .append(NEWLINE) + .append(" Test") + .append(NEWLINE) + .append("") + .append(NEWLINE) + .toString()); + } - JAXBDecoder decoder = new JAXBDecoder(new JAXBContextFactory.Builder().build()); + @Test public void decodesXml() throws Exception { + MockObject mock = new MockObject(); + mock.value = "Test"; - assertEquals(mock, decoder.decode(response, MockObject.class)); + String mockXml = "" + + "Test"; + + Response response = Response.create(200, "OK", Collections.>emptyMap(), mockXml, UTF_8); + + JAXBDecoder decoder = new JAXBDecoder(new JAXBContextFactory.Builder().build()); + + assertEquals(mock, decoder.decode(response, MockObject.class)); + } + + @Test public void doesntDecodeParameterizedTypes() throws Exception { + thrown.expect(UnsupportedOperationException.class); + thrown.expectMessage("JAXB only supports decoding raw types. Found java.util.Map"); + + class ParameterizedHolder { + Map field; } + Type parameterized = ParameterizedHolder.class.getDeclaredField("field").getGenericType(); + + Response response = Response.create(200, "OK", Collections.>emptyMap(), "", UTF_8); + + new JAXBDecoder(new JAXBContextFactory.Builder().build()).decode(response, parameterized); + } } diff --git a/jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java b/jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java index b9ffbd30..4410a666 100644 --- a/jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java +++ b/jaxb/src/test/java/feign/jaxb/JAXBContextFactoryTest.java @@ -22,55 +22,41 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class JAXBContextFactoryTest { - @Test - public void buildsMarshallerWithJAXBEncodingProperty() throws Exception { - JAXBContextFactory factory = new JAXBContextFactory.Builder() - .withMarshallerJAXBEncoding("UTF-16") - .build(); + @Test public void buildsMarshallerWithJAXBEncodingProperty() throws Exception { + JAXBContextFactory factory = new JAXBContextFactory.Builder().withMarshallerJAXBEncoding("UTF-16").build(); - Marshaller marshaller = factory.createMarshaller(Object.class); - assertEquals("UTF-16", marshaller.getProperty(Marshaller.JAXB_ENCODING)); - } + Marshaller marshaller = factory.createMarshaller(Object.class); + assertEquals("UTF-16", marshaller.getProperty(Marshaller.JAXB_ENCODING)); + } - @Test - public void buildsMarshallerWithSchemaLocationProperty() throws Exception { - JAXBContextFactory factory = new JAXBContextFactory.Builder() - .withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd") - .build(); + @Test public void buildsMarshallerWithSchemaLocationProperty() throws Exception { + JAXBContextFactory factory = + new JAXBContextFactory.Builder().withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd") + .build(); - Marshaller marshaller = factory.createMarshaller(Object.class); - assertEquals("http://apihost http://apihost/schema.xsd", - marshaller.getProperty(Marshaller.JAXB_SCHEMA_LOCATION)); - } + Marshaller marshaller = factory.createMarshaller(Object.class); + assertEquals("http://apihost http://apihost/schema.xsd", marshaller.getProperty(Marshaller.JAXB_SCHEMA_LOCATION)); + } - @Test - public void buildsMarshallerWithNoNamespaceSchemaLocationProperty() throws Exception { - JAXBContextFactory factory = new JAXBContextFactory.Builder() - .withMarshallerNoNamespaceSchemaLocation("http://apihost/schema.xsd") - .build(); + @Test public void buildsMarshallerWithNoNamespaceSchemaLocationProperty() throws Exception { + JAXBContextFactory factory = + new JAXBContextFactory.Builder().withMarshallerNoNamespaceSchemaLocation("http://apihost/schema.xsd").build(); - Marshaller marshaller = factory.createMarshaller(Object.class); - assertEquals("http://apihost/schema.xsd", - marshaller.getProperty(Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION)); - } + Marshaller marshaller = factory.createMarshaller(Object.class); + assertEquals("http://apihost/schema.xsd", marshaller.getProperty(Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION)); + } - @Test - public void buildsMarshallerWithFormattedOutputProperty() throws Exception { - JAXBContextFactory factory = new JAXBContextFactory.Builder() - .withMarshallerFormattedOutput(true) - .build(); + @Test public void buildsMarshallerWithFormattedOutputProperty() throws Exception { + JAXBContextFactory factory = new JAXBContextFactory.Builder().withMarshallerFormattedOutput(true).build(); - Marshaller marshaller = factory.createMarshaller(Object.class); - assertTrue((Boolean) marshaller.getProperty(Marshaller.JAXB_FORMATTED_OUTPUT)); - } + Marshaller marshaller = factory.createMarshaller(Object.class); + assertTrue((Boolean) marshaller.getProperty(Marshaller.JAXB_FORMATTED_OUTPUT)); + } - @Test - public void buildsMarshallerWithFragmentProperty() throws Exception { - JAXBContextFactory factory = new JAXBContextFactory.Builder() - .withMarshallerFragment(true) - .build(); + @Test public void buildsMarshallerWithFragmentProperty() throws Exception { + JAXBContextFactory factory = new JAXBContextFactory.Builder().withMarshallerFragment(true).build(); - Marshaller marshaller = factory.createMarshaller(Object.class); - assertTrue((Boolean) marshaller.getProperty(Marshaller.JAXB_FRAGMENT)); - } + Marshaller marshaller = factory.createMarshaller(Object.class); + assertTrue((Boolean) marshaller.getProperty(Marshaller.JAXB_FRAGMENT)); + } } diff --git a/jaxb/src/test/java/feign/jaxb/examples/AWSSignatureVersion4.java b/jaxb/src/test/java/feign/jaxb/examples/AWSSignatureVersion4.java index aaacbe71..683638fd 100644 --- a/jaxb/src/test/java/feign/jaxb/examples/AWSSignatureVersion4.java +++ b/jaxb/src/test/java/feign/jaxb/examples/AWSSignatureVersion4.java @@ -30,133 +30,132 @@ import static feign.Util.UTF_8; // http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html public class AWSSignatureVersion4 { - String region = "us-east-1"; - String service = "iam"; - String accessKey; - String secretKey; - - public AWSSignatureVersion4(String accessKey, String secretKey) { - this.accessKey = accessKey; - this.secretKey = secretKey; - } - - public Request apply(RequestTemplate input) { - if (!input.headers().isEmpty()) throw new UnsupportedOperationException("headers not supported"); - if (input.body() != null) throw new UnsupportedOperationException("body not supported"); - - String host = URI.create(input.url()).getHost(); - - String timestamp; - synchronized (iso8601) { - timestamp = iso8601.format(new Date()); - } - - String credentialScope = String.format("%s/%s/%s/%s", timestamp.substring(0, 8), region, service, "aws4_request"); - - input.query("X-Amz-Algorithm", "AWS4-HMAC-SHA256"); - input.query("X-Amz-Credential", accessKey + "/" + credentialScope); - input.query("X-Amz-Date", timestamp); - input.query("X-Amz-SignedHeaders", "host"); - input.header("Host", host); - - String canonicalString = canonicalString(input, host); - String toSign = toSign(timestamp, credentialScope, canonicalString); + String region = "us-east-1"; + String service = "iam"; + String accessKey; + String secretKey; - byte[] signatureKey = signatureKey(secretKey, timestamp); - String signature = hex(hmacSHA256(toSign, signatureKey)); + public AWSSignatureVersion4(String accessKey, String secretKey) { + this.accessKey = accessKey; + this.secretKey = secretKey; + } - input.query("X-Amz-Signature", signature); + public Request apply(RequestTemplate input) { + if (!input.headers().isEmpty()) throw new UnsupportedOperationException("headers not supported"); + if (input.body() != null) throw new UnsupportedOperationException("body not supported"); - return input.request(); - } + String host = URI.create(input.url()).getHost(); - byte[] signatureKey(String secretKey, String timestamp) { - byte[] kSecret = ("AWS4" + secretKey).getBytes(UTF_8); - byte[] kDate = hmacSHA256(timestamp.substring(0, 8), kSecret); - byte[] kRegion = hmacSHA256(region, kDate); - byte[] kService = hmacSHA256(service, kRegion); - byte[] kSigning = hmacSHA256("aws4_request", kService); - return kSigning; + String timestamp; + synchronized (iso8601) { + timestamp = iso8601.format(new Date()); } - static byte[] hmacSHA256(String data, byte[] key) { - try { - String algorithm = "HmacSHA256"; - Mac mac = Mac.getInstance(algorithm); - mac.init(new SecretKeySpec(key, algorithm)); - return mac.doFinal(data.getBytes(UTF_8)); - } catch (Exception e) { - throw new RuntimeException(e); - } + String credentialScope = String.format("%s/%s/%s/%s", timestamp.substring(0, 8), region, service, "aws4_request"); + + input.query("X-Amz-Algorithm", "AWS4-HMAC-SHA256"); + input.query("X-Amz-Credential", accessKey + "/" + credentialScope); + input.query("X-Amz-Date", timestamp); + input.query("X-Amz-SignedHeaders", "host"); + input.header("Host", host); + + String canonicalString = canonicalString(input, host); + String toSign = toSign(timestamp, credentialScope, canonicalString); + + byte[] signatureKey = signatureKey(secretKey, timestamp); + String signature = hex(hmacSHA256(toSign, signatureKey)); + + input.query("X-Amz-Signature", signature); + + return input.request(); + } + + byte[] signatureKey(String secretKey, String timestamp) { + byte[] kSecret = ("AWS4" + secretKey).getBytes(UTF_8); + byte[] kDate = hmacSHA256(timestamp.substring(0, 8), kSecret); + byte[] kRegion = hmacSHA256(region, kDate); + byte[] kService = hmacSHA256(service, kRegion); + byte[] kSigning = hmacSHA256("aws4_request", kService); + return kSigning; + } + + static byte[] hmacSHA256(String data, byte[] key) { + try { + String algorithm = "HmacSHA256"; + Mac mac = Mac.getInstance(algorithm); + mac.init(new SecretKeySpec(key, algorithm)); + return mac.doFinal(data.getBytes(UTF_8)); + } catch (Exception e) { + throw new RuntimeException(e); } + } - private static final String EMPTY_STRING_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + private static final String EMPTY_STRING_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; - private static String canonicalString(RequestTemplate input, String host) { - StringBuilder canonicalRequest = new StringBuilder(); - // HTTPRequestMethod + '\n' + - canonicalRequest.append(input.method()).append('\n'); + private static String canonicalString(RequestTemplate input, String host) { + StringBuilder canonicalRequest = new StringBuilder(); + // HTTPRequestMethod + '\n' + + canonicalRequest.append(input.method()).append('\n'); - // CanonicalURI + '\n' + - canonicalRequest.append(URI.create(input.url()).getPath()).append('\n'); + // CanonicalURI + '\n' + + canonicalRequest.append(URI.create(input.url()).getPath()).append('\n'); - // CanonicalQueryString + '\n' + - canonicalRequest.append(input.queryLine().substring(1)); - canonicalRequest.append('\n'); + // CanonicalQueryString + '\n' + + canonicalRequest.append(input.queryLine().substring(1)); + canonicalRequest.append('\n'); - // CanonicalHeaders + '\n' + - canonicalRequest.append("host:").append(host).append('\n'); + // CanonicalHeaders + '\n' + + canonicalRequest.append("host:").append(host).append('\n'); - canonicalRequest.append('\n'); + canonicalRequest.append('\n'); - // SignedHeaders + '\n' + - canonicalRequest.append("host").append('\n'); + // SignedHeaders + '\n' + + canonicalRequest.append("host").append('\n'); - // HexEncode(Hash(Payload)) - String bodyText = - input.charset() != null && input.body() != null ? new String(input.body(), input.charset()) : null; - if (bodyText != null) { - canonicalRequest.append(hex(sha256(bodyText))); - } else { - canonicalRequest.append(EMPTY_STRING_HASH); - } - return canonicalRequest.toString(); + // HexEncode(Hash(Payload)) + String bodyText = + input.charset() != null && input.body() != null ? new String(input.body(), input.charset()) : null; + if (bodyText != null) { + canonicalRequest.append(hex(sha256(bodyText))); + } else { + canonicalRequest.append(EMPTY_STRING_HASH); } - - private static String toSign(String timestamp, String credentialScope, String canonicalRequest) { - StringBuilder toSign = new StringBuilder(); - // Algorithm + '\n' + - toSign.append("AWS4-HMAC-SHA256").append('\n'); - // RequestDate + '\n' + - toSign.append(timestamp).append('\n'); - // CredentialScope + '\n' + - toSign.append(credentialScope).append('\n'); - // HexEncode(Hash(CanonicalRequest)) - toSign.append(hex(sha256(canonicalRequest))); - return toSign.toString(); + return canonicalRequest.toString(); + } + + private static String toSign(String timestamp, String credentialScope, String canonicalRequest) { + StringBuilder toSign = new StringBuilder(); + // Algorithm + '\n' + + toSign.append("AWS4-HMAC-SHA256").append('\n'); + // RequestDate + '\n' + + toSign.append(timestamp).append('\n'); + // CredentialScope + '\n' + + toSign.append(credentialScope).append('\n'); + // HexEncode(Hash(CanonicalRequest)) + toSign.append(hex(sha256(canonicalRequest))); + return toSign.toString(); + } + + private static String hex(byte[] data) { + StringBuilder result = new StringBuilder(data.length * 2); + for (byte b : data) { + result.append(String.format("%02x", b & 0xff)); } - - - private static String hex(byte[] data) { - StringBuilder result = new StringBuilder(data.length * 2); - for (byte b : data) { - result.append(String.format("%02x", b & 0xff)); - } - return result.toString(); + return result.toString(); + } + + static byte[] sha256(String data) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + return digest.digest(data.getBytes(UTF_8)); + } catch (Exception e) { + throw new RuntimeException(e); } + } - static byte[] sha256(String data) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - return digest.digest(data.getBytes(UTF_8)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static final SimpleDateFormat iso8601 = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); + private static final SimpleDateFormat iso8601 = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); - static { - iso8601.setTimeZone(TimeZone.getTimeZone("GMT")); - } + static { + iso8601.setTimeZone(TimeZone.getTimeZone("GMT")); + } } diff --git a/jaxb/src/test/java/feign/jaxb/examples/IAMExample.java b/jaxb/src/test/java/feign/jaxb/examples/IAMExample.java index cdf64245..e8443ffd 100644 --- a/jaxb/src/test/java/feign/jaxb/examples/IAMExample.java +++ b/jaxb/src/test/java/feign/jaxb/examples/IAMExample.java @@ -30,61 +30,53 @@ import javax.xml.bind.annotation.XmlType; public class IAMExample { - interface IAM { - @RequestLine("GET /?Action=GetUser&Version=2010-05-08") GetUserResponse userResponse(); - } - - public static void main(String... args) { - IAM iam = Feign.builder() - .decoder(new JAXBDecoder(new JAXBContextFactory.Builder().build())) - .target(new IAMTarget(args[0], args[1])); - - GetUserResponse response = iam.userResponse(); - System.out.println("UserId: " + response.result.user.id); - } - - static class IAMTarget extends AWSSignatureVersion4 implements Target { + interface IAM { + @RequestLine("GET /?Action=GetUser&Version=2010-05-08") GetUserResponse userResponse(); + } - @Override public Class type() { - return IAM.class; - } + public static void main(String... args) { + IAM iam = Feign.builder() + .decoder(new JAXBDecoder(new JAXBContextFactory.Builder().build())) + .target(new IAMTarget(args[0], args[1])); - @Override public String name() { - return "iam"; - } + GetUserResponse response = iam.userResponse(); + System.out.println("UserId: " + response.result.user.id); + } - @Override public String url() { - return "https://iam.amazonaws.com"; - } + static class IAMTarget extends AWSSignatureVersion4 implements Target { - private IAMTarget(String accessKey, String secretKey) { - super(accessKey, secretKey); - } + @Override public Class type() { + return IAM.class; + } - @Override public Request apply(RequestTemplate in) { - in.insert(0, url()); - return super.apply(in); - } + @Override public String name() { + return "iam"; } - @XmlRootElement(name = "GetUserResponse", namespace = "https://iam.amazonaws.com/doc/2010-05-08/") - @XmlAccessorType(XmlAccessType.FIELD) - static class GetUserResponse { - @XmlElement(name = "GetUserResult") - private GetUserResult result; + @Override public String url() { + return "https://iam.amazonaws.com"; } - @XmlAccessorType(XmlAccessType.FIELD) - @XmlType(name = "GetUserResult") - static class GetUserResult { - @XmlElement(name = "User") - private User user; + private IAMTarget(String accessKey, String secretKey) { + super(accessKey, secretKey); } - @XmlAccessorType(XmlAccessType.FIELD) - @XmlType(name = "User") - static class User { - @XmlElement(name = "UserId") - private String id; + @Override public Request apply(RequestTemplate in) { + in.insert(0, url()); + return super.apply(in); } + } + + @XmlRootElement(name = "GetUserResponse", namespace = "https://iam.amazonaws.com/doc/2010-05-08/") + @XmlAccessorType(XmlAccessType.FIELD) static class GetUserResponse { + @XmlElement(name = "GetUserResult") private GetUserResult result; + } + + @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "GetUserResult") static class GetUserResult { + @XmlElement(name = "User") private User user; + } + + @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "User") static class User { + @XmlElement(name = "UserId") private String id; + } } diff --git a/jaxb/src/test/java/feign/jaxb/examples/package-info.java b/jaxb/src/test/java/feign/jaxb/examples/package-info.java index 0038947a..d52c85ad 100644 --- a/jaxb/src/test/java/feign/jaxb/examples/package-info.java +++ b/jaxb/src/test/java/feign/jaxb/examples/package-info.java @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@javax.xml.bind.annotation.XmlSchema(namespace = "https://iam.amazonaws.com/doc/2010-05-08/", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED) -package feign.jaxb.examples; +@javax.xml.bind.annotation.XmlSchema(namespace = "https://iam.amazonaws.com/doc/2010-05-08/", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED) package feign.jaxb.examples; diff --git a/jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java b/jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java index e3cb2872..a4b28678 100644 --- a/jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java +++ b/jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java @@ -44,7 +44,7 @@ import static java.util.Arrays.asList; import static org.assertj.core.data.MapEntry.entry; /** - * Tests interfaces defined per {@link feign.jaxrs.JAXRSContract} are interpreted into expected {@link feign + * Tests interfaces defined per {@link JAXRSContract} are interpreted into expected {@link feign * .RequestTemplate template} * instances. */ @@ -332,6 +332,14 @@ public class JAXRSContractTest { ); } + /** Body type is only for the body param. */ + @Test public void formParamsDoesNotSetBodyType() throws Exception { + MethodMetadata md = contract.parseAndValidatateMetadata(FormParams.class.getDeclaredMethod("login", String.class, + String.class, String.class)); + + assertThat(md.bodyType()).isNull(); + } + @Test public void emptyFormParam() throws Exception { thrown.expect(IllegalStateException.class); thrown.expectMessage("FormParam.value() was empty on parameter 0");