mirror of https://github.com/apache/jclouds.git
Make JAXBResponseParser parameterizable
By default, RestAnnotationProcessor builds the JAXBParser with the return type of the invoked method. This, however, can make it impossible to combine the JAXBResponseParser with the Transform annotation. The payload of the response to be parsed with the JAXBResponseParser could be of a different type than the output generated by the transformer function. This makes it necessary to configure the type for the response parser in the JAXBResponseParser annotation and set the return type of the invoked method to the type generated by the transformer function.
This commit is contained in:
parent
0b6b980adb
commit
f1d81eaf12
|
@ -24,6 +24,10 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.Target;
|
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
|
* Shows the transformer type used to parse XML with the
|
||||||
* {@link ParseXMLWithJAXB} parser in a HttpResponse.
|
* {@link ParseXMLWithJAXB} parser in a HttpResponse.
|
||||||
|
@ -34,4 +38,10 @@ import java.lang.annotation.Target;
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
public @interface JAXBResponseParser {
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ import java.util.Set;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import javax.lang.model.type.NullType;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.FormParam;
|
import javax.ws.rs.FormParam;
|
||||||
import javax.ws.rs.HeaderParam;
|
import javax.ws.rs.HeaderParam;
|
||||||
|
@ -815,10 +816,16 @@ public abstract class RestAnnotationProcessor {
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static Key<? extends Function<HttpResponse, ?>> getJAXBParserKeyForMethod(Method method) {
|
public static Key<? extends Function<HttpResponse, ?>> getJAXBParserKeyForMethod(Method method) {
|
||||||
Type returnVal = getReturnTypeForMethod(method);
|
Optional<Type> configuredReturnVal = Optional.absent();
|
||||||
Type parserType = Types.newParameterizedType(ParseXMLWithJAXB.class, returnVal);
|
if (method.isAnnotationPresent(JAXBResponseParser.class)) {
|
||||||
return (Key<? extends Function<HttpResponse, ?>>) Key.get(parserType);
|
Type configuredClass = method.getAnnotation(JAXBResponseParser.class).value();
|
||||||
}
|
configuredReturnVal = configuredClass.equals(NullType.class) ? Optional.<Type> absent() : Optional
|
||||||
|
.<Type> of(configuredClass);
|
||||||
|
}
|
||||||
|
Type returnVal = configuredReturnVal.or(getReturnTypeForMethod(method));
|
||||||
|
Type parserType = Types.newParameterizedType(ParseXMLWithJAXB.class, returnVal);
|
||||||
|
return (Key<? extends Function<HttpResponse, ?>>) Key.get(parserType);
|
||||||
|
}
|
||||||
|
|
||||||
public static Type getReturnTypeForMethod(Method method) {
|
public static Type getReturnTypeForMethod(Method method) {
|
||||||
Type returnVal;
|
Type returnVal;
|
||||||
|
|
|
@ -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<JAXBResponseParserAnnotationExpectTest.TestJAXBApi> {
|
||||||
|
|
||||||
|
@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<TestJAXBDomain> jaxbGetWithAnnotation();
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/jaxb/custom")
|
||||||
|
@JAXBResponseParser(TestJAXBDomain.class)
|
||||||
|
public ListenableFuture<Object> jaxbGetWithAnnotationAndCustomClass();
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/jaxb/header")
|
||||||
|
@Consumes(MediaType.APPLICATION_XML)
|
||||||
|
public ListenableFuture<TestJAXBDomain> jaxbGetWithAcceptHeader();
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/jaxb/transformer")
|
||||||
|
@JAXBResponseParser(TestJAXBDomain.class)
|
||||||
|
@Transform(ToString.class)
|
||||||
|
public ListenableFuture<String> jaxbGetWithTransformer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ToString implements Function<Object, String> {
|
||||||
|
@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("<test><elem>Hello World</elem></test>").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("<test><elem>Hello World</elem></test>").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("<test><elem>Hello World</elem></test>").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("<test><elem>Hello World</elem></test>").build());
|
||||||
|
|
||||||
|
String result = api.jaxbGetWithTransformer();
|
||||||
|
assertEquals(result, "TestJAXBDomain [elem=Hello World]");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProviderMetadata createProviderMetadata() {
|
||||||
|
return forClientMappedToAsyncClientOnEndpoint(TestJAXBApi.class, TestJAXBAsyncApi.class, "http://mock");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -20,8 +20,9 @@ package org.jclouds.rest.binders;
|
||||||
|
|
||||||
import static org.testng.Assert.assertEquals;
|
import static org.testng.Assert.assertEquals;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
|
||||||
import org.jclouds.http.HttpRequest;
|
import org.jclouds.http.HttpRequest;
|
||||||
import org.jclouds.rest.internal.RestAnnotationProcessorTest.TestJAXBDomain;
|
|
||||||
import org.jclouds.xml.XMLParser;
|
import org.jclouds.xml.XMLParser;
|
||||||
import org.jclouds.xml.internal.JAXBParser;
|
import org.jclouds.xml.internal.JAXBParser;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
@ -48,7 +49,8 @@ public class BindToXMLPayloadTest {
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.builder().method("GET").endpoint("http://momma").build();
|
HttpRequest request = HttpRequest.builder().method("GET").endpoint("http://momma").build();
|
||||||
request = binder.bindToRequest(request, obj);
|
request = binder.bindToRequest(request, obj);
|
||||||
assertEquals(request.getPayload().getRawContent(), XMLParser.DEFAULT_XML_HEADER + "\n<test>\n <elem>Hello World</elem>\n</test>\n");
|
assertEquals(request.getPayload().getRawContent(), XMLParser.DEFAULT_XML_HEADER
|
||||||
|
+ "\n<test>\n <elem>Hello World</elem>\n</test>\n");
|
||||||
assertEquals(request.getPayload().getContentMetadata().getContentType(), "application/xml");
|
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
|
// Add the unknown content-type header to verify it is changed by the
|
||||||
// binder
|
// binder
|
||||||
Multimap<String, String> headers = ImmutableMultimap.<String, String> of("Content-type", "application/unknown");
|
Multimap<String, String> headers = ImmutableMultimap.<String, String> of("Content-type", "application/unknown");
|
||||||
HttpRequest request = HttpRequest.builder().method("GET").endpoint("http://momma").headers(headers)
|
HttpRequest request = HttpRequest.builder().method("GET").endpoint("http://momma").headers(headers).build();
|
||||||
.build();
|
|
||||||
|
|
||||||
request = binder.bindToRequest(request, obj);
|
request = binder.bindToRequest(request, obj);
|
||||||
assertEquals(request.getPayload().getRawContent(), XMLParser.DEFAULT_XML_HEADER + "\n<test>\n <elem>Hello World</elem>\n</test>\n");
|
assertEquals(request.getPayload().getRawContent(), XMLParser.DEFAULT_XML_HEADER
|
||||||
|
+ "\n<test>\n <elem>Hello World</elem>\n</test>\n");
|
||||||
assertEquals(request.getPayload().getContentMetadata().getContentType(), "application/xml");
|
assertEquals(request.getPayload().getContentMetadata().getContentType(), "application/xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,4 +85,18 @@ public class BindToXMLPayloadTest {
|
||||||
HttpRequest request = HttpRequest.builder().method("GET").endpoint("http://momma").build();
|
HttpRequest request = HttpRequest.builder().method("GET").endpoint("http://momma").build();
|
||||||
request = binder.bindToRequest(request, new Object());
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2437,87 +2437,6 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
|
||||||
assertEquals(form, "x-amz-copy-source=/eggs/robot");
|
assertEquals(form, "x-amz-copy-source=/eggs/robot");
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface TestJAXBResponseParser {
|
|
||||||
@GET
|
|
||||||
@Path("/jaxb/annotation")
|
|
||||||
@JAXBResponseParser
|
|
||||||
public ListenableFuture<TestJAXBDomain> jaxbGetWithAnnotation();
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/jaxb/header")
|
|
||||||
@Consumes("application/xml")
|
|
||||||
public ListenableFuture<TestJAXBDomain> 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<HttpResponse, ?> 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<HttpResponse, ?> 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<HttpResponse, TestJAXBDomain> parser = (Function<HttpResponse, TestJAXBDomain>) RestAnnotationProcessor
|
|
||||||
.createResponseParser(parserFactory, injector, method, request);
|
|
||||||
|
|
||||||
StringBuilder payload = new StringBuilder(XMLParser.DEFAULT_XML_HEADER);
|
|
||||||
payload.append("<test><elem>Hello World</elem></test>");
|
|
||||||
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<HttpResponse, TestJAXBDomain> parser = (Function<HttpResponse, TestJAXBDomain>) RestAnnotationProcessor
|
|
||||||
.createResponseParser(parserFactory, injector, method, request);
|
|
||||||
|
|
||||||
StringBuilder payload = new StringBuilder(XMLParser.DEFAULT_XML_HEADER);
|
|
||||||
payload.append("<test><elem>Hello World</elem></test>");
|
|
||||||
TestJAXBDomain domain = parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload(payload.toString()).build());
|
|
||||||
assertEquals(domain.getElem(), "Hello World");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expectedExceptions = NullPointerException.class)
|
@Test(expectedExceptions = NullPointerException.class)
|
||||||
public void testAddHostNullWithHost() throws Exception {
|
public void testAddHostNullWithHost() throws Exception {
|
||||||
assertNull(RestAnnotationProcessor.addHostIfMissing(null, null));
|
assertNull(RestAnnotationProcessor.addHostIfMissing(null, null));
|
||||||
|
|
Loading…
Reference in New Issue