First wave of RestAnnotationParser refactoring introduces Invokable, removes SeedAnnotationCache and associated race conditions

This commit is contained in:
Adrian Cole 2013-01-04 11:43:11 -08:00
parent 37759d1c8c
commit afc070ac07
29 changed files with 917 additions and 1107 deletions

View File

@ -25,7 +25,7 @@ import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
import org.jclouds.internal.ClassMethodArgsAndReturnVal; import org.jclouds.internal.ClassInvokerArgsAndReturnVal;
import org.jclouds.openstack.v2_0.domain.Extension; import org.jclouds.openstack.v2_0.domain.Extension;
import org.jclouds.openstack.v2_0.predicates.ExtensionPredicates; import org.jclouds.openstack.v2_0.predicates.ExtensionPredicates;
import org.jclouds.rest.functions.ImplicitOptionalConverter; import org.jclouds.rest.functions.ImplicitOptionalConverter;
@ -57,17 +57,17 @@ public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensio
} }
@Override @Override
public Optional<Object> apply(ClassMethodArgsAndReturnVal input) { public Optional<Object> apply(ClassInvokerArgsAndReturnVal input) {
Optional<org.jclouds.openstack.v2_0.services.Extension> ext = Optional.fromNullable(input.getClazz().getAnnotation( Optional<org.jclouds.openstack.v2_0.services.Extension> ext = Optional.fromNullable(input.getClazz().getAnnotation(
org.jclouds.openstack.v2_0.services.Extension.class)); org.jclouds.openstack.v2_0.services.Extension.class));
if (ext.isPresent()) { if (ext.isPresent()) {
URI namespace = URI.create(ext.get().namespace()); URI namespace = URI.create(ext.get().namespace());
if (input.getArgs().length == 0) { if (input.getArgs().isEmpty()) {
if (Iterables.any(extensions.getUnchecked(""), if (Iterables.any(extensions.getUnchecked(""),
ExtensionPredicates.namespaceOrAliasEquals(namespace, aliases.get(namespace)))) ExtensionPredicates.namespaceOrAliasEquals(namespace, aliases.get(namespace))))
return Optional.of(input.getReturnVal()); return Optional.of(input.getReturnVal());
} else if (input.getArgs().length == 1) { } else if (input.getArgs().size() == 1) {
if (Iterables.any(extensions.getUnchecked(checkNotNull(input.getArgs()[0], "arg[0] in %s", input).toString()), if (Iterables.any(extensions.getUnchecked(checkNotNull(input.getArgs().get(0), "arg[0] in %s", input).toString()),
ExtensionPredicates.namespaceOrAliasEquals(namespace, aliases.get(namespace)))) ExtensionPredicates.namespaceOrAliasEquals(namespace, aliases.get(namespace))))
return Optional.of(input.getReturnVal()); return Optional.of(input.getReturnVal());
} else { } else {

View File

@ -6,7 +6,7 @@ import java.net.URI;
import java.util.Set; import java.util.Set;
import org.jclouds.date.internal.SimpleDateFormatDateService; import org.jclouds.date.internal.SimpleDateFormatDateService;
import org.jclouds.internal.ClassMethodArgsAndReturnVal; import org.jclouds.internal.ClassInvokerArgsAndReturnVal;
import org.jclouds.openstack.v2_0.ServiceType; import org.jclouds.openstack.v2_0.ServiceType;
import org.jclouds.openstack.v2_0.domain.Extension; import org.jclouds.openstack.v2_0.domain.Extension;
import org.jclouds.rest.annotations.Delegate; import org.jclouds.rest.annotations.Delegate;
@ -17,10 +17,12 @@ import com.google.common.base.Optional;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.reflect.Invokable;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Provides; import com.google.inject.Provides;
@ -61,16 +63,19 @@ public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensio
} }
ClassMethodArgsAndReturnVal getFloatingIPExtension() throws SecurityException, NoSuchMethodException { ClassInvokerArgsAndReturnVal getFloatingIPExtension() throws SecurityException, NoSuchMethodException {
return ClassMethodArgsAndReturnVal.builder().clazz(FloatingIPAsyncApi.class).method( return ClassInvokerArgsAndReturnVal
NovaAsyncApi.class.getDeclaredMethod("getFloatingIPExtensionForZone", String.class)).args( .builder()
new Object[] { "zone" }).returnVal("foo").build(); .clazz(FloatingIPAsyncApi.class)
.invoker(
Invokable.from(NovaAsyncApi.class.getDeclaredMethod("getFloatingIPExtensionForZone", String.class)))
.args(ImmutableList.<Object> of("zone")).returnVal("foo").build();
} }
ClassMethodArgsAndReturnVal getKeyPairExtension() throws SecurityException, NoSuchMethodException { ClassInvokerArgsAndReturnVal getKeyPairExtension() throws SecurityException, NoSuchMethodException {
return ClassMethodArgsAndReturnVal.builder().clazz(KeyPairAsyncApi.class).method( return ClassInvokerArgsAndReturnVal.builder().clazz(KeyPairAsyncApi.class)
NovaAsyncApi.class.getDeclaredMethod("getKeyPairExtensionForZone", String.class)).args( .invoker(Invokable.from(NovaAsyncApi.class.getDeclaredMethod("getKeyPairExtensionForZone", String.class)))
new Object[] { "zone" }).returnVal("foo").build(); .args(ImmutableList.<Object> of("zone")).returnVal("foo").build();
} }
public void testPresentWhenExtensionsIncludeNamespaceFromAnnotationAbsentWhenNot() throws SecurityException, NoSuchMethodException { public void testPresentWhenExtensionsIncludeNamespaceFromAnnotationAbsentWhenNot() throws SecurityException, NoSuchMethodException {
@ -83,9 +88,9 @@ public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensio
public void testZoneWithoutExtensionsReturnsAbsent() throws SecurityException, NoSuchMethodException { public void testZoneWithoutExtensionsReturnsAbsent() throws SecurityException, NoSuchMethodException {
assertEquals(whenExtensionsInZoneInclude("zone", floatingIps).apply( assertEquals(whenExtensionsInZoneInclude("zone", floatingIps).apply(
getFloatingIPExtension().toBuilder().args(new Object[] { "differentzone" }).build()), Optional.absent()); getFloatingIPExtension().toBuilder().args(ImmutableList.<Object> of("differentzone")).build()), Optional.absent());
assertEquals(whenExtensionsInZoneInclude("zone", keypairs).apply( assertEquals(whenExtensionsInZoneInclude("zone", keypairs).apply(
getKeyPairExtension().toBuilder().args(new Object[] { "differentzone" }).build()), Optional.absent()); getKeyPairExtension().toBuilder().args(ImmutableList.<Object> of("differentzone")).build()), Optional.absent());
} }
/** /**

View File

@ -63,8 +63,8 @@ public abstract class CallerArg0ToPagedIterable<T, I extends CallerArg0ToPagedIt
return PagedIterables.of(input); return PagedIterables.of(input);
Optional<String> arg0Option = Optional.absent(); Optional<String> arg0Option = Optional.absent();
if (request.getCaller().get().getArgs().length > 0) { if (request.getCaller().get().getArgs().size() > 0) {
Object arg0 = request.getCaller().get().getArgs()[0]; Object arg0 = request.getCaller().get().getArgs().get(0);
if (arg0 != null) if (arg0 != null)
arg0Option = Optional.of(arg0.toString()); arg0Option = Optional.of(arg0.toString());
} }

View File

@ -33,8 +33,8 @@ import javax.annotation.Resource;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import org.jclouds.internal.ClassMethodArgs; import org.jclouds.internal.ClassInvokerArgs;
import org.jclouds.internal.ClassMethodArgsAndReturnVal; import org.jclouds.internal.ClassInvokerArgsAndReturnVal;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import org.jclouds.rest.annotations.Delegate; import org.jclouds.rest.annotations.Delegate;
import org.jclouds.util.Optionals2; import org.jclouds.util.Optionals2;
@ -74,20 +74,20 @@ public final class SyncProxy extends AbstractInvocationHandler {
@Resource @Resource
private Logger logger = Logger.NULL; private Logger logger = Logger.NULL;
private final Function<ClassMethodArgsAndReturnVal, Optional<Object>> optionalConverter; private final Function<ClassInvokerArgsAndReturnVal, Optional<Object>> optionalConverter;
private final Object delegate; private final Object delegate;
private final Class<?> declaring; private final Class<?> declaring;
private final Map<Method, Method> methodMap; private final Map<Method, Method> methodMap;
private final Map<Method, Method> syncMethodMap; private final Map<Method, Method> syncMethodMap;
private final Map<Method, Optional<Long>> timeoutMap; private final Map<Method, Optional<Long>> timeoutMap;
private final LoadingCache<ClassMethodArgs, Object> delegateMap; private final LoadingCache<ClassInvokerArgs, Object> delegateMap;
private final Map<Class<?>, Class<?>> sync2Async; private final Map<Class<?>, Class<?>> sync2Async;
private static final Set<Method> objectMethods = ImmutableSet.copyOf(Object.class.getMethods()); private static final Set<Method> objectMethods = ImmutableSet.copyOf(Object.class.getMethods());
@Inject @Inject
@VisibleForTesting @VisibleForTesting
SyncProxy(Function<ClassMethodArgsAndReturnVal, Optional<Object>> optionalConverter, SyncProxy(Function<ClassInvokerArgsAndReturnVal, Optional<Object>> optionalConverter,
@Named("sync") LoadingCache<ClassMethodArgs, Object> delegateMap, Map<Class<?>, Class<?>> sync2Async, @Named("sync") LoadingCache<ClassInvokerArgs, Object> delegateMap, Map<Class<?>, Class<?>> sync2Async,
@Named("TIMEOUTS") Map<String, Long> timeouts, @Assisted Class<?> declaring, @Assisted Object async) @Named("TIMEOUTS") Map<String, Long> timeouts, @Assisted Class<?> declaring, @Assisted Object async)
throws SecurityException, NoSuchMethodException { throws SecurityException, NoSuchMethodException {
this.optionalConverter = optionalConverter; this.optionalConverter = optionalConverter;
@ -138,10 +138,10 @@ public final class SyncProxy extends AbstractInvocationHandler {
// pass any parameters necessary to get a relevant instance of that async class // pass any parameters necessary to get a relevant instance of that async class
// ex. getClientForRegion("north") might return an instance whose endpoint is // ex. getClientForRegion("north") might return an instance whose endpoint is
// different that "south" // different that "south"
ClassMethodArgs cma = new ClassMethodArgs(asyncClass, method, args); ClassInvokerArgs cma = ClassInvokerArgs.builder().clazz(asyncClass).invoker(method).args(args).build();
Object returnVal = delegateMap.get(cma); Object returnVal = delegateMap.get(cma);
if (Optionals2.isReturnTypeOptional(method)){ if (Optionals2.isReturnTypeOptional(method)){
ClassMethodArgsAndReturnVal cmar = ClassMethodArgsAndReturnVal.builder().fromClassMethodArgs(cma) ClassInvokerArgsAndReturnVal cmar = ClassInvokerArgsAndReturnVal.builder().fromClassInvokerArgs(cma)
.returnVal(returnVal).build(); .returnVal(returnVal).build();
return optionalConverter.apply(cmar); return optionalConverter.apply(cmar);
} }

View File

@ -19,12 +19,17 @@
package org.jclouds.http; package org.jclouds.http;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Predicates.and;
import static com.google.common.base.Predicates.equalTo; import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.in;
import static com.google.common.base.Predicates.not;
import static com.google.common.base.Predicates.notNull;
import static com.google.common.base.Throwables.getCausalChain; import static com.google.common.base.Throwables.getCausalChain;
import static com.google.common.base.Throwables.propagate; import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Iterables.filter; import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.get; import static com.google.common.collect.Iterables.get;
import static com.google.common.collect.Iterables.size; import static com.google.common.collect.Iterables.size;
import static com.google.common.collect.Multimaps.filterKeys;
import static com.google.common.io.BaseEncoding.base64; import static com.google.common.io.BaseEncoding.base64;
import static com.google.common.io.ByteStreams.toByteArray; import static com.google.common.io.ByteStreams.toByteArray;
import static com.google.common.io.Closeables.closeQuietly; import static com.google.common.io.Closeables.closeQuietly;
@ -38,11 +43,13 @@ import static com.google.common.net.HttpHeaders.EXPIRES;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.util.Collection; import java.util.Collection;
import java.util.Map.Entry; import java.util.Map.Entry;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.ws.rs.HttpMethod;
import org.jclouds.Constants; import org.jclouds.Constants;
import org.jclouds.io.ContentMetadata; import org.jclouds.io.ContentMetadata;
@ -53,7 +60,13 @@ import org.jclouds.io.Payloads;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import org.jclouds.logging.internal.Wire; import org.jclouds.logging.internal.Wire;
import com.google.common.base.Optional;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.ImmutableSet.Builder;
import com.google.common.reflect.Invokable;
import com.google.inject.Inject; import com.google.inject.Inject;
/** /**
@ -172,6 +185,24 @@ public class HttpUtils {
return null; return null;
} }
public static Optional<String> tryFindHttpMethod(Invokable<?, ?> method) {
Builder<String> methodsBuilder = ImmutableSet.builder();
for (Annotation annotation : method.getAnnotations()) {
HttpMethod http = annotation.annotationType().getAnnotation(HttpMethod.class);
if (http != null)
methodsBuilder.add(http.value());
}
Collection<String> methods = methodsBuilder.build();
switch (methods.size()) {
case 0:
return Optional.absent();
case 1:
return Optional.of(get(methods, 0));
default:
throw new IllegalStateException("You must specify at most one HttpMethod annotation on: " + method);
}
}
/** /**
* Content stream may need to be read. However, we should always close the http stream. * Content stream may need to be read. However, we should always close the http stream.
* *
@ -322,6 +353,11 @@ public class HttpUtils {
return null; return null;
} }
public static Multimap<String, String> filterOutContentHeaders(Multimap<String, String> headers) {
// http message usually comes in as a null key header, let's filter it out.
return ImmutableMultimap.copyOf(filterKeys(headers, and(notNull(), not(in(ContentMetadata.HTTP_HEADERS)))));
}
public static boolean contains404(Throwable t) { public static boolean contains404(Throwable t) {
return returnValueOnCodeOrNull(t, true, equalTo(404)) != null; return returnValueOnCodeOrNull(t, true, equalTo(404)) != null;
} }

View File

@ -26,6 +26,7 @@ import static com.google.common.io.Closeables.closeQuietly;
import static com.google.common.net.HttpHeaders.CONTENT_LENGTH; import static com.google.common.net.HttpHeaders.CONTENT_LENGTH;
import static com.google.common.net.HttpHeaders.HOST; import static com.google.common.net.HttpHeaders.HOST;
import static com.google.common.net.HttpHeaders.USER_AGENT; import static com.google.common.net.HttpHeaders.USER_AGENT;
import static org.jclouds.http.HttpUtils.filterOutContentHeaders;
import static org.jclouds.io.Payloads.newInputStreamPayload; import static org.jclouds.io.Payloads.newInputStreamPayload;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -65,7 +66,6 @@ import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.io.MutableContentMetadata; import org.jclouds.io.MutableContentMetadata;
import org.jclouds.io.Payload; import org.jclouds.io.Payload;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import org.jclouds.rest.internal.RestAnnotationProcessor;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap;
@ -142,7 +142,7 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
contentMetadataCodec.fromHeaders(payload.getContentMetadata(), headers); contentMetadataCodec.fromHeaders(payload.getContentMetadata(), headers);
builder.payload(payload); builder.payload(payload);
} }
builder.headers(RestAnnotationProcessor.filterOutContentHeaders(headers)); builder.headers(filterOutContentHeaders(headers));
return builder.build(); return builder.build();
} }

View File

@ -22,22 +22,25 @@ import static com.google.common.base.Objects.equal;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.List;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.base.Objects.ToStringHelper; import com.google.common.base.Objects.ToStringHelper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.reflect.Invokable;
/** /**
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
public class ClassMethodArgs { public class ClassInvokerArgs {
public static Builder<?> builder() { public static Builder<?> builder() {
return new ConcreteBuilder(); return new ConcreteBuilder();
} }
public Builder<?> toBuilder() { public Builder<?> toBuilder() {
return builder().fromClassMethodArgs(this); return builder().fromClassInvokerArgs(this);
} }
private static class ConcreteBuilder extends Builder<ConcreteBuilder> { private static class ConcreteBuilder extends Builder<ConcreteBuilder> {
@ -45,8 +48,8 @@ public class ClassMethodArgs {
public abstract static class Builder<B extends Builder<B>> { public abstract static class Builder<B extends Builder<B>> {
private Class<?> clazz; private Class<?> clazz;
private Method method; private Invokable<?, ?> invoker;
private Object[] args = {}; private List<Object> args = ImmutableList.of();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected B self() { protected B self() {
@ -54,7 +57,7 @@ public class ClassMethodArgs {
} }
/** /**
* @see ClassMethodArgs#getClazz() * @see ClassInvokerArgs#getClazz()
*/ */
public B clazz(Class<?> clazz) { public B clazz(Class<?> clazz) {
this.clazz = clazz; this.clazz = clazz;
@ -62,53 +65,78 @@ public class ClassMethodArgs {
} }
/** /**
* @see ClassMethodArgs#getMethod() * @see ClassInvokerArgs#getInvoker()
*/ */
public B method(Method method) { public B invoker(Invokable<?, ?> invoker) {
this.method = method; this.invoker = invoker;
return self(); return self();
} }
/**
* @see ClassInvokerArgs#getInvoker()
*/
@Deprecated
public B invoker(Method method) {
return invoker(Invokable.from(method));
}
/** /**
* @see ClassMethodArgs#getArgs() * @see ClassInvokerArgs#getArgs()
*/ */
@Deprecated
public B args(Object[] args) { public B args(Object[] args) {
return args(args == null ? Lists.newArrayList(new Object[] { null }) : Lists.newArrayList(args));
}
/**
* @see ClassInvokerArgs#getArgs()
*/
public B args(List<Object> args) {
this.args = args; this.args = args;
return self(); return self();
} }
public ClassMethodArgs build() { public ClassInvokerArgs build() {
return new ClassMethodArgs(this); return new ClassInvokerArgs(this);
} }
public B fromClassMethodArgs(ClassMethodArgs in) { public B fromClassInvokerArgs(ClassInvokerArgs in) {
return clazz(in.getClazz()).method(in.getMethod()).args(in.getArgs()); return clazz(in.getClazz()).invoker(in.getInvoker()).args(in.getArgs());
} }
} }
private final Class<?> clazz; private final Class<?> clazz;
private final Method method; private final Invokable<?, ?> invoker;
private final Object[] args; private final List<Object> args;
public ClassMethodArgs(Builder<?> builder) { public ClassInvokerArgs(Builder<?> builder) {
this(builder.clazz, builder.method, builder.args); this(builder.clazz, builder.invoker, builder.args);
} }
public ClassMethodArgs(Class<?> clazz, Method method, Object[] args) { /**
* @param args as these represent parameters, can contain nulls
*/
public ClassInvokerArgs(Class<?> clazz, Invokable<?, ?> invoker, List<Object> args) {
this.clazz = checkNotNull(clazz, "clazz"); this.clazz = checkNotNull(clazz, "clazz");
this.method = checkNotNull(method, "method"); this.invoker = checkNotNull(invoker, "invoker");
this.args = checkNotNull(args, "args"); this.args = checkNotNull(args, "args");
} }
/**
* not necessarily the declaring class of the invoker.
*/
public Class<?> getClazz() { public Class<?> getClazz() {
return clazz; return clazz;
} }
public Method getMethod() { public Invokable<?, ?> getInvoker() {
return method; return invoker;
} }
public Object[] getArgs() { /**
* @param args as these represent parameters, can contain nulls
*/
public List<Object> getArgs() {
return args; return args;
} }
@ -118,13 +146,13 @@ public class ClassMethodArgs {
return true; return true;
if (o == null || getClass() != o.getClass()) if (o == null || getClass() != o.getClass())
return false; return false;
ClassMethodArgs that = ClassMethodArgs.class.cast(o); ClassInvokerArgs that = ClassInvokerArgs.class.cast(o);
return equal(this.clazz, that.clazz) && equal(this.method, that.method) && equal(this.args, that.args); return equal(this.clazz, that.clazz) && equal(this.invoker, that.invoker) && equal(this.args, that.args);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hashCode(clazz, method, args); return Objects.hashCode(clazz, invoker, args);
} }
@Override @Override
@ -133,7 +161,7 @@ public class ClassMethodArgs {
} }
protected ToStringHelper string() { protected ToStringHelper string() {
return Objects.toStringHelper("").omitNullValues().add("clazz", clazz).add("method", method) return Objects.toStringHelper("").omitNullValues().add("clazz", clazz).add("invoker", invoker)
.add("args", args.length != 0 ? Arrays.asList(args) : null); .add("args", args.size() != 0 ? args : null);
} }
} }

View File

@ -20,31 +20,32 @@ package org.jclouds.internal;
import static com.google.common.base.Objects.equal; import static com.google.common.base.Objects.equal;
import java.lang.reflect.Method; import java.util.List;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.base.Objects.ToStringHelper; import com.google.common.base.Objects.ToStringHelper;
import com.google.common.reflect.Invokable;
/** /**
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
public final class ClassMethodArgsAndReturnVal extends ClassMethodArgs { public final class ClassInvokerArgsAndReturnVal extends ClassInvokerArgs {
public static Builder builder() { public static Builder builder() {
return new Builder(); return new Builder();
} }
public Builder toBuilder() { public Builder toBuilder() {
return builder().fromClassMethodArgsAndReturnVal(this); return builder().fromClassInvokerArgsAndReturnVal(this);
} }
public final static class Builder extends ClassMethodArgs.Builder<Builder> { public final static class Builder extends ClassInvokerArgs.Builder<Builder> {
private Object returnVal; private Object returnVal;
/** /**
* @see ClassMethodArgsAndReturnVal#getReturnVal() * @see ClassInvokerArgsAndReturnVal#getReturnVal()
*/ */
public Builder returnVal(Object returnVal) { public Builder returnVal(Object returnVal) {
this.returnVal = returnVal; this.returnVal = returnVal;
@ -52,23 +53,23 @@ public final class ClassMethodArgsAndReturnVal extends ClassMethodArgs {
} }
@Override @Override
public ClassMethodArgsAndReturnVal build() { public ClassInvokerArgsAndReturnVal build() {
return new ClassMethodArgsAndReturnVal(this); return new ClassInvokerArgsAndReturnVal(this);
} }
public Builder fromClassMethodArgsAndReturnVal(ClassMethodArgsAndReturnVal in) { public Builder fromClassInvokerArgsAndReturnVal(ClassInvokerArgsAndReturnVal in) {
return fromClassMethodArgs(in).returnVal(in.getReturnVal()); return fromClassInvokerArgs(in).returnVal(in.getReturnVal());
} }
} }
private final Object returnVal; private final Object returnVal;
private ClassMethodArgsAndReturnVal(Class<?> clazz, Method method, Object[] args, Object returnVal) { private ClassInvokerArgsAndReturnVal(Class<?> clazz, Invokable<?, ?> invoker, List<Object> args, Object returnVal) {
super(clazz, method, args); super(clazz, invoker, args);
this.returnVal = returnVal; this.returnVal = returnVal;
} }
private ClassMethodArgsAndReturnVal(Builder builder) { private ClassInvokerArgsAndReturnVal(Builder builder) {
super(builder); super(builder);
this.returnVal = builder.returnVal; this.returnVal = builder.returnVal;
} }
@ -83,7 +84,7 @@ public final class ClassMethodArgsAndReturnVal extends ClassMethodArgs {
return true; return true;
if (o == null || getClass() != o.getClass()) if (o == null || getClass() != o.getClass())
return false; return false;
ClassMethodArgsAndReturnVal that = ClassMethodArgsAndReturnVal.class.cast(o); ClassInvokerArgsAndReturnVal that = ClassInvokerArgsAndReturnVal.class.cast(o);
return super.equals(that) && equal(this.returnVal, that.returnVal); return super.equals(that) && equal(this.returnVal, that.returnVal);
} }

View File

@ -32,7 +32,7 @@ import javax.inject.Singleton;
import org.jclouds.collect.Memoized; import org.jclouds.collect.Memoized;
import org.jclouds.domain.Location; import org.jclouds.domain.Location;
import org.jclouds.internal.ClassMethodArgsAndReturnVal; import org.jclouds.internal.ClassInvokerArgsAndReturnVal;
import org.jclouds.location.Iso3166; import org.jclouds.location.Iso3166;
import org.jclouds.location.Provider; import org.jclouds.location.Provider;
import org.jclouds.location.Region; import org.jclouds.location.Region;
@ -73,7 +73,7 @@ public class LocationModule extends AbstractModule {
@Override @Override
protected void configure() { protected void configure() {
bind(new TypeLiteral<Function<ClassMethodArgsAndReturnVal, Optional<Object>>>(){}).to(ImplicitOptionalConverter.class); bind(new TypeLiteral<Function<ClassInvokerArgsAndReturnVal, Optional<Object>>>(){}).to(ImplicitOptionalConverter.class);
} }
@Provides @Provides

View File

@ -19,130 +19,140 @@
package org.jclouds.rest; package org.jclouds.rest;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Collections2.filter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.predicates.Validator; import org.jclouds.predicates.Validator;
import org.jclouds.rest.annotations.ParamValidators; import org.jclouds.rest.annotations.ParamValidators;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter;
import com.google.inject.Injector; import com.google.inject.Injector;
/** /**
* Validates method parameters. * Validates method parameters.
* *
* Checks the {@link ParamValidators} annotation for validators. There can be method-level * Checks the {@link ParamValidators} annotation for validators. There can be method-level validators that apply to all
* validators that apply to all parameters, and parameter-level validators. When validation on at * parameters, and parameter-level validators. When validation on at least one parameter doesn't pass, throws
* least one parameter doesn't pass, throws {@link IllegalStateException}. * {@link IllegalStateException}.
* *
* @author Oleksiy Yarmula * @author Oleksiy Yarmula
*/ */
public class InputParamValidator { public class InputParamValidator {
private final Injector injector; private final Injector injector;
@Inject @Inject
public InputParamValidator(Injector injector) { public InputParamValidator(Injector injector) {
this.injector = injector; this.injector = injector;
} }
public InputParamValidator() { public InputParamValidator() {
injector = null; injector = null;
} }
/** /**
* Validates that method parameters are correct, according to {@link ParamValidators}. * Validates that method parameters are correct, according to {@link ParamValidators}.
* *
* @param method * @param method
* method with optionally set {@link ParamValidators} * method with optionally set {@link ParamValidators}
* @param args * @param args
* method arguments with optionally set {@link ParamValidators} * method arguments with optionally set {@link ParamValidators}
* @see ParamValidators * @see ParamValidators
* @see Validator * @see Validator
* *
* @throws IllegalStateException * @throws IllegalStateException
* if validation failed * if validation failed
*/ */
public void validateMethodParametersOrThrow(Method method, Object... args) { @Deprecated
public void validateMethodParametersOrThrow(Method method, @Nullable Object... args) {
validateMethodParametersOrThrow(Invokable.from(checkNotNull(method, "method")),
Lists.newArrayList(args));
}
try { public void validateMethodParametersOrThrow(Invokable<?, ?> method, List<Object> args) {
performMethodValidation(method, args); try {
performParameterValidation(method.getParameterAnnotations(), args); performMethodValidation(checkNotNull(method, "method"), args);
} catch(IllegalArgumentException e) { performParameterValidation(method.getParameters(), args);
String argsString = Iterables.toString(Arrays.asList(args)); } catch (IllegalArgumentException e) {
throw new IllegalArgumentException(String.format("Validation on '%s#%s' didn't pass for arguments: " + throw new IllegalArgumentException(String.format("Validation on '%s#%s' didn't pass for arguments: "
"%s. %n Reason: %s.", method.getDeclaringClass().getName(), method.getName(), argsString, + "%s. %n Reason: %s.", method.getDeclaringClass().getName(), method.getName(), args,
e.getMessage())); e.getMessage()));
} }
} }
/** /**
* Returns if all the method parameters passed all of the method-level * Returns if all the method parameters passed all of the method-level validators or throws an
* validators or throws an {@link IllegalArgumentException}. * {@link IllegalArgumentException}.
* @param method *
* method with optionally set {@link ParamValidators}. This can not be null. * @param method
* @param args * method with optionally set {@link ParamValidators}. This can not be null.
* method's parameters * @param args
*/ * method's parameters
private void performMethodValidation(Method method, Object... args) { */
ParamValidators paramValidatorsAnnotation = checkNotNull(method).getAnnotation( private void performMethodValidation(Invokable<?, ?> method, List<Object> args) {
ParamValidators.class); ParamValidators paramValidatorsAnnotation = method.getAnnotation(ParamValidators.class);
if (paramValidatorsAnnotation == null) if (paramValidatorsAnnotation == null)
return; // by contract return; // by contract
List<Validator<?>> methodValidators = getValidatorsFromAnnotation(paramValidatorsAnnotation); List<Validator<?>> methodValidators = getValidatorsFromAnnotation(paramValidatorsAnnotation);
runPredicatesAgainstArgs(methodValidators, args); runPredicatesAgainstArgs(methodValidators, args);
} }
/** /**
* Returns if all the method parameters passed all of their corresponding * Returns if all the method parameters passed all of their corresponding validators or throws an
* validators or throws an {@link IllegalArgumentException}. * {@link IllegalArgumentException}.
* *
* @param annotations * @param parameters
* annotations for method's arguments * annotations for method's arguments
* @param args * @param args
* arguments that correspond to the array of annotations * arguments that correspond to the array of annotations
*/ */
private void performParameterValidation(Annotation[][] annotations, Object... args) { private void performParameterValidation(List<Parameter> parameters, List<Object> args) {
for (int currentParameterIndex = 0; currentParameterIndex < annotations.length; currentParameterIndex++) { for (Parameter param : filter(parameters, new Predicate<Parameter>() {
ParamValidators annotation = findParamValidatorsAnnotationOrReturnNull(annotations[currentParameterIndex]); public boolean apply(Parameter in) {
if (annotation == null) return in.isAnnotationPresent(ParamValidators.class);
continue; }
List<Validator<?>> parameterValidators = getValidatorsFromAnnotation(annotation); })) {
runPredicatesAgainstArgs(parameterValidators, ParamValidators annotation = param.getAnnotation(ParamValidators.class);
args[currentParameterIndex]); if (annotation == null)
} continue;
} List<Validator<?>> parameterValidators = getValidatorsFromAnnotation(annotation);
runPredicatesAgainstArg(parameterValidators, args.get(param.hashCode()));// TODO position guava issue 1243
}
}
private List<Validator<?>> getValidatorsFromAnnotation(ParamValidators paramValidatorsAnnotation) { private List<Validator<?>> getValidatorsFromAnnotation(ParamValidators paramValidatorsAnnotation) {
List<Validator<?>> validators = Lists.newArrayList(); List<Validator<?>> validators = Lists.newArrayList();
for (Class<? extends Validator<?>> validator : paramValidatorsAnnotation.value()) { for (Class<? extends Validator<?>> validator : paramValidatorsAnnotation.value()) {
validators.add(checkNotNull(injector.getInstance(validator))); validators.add(checkNotNull(injector.getInstance(validator)));
} }
return validators; return validators;
} }
@SuppressWarnings("unchecked")
private void runPredicatesAgainstArg(List<Validator<?>> predicates, Object arg) {
for (@SuppressWarnings("rawtypes")
Validator validator : predicates) {
validator.apply(arg);
}
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void runPredicatesAgainstArgs(List<Validator<?>> predicates, Object... args) { private void runPredicatesAgainstArgs(List<Validator<?>> predicates, List<Object> args) {
for (@SuppressWarnings("rawtypes") Validator validator : predicates) { for (@SuppressWarnings("rawtypes")
Iterables.all(Arrays.asList(args), validator); Validator validator : predicates) {
} Iterables.all(args, validator);
} }
}
private ParamValidators findParamValidatorsAnnotationOrReturnNull(
Annotation[] parameterAnnotations) {
for (Annotation annotation : parameterAnnotations) {
if (annotation instanceof ParamValidators)
return (ParamValidators) annotation;
}
return null;
}
} }

View File

@ -21,7 +21,6 @@ package org.jclouds.rest.config;
import static org.jclouds.Constants.PROPERTY_TIMEOUTS_PREFIX; import static org.jclouds.Constants.PROPERTY_TIMEOUTS_PREFIX;
import static org.jclouds.rest.config.BinderUtils.bindClientAndAsyncClient; import static org.jclouds.rest.config.BinderUtils.bindClientAndAsyncClient;
import java.lang.reflect.Method;
import java.net.URI; import java.net.URI;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -32,7 +31,7 @@ import javax.inject.Singleton;
import org.jclouds.concurrent.internal.SyncProxy; import org.jclouds.concurrent.internal.SyncProxy;
import org.jclouds.functions.IdentityFunction; import org.jclouds.functions.IdentityFunction;
import org.jclouds.http.functions.config.SaxParserModule; import org.jclouds.http.functions.config.SaxParserModule;
import org.jclouds.internal.ClassMethodArgs; import org.jclouds.internal.ClassInvokerArgs;
import org.jclouds.internal.FilterStringsBoundToInjectorByName; import org.jclouds.internal.FilterStringsBoundToInjectorByName;
import org.jclouds.json.config.GsonModule; import org.jclouds.json.config.GsonModule;
import org.jclouds.location.config.LocationModule; import org.jclouds.location.config.LocationModule;
@ -44,15 +43,12 @@ import org.jclouds.rest.internal.AsyncRestClientProxy;
import org.jclouds.rest.internal.CreateAsyncClientForCaller; import org.jclouds.rest.internal.CreateAsyncClientForCaller;
import org.jclouds.rest.internal.CreateClientForCaller; import org.jclouds.rest.internal.CreateClientForCaller;
import org.jclouds.rest.internal.RestAnnotationProcessor; import org.jclouds.rest.internal.RestAnnotationProcessor;
import org.jclouds.rest.internal.RestAnnotationProcessor.MethodKey;
import org.jclouds.rest.internal.SeedAnnotationCache;
import org.jclouds.util.Maps2; import org.jclouds.util.Maps2;
import org.jclouds.util.Predicates2; import org.jclouds.util.Predicates2;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
@ -124,24 +120,18 @@ public class RestModule extends AbstractModule {
}); });
} }
@Provides
@Singleton
private LoadingCache<Class<?>, Cache<MethodKey, Method>> seedAnnotationCache(SeedAnnotationCache seedAnnotationCache) {
return CacheBuilder.newBuilder().build(seedAnnotationCache);
}
@Provides @Provides
@Singleton @Singleton
@Named("async") @Named("async")
LoadingCache<ClassMethodArgs, Object> provideAsyncDelegateMap(CreateAsyncClientForCaller createAsyncClientForCaller) { LoadingCache<ClassInvokerArgs, Object> provideAsyncDelegateMap(CreateAsyncClientForCaller createAsyncClientForCaller) {
return CacheBuilder.newBuilder().build(createAsyncClientForCaller); return CacheBuilder.newBuilder().build(createAsyncClientForCaller);
} }
@Provides @Provides
@Singleton @Singleton
@Named("sync") @Named("sync")
LoadingCache<ClassMethodArgs, Object> provideSyncDelegateMap(CreateClientForCaller createClientForCaller) { LoadingCache<ClassInvokerArgs, Object> provideSyncDelegateMap(CreateClientForCaller createClientForCaller) {
return CacheBuilder.newBuilder().build(createClientForCaller); return CacheBuilder.newBuilder().build(createClientForCaller);
} }

View File

@ -18,7 +18,7 @@
*/ */
package org.jclouds.rest.functions; package org.jclouds.rest.functions;
import org.jclouds.internal.ClassMethodArgsAndReturnVal; import org.jclouds.internal.ClassInvokerArgsAndReturnVal;
import com.google.common.annotations.Beta; import com.google.common.annotations.Beta;
import com.google.common.base.Optional; import com.google.common.base.Optional;
@ -31,7 +31,7 @@ import com.google.common.base.Optional;
public class AlwaysPresentImplicitOptionalConverter implements ImplicitOptionalConverter { public class AlwaysPresentImplicitOptionalConverter implements ImplicitOptionalConverter {
@Override @Override
public Optional<Object> apply(ClassMethodArgsAndReturnVal input) { public Optional<Object> apply(ClassInvokerArgsAndReturnVal input) {
return Optional.of(input.getReturnVal()); return Optional.of(input.getReturnVal());
} }

View File

@ -18,7 +18,7 @@
*/ */
package org.jclouds.rest.functions; package org.jclouds.rest.functions;
import org.jclouds.internal.ClassMethodArgsAndReturnVal; import org.jclouds.internal.ClassInvokerArgsAndReturnVal;
import org.jclouds.rest.config.RestClientModule; import org.jclouds.rest.config.RestClientModule;
import com.google.common.annotations.Beta; import com.google.common.annotations.Beta;
@ -39,19 +39,19 @@ import com.google.inject.ImplementedBy;
* } * }
* </pre> * </pre>
* *
* The input object of type {@link ClassMethodArgsAndReturnVal} will include the * The input object of type {@link ClassInvokerArgsAndReturnVal} will include the
* following. * following.
* <ol> * <ol>
* <li>the class declaring the method that returns optional: * <li>the class declaring the method that returns optional:
* {@link ClassMethodArgsAndReturnVal#getClazz}; in the example above, * {@link ClassInvokerArgsAndReturnVal#getClazz}; in the example above,
* {@code MyCloud}</li> * {@code MyCloud}</li>
* <li>the method returning the optional: * <li>the method returning the optional:
* {@link ClassMethodArgsAndReturnVal#getMethod}; in the example above, * {@link ClassInvokerArgsAndReturnVal#getMethod}; in the example above,
* {@code getKeyPairExtensionForRegion}</li> * {@code getKeyPairExtensionForRegion}</li>
* <li>the args passed to that method at runtime: * <li>the args passed to that method at runtime:
* {@link ClassMethodArgsAndReturnVal#getArgs}; for example {@code North}</li> * {@link ClassInvokerArgsAndReturnVal#getArgs}; for example {@code North}</li>
* <li>the rest client to be enclosed in the optional, should you choose to * <li>the rest client to be enclosed in the optional, should you choose to
* return it: {@link ClassMethodArgsAndReturnVal#getReturnVal}; in the example * return it: {@link ClassInvokerArgsAndReturnVal#getReturnVal}; in the example
* above, an implementation of {@code KeyPairClient}</li> * above, an implementation of {@code KeyPairClient}</li>
* </ol> * </ol>
* *
@ -80,6 +80,6 @@ import com.google.inject.ImplementedBy;
*/ */
@Beta @Beta
@ImplementedBy(PresentWhenApiVersionLexicographicallyAtOrAfterSinceApiVersion.class) @ImplementedBy(PresentWhenApiVersionLexicographicallyAtOrAfterSinceApiVersion.class)
public interface ImplicitOptionalConverter extends Function<ClassMethodArgsAndReturnVal, Optional<Object>> { public interface ImplicitOptionalConverter extends Function<ClassInvokerArgsAndReturnVal, Optional<Object>> {
} }

View File

@ -23,7 +23,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.jclouds.internal.ClassMethodArgsAndReturnVal; import org.jclouds.internal.ClassInvokerArgsAndReturnVal;
import org.jclouds.rest.annotations.ApiVersion; import org.jclouds.rest.annotations.ApiVersion;
import org.jclouds.rest.annotations.SinceApiVersion; import org.jclouds.rest.annotations.SinceApiVersion;
@ -43,7 +43,7 @@ import com.google.common.cache.LoadingCache;
public class PresentWhenApiVersionLexicographicallyAtOrAfterSinceApiVersion implements ImplicitOptionalConverter { public class PresentWhenApiVersionLexicographicallyAtOrAfterSinceApiVersion implements ImplicitOptionalConverter {
@VisibleForTesting @VisibleForTesting
static final class Loader extends CacheLoader<ClassMethodArgsAndReturnVal, Optional<Object>> { static final class Loader extends CacheLoader<ClassInvokerArgsAndReturnVal, Optional<Object>> {
private final String apiVersion; private final String apiVersion;
@Inject @Inject
@ -52,7 +52,7 @@ public class PresentWhenApiVersionLexicographicallyAtOrAfterSinceApiVersion impl
} }
@Override @Override
public Optional<Object> load(ClassMethodArgsAndReturnVal input) { public Optional<Object> load(ClassInvokerArgsAndReturnVal input) {
Optional<SinceApiVersion> sinceApiVersion = Optional.fromNullable(input.getClazz().getAnnotation( Optional<SinceApiVersion> sinceApiVersion = Optional.fromNullable(input.getClazz().getAnnotation(
SinceApiVersion.class)); SinceApiVersion.class));
if (sinceApiVersion.isPresent()) { if (sinceApiVersion.isPresent()) {
@ -67,7 +67,7 @@ public class PresentWhenApiVersionLexicographicallyAtOrAfterSinceApiVersion impl
} }
} }
private final LoadingCache<ClassMethodArgsAndReturnVal, Optional<Object>> lookupCache; private final LoadingCache<ClassInvokerArgsAndReturnVal, Optional<Object>> lookupCache;
@Inject @Inject
protected PresentWhenApiVersionLexicographicallyAtOrAfterSinceApiVersion(@ApiVersion String apiVersion) { protected PresentWhenApiVersionLexicographicallyAtOrAfterSinceApiVersion(@ApiVersion String apiVersion) {
@ -76,7 +76,7 @@ public class PresentWhenApiVersionLexicographicallyAtOrAfterSinceApiVersion impl
} }
@Override @Override
public Optional<Object> apply(ClassMethodArgsAndReturnVal input) { public Optional<Object> apply(ClassInvokerArgsAndReturnVal input) {
return lookupCache.getUnchecked(input); return lookupCache.getUnchecked(input);
} }

View File

@ -18,14 +18,34 @@
*/ */
package org.jclouds.rest.internal; package org.jclouds.rest.internal;
import static com.google.common.base.Functions.compose;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.find;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.difference;
import static com.google.common.util.concurrent.Callables.returning;
import static com.google.common.util.concurrent.Futures.immediateFailedFuture; import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
import static com.google.common.util.concurrent.Futures.transform; import static com.google.common.util.concurrent.Futures.transform;
import static com.google.common.util.concurrent.Futures.withFallback; import static com.google.common.util.concurrent.Futures.withFallback;
import static com.google.inject.util.Types.newParameterizedType;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static javax.ws.rs.core.MediaType.APPLICATION_XML;
import static org.jclouds.concurrent.Futures.makeListenable; import static org.jclouds.concurrent.Futures.makeListenable;
import static org.jclouds.http.HttpUtils.tryFindHttpMethod;
import static org.jclouds.util.Optionals2.isReturnTypeOptional;
import static org.jclouds.util.Optionals2.returnTypeOrTypeOfOptional;
import static org.jclouds.util.Throwables2.getFirstThrowableOfType;
import java.io.InputStream;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.net.URI;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -34,34 +54,60 @@ import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Qualifier; import javax.inject.Qualifier;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.lang.model.type.NullType;
import javax.ws.rs.Path;
import org.jclouds.Constants; import org.jclouds.Constants;
import org.jclouds.fallbacks.MapHttp4xxCodesToExceptions; import org.jclouds.fallbacks.MapHttp4xxCodesToExceptions;
import org.jclouds.functions.IdentityFunction;
import org.jclouds.functions.OnlyElementOrNull;
import org.jclouds.http.HttpCommand; import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpCommandExecutorService; import org.jclouds.http.HttpCommandExecutorService;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import org.jclouds.internal.ClassMethodArgs; import org.jclouds.http.functions.ParseFirstJsonValueNamed;
import org.jclouds.internal.ClassMethodArgsAndReturnVal; import org.jclouds.http.functions.ParseJson;
import org.jclouds.http.functions.ParseSax;
import org.jclouds.http.functions.ParseSax.HandlerWithResult;
import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x;
import org.jclouds.http.functions.ParseXMLWithJAXB;
import org.jclouds.http.functions.ReleasePayloadAndReturn;
import org.jclouds.http.functions.ReturnInputStream;
import org.jclouds.http.functions.ReturnStringIf2xx;
import org.jclouds.http.functions.ReturnTrueIf2xx;
import org.jclouds.http.functions.UnwrapOnlyJsonValue;
import org.jclouds.internal.ClassInvokerArgs;
import org.jclouds.internal.ClassInvokerArgsAndReturnVal;
import org.jclouds.json.internal.GsonWrapper;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import org.jclouds.rest.AuthorizationException; import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.InvocationContext; import org.jclouds.rest.InvocationContext;
import org.jclouds.rest.annotations.Delegate; import org.jclouds.rest.annotations.Delegate;
import org.jclouds.rest.annotations.Fallback; import org.jclouds.rest.annotations.Fallback;
import org.jclouds.util.Optionals2; import org.jclouds.rest.annotations.JAXBResponseParser;
import org.jclouds.util.Throwables2; import org.jclouds.rest.annotations.OnlyElement;
import org.jclouds.rest.annotations.ResponseParser;
import org.jclouds.rest.annotations.SelectJson;
import org.jclouds.rest.annotations.Transform;
import org.jclouds.rest.annotations.Unwrap;
import org.jclouds.rest.annotations.XMLResponseParser;
import org.jclouds.rest.internal.RestAnnotationProcessor.InvokerKey;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.base.Throwables; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables; import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.AbstractInvocationHandler; import com.google.common.reflect.AbstractInvocationHandler;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.FutureFallback; import com.google.common.util.concurrent.FutureFallback;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.inject.Binding; import com.google.inject.Binding;
@ -70,27 +116,23 @@ import com.google.inject.Injector;
import com.google.inject.Key; import com.google.inject.Key;
import com.google.inject.Provides; import com.google.inject.Provides;
import com.google.inject.ProvisionException; import com.google.inject.ProvisionException;
import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
import com.google.inject.util.Types;
/** /**
* Generates RESTful clients from appropriately annotated interfaces. * Generates RESTful clients from appropriately annotated interfaces.
* <p/> * <p/>
* Particularly, this code delegates calls to other things. * Particularly, this code delegates calls to other things.
* <ol> * <ol>
* <li>if the method has a {@link Provides} annotation, it responds via a * <li>if the method has a {@link Provides} annotation, it responds via a {@link Injector} lookup</li>
* {@link Injector} lookup</li> * <li>if the method has a {@link Delegate} annotation, it responds with an instance of interface set in returnVal,
* <li>if the method has a {@link Delegate} annotation, it responds with an * adding the current JAXrs annotations to whatever are on that class.</li>
* instance of interface set in returnVal, adding the current JAXrs annotations
* to whatever are on that class.</li>
* <ul> * <ul>
* <li>ex. if the method with {@link Delegate} has a {@code Path} annotation, * <li>ex. if the method with {@link Delegate} has a {@code Path} annotation, and the returnval interface also has
* and the returnval interface also has {@code Path}, these values are combined. * {@code Path}, these values are combined.</li>
* </li>
* </ul> * </ul>
* <li>if {@link RestAnnotationProcessor#delegationMap} contains a mapping for * <li>if {@link RestAnnotationProcessor#delegationMap} contains a mapping for this, and the returnVal is properly
* this, and the returnVal is properly assigned as a {@link ListenableFuture}, * assigned as a {@link ListenableFuture}, it responds with an http implementation.</li>
* it responds with an http implementation.</li>
* <li>otherwise a RuntimeException is thrown with a message including: * <li>otherwise a RuntimeException is thrown with a message including:
* {@code method is intended solely to set constants}</li> * {@code method is intended solely to set constants}</li>
* </ol> * </ol>
@ -102,51 +144,86 @@ public abstract class AsyncRestClientProxy extends AbstractInvocationHandler {
public static interface Factory { public static interface Factory {
Declaring declaring(Class<?> declaring); Declaring declaring(Class<?> declaring);
Caller caller(ClassMethodArgs caller); Caller caller(ClassInvokerArgs caller);
} }
public final static class Declaring extends AsyncRestClientProxy { public final static class Declaring extends AsyncRestClientProxy {
@Inject @Inject
private Declaring(Injector injector, Function<ClassMethodArgsAndReturnVal, Optional<Object>> optionalConverter, private Declaring(Injector injector, Function<ClassInvokerArgsAndReturnVal, Optional<Object>> optionalConverter,
HttpCommandExecutorService http, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads, HttpCommandExecutorService http, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads,
@Named("async") LoadingCache<ClassMethodArgs, Object> delegateMap, RestAnnotationProcessor.Factory rap, @Named("async") LoadingCache<ClassInvokerArgs, Object> delegateMap, RestAnnotationProcessor.Factory rap,
@Assisted Class<?> declaring) { ParseSax.Factory parserFactory, @Assisted Class<?> declaring) {
super(injector, optionalConverter, http, userThreads, delegateMap, rap.declaring(declaring), declaring); super(injector, optionalConverter, http, userThreads, delegateMap, rap.declaring(declaring), parserFactory,
declaring);
} }
} }
public final static class Caller extends AsyncRestClientProxy { public final static class Caller extends AsyncRestClientProxy {
@Inject @Inject
private Caller(Injector injector, Function<ClassMethodArgsAndReturnVal, Optional<Object>> optionalConverter, private Caller(Injector injector, Function<ClassInvokerArgsAndReturnVal, Optional<Object>> optionalConverter,
HttpCommandExecutorService http, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads, HttpCommandExecutorService http, @Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads,
@Named("async") LoadingCache<ClassMethodArgs, Object> delegateMap, RestAnnotationProcessor.Factory rap, @Named("async") LoadingCache<ClassInvokerArgs, Object> delegateMap, RestAnnotationProcessor.Factory rap,
@Assisted ClassMethodArgs caller) { ParseSax.Factory parserFactory, @Assisted ClassInvokerArgs caller) {
super(injector, optionalConverter, http, userThreads, delegateMap, rap.caller(caller), caller.getClazz()); super(injector, optionalConverter, http, userThreads, delegateMap, rap.caller(caller), parserFactory, caller
.getClazz());
} }
} }
@Resource @Resource
private Logger logger = Logger.NULL; private Logger logger = Logger.NULL;
private final Injector injector; private final Injector injector;
private final HttpCommandExecutorService http; private final HttpCommandExecutorService http;
private final ExecutorService userThreads; private final ExecutorService userThreads;
private final Function<ClassMethodArgsAndReturnVal, Optional<Object>> optionalConverter; private final Function<ClassInvokerArgsAndReturnVal, Optional<Object>> optionalConverter;
private final LoadingCache<ClassMethodArgs, Object> delegateMap; private final LoadingCache<ClassInvokerArgs, Object> delegateMap;
private final RestAnnotationProcessor annotationProcessor; private final RestAnnotationProcessor annotationProcessor;
private final ParseSax.Factory parserFactory;
private final Class<?> declaring; private final Class<?> declaring;
private static final LoadingCache<Class<?>, Cache<InvokerKey, Invokable<?, ?>>> delegationMapCache = CacheBuilder
.newBuilder().build(new CacheLoader<Class<?>, Cache<InvokerKey, Invokable<?, ?>>>() {
public Cache<InvokerKey, Invokable<?, ?>> load(Class<?> declaring) throws ExecutionException {
Cache<InvokerKey, Invokable<?, ?>> delegationMap = CacheBuilder.newBuilder()
.<InvokerKey, Invokable<?, ?>> build();
for (Method method : difference(ImmutableSet.copyOf(declaring.getMethods()),
ImmutableSet.copyOf(Object.class.getMethods()))) {
Invokable<?, ?> invoker = Invokable.from(method);
if (isHttpMethod(invoker) || method.isAnnotationPresent(Delegate.class)) {
delegationMap.get(new InvokerKey(invoker), returning(invoker));
} else if (!method.getDeclaringClass().equals(declaring)) { // potentially overridden
} else if (method.isAnnotationPresent(Provides.class)) {
}
}
return delegationMap;
}
});
private Invokable<?, ?> getDelegateOrNull(Invokable<?, ?> in) {
return delegationMapCache.getUnchecked(declaring).getIfPresent(new InvokerKey(in));
}
private static boolean isHttpMethod(Invokable<?, ?> invoker) {
return invoker.isAnnotationPresent(Path.class) || tryFindHttpMethod(invoker).isPresent()
|| any(invoker.getParameters(), new Predicate<Parameter>() {
public boolean apply(Parameter in) {
return in.getType().isAssignableFrom(HttpRequest.class);
}
});
}
private AsyncRestClientProxy(Injector injector, private AsyncRestClientProxy(Injector injector,
Function<ClassMethodArgsAndReturnVal, Optional<Object>> optionalConverter, HttpCommandExecutorService http, Function<ClassInvokerArgsAndReturnVal, Optional<Object>> optionalConverter, HttpCommandExecutorService http,
ExecutorService userThreads, LoadingCache<ClassMethodArgs, Object> delegateMap, ExecutorService userThreads, LoadingCache<ClassInvokerArgs, Object> delegateMap,
RestAnnotationProcessor annotationProcessor, Class<?> declaring) { RestAnnotationProcessor annotationProcessor, ParseSax.Factory parserFactory, Class<?> declaring) {
this.injector = injector; this.injector = injector;
this.optionalConverter = optionalConverter; this.optionalConverter = optionalConverter;
this.http = http; this.http = http;
this.userThreads = userThreads; this.userThreads = userThreads;
this.delegateMap = delegateMap; this.delegateMap = delegateMap;
this.declaring = declaring;
this.annotationProcessor = annotationProcessor; this.annotationProcessor = annotationProcessor;
this.parserFactory = parserFactory;
this.declaring = declaring;
} }
private static final Predicate<Annotation> isQualifierPresent = new Predicate<Annotation>() { private static final Predicate<Annotation> isQualifierPresent = new Predicate<Annotation>() {
@ -172,44 +249,42 @@ public abstract class AsyncRestClientProxy extends AbstractInvocationHandler {
} }
} }
public boolean isRestCall(Method method) { private boolean isRestCall(Method method) {
return annotationProcessor.getDelegateOrNull(method) != null return getDelegateOrNull(Invokable.from(method)) != null
&& ListenableFuture.class.isAssignableFrom(method.getReturnType()); && ListenableFuture.class.isAssignableFrom(method.getReturnType());
} }
public Object propagateContextToDelegate(Method method, Object[] args) throws ExecutionException { private Object propagateContextToDelegate(Method method, Object[] args) throws ExecutionException {
Class<?> asyncClass = Optionals2.returnTypeOrTypeOfOptional(method); Class<?> asyncClass = returnTypeOrTypeOfOptional(method);
ClassMethodArgs cma = new ClassMethodArgs(asyncClass, method, args); ClassInvokerArgs cma = ClassInvokerArgs.builder().clazz(asyncClass).invoker(method).args(args).build();
Object returnVal = delegateMap.get(cma); Object returnVal = delegateMap.get(cma);
if (Optionals2.isReturnTypeOptional(method)) { if (isReturnTypeOptional(method)) {
ClassMethodArgsAndReturnVal cmar = ClassMethodArgsAndReturnVal.builder().fromClassMethodArgs(cma) ClassInvokerArgsAndReturnVal cmar = ClassInvokerArgsAndReturnVal.builder().fromClassInvokerArgs(cma)
.returnVal(returnVal).build(); .returnVal(returnVal).build();
return optionalConverter.apply(cmar); return optionalConverter.apply(cmar);
} }
return returnVal; return returnVal;
} }
public Object lookupValueFromGuice(Method method) { private Object lookupValueFromGuice(Method method) {
try { try {
// TODO: tidy
Type genericReturnType = method.getGenericReturnType(); Type genericReturnType = method.getGenericReturnType();
try { try {
Annotation qualifier = Iterables.find(ImmutableList.copyOf(method.getAnnotations()), isQualifierPresent); Annotation qualifier = find(ImmutableList.copyOf(method.getAnnotations()), isQualifierPresent);
return getInstanceOfTypeWithQualifier(genericReturnType, qualifier); return getInstanceOfTypeWithQualifier(genericReturnType, qualifier);
} catch (ProvisionException e) { } catch (ProvisionException e) {
throw Throwables.propagate(e.getCause()); throw propagate(e.getCause());
} catch (RuntimeException e) { } catch (RuntimeException e) {
return instanceOfTypeOrPropagate(genericReturnType, e); return instanceOfTypeOrPropagate(genericReturnType, e);
} }
} catch (ProvisionException e) { } catch (ProvisionException e) {
AuthorizationException aex = Throwables2.getFirstThrowableOfType(e, AuthorizationException.class); AuthorizationException aex = getFirstThrowableOfType(e, AuthorizationException.class);
if (aex != null) if (aex != null)
throw aex; throw aex;
throw e; throw e;
} }
} }
// TODO: tidy
private Object instanceOfTypeOrPropagate(Type genericReturnType, RuntimeException e) { private Object instanceOfTypeOrPropagate(Type genericReturnType, RuntimeException e) {
try { try {
// look for an existing binding // look for an existing binding
@ -218,7 +293,7 @@ public abstract class AsyncRestClientProxy extends AbstractInvocationHandler {
return binding.getProvider().get(); return binding.getProvider().get();
// then, try looking via supplier // then, try looking via supplier
binding = injector.getExistingBinding(Key.get(Types.newParameterizedType(Supplier.class, genericReturnType))); binding = injector.getExistingBinding(Key.get(newParameterizedType(Supplier.class, genericReturnType)));
if (binding != null) if (binding != null)
return Supplier.class.cast(binding.getProvider().get()).get(); return Supplier.class.cast(binding.getProvider().get()).get();
@ -229,7 +304,6 @@ public abstract class AsyncRestClientProxy extends AbstractInvocationHandler {
} }
} }
// TODO: tidy
private Object getInstanceOfTypeWithQualifier(Type genericReturnType, Annotation qualifier) { private Object getInstanceOfTypeWithQualifier(Type genericReturnType, Annotation qualifier) {
// look for an existing binding // look for an existing binding
Binding<?> binding = injector.getExistingBinding(Key.get(genericReturnType, qualifier)); Binding<?> binding = injector.getExistingBinding(Key.get(genericReturnType, qualifier));
@ -237,40 +311,102 @@ public abstract class AsyncRestClientProxy extends AbstractInvocationHandler {
return binding.getProvider().get(); return binding.getProvider().get();
// then, try looking via supplier // then, try looking via supplier
binding = injector.getExistingBinding(Key.get(Types.newParameterizedType(Supplier.class, genericReturnType), binding = injector
qualifier)); .getExistingBinding(Key.get(newParameterizedType(Supplier.class, genericReturnType), qualifier));
if (binding != null) if (binding != null)
return Supplier.class.cast(binding.getProvider().get()).get(); return Supplier.class.cast(binding.getProvider().get()).get();
// else try to create an instance // else try to create an instance
return injector.getInstance(Key.get(genericReturnType, qualifier)); return injector.getInstance(Key.get(genericReturnType, qualifier));
} }
@Deprecated
static Function<HttpResponse, ?> createResponseParser(ParseSax.Factory parserFactory, Injector injector,
Method method, HttpRequest request) {
return createResponseParser(parserFactory, injector, Invokable.from(method), request);
}
@SuppressWarnings("unchecked")
@VisibleForTesting
private static Function<HttpResponse, ?> createResponseParser(ParseSax.Factory parserFactory, Injector injector,
Invokable<?, ?> method, HttpRequest request) {
Function<HttpResponse, ?> transformer;
Class<? extends HandlerWithResult<?>> handler = getSaxResponseParserClassOrNull(method);
if (handler != null) {
transformer = parserFactory.create(injector.getInstance(handler));
} else {
transformer = getTransformerForMethod(method, injector);
}
if (transformer instanceof InvocationContext<?>) {
((InvocationContext<?>) transformer).setContext(request);
}
if (method.isAnnotationPresent(Transform.class)) {
Function<?, ?> wrappingTransformer = injector.getInstance(method.getAnnotation(Transform.class).value());
if (wrappingTransformer instanceof InvocationContext<?>) {
((InvocationContext<?>) wrappingTransformer).setContext(request);
}
transformer = compose(Function.class.cast(wrappingTransformer), transformer);
}
return transformer;
}
private static Class<? extends HandlerWithResult<?>> getSaxResponseParserClassOrNull(Invokable<?, ?> method) {
XMLResponseParser annotation = method.getAnnotation(XMLResponseParser.class);
if (annotation != null) {
return annotation.value();
}
return null;
}
// TODO: refactor this out of here
@VisibleForTesting
@SuppressWarnings({ "rawtypes", "unchecked" })
public static Function<HttpResponse, ?> getTransformerForMethod(Invokable<?, ?> method, Injector injector) {
Function<HttpResponse, ?> transformer;
if (method.isAnnotationPresent(SelectJson.class)) {
Type returnVal = getReturnTypeFor(method.getReturnType());
if (method.isAnnotationPresent(OnlyElement.class))
returnVal = newParameterizedType(Set.class, returnVal);
transformer = new ParseFirstJsonValueNamed(injector.getInstance(GsonWrapper.class),
TypeLiteral.get(returnVal), method.getAnnotation(SelectJson.class).value());
if (method.isAnnotationPresent(OnlyElement.class))
transformer = compose(new OnlyElementOrNull(), transformer);
} else {
transformer = injector.getInstance(getParserOrThrowException(method));
}
return transformer;
}
private ListenableFuture<?> createListenableFutureForHttpRequestMappedToMethodAndArgs(Method method, Object[] args) private ListenableFuture<?> createListenableFutureForHttpRequestMappedToMethodAndArgs(Method method, Object[] args)
throws ExecutionException { throws ExecutionException {
method = annotationProcessor.getDelegateOrNull(method); return createListenableFutureForHttpRequestMappedToMethodAndArgs(method, Invokable.from(method),
String name = method.getDeclaringClass().getSimpleName() + "." + method.getName(); args == null ? newArrayList(new Object[] { null }) : newArrayList(args));
}
private ListenableFuture<?> createListenableFutureForHttpRequestMappedToMethodAndArgs(Method method,
Invokable<?, ?> invoker, List<Object> args) throws ExecutionException {
String name = invoker.getDeclaringClass().getSimpleName() + "." + invoker.getName();
logger.trace(">> converting %s", name); logger.trace(">> converting %s", name);
FutureFallback<?> fallback = fallbacks.getUnchecked(method); FutureFallback<?> fallback = fallbacks.getUnchecked(invoker);
// in case there is an exception creating the request, we should at least pass in args // in case there is an exception creating the request, we should at least pass in args
if (fallback instanceof InvocationContext) { if (fallback instanceof InvocationContext) {
InvocationContext.class.cast(fallback).setContext((HttpRequest) null); InvocationContext.class.cast(fallback).setContext((HttpRequest) null);
} }
ListenableFuture<?> result; ListenableFuture<?> result;
try { try {
GeneratedHttpRequest request = annotationProcessor.createRequest(method, args); GeneratedHttpRequest request = annotationProcessor.createRequest(method, invoker, newArrayList(args));
if (fallback instanceof InvocationContext) { if (fallback instanceof InvocationContext) {
InvocationContext.class.cast(fallback).setContext(request); InvocationContext.class.cast(fallback).setContext(request);
} }
logger.trace("<< converted %s to %s", name, request.getRequestLine()); logger.trace("<< converted %s to %s", name, request.getRequestLine());
Function<HttpResponse, ?> transformer = annotationProcessor.createResponseParser(method, request); Function<HttpResponse, ?> transformer = createResponseParser(parserFactory, injector, invoker, request);
logger.trace("<< response from %s is parsed by %s", name, transformer.getClass().getSimpleName()); logger.trace("<< response from %s is parsed by %s", name, transformer.getClass().getSimpleName());
logger.debug(">> invoking %s", name); logger.debug(">> invoking %s", name);
result = transform(makeListenable(http.submit(new HttpCommand(request)), userThreads), transformer); result = transform(makeListenable(http.submit(new HttpCommand(request)), userThreads), transformer);
} catch (RuntimeException e) { } catch (RuntimeException e) {
AuthorizationException aex = Throwables2.getFirstThrowableOfType(e, AuthorizationException.class); AuthorizationException aex = getFirstThrowableOfType(e, AuthorizationException.class);
if (aex != null) if (aex != null)
e = aex; e = aex;
try { try {
@ -283,15 +419,16 @@ public abstract class AsyncRestClientProxy extends AbstractInvocationHandler {
return withFallback(result, fallback); return withFallback(result, fallback);
} }
@Override
public String toString() { public String toString() {
return "Client Proxy for :" + declaring.getName(); return "Client Proxy for :" + declaring.getName();
} }
private final LoadingCache<Method, FutureFallback<?>> fallbacks = CacheBuilder.newBuilder().build( private final LoadingCache<Invokable<?, ?>, FutureFallback<?>> fallbacks = CacheBuilder.newBuilder().build(
new CacheLoader<Method, FutureFallback<?>>() { new CacheLoader<Invokable<?, ?>, FutureFallback<?>>() {
@Override @Override
public FutureFallback<?> load(Method key) throws Exception { public FutureFallback<?> load(Invokable<?, ?> key) throws Exception {
Fallback annotation = key.getAnnotation(Fallback.class); Fallback annotation = key.getAnnotation(Fallback.class);
if (annotation != null) { if (annotation != null) {
return injector.getInstance(annotation.value()); return injector.getInstance(annotation.value());
@ -301,4 +438,96 @@ public abstract class AsyncRestClientProxy extends AbstractInvocationHandler {
}); });
private static final TypeToken<ListenableFuture<Boolean>> futureBooleanLiteral = new TypeToken<ListenableFuture<Boolean>>() {
private static final long serialVersionUID = 1L;
};
private static final TypeToken<ListenableFuture<String>> futureStringLiteral = new TypeToken<ListenableFuture<String>>() {
private static final long serialVersionUID = 1L;
};
private static final TypeToken<ListenableFuture<Void>> futureVoidLiteral = new TypeToken<ListenableFuture<Void>>() {
private static final long serialVersionUID = 1L;
};
private static final TypeToken<ListenableFuture<URI>> futureURILiteral = new TypeToken<ListenableFuture<URI>>() {
private static final long serialVersionUID = 1L;
};
private static final TypeToken<ListenableFuture<InputStream>> futureInputStreamLiteral = new TypeToken<ListenableFuture<InputStream>>() {
private static final long serialVersionUID = 1L;
};
private static final TypeToken<ListenableFuture<HttpResponse>> futureHttpResponseLiteral = new TypeToken<ListenableFuture<HttpResponse>>() {
private static final long serialVersionUID = 1L;
};
@Deprecated
static Key<? extends Function<HttpResponse, ?>> getParserOrThrowException(Method method) {
return getParserOrThrowException(Invokable.from(method));
}
@SuppressWarnings("unchecked")
private static Key<? extends Function<HttpResponse, ?>> getParserOrThrowException(Invokable<?, ?> method) {
ResponseParser annotation = method.getAnnotation(ResponseParser.class);
if (annotation == null) {
if (method.getReturnType().equals(void.class) || method.getReturnType().equals(futureVoidLiteral)) {
return Key.get(ReleasePayloadAndReturn.class);
} else if (method.getReturnType().equals(boolean.class) || method.getReturnType().equals(Boolean.class)
|| method.getReturnType().equals(futureBooleanLiteral)) {
return Key.get(ReturnTrueIf2xx.class);
} else if (method.getReturnType().equals(InputStream.class)
|| method.getReturnType().equals(futureInputStreamLiteral)) {
return Key.get(ReturnInputStream.class);
} else if (method.getReturnType().equals(HttpResponse.class)
|| method.getReturnType().equals(futureHttpResponseLiteral)) {
return Key.get(Class.class.cast(IdentityFunction.class));
} else if (RestAnnotationProcessor.getAcceptHeadersOrNull(method).contains(APPLICATION_JSON)) {
return getJsonParserKeyForMethod(method);
} else if (RestAnnotationProcessor.getAcceptHeadersOrNull(method).contains(APPLICATION_XML)
|| method.isAnnotationPresent(JAXBResponseParser.class)) {
return getJAXBParserKeyForMethod(method);
} else if (method.getReturnType().equals(String.class) || method.getReturnType().equals(futureStringLiteral)) {
return Key.get(ReturnStringIf2xx.class);
} else if (method.getReturnType().equals(URI.class) || method.getReturnType().equals(futureURILiteral)) {
return Key.get(ParseURIFromListOrLocationHeaderIf20x.class);
} else {
throw new IllegalStateException("You must specify a ResponseParser annotation on: " + method.toString());
}
}
return Key.get(annotation.value());
}
@SuppressWarnings("unchecked")
private static Key<? extends Function<HttpResponse, ?>> getJAXBParserKeyForMethod(Invokable<?, ?> method) {
Optional<Type> configuredReturnVal = Optional.absent();
if (method.isAnnotationPresent(JAXBResponseParser.class)) {
Type configuredClass = method.getAnnotation(JAXBResponseParser.class).value();
configuredReturnVal = configuredClass.equals(NullType.class) ? Optional.<Type> absent() : Optional
.<Type> of(configuredClass);
}
Type returnVal = configuredReturnVal.or(getReturnTypeFor(method.getReturnType()));
Type parserType = newParameterizedType(ParseXMLWithJAXB.class, returnVal);
return (Key<? extends Function<HttpResponse, ?>>) Key.get(parserType);
}
@SuppressWarnings({ "unchecked" })
private static Key<? extends Function<HttpResponse, ?>> getJsonParserKeyForMethod(Invokable<?, ?> method) {
ParameterizedType parserType;
if (method.isAnnotationPresent(Unwrap.class)) {
parserType = newParameterizedType(UnwrapOnlyJsonValue.class, getReturnTypeFor(method.getReturnType()));
} else {
parserType = newParameterizedType(ParseJson.class, getReturnTypeFor(method.getReturnType()));
}
return (Key<? extends Function<HttpResponse, ?>>) Key.get(parserType);
}
private static Type getReturnTypeFor(TypeToken<?> typeToken) {
Type returnVal = typeToken.getType();
if (typeToken.getRawType().getTypeParameters().length == 0) {
returnVal = typeToken.getRawType();
} else if (typeToken.getRawType().equals(ListenableFuture.class)) {
ParameterizedType futureType = (ParameterizedType) typeToken.getType();
returnVal = futureType.getActualTypeArguments()[0];
if (returnVal instanceof WildcardType)
returnVal = WildcardType.class.cast(returnVal).getUpperBounds()[0];
}
return returnVal;
}
} }

View File

@ -20,13 +20,13 @@ package org.jclouds.rest.internal;
import static com.google.common.reflect.Reflection.newProxy; import static com.google.common.reflect.Reflection.newProxy;
import org.jclouds.internal.ClassMethodArgs; import org.jclouds.internal.ClassInvokerArgs;
import org.jclouds.rest.internal.AsyncRestClientProxy.Factory; import org.jclouds.rest.internal.AsyncRestClientProxy.Factory;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
import com.google.inject.Inject; import com.google.inject.Inject;
public final class CreateAsyncClientForCaller extends CacheLoader<ClassMethodArgs, Object> { public final class CreateAsyncClientForCaller extends CacheLoader<ClassInvokerArgs, Object> {
private final Factory factory; private final Factory factory;
@Inject @Inject
@ -35,7 +35,7 @@ public final class CreateAsyncClientForCaller extends CacheLoader<ClassMethodArg
} }
@Override @Override
public Object load(ClassMethodArgs from) { public Object load(ClassInvokerArgs from) {
return newProxy(from.getClazz(), factory.caller(from)); return newProxy(from.getClazz(), factory.caller(from));
} }
} }

View File

@ -27,7 +27,7 @@ import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import org.jclouds.concurrent.internal.SyncProxy; import org.jclouds.concurrent.internal.SyncProxy;
import org.jclouds.internal.ClassMethodArgs; import org.jclouds.internal.ClassInvokerArgs;
import org.jclouds.util.Optionals2; import org.jclouds.util.Optionals2;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
@ -39,22 +39,22 @@ import com.google.common.cache.LoadingCache;
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
public class CreateClientForCaller extends CacheLoader<ClassMethodArgs, Object> { public class CreateClientForCaller extends CacheLoader<ClassInvokerArgs, Object> {
private final SyncProxy.Factory factory; private final SyncProxy.Factory factory;
private final LoadingCache<ClassMethodArgs, Object> asyncMap; private final LoadingCache<ClassInvokerArgs, Object> asyncMap;
private final Map<Class<?>, Class<?>> sync2Async; private final Map<Class<?>, Class<?>> sync2Async;
@Inject @Inject
private CreateClientForCaller(SyncProxy.Factory factory, private CreateClientForCaller(SyncProxy.Factory factory,
@Named("async") LoadingCache<ClassMethodArgs, Object> asyncMap, Map<Class<?>, Class<?>> sync2Async) { @Named("async") LoadingCache<ClassInvokerArgs, Object> asyncMap, Map<Class<?>, Class<?>> sync2Async) {
this.factory = factory; this.factory = factory;
this.asyncMap = asyncMap; this.asyncMap = asyncMap;
this.sync2Async = sync2Async; this.sync2Async = sync2Async;
} }
@Override @Override
public Object load(ClassMethodArgs from) { public Object load(ClassInvokerArgs from) {
Class<?> syncClass = Optionals2.returnTypeOrTypeOfOptional(from.getMethod()); Class<?> syncClass = Optionals2.returnTypeOrTypeOfOptional(from.getInvoker());
Class<?> asyncClass = sync2Async.get(syncClass); Class<?> asyncClass = sync2Async.get(syncClass);
checkState(asyncClass != null, "configuration error, sync class " + syncClass + " not mapped to an async class"); checkState(asyncClass != null, "configuration error, sync class " + syncClass + " not mapped to an async class");
Object asyncClient = asyncMap.getUnchecked(from); Object asyncClient = asyncMap.getUnchecked(from);

View File

@ -28,13 +28,14 @@ import java.util.List;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpRequestFilter; import org.jclouds.http.HttpRequestFilter;
import org.jclouds.internal.ClassMethodArgs; import org.jclouds.internal.ClassInvokerArgs;
import org.jclouds.io.Payload; import org.jclouds.io.Payload;
import org.jclouds.javax.annotation.Nullable; import org.jclouds.javax.annotation.Nullable;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.reflect.Invokable;
/** /**
* Represents a request generated from annotations * Represents a request generated from annotations
@ -54,9 +55,10 @@ public class GeneratedHttpRequest extends HttpRequest {
public static class Builder extends HttpRequest.Builder<Builder> { public static class Builder extends HttpRequest.Builder<Builder> {
protected Class<?> declaring; protected Class<?> declaring;
protected Method javaMethod; protected Method javaMethod;
protected Invokable<?, ?> invoker;
// args can be null, so cannot use immutable list // args can be null, so cannot use immutable list
protected List<Object> args = Lists.newArrayList(); protected List<Object> args = Lists.newArrayList();
protected Optional<ClassMethodArgs> caller = Optional.absent(); protected Optional<ClassInvokerArgs> caller = Optional.absent();
/** /**
* @see GeneratedHttpRequest#getDeclaring() * @see GeneratedHttpRequest#getDeclaring()
@ -69,11 +71,21 @@ public class GeneratedHttpRequest extends HttpRequest {
/** /**
* @see GeneratedHttpRequest#getJavaMethod() * @see GeneratedHttpRequest#getJavaMethod()
*/ */
@Deprecated
public Builder javaMethod(Method javaMethod) { public Builder javaMethod(Method javaMethod) {
this.javaMethod = checkNotNull(javaMethod, "javaMethod"); this.javaMethod = checkNotNull(javaMethod, "javaMethod");
this.invoker(Invokable.from(javaMethod));
return this; return this;
} }
/**
* @see GeneratedHttpRequest#getInvoker()
*/
public Builder invoker(Invokable<?, ?> invoker) {
this.invoker = checkNotNull(invoker, "invoker");
return this;
}
/** /**
* @see GeneratedHttpRequest#getArgs() * @see GeneratedHttpRequest#getArgs()
*/ */
@ -100,20 +112,21 @@ public class GeneratedHttpRequest extends HttpRequest {
/** /**
* @see GeneratedHttpRequest#getCaller() * @see GeneratedHttpRequest#getCaller()
*/ */
public Builder caller(@Nullable ClassMethodArgs caller) { public Builder caller(@Nullable ClassInvokerArgs caller) {
this.caller = Optional.fromNullable(caller); this.caller = Optional.fromNullable(caller);
return this; return this;
} }
public GeneratedHttpRequest build() { public GeneratedHttpRequest build() {
return new GeneratedHttpRequest(method, endpoint, headers.build(), payload, declaring, javaMethod, return new GeneratedHttpRequest(method, endpoint, headers.build(), payload, declaring, javaMethod, invoker,
args, filters.build(), caller); args, filters.build(), caller);
} }
public Builder fromGeneratedHttpRequest(GeneratedHttpRequest in) { public Builder fromGeneratedHttpRequest(GeneratedHttpRequest in) {
return super.fromHttpRequest(in) return super.fromHttpRequest(in)
.declaring(in.getDeclaring()) .declaring(in.getDeclaring())
.javaMethod(in.getJavaMethod()) .javaMethod(in.getJavaMethod())
.invoker(in.invoker)
.args(in.getArgs()) .args(in.getArgs())
.caller(in.getCaller().orNull()); .caller(in.getCaller().orNull());
} }
@ -126,15 +139,17 @@ public class GeneratedHttpRequest extends HttpRequest {
private final Class<?> declaring; private final Class<?> declaring;
private final Method javaMethod; private final Method javaMethod;
private final Invokable<?, ?> invoker;
private final List<Object> args; private final List<Object> args;
private final Optional<ClassMethodArgs> caller; private final Optional<ClassInvokerArgs> caller;
protected GeneratedHttpRequest(String method, URI endpoint, Multimap<String, String> headers, protected GeneratedHttpRequest(String method, URI endpoint, Multimap<String, String> headers,
@Nullable Payload payload, Class<?> declaring, Method javaMethod, Iterable<Object> args, @Nullable Payload payload, Class<?> declaring, Method javaMethod, Invokable<?, ?> invoker,
Iterable<HttpRequestFilter> filters, Optional<ClassMethodArgs> caller) { Iterable<Object> args, Iterable<HttpRequestFilter> filters, Optional<ClassInvokerArgs> caller) {
super(method, endpoint, headers, payload, filters); super(method, endpoint, headers, payload, filters);
this.declaring = checkNotNull(declaring, "declaring"); this.declaring = checkNotNull(declaring, "declaring");
this.javaMethod = checkNotNull(javaMethod, "javaMethod"); this.javaMethod = checkNotNull(javaMethod, "javaMethod");
this.invoker = checkNotNull(invoker, "invoker");
// TODO make immutable. ImmutableList.of() doesn't accept nulls // TODO make immutable. ImmutableList.of() doesn't accept nulls
this.args = Lists.newArrayList(checkNotNull(args, "args")); this.args = Lists.newArrayList(checkNotNull(args, "args"));
this.caller = checkNotNull(caller, "caller"); this.caller = checkNotNull(caller, "caller");
@ -144,15 +159,23 @@ public class GeneratedHttpRequest extends HttpRequest {
return declaring; return declaring;
} }
/**
* @deprecated see {@link #getInvoker()}
*/
@Deprecated
public Method getJavaMethod() { public Method getJavaMethod() {
return javaMethod; return javaMethod;
} }
public Invokable<?,?> getInvoker() {
return invoker;
}
public List<Object> getArgs() { public List<Object> getArgs() {
return Collections.unmodifiableList(args); return Collections.unmodifiableList(args);
} }
public Optional<ClassMethodArgs> getCaller() { public Optional<ClassInvokerArgs> getCaller() {
return caller; return caller;
} }
} }

View File

@ -1,106 +0,0 @@
/**
* 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.internal;
import static com.google.common.collect.Sets.difference;
import static org.jclouds.rest.internal.RestAnnotationProcessor.getHttpMethods;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToBinderParamAnnotation;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToEndpointAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToEndpointParamAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToFormParamAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToHeaderParamAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToParamParserAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToPartParamAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToPathParamAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToPostParamAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToQueryParamAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToWrapWithAnnotation;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexesOfOptions;
import java.lang.reflect.Method;
import java.util.concurrent.ExecutionException;
import javax.annotation.Resource;
import javax.inject.Singleton;
import javax.ws.rs.Path;
import org.jclouds.http.HttpRequest;
import org.jclouds.logging.Logger;
import org.jclouds.rest.RestContext;
import org.jclouds.rest.annotations.Delegate;
import org.jclouds.rest.internal.RestAnnotationProcessor.MethodKey;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.Callables;
import com.google.inject.Provides;
/**
* seeds the annotation cache located at
* {@link RestAnnotationProcessor#delegationMap}. Note this call is only
* intended to be called once per {@link RestContext} and avoids expensive
* lookups on each call.
*
* @author Adrian Cole
*/
@Singleton
public final class SeedAnnotationCache extends CacheLoader<Class<?>, Cache<MethodKey, Method>> {
@Resource
private Logger logger = Logger.NULL;
@Override
public Cache<MethodKey, Method> load(Class<?> declaring) throws ExecutionException {
Cache<MethodKey, Method> delegationMap = CacheBuilder.newBuilder().<MethodKey, Method>build();
for (Method method : difference(ImmutableSet.copyOf(declaring.getMethods()), ImmutableSet.copyOf(Object.class
.getMethods()))) {
if (isHttpMethod(method) || method.isAnnotationPresent(Delegate.class)) {
for (int index = 0; index < method.getParameterTypes().length; index++) {
methodToIndexOfParamToBinderParamAnnotation.get(method).get(index);
methodToIndexOfParamToWrapWithAnnotation.get(method).get(index);
methodToIndexOfParamToHeaderParamAnnotations.get(method).get(index);
methodToIndexOfParamToFormParamAnnotations.get(method).get(index);
methodToIndexOfParamToQueryParamAnnotations.get(method).get(index);
methodToIndexOfParamToEndpointAnnotations.get(method).get(index);
methodToIndexOfParamToEndpointParamAnnotations.get(method).get(index);
methodToIndexOfParamToPathParamAnnotations.get(method).get(index);
methodToIndexOfParamToPostParamAnnotations.get(method).get(index);
methodToIndexOfParamToParamParserAnnotations.get(method).get(index);
methodToIndexOfParamToPartParamAnnotations.get(method).get(index);
methodToIndexesOfOptions.get(method);
}
delegationMap.get(new MethodKey(method), Callables.returning(method));
} else if (!method.getDeclaringClass().equals(declaring)) {
logger.trace("skipping potentially overridden method %s", method);
} else if (method.isAnnotationPresent(Provides.class)) {
logger.trace("skipping provider method %s", method);
} else {
logger.trace("Method is not annotated as either http or provider method: %s", method);
}
}
return delegationMap;
}
public static boolean isHttpMethod(Method method) {
return method.isAnnotationPresent(Path.class) || !getHttpMethods(method).isEmpty()
|| ImmutableSet.copyOf(method.getParameterTypes()).contains(HttpRequest.class);
}
}

View File

@ -24,18 +24,28 @@ import java.lang.reflect.Type;
import java.lang.reflect.WildcardType; import java.lang.reflect.WildcardType;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.TypeToken;
/** /**
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
public class Optionals2 { public class Optionals2 {
public static Class<?> returnTypeOrTypeOfOptional(Invokable<?, ?> method) {
TypeToken<?> type = method.getReturnType();
return returnTypeOrTypeOfOptional(type.getRawType(), type.getType());
}
public static Class<?> returnTypeOrTypeOfOptional(Method method) { public static Class<?> returnTypeOrTypeOfOptional(Method method) {
boolean optional = isReturnTypeOptional(method); Class<?> syncClass = method.getReturnType();
Class<?> syncClass; Type genericType = method.getGenericReturnType();
if (optional) { return returnTypeOrTypeOfOptional(syncClass, genericType);
ParameterizedType futureType = ParameterizedType.class.cast(method.getGenericReturnType()); }
private static Class<?> returnTypeOrTypeOfOptional(Class<?> syncClass, Type genericType) {
if (syncClass.isAssignableFrom(Optional.class)) {
ParameterizedType futureType = ParameterizedType.class.cast(genericType);
// TODO: error checking in case this is a type, not a class. // TODO: error checking in case this is a type, not a class.
Type t = futureType.getActualTypeArguments()[0]; Type t = futureType.getActualTypeArguments()[0];
if (t instanceof WildcardType) { if (t instanceof WildcardType) {
@ -43,14 +53,12 @@ public class Optionals2 {
} }
syncClass = Class.class.cast(t); syncClass = Class.class.cast(t);
} else { } else {
syncClass = method.getReturnType();
} }
return syncClass; return syncClass;
} }
public static boolean isReturnTypeOptional(Method method) { public static boolean isReturnTypeOptional(Method method) {
boolean optional = method.getReturnType().isAssignableFrom(Optional.class); return method.getReturnType().isAssignableFrom(Optional.class);
return optional;
} }
} }

View File

@ -29,7 +29,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import org.jclouds.internal.ClassMethodArgs; import org.jclouds.internal.ClassInvokerArgs;
import org.jclouds.rest.functions.AlwaysPresentImplicitOptionalConverter; import org.jclouds.rest.functions.AlwaysPresentImplicitOptionalConverter;
import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -103,7 +103,7 @@ public class SyncProxyTest {
} }
private Sync syncProxyForTimeouts(ImmutableMap<String, Long> timeouts) throws NoSuchMethodException { private Sync syncProxyForTimeouts(ImmutableMap<String, Long> timeouts) throws NoSuchMethodException {
LoadingCache<ClassMethodArgs, Object> cache = CacheBuilder.newBuilder().build( LoadingCache<ClassInvokerArgs, Object> cache = CacheBuilder.newBuilder().build(
CacheLoader.from(Functions.<Object> constant(null))); CacheLoader.from(Functions.<Object> constant(null)));
return newProxy( return newProxy(
Sync.class, Sync.class,

View File

@ -31,11 +31,12 @@ import org.jclouds.http.HttpResponse;
import org.jclouds.io.Payload; import org.jclouds.io.Payload;
import org.jclouds.io.Payloads; import org.jclouds.io.Payloads;
import org.jclouds.json.config.GsonModule; import org.jclouds.json.config.GsonModule;
import org.jclouds.rest.internal.RestAnnotationProcessor; import org.jclouds.rest.internal.AsyncRestClientProxy;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.reflect.Invokable;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
@ -55,8 +56,8 @@ public abstract class BaseParserTest<T, G> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected Function<HttpResponse, T> parser(Injector i) { protected Function<HttpResponse, T> parser(Injector i) {
try { try {
return (Function<HttpResponse, T>) RestAnnotationProcessor.getTransformerForMethod(getClass() return (Function<HttpResponse, T>) AsyncRestClientProxy.getTransformerForMethod(
.getMethod("expected"), i); Invokable.from(getClass().getMethod("expected")), i);
} catch (Exception e) { } catch (Exception e) {
throw Throwables.propagate(e); throw Throwables.propagate(e);
} }

View File

@ -25,7 +25,7 @@ import static org.testng.Assert.assertTrue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.jclouds.internal.ClassMethodArgsAndReturnVal; import org.jclouds.internal.ClassInvokerArgsAndReturnVal;
import org.jclouds.rest.annotations.Delegate; import org.jclouds.rest.annotations.Delegate;
import org.jclouds.rest.annotations.SinceApiVersion; import org.jclouds.rest.annotations.SinceApiVersion;
import org.jclouds.rest.functions.PresentWhenApiVersionLexicographicallyAtOrAfterSinceApiVersion.Loader; import org.jclouds.rest.functions.PresentWhenApiVersionLexicographicallyAtOrAfterSinceApiVersion.Loader;
@ -120,7 +120,7 @@ public class PresentWhenApiVersionLexicographicallyAtOrAfterSinceApiVersionTest
} }
public void testCacheIsFasterWhenNoAnnotationPresent() { public void testCacheIsFasterWhenNoAnnotationPresent() {
ClassMethodArgsAndReturnVal keyPairApi = getKeyPairApi(); ClassInvokerArgsAndReturnVal keyPairApi = getKeyPairApi();
ImplicitOptionalConverter fn = forApiVersion("2011-07-15"); ImplicitOptionalConverter fn = forApiVersion("2011-07-15");
Stopwatch watch = new Stopwatch().start(); Stopwatch watch = new Stopwatch().start();
fn.apply(keyPairApi); fn.apply(keyPairApi);
@ -134,7 +134,7 @@ public class PresentWhenApiVersionLexicographicallyAtOrAfterSinceApiVersionTest
} }
public void testCacheIsFasterWhenAnnotationPresent() { public void testCacheIsFasterWhenAnnotationPresent() {
ClassMethodArgsAndReturnVal floatingIpApi = getKeyPairApi(); ClassInvokerArgsAndReturnVal floatingIpApi = getKeyPairApi();
ImplicitOptionalConverter fn = forApiVersion("2011-07-15"); ImplicitOptionalConverter fn = forApiVersion("2011-07-15");
Stopwatch watch = new Stopwatch().start(); Stopwatch watch = new Stopwatch().start();
fn.apply(floatingIpApi); fn.apply(floatingIpApi);
@ -148,22 +148,22 @@ public class PresentWhenApiVersionLexicographicallyAtOrAfterSinceApiVersionTest
} }
ClassMethodArgsAndReturnVal getFloatingIPApi() { ClassInvokerArgsAndReturnVal getFloatingIPApi() {
return getApi("Tag", TagAsyncApi.class); return getApi("Tag", TagAsyncApi.class);
} }
ClassMethodArgsAndReturnVal getKeyPairApi() { ClassInvokerArgsAndReturnVal getKeyPairApi() {
return getApi("KeyPair", KeyPairAsyncApi.class); return getApi("KeyPair", KeyPairAsyncApi.class);
} }
ClassMethodArgsAndReturnVal getVpcApi() { ClassInvokerArgsAndReturnVal getVpcApi() {
return getApi("Vpc", VpcAsyncApi.class); return getApi("Vpc", VpcAsyncApi.class);
} }
ClassMethodArgsAndReturnVal getApi(String name, Class<?> type) { ClassInvokerArgsAndReturnVal getApi(String name, Class<?> type) {
try { try {
return ClassMethodArgsAndReturnVal.builder().clazz(type) return ClassInvokerArgsAndReturnVal.builder().clazz(type)
.method(EC2AsyncApi.class.getDeclaredMethod("get" + name + "ApiForRegion", String.class)) .invoker(EC2AsyncApi.class.getDeclaredMethod("get" + name + "ApiForRegion", String.class))
.args(new Object[] { "region" }).returnVal("present").build(); .args(new Object[] { "region" }).returnVal("present").build();
} catch (Exception e) { } catch (Exception e) {
throw propagate(e); throw propagate(e);

View File

@ -22,8 +22,6 @@ import static com.google.common.hash.Hashing.md5;
import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.createMock;
import static org.eclipse.jetty.http.HttpHeaders.TRANSFER_ENCODING; import static org.eclipse.jetty.http.HttpHeaders.TRANSFER_ENCODING;
import static org.jclouds.io.ByteSources.asByteSource; import static org.jclouds.io.ByteSources.asByteSource;
import static org.jclouds.rest.internal.RestAnnotationProcessor.createResponseParser;
import static org.jclouds.rest.internal.RestAnnotationProcessor.getSaxResponseParserClassOrNull;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull; import static org.testng.Assert.assertNull;
@ -44,6 +42,7 @@ import org.jclouds.http.functions.ParseSax;
import org.jclouds.io.MutableContentMetadata; import org.jclouds.io.MutableContentMetadata;
import org.jclouds.javax.annotation.Nullable; import org.jclouds.javax.annotation.Nullable;
import org.jclouds.rest.annotations.Fallback; import org.jclouds.rest.annotations.Fallback;
import org.jclouds.rest.annotations.XMLResponseParser;
import org.jclouds.util.Strings2; import org.jclouds.util.Strings2;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -171,11 +170,13 @@ public abstract class BaseRestApiTest {
} }
protected void assertSaxResponseParserClassEquals(Method method, @Nullable Class<?> parserClass) { protected void assertSaxResponseParserClassEquals(Method method, @Nullable Class<?> parserClass) {
assertEquals(getSaxResponseParserClassOrNull(method), parserClass); XMLResponseParser annotation = method.getAnnotation(XMLResponseParser.class);
Class<?> expected = (annotation != null) ? annotation.value() :null;
assertEquals(expected, parserClass);
} }
protected void assertResponseParserClassEquals(Method method, HttpRequest request, @Nullable Class<?> parserClass) { protected void assertResponseParserClassEquals(Method method, HttpRequest request, @Nullable Class<?> parserClass) {
assertEquals(createResponseParser(parserFactory, injector, method, request).getClass(), parserClass); assertEquals(AsyncRestClientProxy.createResponseParser(parserFactory, injector, method, request).getClass(), parserClass);
} }
protected RestAnnotationProcessor factory(Class<?> clazz) { protected RestAnnotationProcessor factory(Class<?> clazz) {

View File

@ -64,7 +64,6 @@ import javax.ws.rs.PathParam;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam; import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.jetty.http.HttpHeaders; import org.eclipse.jetty.http.HttpHeaders;
import org.jclouds.ContextBuilder; import org.jclouds.ContextBuilder;
@ -81,7 +80,6 @@ import org.jclouds.http.functions.ParseFirstJsonValueNamed;
import org.jclouds.http.functions.ParseJson; import org.jclouds.http.functions.ParseJson;
import org.jclouds.http.functions.ParseSax; import org.jclouds.http.functions.ParseSax;
import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x; import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x;
import org.jclouds.http.functions.ParseXMLWithJAXB;
import org.jclouds.http.functions.ReturnInputStream; import org.jclouds.http.functions.ReturnInputStream;
import org.jclouds.http.functions.ReturnStringIf2xx; import org.jclouds.http.functions.ReturnStringIf2xx;
import org.jclouds.http.functions.ReturnTrueIf2xx; import org.jclouds.http.functions.ReturnTrueIf2xx;
@ -90,7 +88,7 @@ import org.jclouds.http.internal.PayloadEnclosingImpl;
import org.jclouds.http.options.BaseHttpRequestOptions; import org.jclouds.http.options.BaseHttpRequestOptions;
import org.jclouds.http.options.GetOptions; import org.jclouds.http.options.GetOptions;
import org.jclouds.http.options.HttpRequestOptions; import org.jclouds.http.options.HttpRequestOptions;
import org.jclouds.internal.ClassMethodArgsAndReturnVal; import org.jclouds.internal.ClassInvokerArgsAndReturnVal;
import org.jclouds.io.Payload; import org.jclouds.io.Payload;
import org.jclouds.io.PayloadEnclosing; import org.jclouds.io.PayloadEnclosing;
import org.jclouds.io.Payloads; import org.jclouds.io.Payloads;
@ -106,7 +104,6 @@ import org.jclouds.rest.annotations.Endpoint;
import org.jclouds.rest.annotations.EndpointParam; import org.jclouds.rest.annotations.EndpointParam;
import org.jclouds.rest.annotations.FormParams; import org.jclouds.rest.annotations.FormParams;
import org.jclouds.rest.annotations.Headers; import org.jclouds.rest.annotations.Headers;
import org.jclouds.rest.annotations.JAXBResponseParser;
import org.jclouds.rest.annotations.MapBinder; import org.jclouds.rest.annotations.MapBinder;
import org.jclouds.rest.annotations.OnlyElement; import org.jclouds.rest.annotations.OnlyElement;
import org.jclouds.rest.annotations.OverrideRequestFilters; import org.jclouds.rest.annotations.OverrideRequestFilters;
@ -129,7 +126,6 @@ import org.jclouds.rest.config.AsyncClientProvider;
import org.jclouds.rest.config.RestClientModule; import org.jclouds.rest.config.RestClientModule;
import org.jclouds.rest.functions.ImplicitOptionalConverter; import org.jclouds.rest.functions.ImplicitOptionalConverter;
import org.jclouds.util.Strings2; import org.jclouds.util.Strings2;
import org.jclouds.xml.XMLParser;
import org.testng.Assert; import org.testng.Assert;
import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider; import org.testng.annotations.DataProvider;
@ -181,7 +177,6 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
}).annotatedWith(Localhost2.class).toInstance(Suppliers.ofInstance(URI.create("http://localhost:1111"))); }).annotatedWith(Localhost2.class).toInstance(Suppliers.ofInstance(URI.create("http://localhost:1111")));
bind(IOExceptionRetryHandler.class).toInstance(IOExceptionRetryHandler.NEVER_RETRY); bind(IOExceptionRetryHandler.class).toInstance(IOExceptionRetryHandler.NEVER_RETRY);
} }
} }
@Path("/client/{jclouds.api-version}") @Path("/client/{jclouds.api-version}")
@ -394,7 +389,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
bind(ImplicitOptionalConverter.class).toInstance(new ImplicitOptionalConverter() { bind(ImplicitOptionalConverter.class).toInstance(new ImplicitOptionalConverter() {
@Override @Override
public Optional<Object> apply(ClassMethodArgsAndReturnVal input) { public Optional<Object> apply(ClassInvokerArgsAndReturnVal input) {
return Optional.absent(); return Optional.absent();
} }
@ -1177,7 +1172,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
assertResponseParserClassEquals(method, request, ParseJson.class); assertResponseParserClassEquals(method, request, ParseJson.class);
// now test that it works! // now test that it works!
Function<HttpResponse, View> parser = (Function<HttpResponse, View>) RestAnnotationProcessor Function<HttpResponse, View> parser = (Function<HttpResponse, View>) AsyncRestClientProxy
.createResponseParser(parserFactory, injector, method, request); .createResponseParser(parserFactory, injector, method, request);
assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{ foo:\"bar\"}").build()).foo, "bar"); assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{ foo:\"bar\"}").build()).foo, "bar");
@ -1192,7 +1187,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
assertResponseParserClassEquals(method, request, ParseJson.class); assertResponseParserClassEquals(method, request, ParseJson.class);
// now test that it works! // now test that it works!
Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) RestAnnotationProcessor Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) AsyncRestClientProxy
.createResponseParser(parserFactory, injector, method, request); .createResponseParser(parserFactory, injector, method, request);
assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{ foo:\"bar\"}").build()), assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{ foo:\"bar\"}").build()),
@ -1208,7 +1203,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
assertResponseParserClassEquals(method, request, ParseJson.class); assertResponseParserClassEquals(method, request, ParseJson.class);
// now test that it works! // now test that it works!
Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) RestAnnotationProcessor Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) AsyncRestClientProxy
.createResponseParser(parserFactory, injector, method, request); .createResponseParser(parserFactory, injector, method, request);
assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{ foo:\"bar\"}").build()), assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{ foo:\"bar\"}").build()),
@ -1224,7 +1219,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
assertResponseParserClassEquals(method, request, ParseJson.class); assertResponseParserClassEquals(method, request, ParseJson.class);
// now test that it works! // now test that it works!
Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) RestAnnotationProcessor Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) AsyncRestClientProxy
.createResponseParser(parserFactory, injector, method, request); .createResponseParser(parserFactory, injector, method, request);
assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{ foo:\"bar\"}").build()), assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{ foo:\"bar\"}").build()),
@ -1240,7 +1235,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
assertResponseParserClassEquals(method, request, UnwrapOnlyJsonValue.class); assertResponseParserClassEquals(method, request, UnwrapOnlyJsonValue.class);
// now test that it works! // now test that it works!
Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) RestAnnotationProcessor Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) AsyncRestClientProxy
.createResponseParser(parserFactory, injector, method, request); .createResponseParser(parserFactory, injector, method, request);
assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{ foo:\"bar\"}").build()), "bar"); assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{ foo:\"bar\"}").build()), "bar");
@ -1255,7 +1250,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
assertResponseParserClassEquals(method, request, ParseFirstJsonValueNamed.class); assertResponseParserClassEquals(method, request, ParseFirstJsonValueNamed.class);
// now test that it works! // now test that it works!
Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) RestAnnotationProcessor Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) AsyncRestClientProxy
.createResponseParser(parserFactory, injector, method, request); .createResponseParser(parserFactory, injector, method, request);
assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{ foo:\"bar\"}").build()), "bar"); assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{ foo:\"bar\"}").build()), "bar");
@ -1276,7 +1271,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
assertResponseParserClassEquals(method, request, UnwrapOnlyJsonValue.class); assertResponseParserClassEquals(method, request, UnwrapOnlyJsonValue.class);
// now test that it works! // now test that it works!
Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) RestAnnotationProcessor Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) AsyncRestClientProxy
.createResponseParser(parserFactory, injector, method, request); .createResponseParser(parserFactory, injector, method, request);
assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{ foo:\"bar\"}").build()), "bar"); assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{ foo:\"bar\"}").build()), "bar");
@ -1291,7 +1286,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
assertResponseParserClassEquals(method, request, UnwrapOnlyJsonValue.class); assertResponseParserClassEquals(method, request, UnwrapOnlyJsonValue.class);
// now test that it works! // now test that it works!
Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) RestAnnotationProcessor Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) AsyncRestClientProxy
.createResponseParser(parserFactory, injector, method, request); .createResponseParser(parserFactory, injector, method, request);
assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{\"runit\":[\"0.7.0\",\"0.7.1\"]}").build()), assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{\"runit\":[\"0.7.0\",\"0.7.1\"]}").build()),
@ -1306,7 +1301,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
assertResponseParserClassEquals(method, request, UnwrapOnlyJsonValue.class); assertResponseParserClassEquals(method, request, UnwrapOnlyJsonValue.class);
// now test that it works! // now test that it works!
Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) RestAnnotationProcessor Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) AsyncRestClientProxy
.createResponseParser(parserFactory, injector, method, request); .createResponseParser(parserFactory, injector, method, request);
assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{\"runit\":[\"0.7.0\",\"0.7.1\"]}").build()), assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok").payload("{\"runit\":[\"0.7.0\",\"0.7.1\"]}").build()),
@ -1321,7 +1316,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
assertResponseParserClassEquals(method, request, ParseFirstJsonValueNamed.class); assertResponseParserClassEquals(method, request, ParseFirstJsonValueNamed.class);
// now test that it works! // now test that it works!
Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) RestAnnotationProcessor Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) AsyncRestClientProxy
.createResponseParser(parserFactory, injector, method, request); .createResponseParser(parserFactory, injector, method, request);
assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok") assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok")
@ -1333,7 +1328,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
Method method = TestPut.class.getMethod("selectLongAddOne"); Method method = TestPut.class.getMethod("selectLongAddOne");
HttpRequest request = factory(TestPut.class).createRequest(method); HttpRequest request = factory(TestPut.class).createRequest(method);
Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) RestAnnotationProcessor Function<HttpResponse, Map<String, String>> parser = (Function<HttpResponse, Map<String, String>>) AsyncRestClientProxy
.createResponseParser(parserFactory, injector, method, request); .createResponseParser(parserFactory, injector, method, request);
assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok") assertEquals(parser.apply(HttpResponse.builder().statusCode(200).message("ok")
@ -1476,13 +1471,6 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
@Path("/") @Path("/")
public void oneFormParamExtractor(@FormParam("one") @ParamParser(FirstCharacter.class) String one) { public void oneFormParamExtractor(@FormParam("one") @ParamParser(FirstCharacter.class) String one) {
} }
@GET
@Path("/{path}")
@PathParam("path")
@ParamParser(FirstCharacterFirstElement.class)
public void onePathParamExtractorMethod(String path) {
}
} }
@Test @Test
@ -1531,15 +1519,6 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
assertEquals(e.getMessage(), "param{one} for method TestPath.oneFormParamExtractor"); assertEquals(e.getMessage(), "param{one} for method TestPath.oneFormParamExtractor");
} }
} }
@Test
public void testParamExtractorMethod() throws SecurityException, NoSuchMethodException {
Method method = TestPath.class.getMethod("onePathParamExtractorMethod", String.class);
HttpRequest request = factory(TestPath.class).createRequest(method, new Object[] { "localhost" });
assertEquals(request.getEndpoint().getPath(), "/l");
assertEquals(request.getMethod(), HttpMethod.GET);
assertEquals(request.getHeaders().size(), 0);
}
static class FirstCharacter implements Function<Object, String> { static class FirstCharacter implements Function<Object, String> {
public String apply(Object from) { public String apply(Object from) {
@ -1729,25 +1708,19 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
public interface TestTransformers { public interface TestTransformers {
@GET @GET
int noTransformer(); ListenableFuture<Integer> noTransformer();
@GET @GET
@ResponseParser(ReturnStringIf2xx.class) @ResponseParser(ReturnStringIf2xx.class)
void oneTransformer(); ListenableFuture<Void> oneTransformer();
@GET @GET
@ResponseParser(ReturnStringIf200Context.class) @ResponseParser(ReturnStringIf200Context.class)
void oneTransformerWithContext(); ListenableFuture<Void> oneTransformerWithContext();
@GET
InputStream inputStream();
@GET @GET
ListenableFuture<InputStream> futureInputStream(); ListenableFuture<InputStream> futureInputStream();
@GET
URI uri();
@GET @GET
ListenableFuture<URI> futureUri(); ListenableFuture<URI> futureUri();
@ -1874,12 +1847,6 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
assertPayloadEquals(request, "whoops", "application/unknown", true); assertPayloadEquals(request, "whoops", "application/unknown", true);
} }
public void testInputStream() throws SecurityException, NoSuchMethodException {
Method method = TestTransformers.class.getMethod("inputStream");
Class<? extends Function<HttpResponse, ?>> transformer = unwrap(factory(TestTransformers.class), method);
assertEquals(transformer, ReturnInputStream.class);
}
public void testInputStreamListenableFuture() throws SecurityException, NoSuchMethodException { public void testInputStreamListenableFuture() throws SecurityException, NoSuchMethodException {
Method method = TestTransformers.class.getMethod("futureInputStream"); Method method = TestTransformers.class.getMethod("futureInputStream");
Class<? extends Function<HttpResponse, ?>> transformer = unwrap(factory(TestTransformers.class), method); Class<? extends Function<HttpResponse, ?>> transformer = unwrap(factory(TestTransformers.class), method);
@ -1889,16 +1856,10 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Class<? extends Function<HttpResponse, ?>> unwrap(RestAnnotationProcessor processor, public static Class<? extends Function<HttpResponse, ?>> unwrap(RestAnnotationProcessor processor,
Method method) { Method method) {
return (Class<? extends Function<HttpResponse, ?>>) RestAnnotationProcessor.getParserOrThrowException(method) return (Class<? extends Function<HttpResponse, ?>>) AsyncRestClientProxy.getParserOrThrowException(method)
.getTypeLiteral().getRawType(); .getTypeLiteral().getRawType();
} }
public void testURI() throws SecurityException, NoSuchMethodException {
Method method = TestTransformers.class.getMethod("uri");
Class<? extends Function<HttpResponse, ?>> transformer = unwrap(factory(TestTransformers.class), method);
assertEquals(transformer, ParseURIFromListOrLocationHeaderIf20x.class);
}
public void testURIListenableFuture() throws SecurityException, NoSuchMethodException { public void testURIListenableFuture() throws SecurityException, NoSuchMethodException {
Method method = TestTransformers.class.getMethod("futureUri"); Method method = TestTransformers.class.getMethod("futureUri");
Class<? extends Function<HttpResponse, ?>> transformer = unwrap(factory(TestTransformers.class), method); Class<? extends Function<HttpResponse, ?>> transformer = unwrap(factory(TestTransformers.class), method);
@ -1921,7 +1882,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
@Test(expectedExceptions = { RuntimeException.class }) @Test(expectedExceptions = { RuntimeException.class })
public void testNoTransformer() throws SecurityException, NoSuchMethodException { public void testNoTransformer() throws SecurityException, NoSuchMethodException {
Method method = TestTransformers.class.getMethod("noTransformer"); Method method = TestTransformers.class.getMethod("noTransformer");
factory(TestTransformers.class).getParserOrThrowException(method); AsyncRestClientProxy.getParserOrThrowException(method);
} }
public void oneTransformerWithContext() throws SecurityException, NoSuchMethodException { public void oneTransformerWithContext() throws SecurityException, NoSuchMethodException {
@ -1930,7 +1891,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
GeneratedHttpRequest request = GeneratedHttpRequest.builder() GeneratedHttpRequest request = GeneratedHttpRequest.builder()
.method("GET").endpoint("http://localhost").declaring(TestTransformers.class) .method("GET").endpoint("http://localhost").declaring(TestTransformers.class)
.javaMethod(method).args(new Object[] {}).build(); .javaMethod(method).args(new Object[] {}).build();
Function<HttpResponse, ?> transformer = processor.createResponseParser(method, request); Function<HttpResponse, ?> transformer = AsyncRestClientProxy.createResponseParser(parserFactory, injector, method, request);
assertEquals(transformer.getClass(), ReturnStringIf200Context.class); assertEquals(transformer.getClass(), ReturnStringIf200Context.class);
assertEquals(((ReturnStringIf200Context) transformer).request, request); assertEquals(((ReturnStringIf200Context) transformer).request, request);
} }
@ -2272,13 +2233,11 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
} }
@SuppressWarnings("static-access") @Test(expectedExceptions = IllegalStateException.class)
@Test
public void testTwoDifferentEndpointParams() throws SecurityException, NoSuchMethodException, ExecutionException { public void testTwoDifferentEndpointParams() throws SecurityException, NoSuchMethodException, ExecutionException {
Method method = TestEndpointParams.class.getMethod("twoEndpointParams", String.class, String.class); Method method = TestEndpointParams.class.getMethod("twoEndpointParams", String.class, String.class);
URI uri = factory(TestEndpointParams.class).getEndpointInParametersOrNull(method, factory(TestEndpointParams.class).getEndpointInParametersOrNull(method,
new Object[] { "robot", "egg" }, injector); new Object[] { "robot", "egg" }, injector);
assertEquals(uri, URI.create("robot/egg"));
} }
public interface TestPayload { public interface TestPayload {

View File

@ -17,9 +17,9 @@
* under the License. * under the License.
*/ */
package org.jclouds.http.apachehc; package org.jclouds.http.apachehc;
import static com.google.common.hash.Hashing.md5; import static com.google.common.hash.Hashing.md5;
import static com.google.common.io.BaseEncoding.base64; import static com.google.common.io.BaseEncoding.base64;
import static org.jclouds.http.HttpUtils.filterOutContentHeaders;
import static org.jclouds.io.ByteSources.asByteSource; import static org.jclouds.io.ByteSources.asByteSource;
import java.io.IOException; import java.io.IOException;
@ -45,7 +45,6 @@ import org.jclouds.http.internal.HttpWire;
import org.jclouds.io.ContentMetadataCodec; import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.io.Payload; import org.jclouds.io.Payload;
import org.jclouds.io.Payloads; import org.jclouds.io.Payloads;
import org.jclouds.rest.internal.RestAnnotationProcessor;
import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
@ -108,7 +107,7 @@ public class ApacheHCHttpCommandExecutorService extends BaseHttpCommandExecutorS
return HttpResponse.builder().statusCode(apacheResponse.getStatusLine().getStatusCode()) return HttpResponse.builder().statusCode(apacheResponse.getStatusLine().getStatusCode())
.message(apacheResponse.getStatusLine().getReasonPhrase()) .message(apacheResponse.getStatusLine().getReasonPhrase())
.payload(payload) .payload(payload)
.headers(RestAnnotationProcessor.filterOutContentHeaders(headers)).build(); .headers(filterOutContentHeaders(headers)).build();
} }
private org.apache.http.HttpResponse executeRequest(HttpUriRequest nativeRequest) throws IOException, private org.apache.http.HttpResponse executeRequest(HttpUriRequest nativeRequest) throws IOException,

View File

@ -17,6 +17,7 @@
* under the License. * under the License.
*/ */
package org.jclouds.gae; package org.jclouds.gae;
import static org.jclouds.http.HttpUtils.filterOutContentHeaders;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -24,7 +25,6 @@ import org.jclouds.http.HttpResponse;
import org.jclouds.io.ContentMetadataCodec; import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.io.Payload; import org.jclouds.io.Payload;
import org.jclouds.io.Payloads; import org.jclouds.io.Payloads;
import org.jclouds.rest.internal.RestAnnotationProcessor;
import com.google.appengine.api.urlfetch.HTTPHeader; import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPResponse; import com.google.appengine.api.urlfetch.HTTPResponse;
@ -67,6 +67,6 @@ public class ConvertToJcloudsResponse implements Function<HTTPResponse, HttpResp
.statusCode(gaeResponse.getResponseCode()) .statusCode(gaeResponse.getResponseCode())
.message(message) .message(message)
.payload(payload) .payload(payload)
.headers(RestAnnotationProcessor.filterOutContentHeaders(headers)).build(); .headers(filterOutContentHeaders(headers)).build();
} }
} }

View File

@ -19,11 +19,9 @@
package org.jclouds.googlecompute.functions.internal; package org.jclouds.googlecompute.functions.internal;
import com.google.common.annotations.Beta; import static com.google.common.base.Predicates.instanceOf;
import com.google.common.base.Function; import static com.google.common.collect.Iterables.tryFind;
import com.google.common.base.Optional;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import org.jclouds.collect.IterableWithMarker; import org.jclouds.collect.IterableWithMarker;
import org.jclouds.collect.PagedIterable; import org.jclouds.collect.PagedIterable;
import org.jclouds.collect.PagedIterables; import org.jclouds.collect.PagedIterables;
@ -33,14 +31,16 @@ import org.jclouds.http.HttpRequest;
import org.jclouds.rest.InvocationContext; import org.jclouds.rest.InvocationContext;
import org.jclouds.rest.internal.GeneratedHttpRequest; import org.jclouds.rest.internal.GeneratedHttpRequest;
import java.util.Arrays; import com.google.common.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.base.Optional;
/** /**
* @author Adrian Cole * @author Adrian Cole
*/ */
@Beta @Beta
public abstract class BaseToPagedIterable<T, I extends BaseToPagedIterable<T, I>> implements public abstract class BaseToPagedIterable<T, I extends BaseToPagedIterable<T, I>> implements
Function<ListPage<T>, PagedIterable<T>>, InvocationContext<I> { Function<ListPage<T>, PagedIterable<T>>, InvocationContext<I> {
private GeneratedHttpRequest request; private GeneratedHttpRequest request;
@ -49,22 +49,21 @@ public abstract class BaseToPagedIterable<T, I extends BaseToPagedIterable<T, I>
if (input.nextMarker() == null) if (input.nextMarker() == null)
return PagedIterables.of(input); return PagedIterables.of(input);
Optional<Object> project = Iterables.tryFind(Arrays.asList(request.getCaller().get().getArgs()), Optional<Object> project = tryFind(request.getCaller().get().getArgs(), instanceOf(String.class));
Predicates.instanceOf(String.class));
Optional<Object> listOptions = Iterables.tryFind(request.getArgs(), Optional<Object> listOptions = tryFind(request.getArgs(), instanceOf(ListOptions.class));
Predicates.instanceOf(ListOptions.class));
assert project.isPresent() : String.format("programming error, method %s should have a string param for the " + assert project.isPresent() : String.format("programming error, method %s should have a string param for the "
"project", request.getCaller().get().getMethod()); + "project", request.getCaller().get().getInvoker());
return PagedIterables.advance(input, fetchNextPage(project.get().toString(), (String) input.nextMarker().orNull(), return PagedIterables.advance(
(ListOptions) listOptions.orNull())); input,
fetchNextPage(project.get().toString(), (String) input.nextMarker().orNull(),
(ListOptions) listOptions.orNull()));
} }
protected abstract Function<Object, IterableWithMarker<T>> fetchNextPage(String projectName, String marker, protected abstract Function<Object, IterableWithMarker<T>> fetchNextPage(String projectName, String marker,
ListOptions listOptions); ListOptions listOptions);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override