Issue 76: added ParamParser on Methods and added ability for ResponseTransformers to implement RestContext which will give them access to the originating request and args

git-svn-id: http://jclouds.googlecode.com/svn/trunk@1931 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
adrian.f.cole 2009-09-27 18:24:51 +00:00
parent 09b2c80cd2
commit ac8c681eb1
5 changed files with 209 additions and 27 deletions

View File

@ -48,6 +48,7 @@ import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam; import javax.ws.rs.QueryParam;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriBuilderException;
import org.jboss.resteasy.util.IsHttpMethod; import org.jboss.resteasy.util.IsHttpMethod;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
@ -161,7 +162,8 @@ public class JaxrsAnnotationProcessor {
private final ParseSax.Factory parserFactory; private final ParseSax.Factory parserFactory;
@VisibleForTesting @VisibleForTesting
public Function<HttpResponse, ?> createResponseParser(Method method) { public Function<HttpResponse, ?> createResponseParser(Method method, HttpRequest request,
Object[] args) {
Function<HttpResponse, ?> transformer; Function<HttpResponse, ?> transformer;
Class<? extends HandlerWithResult<?>> handler = getXMLTransformerOrNull(method); Class<? extends HandlerWithResult<?>> handler = getXMLTransformerOrNull(method);
if (handler != null) { if (handler != null) {
@ -169,6 +171,9 @@ public class JaxrsAnnotationProcessor {
} else { } else {
transformer = injector.getInstance(getParserOrThrowException(method)); transformer = injector.getInstance(getParserOrThrowException(method));
} }
if (transformer instanceof RestContext) {
((RestContext) transformer).setContext(request, args);
}
return transformer; return transformer;
} }
@ -316,8 +321,12 @@ public class JaxrsAnnotationProcessor {
} else { } else {
endPoint = builder.buildFromEncodedMap(getEncodedPathParamKeyValues(method, args)); endPoint = builder.buildFromEncodedMap(getEncodedPathParamKeyValues(method, args));
} }
} catch (Exception e) { } catch (IllegalArgumentException e) {
throw new IllegalStateException("problem encoding parameters", e); throw new IllegalStateException(e);
} catch (UriBuilderException e) {
throw new IllegalStateException(e);
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
} }
HttpRequest request = new HttpRequest(httpMethod, endPoint, headers); HttpRequest request = new HttpRequest(httpMethod, endPoint, headers);
addHostHeaderIfAnnotatedWithVirtualHost(headers, request.getEndpoint().getHost(), method); addHostHeaderIfAnnotatedWithVirtualHost(headers, request.getEndpoint().getHost(), method);
@ -609,22 +618,22 @@ public class JaxrsAnnotationProcessor {
Headers header) throws UnsupportedEncodingException { Headers header) throws UnsupportedEncodingException {
for (int i = 0; i < header.keys().length; i++) { for (int i = 0; i < header.keys().length; i++) {
String value = header.values()[i]; String value = header.values()[i];
for (Entry<String, Object> tokenValue : getEncodedPathParamKeyValues(method, args) for (Entry<String, String> tokenValue : getEncodedPathParamKeyValues(method, args)
.entrySet()) { .entrySet()) {
value = value.replaceAll("\\{" + tokenValue.getKey() + "\\}", tokenValue.getValue() value = value.replaceAll("\\{" + tokenValue.getKey() + "\\}", tokenValue.getValue());
.toString());
} }
headers.put(header.keys()[i], value); headers.put(header.keys()[i], value);
} }
} }
private Map<String, Object> getEncodedPathParamKeyValues(Method method, Object[] args, private Map<String, String> getEncodedPathParamKeyValues(Method method, Object[] args,
char... skipEncode) throws UnsupportedEncodingException { final char... skipEncode) throws UnsupportedEncodingException {
Map<String, Object> pathParamValues = Maps.newHashMap(); Map<String, String> pathParamValues = Maps.newHashMap();
pathParamValues.putAll(constants); pathParamValues.putAll(constants);
Map<Integer, Set<Annotation>> indexToPathParam = methodToindexOfParamToPathParamAnnotations Map<Integer, Set<Annotation>> indexToPathParam = methodToindexOfParamToPathParamAnnotations
.get(method); .get(method);
Map<Integer, Set<Annotation>> indexToParamExtractor = methodToindexOfParamToParamParserAnnotations Map<Integer, Set<Annotation>> indexToParamExtractor = methodToindexOfParamToParamParserAnnotations
.get(method); .get(method);
for (Entry<Integer, Set<Annotation>> entry : indexToPathParam.entrySet()) { for (Entry<Integer, Set<Annotation>> entry : indexToPathParam.entrySet()) {
@ -638,19 +647,36 @@ public class JaxrsAnnotationProcessor {
} else { } else {
paramValue = args[entry.getKey()].toString(); paramValue = args[entry.getKey()].toString();
} }
paramValue = URLEncoder.encode(paramValue, "UTF-8");
// Web browsers do not always handle '+' characters well, use the well-supported
// '%20' instead.
paramValue = paramValue.replaceAll("\\+", "%20");
for (char c : skipEncode) {
String value = Character.toString(c);
String encoded = URLEncoder.encode(value, "UTF-8");
paramValue = paramValue.replaceAll(encoded, value);
}
pathParamValues.put(paramKey, paramValue); pathParamValues.put(paramKey, paramValue);
} }
} }
return pathParamValues;
if (method.isAnnotationPresent(PathParam.class)
&& method.isAnnotationPresent(ParamParser.class)) {
String paramKey = method.getAnnotation(PathParam.class).value();
String paramValue = injector.getInstance(method.getAnnotation(ParamParser.class).value())
.apply(args);
pathParamValues.put(paramKey, paramValue);
}
return Maps.transformValues(pathParamValues, new Function<String, String>() {
public String apply(String paramValue) {
try {
paramValue = URLEncoder.encode(paramValue, "UTF-8");
// Web browsers do not always handle '+' characters well, use the well-supported
// '%20' instead.
paramValue = paramValue.replaceAll("\\+", "%20");
for (char c : skipEncode) {
String value = Character.toString(c);
String encoded = URLEncoder.encode(value, "UTF-8");
paramValue = paramValue.replaceAll(encoded, value);
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("jclouds only supports UTF-8", e);
}
return paramValue;
}
});
} }
private Map<String, String> getQueryParamKeyValues(Method method, Object[] args) { private Map<String, String> getQueryParamKeyValues(Method method, Object[] args) {

View File

@ -23,6 +23,7 @@
*/ */
package org.jclouds.rest; package org.jclouds.rest;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ -34,12 +35,13 @@ import javax.ws.rs.PathParam;
import com.google.common.base.Function; import com.google.common.base.Function;
/** /**
* Extracts the value of a parameter from an object. * Extracts the value of a parameter from an object. If placed on a method, the function will be
* presented with a Object [] from the method args used to derive the value;
* *
* @see PathParam * @see PathParam
* @author Adrian Cole * @author Adrian Cole
*/ */
@Target(PARAMETER) @Target( { METHOD, PARAMETER })
@Retention(RUNTIME) @Retention(RUNTIME)
public @interface ParamParser { public @interface ParamParser {
Class<? extends Function<Object, String>> value(); Class<? extends Function<Object, String>> value();

View File

@ -30,8 +30,10 @@ package org.jclouds.rest;
*/ */
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.inject.Inject; import javax.inject.Inject;
@ -83,12 +85,51 @@ public class RestClientProxy implements InvocationHandler {
} else if (util.getDelegateOrNull(method) != null) { } else if (util.getDelegateOrNull(method) != null) {
method = util.getDelegateOrNull(method); method = util.getDelegateOrNull(method);
logger.trace("%s - converting method to request", method); logger.trace("%s - converting method to request", method);
HttpRequest request = util.createRequest(method, args);
logger.trace("%s - converted method to request %s", method, request);
Function<HttpResponse, ?> transformer = util.createResponseParser(method);
Function<Exception, ?> exceptionParser = util Function<Exception, ?> exceptionParser = util
.createExceptionParserOrNullIfNotFound(method); .createExceptionParserOrNullIfNotFound(method);
HttpRequest request;
try {
request = util.createRequest(method, args);
} catch (RuntimeException e) {
if (exceptionParser != null) {
final Object toReturn = exceptionParser.apply(e);
if (toReturn == null)
throw e;
if (method.getReturnType().isAssignableFrom(Future.class)) {
return new Future<Object>() {
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
public Object get() throws InterruptedException, ExecutionException {
return toReturn;
}
public Object get(long timeout, TimeUnit unit) throws InterruptedException,
ExecutionException, TimeoutException {
return get();
}
public boolean isCancelled() {
return false;
}
public boolean isDone() {
return true;
}
};
} else {
return toReturn;
}
}
throw e;
}
logger.trace("%s - converted method to request %s", method, request);
Function<HttpResponse, ?> transformer = util.createResponseParser(method, request, args);
logger.trace("%s - creating command for request %s, transformer %s, exceptionParser %s", logger.trace("%s - creating command for request %s, transformer %s, exceptionParser %s",
method, request, transformer, exceptionParser); method, request, transformer, exceptionParser);

View File

@ -0,0 +1,43 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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;
import javax.ws.rs.PathParam;
import org.jclouds.http.HttpRequest;
/**
* Passes parsed Http request and Object [] from the method args used to derive the request into
* this object;
*
* @see PathParam
* @author Adrian Cole
*/
public interface RestContext {
void setContext(HttpRequest request, Object[] args);
Object[] getArgs();
HttpRequest getRequest();
}

View File

@ -116,7 +116,7 @@ public class JaxrsAnnotationProcessorTest {
@QueryParams(keys = { "foo", "fooble" }, values = { "bar", "baz" }) @QueryParams(keys = { "foo", "fooble" }, values = { "bar", "baz" })
public void foo2() { public void foo2() {
} }
@FOO @FOO
@QueryParams(keys = { "foo", "fooble" }, values = { "bar", "baz" }) @QueryParams(keys = { "foo", "fooble" }, values = { "bar", "baz" })
public void foo3(@QueryParam("robbie") String robbie) { public void foo3(@QueryParam("robbie") String robbie) {
@ -147,13 +147,14 @@ public class JaxrsAnnotationProcessorTest {
public void testQuery3() throws SecurityException, NoSuchMethodException { public void testQuery3() throws SecurityException, NoSuchMethodException {
Method method = TestQuery.class.getMethod("foo3", String.class); Method method = TestQuery.class.getMethod("foo3", String.class);
HttpRequest httpMethod = factory.create(TestQuery.class).createRequest(method, HttpRequest httpMethod = factory.create(TestQuery.class).createRequest(method,
new Object[] {"wonder"}); new Object[] { "wonder" });
assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); assertEquals(httpMethod.getEndpoint().getHost(), "localhost");
assertEquals(httpMethod.getEndpoint().getPath(), ""); assertEquals(httpMethod.getEndpoint().getPath(), "");
assertEquals(httpMethod.getEndpoint().getQuery(), assertEquals(httpMethod.getEndpoint().getQuery(),
"x-ms-version=2009-07-17&foo=bar&fooble=baz&robbie=wonder"); "x-ms-version=2009-07-17&foo=bar&fooble=baz&robbie=wonder");
assertEquals(httpMethod.getMethod(), "FOO"); assertEquals(httpMethod.getMethod(), "FOO");
} }
@Endpoint(Localhost.class) @Endpoint(Localhost.class)
public class TestCustomMethod { public class TestCustomMethod {
@FOO @FOO
@ -428,6 +429,33 @@ public class JaxrsAnnotationProcessorTest {
public void onePathParamExtractor( public void onePathParamExtractor(
@PathParam("path") @ParamParser(FirstCharacter.class) String path) { @PathParam("path") @ParamParser(FirstCharacter.class) String path) {
} }
@GET
@Path("{path}")
@PathParam("path")
@ParamParser(FirstCharacterFirstElement.class)
public void onePathParamExtractorMethod(String path) {
}
}
@Test
public void testParamExtractor() throws SecurityException, NoSuchMethodException {
Method method = TestPath.class.getMethod("onePathParamExtractor", String.class);
HttpRequest httpMethod = factory.create(TestPath.class).createRequest(method,
new Object[] { "localhost" });
assertEquals(httpMethod.getEndpoint().getPath(), "/l");
assertEquals(httpMethod.getMethod(), HttpMethod.GET);
assertEquals(httpMethod.getHeaders().size(), 0);
}
@Test
public void testParamExtractorMethod() throws SecurityException, NoSuchMethodException {
Method method = TestPath.class.getMethod("onePathParamExtractorMethod", String.class);
HttpRequest httpMethod = factory.create(TestPath.class).createRequest(method,
new Object[] { "localhost" });
assertEquals(httpMethod.getEndpoint().getPath(), "/l");
assertEquals(httpMethod.getMethod(), HttpMethod.GET);
assertEquals(httpMethod.getHeaders().size(), 0);
} }
static class FirstCharacter implements Function<Object, String> { static class FirstCharacter implements Function<Object, String> {
@ -436,6 +464,12 @@ public class JaxrsAnnotationProcessorTest {
} }
} }
static class FirstCharacterFirstElement implements Function<Object, String> {
public String apply(Object from) {
return ((String) ((Object[]) from)[0]).substring(0, 1);
}
}
@Endpoint(Localhost.class) @Endpoint(Localhost.class)
public class TestHeader { public class TestHeader {
@GET @GET
@ -535,6 +569,31 @@ public class JaxrsAnnotationProcessorTest {
@ResponseParser(ReturnStringIf200.class) @ResponseParser(ReturnStringIf200.class)
public void oneTransformer() { public void oneTransformer() {
} }
@GET
@ResponseParser(ReturnStringIf200Context.class)
public void oneTransformerWithContext() {
}
}
public static class ReturnStringIf200Context extends ReturnStringIf200 implements RestContext {
private Object[] args;
private HttpRequest request;
public Object[] getArgs() {
return args;
}
public HttpRequest getRequest() {
return request;
}
public void setContext(HttpRequest request, Object[] args) {
this.request = request;
this.args = args;
}
} }
@SuppressWarnings("static-access") @SuppressWarnings("static-access")
@ -546,6 +605,17 @@ public class JaxrsAnnotationProcessorTest {
assertEquals(transformer, ReturnStringIf200.class); assertEquals(transformer, ReturnStringIf200.class);
} }
public void oneTransformerWithContext() throws SecurityException, NoSuchMethodException {
Method method = TestTransformers.class.getMethod("oneTransformerWithContext");
HttpRequest request = new HttpRequest("GET", URI.create("http://localhost"));
Object[] args = new Object[] {};
Function<HttpResponse, ?> transformer = factory.create(TestTransformers.class)
.createResponseParser(method, request, args);
assertEquals(transformer.getClass(), ReturnStringIf200Context.class);
assertEquals(((ReturnStringIf200Context) transformer).getArgs(), args);
assertEquals(((ReturnStringIf200Context) transformer).getRequest(), request);
}
@SuppressWarnings("static-access") @SuppressWarnings("static-access")
public void testOneTransformer() throws SecurityException, NoSuchMethodException { public void testOneTransformer() throws SecurityException, NoSuchMethodException {
Method method = TestTransformers.class.getMethod("oneTransformer"); Method method = TestTransformers.class.getMethod("oneTransformer");