/** * Copyright (C) 2007 Google Inc. * * Licensed 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 com.google.inject.throwingproviders; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.inject.Binder; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.Provider; import com.google.inject.ProvisionException; import com.google.inject.Scopes; import com.google.inject.TypeLiteral; import com.google.inject.binder.ScopedBindingBuilder; import com.google.inject.internal.UniqueAnnotations; import com.google.inject.spi.Dependency; import com.google.inject.spi.ProviderWithDependencies; import com.google.inject.util.Types; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Proxy; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Arrays; import java.util.List; import java.util.Set; /** *
Builds a binding for a {@link CheckedProvider}. * *
You can use a fluent API and custom providers: *
ThrowingProviderBinder.create(binder())
* .bind(RemoteProvider.class, Customer.class)
* .to(RemoteCustomerProvider.class)
* .in(RequestScope.class);
*
* or, you can use throwing provider methods:
* class MyModule extends AbstractModule {
* configure() {
* ThrowingProviderBinder.install(this, binder());
* }
*
* {@literal @}CheckedProvides(RemoteProvider.class)
* {@literal @}RequestScope
* Customer provideCustomer(FlakyCustomerCreator creator) throws RemoteException {
* return creator.getCustomerOrThrow();
* }
* }
*
* You also can declare that a CheckedProvider construct
* a particular class whose constructor throws an exception:
* ThrowingProviderBinder.create(binder())
* .bind(RemoteProvider.class, Customer.class)
* .providing(CustomerImpl.class)
* .in(RequestScope.class);
*
*
* @author jmourits@google.com (Jerome Mourits)
* @author jessewilson@google.com (Jesse Wilson)
* @author sameb@google.com (Sam Berlin)
*/
public class ThrowingProviderBinder {
private static final TypeLiteralSecondaryBinder
bind(Class
interfaceType, Type clazz) { return new SecondaryBinder
(interfaceType, clazz); } /** * @since 4.0 */ public
SecondaryBinder
bind(Class
interfaceType, Class (interfaceType, clazz);
}
/**
* @since 4.0
*/
public SecondaryBinder
bind(Class interfaceType, TypeLiteral (interfaceType, typeLiteral.getType());
}
public class SecondaryBinder {
private final Class interfaceType;
private final Type valueType;
private final List interfaceKey;
private boolean scopeExceptions = true;
public SecondaryBinder(Class interfaceType, Type valueType) {
this.interfaceType = checkNotNull(interfaceType, "interfaceType");
this.valueType = checkNotNull(valueType, "valueType");
if(checkInterface()) {
this.exceptionTypes = getExceptionType(interfaceType);
valid = true;
} else {
valid = false;
this.exceptionTypes = ImmutableList.of();
}
}
List getKey() {
return interfaceKey;
}
public SecondaryBinder annotatedWith(Class extends Annotation> annotationType) {
if (!(this.annotationType == null && this.annotation == null)) {
throw new IllegalStateException("Cannot set annotation twice");
}
this.annotationType = annotationType;
return this;
}
public SecondaryBinder annotatedWith(Annotation annotation) {
if (!(this.annotationType == null && this.annotation == null)) {
throw new IllegalStateException("Cannot set annotation twice");
}
this.annotation = annotation;
return this;
}
/**
* Determines if exceptions should be scoped. By default exceptions are scoped.
*
* @param scopeExceptions whether exceptions should be scoped.
* @since 4.0
*/
public SecondaryBinder scopeExceptions(boolean scopeExceptions) {
this.scopeExceptions = scopeExceptions;
return this;
}
public ScopedBindingBuilder to(P target) {
Key targetKey = Key.get(interfaceType, UniqueAnnotations.create());
binder.bind(targetKey).toInstance(target);
return to(targetKey);
}
public ScopedBindingBuilder to(Class extends P> targetType) {
return to(Key.get(targetType));
}
/** @since 4.0 */
public ScopedBindingBuilder providing(Class extends T> cxtorClass) {
return providing(TypeLiteral.get(cxtorClass));
}
/** @since 4.0 */
@SuppressWarnings("unchecked") // safe because this is the cxtor of the literal
public ScopedBindingBuilder providing(TypeLiteral extends T> cxtorLiteral) {
// Find a constructor that has @ThrowingInject.
Constructor extends T> cxtor =
CheckedProvideUtils.findThrowingConstructor(cxtorLiteral, binder);
final Provider () {
private final P instance = interfaceType.cast(Proxy.newProxyInstance(
interfaceType.getClassLoader(), new Class>[] { interfaceType },
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// Allow methods like .equals(..), .hashcode(..), .toString(..) to work.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (scopeExceptions) {
return resultProvider.get().getOrThrow();
} else {
Result result;
try {
result = resultProvider.get();
} catch (ProvisionException pe) {
Throwable cause = pe.getCause();
if (cause instanceof ResultException) {
throw ((ResultException)cause).getCause();
} else {
throw pe;
}
}
return result.getOrThrow();
}
}
}));
@Override
public P get() {
return instance;
}
@Override
public Set interfaceType) {
try {
Method getMethod = interfaceType.getMethod("get");
List createKey() {
TypeLiteral typeLiteral;
if (interfaceType.getTypeParameters().length == 1) {
ParameterizedType type = Types.newParameterizedTypeWithOwner(
interfaceType.getEnclosingClass(), interfaceType, valueType);
typeLiteral = (TypeLiteral ) TypeLiteral.get(type);
} else {
typeLiteral = TypeLiteral.get(interfaceType);
}
if (annotation != null) {
return Key.get(typeLiteral, annotation);
} else if (annotationType != null) {
return Key.get(typeLiteral, annotationType);
} else {
return Key.get(typeLiteral);
}
}
}
/**
* Represents the returned value from a call to {@link CheckedProvider#get()}. This is the value
* that will be scoped by Guice.
*/
static class Result implements Serializable {
private static final long serialVersionUID = 0L;
private final Object value;
private final Exception exception;
private Result(Object value, Exception exception) {
this.value = value;
this.exception = exception;
}
public static Result forValue(Object value) {
return new Result(value, null);
}
public static Result forException(Exception e) {
return new Result(null, e);
}
public Object getOrThrow() throws Exception {
if (exception != null) {
throw exception;
} else {
return value;
}
}
}
/**
* RuntimeException class to wrap exceptions from the checked provider.
* The regular guice provider can throw it and the checked provider proxy extracts
* the underlying exception and rethrows it.
*/
private static class ResultException extends RuntimeException {
ResultException(Exception cause) {
super(cause);
}
}
private static class NotSyntheticOrBridgePredicate implements Predicate