diff --git a/core/src/main/java/org/jclouds/rest/annotations/JAXBResponseParser.java b/core/src/main/java/org/jclouds/rest/annotations/JAXBResponseParser.java index c6cb594ff4..df2d07b8fe 100644 --- a/core/src/main/java/org/jclouds/rest/annotations/JAXBResponseParser.java +++ b/core/src/main/java/org/jclouds/rest/annotations/JAXBResponseParser.java @@ -24,6 +24,10 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import javax.lang.model.type.NullType; + +import org.jclouds.http.functions.ParseXMLWithJAXB; + /** * Shows the transformer type used to parse XML with the * {@link ParseXMLWithJAXB} parser in a HttpResponse. @@ -34,4 +38,10 @@ import java.lang.annotation.Target; @Retention(RUNTIME) public @interface JAXBResponseParser { + /** + * If present, this is the class that will be used to unmarshal the XML + * document. If omitted, the return type of the annotated method will be + * used. + */ + Class value() default NullType.class; } diff --git a/core/src/main/java/org/jclouds/rest/internal/RestAnnotationProcessor.java b/core/src/main/java/org/jclouds/rest/internal/RestAnnotationProcessor.java index 8e8a514a66..3d8e8570f3 100644 --- a/core/src/main/java/org/jclouds/rest/internal/RestAnnotationProcessor.java +++ b/core/src/main/java/org/jclouds/rest/internal/RestAnnotationProcessor.java @@ -67,6 +67,7 @@ import java.util.Set; import java.util.SortedSet; import javax.annotation.Resource; +import javax.lang.model.type.NullType; import javax.ws.rs.Consumes; import javax.ws.rs.FormParam; import javax.ws.rs.HeaderParam; @@ -815,10 +816,16 @@ public abstract class RestAnnotationProcessor { @SuppressWarnings("unchecked") public static Key> getJAXBParserKeyForMethod(Method method) { - Type returnVal = getReturnTypeForMethod(method); - Type parserType = Types.newParameterizedType(ParseXMLWithJAXB.class, returnVal); - return (Key>) Key.get(parserType); - } + Optional configuredReturnVal = Optional.absent(); + if (method.isAnnotationPresent(JAXBResponseParser.class)) { + Type configuredClass = method.getAnnotation(JAXBResponseParser.class).value(); + configuredReturnVal = configuredClass.equals(NullType.class) ? Optional. absent() : Optional + . of(configuredClass); + } + Type returnVal = configuredReturnVal.or(getReturnTypeForMethod(method)); + Type parserType = Types.newParameterizedType(ParseXMLWithJAXB.class, returnVal); + return (Key>) Key.get(parserType); + } public static Type getReturnTypeForMethod(Method method) { Type returnVal; diff --git a/core/src/test/java/org/jclouds/rest/annotationparsing/JAXBResponseParserAnnotationExpectTest.java b/core/src/test/java/org/jclouds/rest/annotationparsing/JAXBResponseParserAnnotationExpectTest.java new file mode 100644 index 0000000000..f4a918cf12 --- /dev/null +++ b/core/src/test/java/org/jclouds/rest/annotationparsing/JAXBResponseParserAnnotationExpectTest.java @@ -0,0 +1,158 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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.jclouds.rest.annotationparsing; + +import static org.jclouds.providers.AnonymousProviderMetadata.forClientMappedToAsyncClientOnEndpoint; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.MediaType; +import javax.xml.bind.annotation.XmlRootElement; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.providers.ProviderMetadata; +import org.jclouds.rest.annotations.JAXBResponseParser; +import org.jclouds.rest.annotations.Transform; +import org.jclouds.rest.internal.BaseRestClientExpectTest; +import org.testng.annotations.Test; + +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Tests the use of the {@link JAXBResponseParser} annotation. + * + * @author Ignasi Barrera + */ +@Test(groups = "unit", testName = "JAXBResponseParserAnnotationExpectTest") +public class JAXBResponseParserAnnotationExpectTest extends + BaseRestClientExpectTest { + + @XmlRootElement(name = "test") + public static class TestJAXBDomain { + private String elem; + + public String getElem() { + return elem; + } + + public void setElem(String elem) { + this.elem = elem; + } + + @Override + public String toString() { + return "TestJAXBDomain [elem=" + elem + "]"; + } + + } + + public interface TestJAXBApi { + public TestJAXBDomain jaxbGetWithAnnotation(); + + public Object jaxbGetWithAnnotationAndCustomClass(); + + public TestJAXBDomain jaxbGetWithAcceptHeader(); + + public String jaxbGetWithTransformer(); + } + + public interface TestJAXBAsyncApi { + @GET + @Path("/jaxb/annotation") + @JAXBResponseParser + public ListenableFuture jaxbGetWithAnnotation(); + + @GET + @Path("/jaxb/custom") + @JAXBResponseParser(TestJAXBDomain.class) + public ListenableFuture jaxbGetWithAnnotationAndCustomClass(); + + @GET + @Path("/jaxb/header") + @Consumes(MediaType.APPLICATION_XML) + public ListenableFuture jaxbGetWithAcceptHeader(); + + @GET + @Path("/jaxb/transformer") + @JAXBResponseParser(TestJAXBDomain.class) + @Transform(ToString.class) + public ListenableFuture jaxbGetWithTransformer(); + } + + private static class ToString implements Function { + @Override + public String apply(Object input) { + return Functions.toStringFunction().apply(input); + } + } + + @Test + public void testJAXBResponseParserAnnotationWithoutValue() throws SecurityException, NoSuchMethodException { + TestJAXBApi api = requestSendsResponse( // + HttpRequest.builder().method("GET").endpoint("http://mock/jaxb/annotation").build(), // + HttpResponse.builder().statusCode(200).payload("Hello World").build()); + + TestJAXBDomain result = api.jaxbGetWithAnnotation(); + assertEquals(result.getElem(), "Hello World"); + } + + @Test + public void testJAXBResponseParserAnnotationWithCustomValue() throws SecurityException, NoSuchMethodException { + TestJAXBApi api = requestSendsResponse( // + HttpRequest.builder().method("GET").endpoint("http://mock/jaxb/custom").build(), // + HttpResponse.builder().statusCode(200).payload("Hello World").build()); + + Object result = api.jaxbGetWithAnnotationAndCustomClass(); + assertTrue(result instanceof TestJAXBDomain); + assertEquals(TestJAXBDomain.class.cast(result).getElem(), "Hello World"); + } + + @Test + public void testJAXBResponseParserAnnotationWithAcceptHeader() throws SecurityException, NoSuchMethodException { + TestJAXBApi api = requestSendsResponse( // + HttpRequest.builder().method("GET").endpoint("http://mock/jaxb/header") + .addHeader("Accept", MediaType.APPLICATION_XML).build(), // + HttpResponse.builder().statusCode(200).payload("Hello World").build()); + + TestJAXBDomain result = api.jaxbGetWithAcceptHeader(); + assertEquals(result.getElem(), "Hello World"); + } + + @Test + public void testJAXBResponseParserAnnotationWithTransformer() throws SecurityException, NoSuchMethodException { + TestJAXBApi api = requestSendsResponse( // + HttpRequest.builder().method("GET").endpoint("http://mock/jaxb/transformer").build(), // + HttpResponse.builder().statusCode(200).payload("Hello World").build()); + + String result = api.jaxbGetWithTransformer(); + assertEquals(result, "TestJAXBDomain [elem=Hello World]"); + } + + @Override + public ProviderMetadata createProviderMetadata() { + return forClientMappedToAsyncClientOnEndpoint(TestJAXBApi.class, TestJAXBAsyncApi.class, "http://mock"); + } + +} diff --git a/core/src/test/java/org/jclouds/rest/binders/BindToXMLPayloadTest.java b/core/src/test/java/org/jclouds/rest/binders/BindToXMLPayloadTest.java index 7111b09f79..926086f079 100644 --- a/core/src/test/java/org/jclouds/rest/binders/BindToXMLPayloadTest.java +++ b/core/src/test/java/org/jclouds/rest/binders/BindToXMLPayloadTest.java @@ -20,8 +20,9 @@ package org.jclouds.rest.binders; import static org.testng.Assert.assertEquals; +import javax.xml.bind.annotation.XmlRootElement; + import org.jclouds.http.HttpRequest; -import org.jclouds.rest.internal.RestAnnotationProcessorTest.TestJAXBDomain; import org.jclouds.xml.XMLParser; import org.jclouds.xml.internal.JAXBParser; import org.testng.annotations.Test; @@ -48,7 +49,8 @@ public class BindToXMLPayloadTest { HttpRequest request = HttpRequest.builder().method("GET").endpoint("http://momma").build(); request = binder.bindToRequest(request, obj); - assertEquals(request.getPayload().getRawContent(), XMLParser.DEFAULT_XML_HEADER + "\n\n Hello World\n\n"); + assertEquals(request.getPayload().getRawContent(), XMLParser.DEFAULT_XML_HEADER + + "\n\n Hello World\n\n"); assertEquals(request.getPayload().getContentMetadata().getContentType(), "application/xml"); } @@ -63,11 +65,11 @@ public class BindToXMLPayloadTest { // Add the unknown content-type header to verify it is changed by the // binder Multimap headers = ImmutableMultimap. of("Content-type", "application/unknown"); - HttpRequest request = HttpRequest.builder().method("GET").endpoint("http://momma").headers(headers) - .build(); + HttpRequest request = HttpRequest.builder().method("GET").endpoint("http://momma").headers(headers).build(); request = binder.bindToRequest(request, obj); - assertEquals(request.getPayload().getRawContent(), XMLParser.DEFAULT_XML_HEADER + "\n\n Hello World\n\n"); + assertEquals(request.getPayload().getRawContent(), XMLParser.DEFAULT_XML_HEADER + + "\n\n Hello World\n\n"); assertEquals(request.getPayload().getContentMetadata().getContentType(), "application/xml"); } @@ -83,4 +85,18 @@ public class BindToXMLPayloadTest { HttpRequest request = HttpRequest.builder().method("GET").endpoint("http://momma").build(); request = binder.bindToRequest(request, new Object()); } + + @XmlRootElement(name = "test") + public static class TestJAXBDomain { + private String elem; + + public String getElem() { + return elem; + } + + public void setElem(String elem) { + this.elem = elem; + } + + } } diff --git a/core/src/test/java/org/jclouds/rest/internal/RestAnnotationProcessorTest.java b/core/src/test/java/org/jclouds/rest/internal/RestAnnotationProcessorTest.java index 691ac86504..28b58230a6 100644 --- a/core/src/test/java/org/jclouds/rest/internal/RestAnnotationProcessorTest.java +++ b/core/src/test/java/org/jclouds/rest/internal/RestAnnotationProcessorTest.java @@ -2437,87 +2437,6 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest { assertEquals(form, "x-amz-copy-source=/eggs/robot"); } - public interface TestJAXBResponseParser { - @GET - @Path("/jaxb/annotation") - @JAXBResponseParser - public ListenableFuture jaxbGetWithAnnotation(); - - @GET - @Path("/jaxb/header") - @Consumes("application/xml") - public ListenableFuture jaxbGetWithAcceptHeader(); - } - - @XmlRootElement(name = "test") - public static class TestJAXBDomain { - private String elem; - - public String getElem() { - return elem; - } - - public void setElem(String elem) { - this.elem = elem; - } - } - - @Test - public void testCreateJAXBResponseParserWithAnnotation() throws SecurityException, NoSuchMethodException { - RestAnnotationProcessor processor = factory(TestJAXBResponseParser.class); - Method method = TestJAXBResponseParser.class.getMethod("jaxbGetWithAnnotation"); - GeneratedHttpRequest request = GeneratedHttpRequest.builder().method("GET").endpoint("http://localhost") - .declaring(TestJAXBResponseParser.class).javaMethod(method).args(new Object[] {}).build(); - Function transformer = processor.createResponseParser(method, request); - assertEquals(transformer.getClass(), ParseXMLWithJAXB.class); - } - - @Test - public void testCreateJAXBResponseParserWithAcceptHeader() throws SecurityException, NoSuchMethodException { - RestAnnotationProcessor processor = factory(TestJAXBResponseParser.class); - Method method = TestJAXBResponseParser.class.getMethod("jaxbGetWithAcceptHeader"); - GeneratedHttpRequest request = GeneratedHttpRequest.builder().method("GET").endpoint("http://localhost") - .declaring(TestJAXBResponseParser.class).javaMethod(method).args(new Object[] {}).build(); - Function transformer = processor.createResponseParser(method, request); - assertEquals(transformer.getClass(), ParseXMLWithJAXB.class); - } - - @SuppressWarnings("unchecked") - @Test - public void testJAXBResponseParserWithAnnotation() throws SecurityException, NoSuchMethodException, IOException { - Method method = TestJAXBResponseParser.class.getMethod("jaxbGetWithAnnotation"); - HttpRequest request = factory(TestJAXBResponseParser.class).createRequest(method); - - assertResponseParserClassEquals(method, request, ParseXMLWithJAXB.class); - // now test that it works! - - Function parser = (Function) RestAnnotationProcessor - .createResponseParser(parserFactory, injector, method, request); - - StringBuilder payload = new StringBuilder(XMLParser.DEFAULT_XML_HEADER); - payload.append("Hello World"); - TestJAXBDomain domain = parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload(payload.toString()).build()); - assertEquals(domain.getElem(), "Hello World"); - } - - @SuppressWarnings("unchecked") - @Test - public void testJAXBResponseParserWithAcceptHeader() throws SecurityException, NoSuchMethodException, IOException { - Method method = TestJAXBResponseParser.class.getMethod("jaxbGetWithAcceptHeader"); - HttpRequest request = factory(TestJAXBResponseParser.class).createRequest(method); - - assertResponseParserClassEquals(method, request, ParseXMLWithJAXB.class); - // now test that it works! - - Function parser = (Function) RestAnnotationProcessor - .createResponseParser(parserFactory, injector, method, request); - - StringBuilder payload = new StringBuilder(XMLParser.DEFAULT_XML_HEADER); - payload.append("Hello World"); - TestJAXBDomain domain = parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload(payload.toString()).build()); - assertEquals(domain.getElem(), "Hello World"); - } - @Test(expectedExceptions = NullPointerException.class) public void testAddHostNullWithHost() throws Exception { assertNull(RestAnnotationProcessor.addHostIfMissing(null, null));