added ability to look up constructors as Invokables

This commit is contained in:
Adrian Cole 2013-01-20 10:48:48 -08:00
parent 03fa9da761
commit a625127fd2
8 changed files with 383 additions and 156 deletions

View File

@ -53,8 +53,7 @@ public class FilterStringsBoundToInjectorByName implements Function<Predicate<St
@Override @Override
public Map<String, String> apply(Predicate<String> filter) { public Map<String, String> apply(Predicate<String> filter) {
List<Binding<String>> stringBindings = injector.findBindingsByType(new TypeLiteral<String>() { List<Binding<String>> stringBindings = injector.findBindingsByType(TypeLiteral.get(String.class));
});
Iterable<Binding<String>> annotatedWithName = Iterables.filter(stringBindings, new Predicate<Binding<String>>() { Iterable<Binding<String>> annotatedWithName = Iterables.filter(stringBindings, new Predicate<Binding<String>>() {
@Override @Override

View File

@ -19,19 +19,18 @@
package org.jclouds.lifecycle.config; package org.jclouds.lifecycle.config;
import static com.google.common.base.Throwables.propagate; import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Sets.newHashSet; import static com.google.common.collect.Iterables.filter;
import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor; import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
import static com.google.inject.matcher.Matchers.any; import static com.google.inject.matcher.Matchers.any;
import static java.util.Arrays.asList;
import static org.jclouds.Constants.PROPERTY_IO_WORKER_THREADS; import static org.jclouds.Constants.PROPERTY_IO_WORKER_THREADS;
import static org.jclouds.Constants.PROPERTY_SCHEDULER_THREADS; import static org.jclouds.Constants.PROPERTY_SCHEDULER_THREADS;
import static org.jclouds.Constants.PROPERTY_USER_THREADS; import static org.jclouds.Constants.PROPERTY_USER_THREADS;
import static org.jclouds.reflect.Reflection2.methods;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -40,6 +39,8 @@ import javax.inject.Named;
import org.jclouds.lifecycle.Closer; import org.jclouds.lifecycle.Closer;
import com.google.common.base.Predicate;
import com.google.common.reflect.Invokable;
import com.google.common.util.concurrent.ExecutionList; import com.google.common.util.concurrent.ExecutionList;
import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
@ -51,15 +52,15 @@ import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener; import com.google.inject.spi.TypeListener;
/** /**
* This associates java lifecycle annotations with guice hooks. For example, we invoke * This associates java lifecycle annotations with guice hooks. For example, we invoke {@link PostConstruct} after
* {@link PostConstruct} after injection, and Associate {@link PreDestroy} with a global * injection, and Associate {@link PreDestroy} with a global {@link Closer} object.
* {@link Closer} object.
* *
* <h3>Important</h3> Make sure you create your injector with {@link Stage#PRODUCTION} and execute * <h3>Important</h3> Make sure you create your injector with {@link Stage#PRODUCTION} and execute the bound
* the bound {@link ExecutionList} prior to using any other objects. * {@link ExecutionList} prior to using any other objects.
* *
* <p/> * <p/>
* Ex. * Ex.
*
* <pre> * <pre>
* *
* </pre> * </pre>
@ -89,7 +90,7 @@ public class LifeCycleModule extends AbstractModule {
ioExecutor.shutdownNow(); ioExecutor.shutdownNow();
// ScheduledExecutor is defined in an optional module // ScheduledExecutor is defined in an optional module
if (scheduledExecutor != null) if (scheduledExecutor != null)
scheduledExecutor.shutdownNow(); scheduledExecutor.shutdownNow();
} }
}; };
@ -103,67 +104,57 @@ public class LifeCycleModule extends AbstractModule {
bind(ExecutionList.class).toInstance(list); bind(ExecutionList.class).toInstance(list);
} }
private static final Predicate<Invokable<?, ?>> isPreDestroy = new Predicate<Invokable<?, ?>>() {
public boolean apply(Invokable<?, ?> in) {
return in.isAnnotationPresent(PreDestroy.class);
}
};
private static final Predicate<Invokable<?, ?>> isPostConstruct = new Predicate<Invokable<?, ?>>() {
public boolean apply(Invokable<?, ?> in) {
return in.isAnnotationPresent(PostConstruct.class);
}
};
protected void bindPostInjectionInvoke(final Closer closer, final ExecutionList list) { protected void bindPostInjectionInvoke(final Closer closer, final ExecutionList list) {
bindListener(any(), new TypeListener() { bindListener(any(), new TypeListener() {
public <I> void hear(TypeLiteral<I> injectableType, TypeEncounter<I> encounter) { public <I> void hear(TypeLiteral<I> injectableType, TypeEncounter<I> encounter) {
Set<Method> methods = newHashSet(); Collection<? extends Invokable<? super I, Object>> methods = methods(injectableType.getRawType());
Class<? super I> type = injectableType.getRawType(); for (final Invokable<? super I, Object> method : filter(methods, isPostConstruct)) {
while (type != null) {
methods.addAll(asList(type.getDeclaredMethods()));
type = type.getSuperclass();
}
for (final Method method : methods) {
invokePostConstructMethodAfterInjection(encounter, method);
associatePreDestroyWithCloser(closer, encounter, method);
}
}
private <I> void associatePreDestroyWithCloser(final Closer closer, TypeEncounter<I> encounter,
final Method method) {
PreDestroy preDestroy = method.getAnnotation(PreDestroy.class);
if (preDestroy != null) {
encounter.register(new InjectionListener<I>() {
public void afterInjection(final I injectee) {
closer.addToClose(new Closeable() {
public void close() throws IOException {
try {
method.invoke(injectee);
} catch (InvocationTargetException ie) {
Throwable e = ie.getTargetException();
throw new IOException(e.getMessage());
} catch (IllegalAccessException e) {
throw new IOException(e.getMessage());
}
}
});
}
});
}
}
private <I> void invokePostConstructMethodAfterInjection(TypeEncounter<I> encounter, final Method method) {
PostConstruct postConstruct = method.getAnnotation(PostConstruct.class);
if (postConstruct != null) {
encounter.register(new InjectionListener<I>() { encounter.register(new InjectionListener<I>() {
public void afterInjection(final I injectee) { public void afterInjection(final I injectee) {
list.add(new Runnable() { list.add(new Runnable() {
public void run() { public void run() {
try { invokeOnInjectee(method, injectee);
method.invoke(injectee);
} catch (InvocationTargetException ie) {
Throwable e = ie.getTargetException();
throw propagate(e);
} catch (IllegalAccessException e) {
throw propagate(e);
}
} }
}, sameThreadExecutor()); }, sameThreadExecutor());
} }
}); });
} }
for (final Invokable<? super I, Object> method : filter(methods, isPreDestroy)) {
encounter.register(new InjectionListener<I>() {
public void afterInjection(final I injectee) {
closer.addToClose(new Closeable() {
public void close() throws IOException {
invokeOnInjectee(method, injectee);
}
});
}
});
}
} }
}); });
} }
private static <I> void invokeOnInjectee(Invokable<? super I, Object> method, I injectee) {
try {
method.invoke(injectee);
} catch (InvocationTargetException ie) {
throw propagate(ie.getTargetException());
} catch (IllegalAccessException e) {
throw propagate(e);
}
}
} }

View File

@ -19,23 +19,33 @@
package org.jclouds.reflect; package org.jclouds.reflect;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.toArray; import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Iterables.tryFind;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Set;
import java.util.concurrent.ExecutionException;
import com.google.common.annotations.Beta; import com.google.common.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
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.ImmutableMap; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.reflect.Invokable; import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter;
import com.google.common.reflect.TypeToken; import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.UncheckedExecutionException;
/** /**
* Utilities that allow access to {@link Invokable}s with {@link Invokable#getOwnerType() owner types}. * Utilities that allow access to {@link Invokable}s with {@link Invokable#getOwnerType() owner types}.
@ -43,14 +53,50 @@ import com.google.common.reflect.TypeToken;
* @since 1.6 * @since 1.6
*/ */
@Beta @Beta
public final class Reflection2 { public class Reflection2 {
/**
* gets a {@link TypeToken} for the given type.
*/
@SuppressWarnings("unchecked")
public static <T> TypeToken<T> typeToken(Type in) {
return (TypeToken<T>) get(typeTokenForType, checkNotNull(in, "class"));
}
/** /**
* gets a {@link TypeToken} for the given class. * gets a {@link TypeToken} for the given class.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> TypeToken<T> typeToken(Class<T> in) { public static <T> TypeToken<T> typeToken(Class<T> in) {
return (TypeToken<T>) typeTokenForClass.apply(checkNotNull(in, "class")); return (TypeToken<T>) get(typeTokenForClass, checkNotNull(in, "class"));
}
/**
* returns an {@link Invokable} object that reflects a constructor present in the {@link TypeToken} type.
*
* @param ownerType
* corresponds to {@link Invokable#getOwnerType()}
* @param parameterTypes
* corresponds to {@link Constructor#getParameterTypes()}
*
* @throws IllegalArgumentException
* if the constructor doesn't exist or a security exception occurred
*/
@SuppressWarnings("unchecked")
public static <T> Invokable<T, T> constructor(Class<T> ownerType, Class<?>... parameterTypes) {
return (Invokable<T, T>) get(constructorForParams, new TypeTokenAndParameterTypes(typeToken(ownerType),
parameterTypes));
}
/**
* return all constructors present in the class as {@link Invokable}s.
*
* @param ownerType
* corresponds to {@link Invokable#getOwnerType()}
*/
@SuppressWarnings("unchecked")
public static <T> Collection<Invokable<T, T>> constructors(TypeToken<T> ownerType) {
return Collection.class.cast(get(constructorsForTypeToken, ownerType));
} }
/** /**
@ -63,7 +109,7 @@ public final class Reflection2 {
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T, R> Invokable<T, R> method(TypeToken<T> ownerType, Method method) { public static <T, R> Invokable<T, R> method(TypeToken<T> ownerType, Method method) {
return (Invokable<T, R>) methods.apply(new TypeTokenAndMethod(ownerType, method)); return (Invokable<T, R>) method(ownerType.getRawType(), method.getName(), method.getParameterTypes());
} }
/** /**
@ -79,7 +125,7 @@ public final class Reflection2 {
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T, R> Invokable<T, R> method(Class<T> ownerType, String name, Class<?>... parameterTypes) { public static <T, R> Invokable<T, R> method(Class<T> ownerType, String name, Class<?>... parameterTypes) {
return (Invokable<T, R>) methodForArgs.apply(new TypeTokenNameAndParameterTypes(typeToken(ownerType), name, return (Invokable<T, R>) get(methodForParams, new TypeTokenNameAndParameterTypes(typeToken(ownerType), name,
parameterTypes)); parameterTypes));
} }
@ -91,51 +137,65 @@ public final class Reflection2 {
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> Collection<Invokable<T, Object>> methods(Class<T> ownerType) { public static <T> Collection<Invokable<T, Object>> methods(Class<T> ownerType) {
return Collection.class.cast(methodsForTypeToken.apply(typeToken(ownerType)).values()); return Collection.class.cast(get(methodsForTypeToken, typeToken(ownerType)));
} }
private static final LoadingCache<TypeTokenAndMethod, Invokable<?, ?>> methods = CacheBuilder.newBuilder().build( /**
new CacheLoader<TypeTokenAndMethod, Invokable<?, ?>>() { * this gets all declared constructors, not just public ones. makes them accessible, as well.
public Invokable<?, ?> load(TypeTokenAndMethod key) { */
return key.type.method(key.method); private static LoadingCache<TypeToken<?>, Set<Invokable<?, ?>>> constructorsForTypeToken = CacheBuilder
.newBuilder().build(new CacheLoader<TypeToken<?>, Set<Invokable<?, ?>>>() {
public Set<Invokable<?, ?>> load(TypeToken<?> key) {
ImmutableSet.Builder<Invokable<?, ?>> builder = ImmutableSet.<Invokable<?, ?>> builder();
for (Constructor<?> ctor : key.getRawType().getDeclaredConstructors()) {
ctor.setAccessible(true);
builder.add(key.constructor(ctor));
}
return builder.build();
} }
}); });
private static class TypeTokenAndMethod { protected static List<Class<?>> toClasses(ImmutableList<Parameter> params) {
return Lists.transform(params, new Function<Parameter, Class<?>>() {
protected final TypeToken<?> type; public Class<?> apply(Parameter input) {
protected final Method method; return input.getType().getRawType();
}
public TypeTokenAndMethod(TypeToken<?> type, Method method) { });
this.type = checkNotNull(type, "type");
this.method = checkNotNull(method, "method");
}
public int hashCode() {
return Objects.hashCode(type, method);
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
TypeTokenAndMethod that = TypeTokenAndMethod.class.cast(obj);
return Objects.equal(this.type, that.type) && Objects.equal(this.method, that.method);
}
} }
private static final LoadingCache<Class<?>, TypeToken<?>> typeTokenForClass = CacheBuilder.newBuilder().build( private static LoadingCache<Type, TypeToken<?>> typeTokenForType = CacheBuilder.newBuilder().build(
new CacheLoader<Class<?>, TypeToken<?>>() { new CacheLoader<Type, TypeToken<?>>() {
public TypeToken<?> load(final Class<?> key) { public TypeToken<?> load(Type key) {
return TypeToken.of(key); return TypeToken.of(key);
} }
}); });
private static LoadingCache<Class<?>, TypeToken<?>> typeTokenForClass = CacheBuilder.newBuilder().build(
new CacheLoader<Class<?>, TypeToken<?>>() {
public TypeToken<?> load(Class<?> key) {
return TypeToken.of(key);
}
});
private static LoadingCache<TypeTokenAndParameterTypes, Invokable<?, ?>> constructorForParams = CacheBuilder
.newBuilder().build(new CacheLoader<TypeTokenAndParameterTypes, Invokable<?, ?>>() {
public Invokable<?, ?> load(final TypeTokenAndParameterTypes key) {
Set<Invokable<?, ?>> constructors = get(constructorsForTypeToken, key.type);
Optional<Invokable<?, ?>> constructor = tryFind(constructors, new Predicate<Invokable<?, ?>>() {
public boolean apply(Invokable<?, ?> input) {
return Objects.equal(toClasses(input.getParameters()), key.parameterTypes);
}
});
if (constructor.isPresent())
return constructor.get();
throw new IllegalArgumentException("no such constructor " + key.toString() + "in: " + constructors);
}
});
private static class TypeTokenAndParameterTypes { private static class TypeTokenAndParameterTypes {
protected final TypeToken<?> type; protected TypeToken<?> type;
protected final List<Class<?>> parameterTypes; protected List<Class<?>> parameterTypes;
public TypeTokenAndParameterTypes(TypeToken<?> type, Class<?>... parameterTypes) { public TypeTokenAndParameterTypes(TypeToken<?> type, Class<?>... parameterTypes) {
this.type = checkNotNull(type, "type"); this.type = checkNotNull(type, "type");
@ -160,23 +220,25 @@ public final class Reflection2 {
} }
} }
private static final LoadingCache<TypeTokenNameAndParameterTypes, Invokable<?, ?>> methodForArgs = CacheBuilder private static LoadingCache<TypeTokenNameAndParameterTypes, Invokable<?, ?>> methodForParams = CacheBuilder
.newBuilder().build(new CacheLoader<TypeTokenNameAndParameterTypes, Invokable<?, ?>>() { .newBuilder().build(new CacheLoader<TypeTokenNameAndParameterTypes, Invokable<?, ?>>() {
public Invokable<?, ?> load(final TypeTokenNameAndParameterTypes key) { public Invokable<?, ?> load(final TypeTokenNameAndParameterTypes key) {
try { Set<Invokable<?, ?>> methods = get(methodsForTypeToken, key.type);
Method method = key.type.getRawType().getMethod(key.name, toArray(key.parameterTypes, Class.class)); Optional<Invokable<?, ?>> method = tryFind(methods, new Predicate<Invokable<?, ?>>() {
return methods.apply(new TypeTokenAndMethod(key.type, method)); public boolean apply(Invokable<?, ?> input) {
} catch (SecurityException e) { return Objects.equal(input.getName(), key.name)
throw new IllegalArgumentException(e.getMessage() + " getting method " + key.toString(), e); && Objects.equal(toClasses(input.getParameters()), key.parameterTypes);
} catch (NoSuchMethodException e) { }
throw new IllegalArgumentException("no such method " + key.toString(), e); });
} if (method.isPresent())
return method.get();
throw new IllegalArgumentException("no such method " + key.toString() + "in: " + methods);
} }
}); });
private static class TypeTokenNameAndParameterTypes extends TypeTokenAndParameterTypes { private static class TypeTokenNameAndParameterTypes extends TypeTokenAndParameterTypes {
private final String name; private String name;
public TypeTokenNameAndParameterTypes(TypeToken<?> type, String name, Class<?>... parameterTypes) { public TypeTokenNameAndParameterTypes(TypeToken<?> type, String name, Class<?>... parameterTypes) {
super(type, parameterTypes); super(type, parameterTypes);
@ -201,14 +263,36 @@ public final class Reflection2 {
} }
} }
private static final LoadingCache<TypeToken<?>, Map<Method, Invokable<?, ?>>> methodsForTypeToken = CacheBuilder /**
.newBuilder().build(new CacheLoader<TypeToken<?>, Map<Method, Invokable<?, ?>>>() { * this gets all declared methods, not just public ones. makes them accessible. Does not include Object methods.
public Map<Method, Invokable<?, ?>> load(final TypeToken<?> key) { */
Builder<Method, Invokable<?, ?>> builder = ImmutableMap.<Method, Invokable<?, ?>> builder(); private static LoadingCache<TypeToken<?>, Set<Invokable<?, ?>>> methodsForTypeToken = CacheBuilder
for (Method method : key.getRawType().getMethods()) .newBuilder().build(new CacheLoader<TypeToken<?>, Set<Invokable<?, ?>>>() {
builder.put(method, method(key, method)); public Set<Invokable<?, ?>> load(TypeToken<?> key) {
ImmutableSet.Builder<Invokable<?, ?>> builder = ImmutableSet.<Invokable<?, ?>> builder();
for (TypeToken<?> token : key.getTypes()) {
Class<?> raw = token.getRawType();
if (raw == Object.class)
continue;
for (Method method : raw.getDeclaredMethods()) {
method.setAccessible(true);
builder.add(key.method(method));
}
}
return builder.build(); return builder.build();
} }
}); });
/**
* ensures that exceptions are not doubly-wrapped
*/
private static <K, V> V get(LoadingCache<K, V> cache, K key) {
try {
return cache.get(key);
} catch (UncheckedExecutionException e) {
throw propagate(e.getCause());
} catch (ExecutionException e) {
throw propagate(e.getCause());
}
}
} }

View File

@ -20,7 +20,6 @@ package org.jclouds.rest.config;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Iterables.toArray; import static com.google.common.collect.Iterables.toArray;
import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Maps.transformValues; import static com.google.common.collect.Maps.transformValues;
@ -32,11 +31,9 @@ import static org.jclouds.rest.config.BinderUtils.bindHttpApi;
import static org.jclouds.util.Maps2.transformKeys; import static org.jclouds.util.Maps2.transformKeys;
import static org.jclouds.util.Predicates2.startsWith; import static org.jclouds.util.Predicates2.startsWith;
import java.lang.reflect.Method;
import java.net.Proxy; import java.net.Proxy;
import java.net.URI; import java.net.URI;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Named; import javax.inject.Named;
@ -47,7 +44,6 @@ import org.jclouds.http.functions.config.SaxParserModule;
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;
import com.google.common.reflect.Invokable;
import org.jclouds.proxy.ProxyForURI; import org.jclouds.proxy.ProxyForURI;
import org.jclouds.rest.AuthorizationException; import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.HttpAsyncClient; import org.jclouds.rest.HttpAsyncClient;
@ -55,13 +51,14 @@ import org.jclouds.rest.HttpClient;
import org.jclouds.rest.binders.BindToJsonPayloadWrappedWith; import org.jclouds.rest.binders.BindToJsonPayloadWrappedWith;
import org.jclouds.rest.internal.BlockOnFuture; import org.jclouds.rest.internal.BlockOnFuture;
import com.google.common.annotations.VisibleForTesting;
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.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter; import com.google.common.reflect.Parameter;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Provides; import com.google.inject.Provides;
@ -80,8 +77,6 @@ public class RestModule extends AbstractModule {
this(ImmutableMap.<Class<?>, Class<?>> of()); this(ImmutableMap.<Class<?>, Class<?>> of());
} }
private static final Set<Method> objectMethods = ImmutableSet.copyOf(Object.class.getMethods());
public RestModule(Map<Class<?>, Class<?>> sync2Async) { public RestModule(Map<Class<?>, Class<?>> sync2Async) {
this.sync2Async = sync2Async; this.sync2Async = sync2Async;
} }
@ -92,6 +87,11 @@ public class RestModule extends AbstractModule {
@Provides @Provides
@Singleton @Singleton
protected Cache<Invokable<?, ?>, Invokable<?, ?>> seedKnownSync2AsyncInvokables() { protected Cache<Invokable<?, ?>, Invokable<?, ?>> seedKnownSync2AsyncInvokables() {
return seedKnownSync2AsyncInvokables(sync2Async);
}
@VisibleForTesting
static Cache<Invokable<?, ?>, Invokable<?, ?>> seedKnownSync2AsyncInvokables(Map<Class<?>, Class<?>> sync2Async) {
Cache<Invokable<?, ?>, Invokable<?, ?>> sync2AsyncBuilder = CacheBuilder.newBuilder().build(); Cache<Invokable<?, ?>, Invokable<?, ?>> sync2AsyncBuilder = CacheBuilder.newBuilder().build();
putInvokables(HttpClient.class, HttpAsyncClient.class, sync2AsyncBuilder); putInvokables(HttpClient.class, HttpAsyncClient.class, sync2AsyncBuilder);
for (Class<?> s : sync2Async.keySet()) { for (Class<?> s : sync2Async.keySet()) {
@ -103,18 +103,10 @@ public class RestModule extends AbstractModule {
// accessible for ClientProvider // accessible for ClientProvider
public static void putInvokables(Class<?> sync, Class<?> async, Cache<Invokable<?, ?>, Invokable<?, ?>> cache) { public static void putInvokables(Class<?> sync, Class<?> async, Cache<Invokable<?, ?>, Invokable<?, ?>> cache) {
for (Invokable<?, ?> invoked : methods(sync)) { for (Invokable<?, ?> invoked : methods(sync)) {
if (!objectMethods.contains(invoked)) { Invokable<?, ?> delegatedMethod = method(async, invoked.getName(), getParameterTypes(invoked));
try { checkArgument(delegatedMethod.getExceptionTypes().equals(invoked.getExceptionTypes()),
Invokable<?, ?> delegatedMethod = method(async, invoked.getName(), getParameterTypes(invoked)); "invoked %s has different typed exceptions than target %s", invoked, delegatedMethod);
checkArgument(delegatedMethod.getExceptionTypes().equals(invoked.getExceptionTypes()), cache.put(invoked, delegatedMethod);
"invoked %s has different typed exceptions than delegated invoked %s", invoked, delegatedMethod);
invoked.setAccessible(true);
delegatedMethod.setAccessible(true);
cache.put(invoked, delegatedMethod);
} catch (SecurityException e) {
throw propagate(e);
}
}
} }
} }

View File

@ -19,14 +19,13 @@
package org.jclouds.rest.config; package org.jclouds.rest.config;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static com.google.inject.name.Names.named;
import org.jclouds.reflect.Invocation; import org.jclouds.reflect.Invocation;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Key; import com.google.inject.Key;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
/** /**
* Allows the provider to supply a value set in a threadlocal. * Allows the provider to supply a value set in a threadlocal.
@ -55,8 +54,7 @@ public class SetCaller {
} }
} }
private static final Key<Invocation> CALLER_INVOCATION = Key.get(new TypeLiteral<Invocation>() { private static final Key<Invocation> CALLER_INVOCATION = Key.get(Invocation.class, named("caller"));
}, Names.named("caller"));
class CallerInvocationProvider implements Provider<Invocation> { class CallerInvocationProvider implements Provider<Invocation> {
@Override @Override

View File

@ -19,12 +19,15 @@
package org.jclouds.reflect; package org.jclouds.reflect;
import static com.google.common.base.Functions.toStringFunction; import static com.google.common.base.Functions.toStringFunction;
import static org.jclouds.reflect.Reflection2.constructors;
import static org.jclouds.reflect.Reflection2.method; import static org.jclouds.reflect.Reflection2.method;
import static org.jclouds.reflect.Reflection2.methods; import static org.jclouds.reflect.Reflection2.methods;
import static org.jclouds.reflect.Reflection2.typeToken; import static org.jclouds.reflect.Reflection2.typeToken;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.SortedSet;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -32,7 +35,9 @@ import com.google.common.base.Function;
import com.google.common.collect.FluentIterable; import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.Invokable; import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter;
import com.google.common.reflect.TypeToken; import com.google.common.reflect.TypeToken;
import com.google.inject.TypeLiteral;
/** /**
* *
@ -41,7 +46,31 @@ import com.google.common.reflect.TypeToken;
@Test @Test
public class Reflection2Test { public class Reflection2Test {
public void testTypeToken() { /**
* useful when converting to and from type literals from other libraries such as guice and gson.
*/
public void testTypeTokenForType() {
TypeLiteral<Set<String>> guice = new TypeLiteral<Set<String>>() {
};
assertEquals(typeToken(guice.getType()), new TypeToken<Set<String>>() {
private static final long serialVersionUID = 1L;
});
}
public void testConstructors() {
Set<String> ctorParams = FluentIterable.from(constructors(TypeToken.of(HashSet.class)))
.transform(new Function<Invokable<?, ?>, Iterable<Parameter>>() {
public Iterable<Parameter> apply(Invokable<?, ?> input) {
return input.getParameters();
}
}).transform(toStringFunction()).toSet();
assertEquals(ctorParams, ImmutableSet.of("[]", "[java.util.Collection<? extends E> arg0]",
"[int arg0, float arg1]", "[int arg0]", "[int arg0, float arg1, boolean arg2]"));
}
public void testTypeTokenForClass() {
assertEquals(typeToken(String.class), TypeToken.of(String.class)); assertEquals(typeToken(String.class), TypeToken.of(String.class));
} }
@ -65,15 +94,28 @@ public class Reflection2Test {
assertEquals(methodInSuper.getParameters().get(0).getType().getRawType(), Object.class); assertEquals(methodInSuper.getParameters().get(0).getType().getRawType(), Object.class);
} }
public void testMethods() { ImmutableSet<String> setMethods = ImmutableSet.of("add", "equals", "hashCode", "clear", "isEmpty", "contains",
Set<String> methodNames = FluentIterable.from(methods(Set.class)) "addAll", "size", "toArray", "iterator", "remove", "removeAll", "containsAll", "retainAll");
.transform(new Function<Invokable<?, ?>, String>() {
public String apply(Invokable<?, ?> input) {
return input.getName();
}
}).transform(toStringFunction()).toSet();
assertEquals(methodNames, ImmutableSet.of("add", "equals", "hashCode", "clear", "isEmpty", "contains", "addAll", public void testMethods() {
"size", "toArray", "iterator", "remove", "removeAll", "containsAll", "retainAll")); Set<String> methodNames = FluentIterable.from(methods(Set.class)).transform(invokableToName)
.transform(toStringFunction()).toSet();
assertEquals(methodNames, setMethods);
} }
public void testMethodsSubClass() {
Set<String> methodNames = FluentIterable.from(methods(SortedSet.class)).transform(invokableToName)
.transform(toStringFunction()).toSet();
assertEquals(methodNames,
ImmutableSet.builder().add("comparator", "last", "first", "subSet", "headSet", "tailSet")
.addAll(setMethods).build());
}
static final Function<Invokable<?, ?>, String> invokableToName = new Function<Invokable<?, ?>, String>() {
public String apply(Invokable<?, ?> input) {
return input.getName();
}
};
} }

View File

@ -0,0 +1,120 @@
/**
* 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.config;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Maps.filterEntries;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;
import org.jclouds.rest.HttpAsyncClient;
import org.jclouds.rest.HttpClient;
import org.testng.annotations.Test;
import com.google.common.base.Predicate;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableMap;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.ListenableFuture;
/**
*
* @author Adrian Cole
*/
@Test(groups = "unit")
public class RestModuleTest {
static interface Sync {
String get();
}
private static interface Async {
ListenableFuture<String> get();
}
public void testPutInvokablesWhenInterfacesMatch() {
Cache<Invokable<?, ?>, Invokable<?, ?>> cache = CacheBuilder.newBuilder().build();
RestModule.putInvokables(Sync.class, Async.class, cache);
assertEquals(cache.size(), 1);
Invokable<?, ?> sync = cache.asMap().keySet().iterator().next();
assertEquals(sync.getOwnerType().getRawType(), Sync.class);
assertEquals(sync.getName(), "get");
assertEquals(sync.getReturnType(), TypeToken.of(String.class));
Invokable<?, ?> async = cache.getIfPresent(sync);
assertEquals(async.getOwnerType().getRawType(), Async.class);
assertEquals(async.getName(), "get");
assertEquals(async.getReturnType(), new TypeToken<ListenableFuture<String>>() {
private static final long serialVersionUID = 1L;
});
}
private static interface AsyncWithException {
ListenableFuture<String> get() throws IOException;
}
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".* has different typed exceptions than target .*")
public void testPutInvokablesWhenInterfacesMatchExceptExceptions() {
Cache<Invokable<?, ?>, Invokable<?, ?>> cache = CacheBuilder.newBuilder().build();
RestModule.putInvokables(Sync.class, AsyncWithException.class, cache);
}
private static interface AsyncWithMisnamedMethod {
ListenableFuture<String> got();
}
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "no such method .*")
public void testPutInvokablesWhenTargetMethodNotFound() {
Cache<Invokable<?, ?>, Invokable<?, ?>> cache = CacheBuilder.newBuilder().build();
RestModule.putInvokables(Sync.class, AsyncWithMisnamedMethod.class, cache);
}
static final Predicate<Entry<Invokable<?, ?>, Invokable<?, ?>>> isHttpInvokable = new Predicate<Map.Entry<Invokable<?, ?>, Invokable<?, ?>>>() {
public boolean apply(Map.Entry<Invokable<?, ?>, Invokable<?, ?>> in) {
return in.getKey().getOwnerType().getRawType().equals(HttpClient.class)
&& in.getValue().getOwnerType().getRawType().equals(HttpAsyncClient.class);
}
};
public void testSeedKnownSync2AsyncIncludesHttpClientByDefault() {
Map<Invokable<?, ?>, Invokable<?, ?>> cache = RestModule.seedKnownSync2AsyncInvokables(
ImmutableMap.<Class<?>, Class<?>> of()).asMap();
assertEquals(cache.size(), 6);
assertEquals(filterEntries(cache, isHttpInvokable), cache);
}
public void testSeedKnownSync2AsyncInvokablesInterfacesMatch() {
Map<Invokable<?, ?>, Invokable<?, ?>> cache = RestModule.seedKnownSync2AsyncInvokables(
ImmutableMap.<Class<?>, Class<?>> of(Sync.class, Async.class)).asMap();
assertEquals(cache.size(), 7);
cache = filterEntries(cache, not(isHttpInvokable));
assertEquals(cache.size(), 1);
}
}

View File

@ -22,8 +22,9 @@ package org.jclouds.abiquo.domain;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.concat; import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Iterables.transform;
import static org.jclouds.reflect.Reflection2.constructor;
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException;
import java.net.URI; import java.net.URI;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -44,6 +45,7 @@ import com.abiquo.server.core.task.TaskDto;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.reflect.Invokable;
/** /**
* This class is used to decorate transport objects with high level * This class is used to decorate transport objects with high level
@ -103,13 +105,12 @@ public abstract class DomainWrapper<T extends SingleResourceTransportDto> {
} }
try { try {
Constructor<W> cons = wrapperClass.getDeclaredConstructor(RestContext.class, target.getClass()); Invokable<W, W> cons = constructor(wrapperClass, RestContext.class, target.getClass());
if (!cons.isAccessible()) { return cons.invoke(null, context, target);
cons.setAccessible(true); } catch (InvocationTargetException e) {
} throw new WrapperException(wrapperClass, target, e.getTargetException());
return cons.newInstance(context, target); } catch (IllegalAccessException e) {
} catch (Exception ex) { throw new WrapperException(wrapperClass, target, e);
throw new WrapperException(wrapperClass, target, ex);
} }
} }