All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.inject.throwingproviders.ThrowingProviderBinder Maven / Gradle / Ivy

There is a newer version: 0.68.0.1
Show newest version
/*
 * 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.Optional;
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.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() {
 *     install(ThrowingProviderBinder.forModule(this));
 *   }
 *
 *   {@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 [email protected] (Jerome Mourits) * @author [email protected] (Jesse Wilson) * @author [email protected] (Sam Berlin) */ public class ThrowingProviderBinder { private static final TypeLiteral> CHECKED_PROVIDER_TYPE = new TypeLiteral>() {}; private static final TypeLiteral> CHECKED_PROVIDER_METHOD_TYPE = new TypeLiteral>() {}; private final Binder binder; private ThrowingProviderBinder(Binder binder) { this.binder = binder; } public static ThrowingProviderBinder create(Binder binder) { return new ThrowingProviderBinder( binder.skipSources( ThrowingProviderBinder.class, ThrowingProviderBinder.SecondaryBinder.class)); } /** * Returns a module that installs {@literal @}{@link CheckedProvides} methods. * * @since 3.0 */ public static Module forModule(Module module) { return CheckedProviderMethodsModule.forModule(module); } /** @deprecated Use {@link #bind(Class, Class)} or {@link #bind(Class, TypeLiteral)} instead. */ @Deprecated public

SecondaryBinder bind( Class

interfaceType, Type clazz) { return new SecondaryBinder(interfaceType, clazz); } /** @since 4.0 */ public

SecondaryBinder bind( Class

interfaceType, Class clazz) { return new SecondaryBinder(interfaceType, clazz); } /** @since 4.0 */ public

SecondaryBinder bind( Class

interfaceType, TypeLiteral typeLiteral) { return new SecondaryBinder(interfaceType, typeLiteral.getType()); } public class SecondaryBinder

{ private final Class

interfaceType; private final Type valueType; private final List> exceptionTypes; private final boolean valid; private Class annotationType; private Annotation annotation; private Key

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> getExceptionTypes() { return exceptionTypes; } Key

getKey() { return interfaceKey; } public SecondaryBinder annotatedWith(Class 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 targetType) { return to(Key.get(targetType)); } /** @since 4.0 */ public ScopedBindingBuilder providing(Class cxtorClass) { return providing(TypeLiteral.get(cxtorClass)); } /** @since 4.0 */ @SuppressWarnings("unchecked") // safe because this is the cxtor of the literal public ScopedBindingBuilder providing(TypeLiteral cxtorLiteral) { // Find a constructor that has @ThrowingInject. Constructor cxtor = CheckedProvideUtils.findThrowingConstructor(cxtorLiteral, binder); final Provider typeProvider; final Key typeKey; // If we found an injection point, then bind the cxtor to a unique key if (cxtor != null) { // Validate the exceptions are consistent with the CheckedProvider interface. CheckedProvideUtils.validateExceptions( binder, cxtorLiteral.getExceptionTypes(cxtor), exceptionTypes, interfaceType); typeKey = Key.get(cxtorLiteral, UniqueAnnotations.create()); binder.bind(typeKey).toConstructor((Constructor) cxtor).in(Scopes.NO_SCOPE); typeProvider = binder.getProvider((Key) typeKey); } else { // never used, but need it assigned. typeProvider = null; typeKey = null; } // Create a CheckedProvider that calls our cxtor CheckedProvider checkedProvider = new CheckedProviderWithDependencies() { @Override public T get() throws Exception { try { return typeProvider.get(); } catch (ProvisionException pe) { // Rethrow the provision cause as the actual exception if (pe.getCause() instanceof Exception) { throw (Exception) pe.getCause(); } else if (pe.getCause() instanceof Error) { throw (Error) pe.getCause(); } else { // If this failed because of multiple reasons (ie, more than // one dependency failed due to scoping errors), then // the ProvisionException won't have a cause, so we need // to rethrow it as-is. throw pe; } } } @Override public Set> getDependencies() { return ImmutableSet.>of(Dependency.get(typeKey)); } }; Key> targetKey = Key.get(CHECKED_PROVIDER_TYPE, UniqueAnnotations.create()); binder.bind(targetKey).toInstance(checkedProvider); return toInternal(targetKey); } ScopedBindingBuilder toProviderMethod(CheckedProviderMethod target) { Key> targetKey = Key.get(CHECKED_PROVIDER_METHOD_TYPE, UniqueAnnotations.create()); binder.bind(targetKey).toInstance(target); return toInternal(targetKey); } @SuppressWarnings("unchecked") // P only extends the raw type of CheckedProvider public ScopedBindingBuilder to(Key targetKey) { checkNotNull(targetKey, "targetKey"); return toInternal((Key>) targetKey); } private ScopedBindingBuilder toInternal(final Key> targetKey) { final Key resultKey = Key.get(Result.class, UniqueAnnotations.create()); // Note that this provider will behave like the final provider Guice creates. // It will especially do scoping if the user adds that. final Provider resultProvider = binder.getProvider(resultKey); final Provider> targetProvider = binder.getProvider(targetKey); interfaceKey = createKey(); // don't bother binding the proxy type if this is in an invalid state. if (valid) { binder .bind(interfaceKey) .toProvider( new ProviderWithDependencies

() { private final P instance = interfaceType.cast( Proxy.newProxyInstance( interfaceType.getClassLoader(), new Class[] {interfaceType}, new InvocationHandler() { @Override 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> getDependencies() { return ImmutableSet.>of(Dependency.get(resultKey)); } }); } // The provider is unscoped, but the user may apply a scope to it through the // ScopedBindingBuilder this returns. return binder.bind(resultKey).toProvider(createResultProvider(targetKey, targetProvider)); } private ProviderWithDependencies createResultProvider( final Key> targetKey, final Provider> targetProvider) { return new ProviderWithDependencies() { @Override public Result get() { try { return Result.forValue(targetProvider.get().get()); } catch (Exception e) { for (Class exceptionType : exceptionTypes) { if (exceptionType.isInstance(e)) { if (scopeExceptions) { return Result.forException(e); } else { throw new ResultException(e); } } } if (e instanceof RuntimeException) { throw (RuntimeException) e; } else { // this should never happen throw new RuntimeException(e); } } } @Override public Set> getDependencies() { return ImmutableSet.>of(Dependency.get(targetKey)); } }; } /** * Returns the exception type declared to be thrown by the get method of {@code interfaceType}. */ private List> getExceptionType(Class

interfaceType) { try { Method getMethod = interfaceType.getMethod("get"); List> exceptionLiterals = TypeLiteral.get(interfaceType).getExceptionTypes(getMethod); List> results = Lists.newArrayList(); for (TypeLiteral exLiteral : exceptionLiterals) { results.add(exLiteral.getRawType().asSubclass(Throwable.class)); } return results; } catch (SecurityException e) { throw new IllegalStateException("Not allowed to inspect exception types", e); } catch (NoSuchMethodException e) { throw new IllegalStateException("No 'get'method available", e); } } private boolean checkInterface() { try { ProviderChecker.checkInterface(interfaceType, Optional.of(valueType)); return true; } catch (IllegalArgumentException e) { binder.addError(e.getMessage()); return false; } } @SuppressWarnings({"unchecked"}) private Key

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); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy