/* * Grapht, an open source dependency injector. * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt) * Copyright 2010-2014 Regents of the University of Minnesota * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.grouplens.grapht.solver; import org.grouplens.grapht.CachePolicy; import org.grouplens.grapht.ResolutionException; import org.grouplens.grapht.annotation.*; import org.grouplens.grapht.reflect.Desire; import org.grouplens.grapht.reflect.Qualifiers; import org.grouplens.grapht.reflect.Satisfaction; import org.grouplens.grapht.reflect.Satisfactions; import org.grouplens.grapht.util.Preconditions; import org.grouplens.grapht.util.Types; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Provider; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * A binding function that looks for {@link DefaultImplementation} or * {@link DefaultProvider} on the desired type or the qualifier. For constants, * it will also check for {@link DefaultDouble}, {@link DefaultInteger}, * {@link DefaultBoolean}, and {@link DefaultString}. * * @author <a href="http://grouplens.org">GroupLens Research</a> */ public class DefaultDesireBindingFunction implements BindingFunction { private static final String META_INF_DEFAULTS = "META-INF/grapht/defaults/"; private final Logger logger = LoggerFactory.getLogger(DefaultDesireBindingFunction.class); private final ClassLoader classLoader; private final Map<Class<?>, BindingResult> metaInfCache = new HashMap<Class<?>, BindingResult>(); DefaultDesireBindingFunction(ClassLoader loader) { Preconditions.notNull("spi", loader); classLoader = loader; } public static DefaultDesireBindingFunction create(ClassLoader loader) { if (loader == null) { loader = Thread.currentThread().getContextClassLoader(); } if (loader == null) { loader = DefaultDesireBindingFunction.class.getClassLoader(); } return new DefaultDesireBindingFunction(loader); } public static DefaultDesireBindingFunction create() { return create(null); } @Override public BindingResult bind(InjectionContext context, DesireChain dchain) throws ResolutionException { Desire desire = dchain.getCurrentDesire(); BindingResult result = null; Annotation qualifier = desire.getInjectionPoint().getQualifier(); // Only use qualifier defaults if this is the first desire // (i.e. the desire that declared any qualifier) // REVIEW If it is not the first desire, can a qualifier exist? if (dchain.getPreviousDesires().isEmpty() && qualifier != null) { Class<? extends Annotation> annotType = qualifier.annotationType(); annotType = Qualifiers.resolveAliases(annotType); result = getDefaultValue(desire, annotType); if (result == null) { result = getAnnotatedDefault(desire, annotType); } // if the qualifier does not allow fall-through, we're done if (!annotType.isAnnotationPresent(AllowDefaultMatch.class) && !annotType.isAnnotationPresent(AllowUnqualifiedMatch.class)) { return result; } } // Now check the desired type for @DefaultImplementation or @DefaultProvider if the type // source has not been disabled. if (result == null) { result = getAnnotatedDefault(desire, desire.getDesiredType()); } // Last-ditch, try to get a default from META-INF if (result == null) { result = getMetaInfDefault(desire, desire.getDesiredType()); } // There are no annotations on the {@link Qualifier} or the type that indicate a // default binding or value, or the defaults have been disabled, // so we return null return result; } /** * Get a default value (double, integer, string, etc.). * @param desire The desire to satisfy. * @param type The class to scan for annotations. * @return The binding result, or {@code null} if there are no relevant annotations. */ private BindingResult getDefaultValue(Desire desire, Class<?> type) { // FIXME Check whether the annotation type is actually relevant for the desire BindingResult.Builder bld = null; DefaultDouble dfltDouble = type.getAnnotation(DefaultDouble.class); if (dfltDouble != null) { bld = BindingResult.newBuilder() .setDesire(desire.restrict(Satisfactions.instance(dfltDouble.value()))); } DefaultInteger dfltInt = type.getAnnotation(DefaultInteger.class); if (dfltInt != null) { bld = BindingResult.newBuilder().setDesire(desire.restrict(Satisfactions.instance(dfltInt.value()))); } DefaultBoolean dfltBool = type.getAnnotation(DefaultBoolean.class); if (dfltBool != null) { bld = BindingResult.newBuilder().setDesire(desire.restrict(Satisfactions.instance(dfltBool.value()))); } DefaultString dfltStr = type.getAnnotation(DefaultString.class); if (dfltStr != null) { bld = BindingResult.newBuilder().setDesire(desire.restrict(Satisfactions.instance(dfltStr.value()))); } if (bld != null) { return bld.setCachePolicy(CachePolicy.NO_PREFERENCE) .addFlag(BindingFlag.TERMINAL) .build(); } else { return null; } } /** * Get the default from annotations on the class, if present. * * @param type The type to scan for annotations. * @return A binding result, or {@code null} if no usable annotations are present. */ private BindingResult getAnnotatedDefault(Desire desire, Class<?> type) { DefaultProvider provider = type.getAnnotation(DefaultProvider.class); BindingResult.Builder brb = null; if (provider != null) { brb = BindingResult.newBuilder() .setDesire(desire.restrict(Satisfactions.providerType(provider.value()))) .setCachePolicy(provider.cachePolicy()) .addFlag(BindingFlag.TERMINAL); if (provider.skipIfUnusable()) { brb.addFlag(BindingFlag.SKIPPABLE); } } DefaultImplementation impl = type.getAnnotation(DefaultImplementation.class); if (impl != null) { brb = BindingResult.newBuilder() .setCachePolicy(impl.cachePolicy()); if (Types.isInstantiable(impl.value())) { brb.setDesire(desire.restrict(Satisfactions.type(impl.value()))); } else { brb.setDesire(desire.restrict(impl.value())); } if (impl.skipIfUnusable()) { brb.addFlag(BindingFlag.SKIPPABLE); } } DefaultNull dnull = type.getAnnotation(DefaultNull.class); if (dnull != null) { brb = BindingResult.newBuilder() .setDesire(desire.restrict(Satisfactions.nullOfType(desire.getDesiredType()))) .setCachePolicy(CachePolicy.NO_PREFERENCE) .addFlag(BindingFlag.TERMINAL); } return brb != null ? brb.build() : null; } @SuppressWarnings("unchecked") private BindingResult getMetaInfDefault(Desire desire, Class<?> type) throws ResolutionException { synchronized (metaInfCache) { if (metaInfCache.containsKey(type)) { return metaInfCache.get(type); } } BindingResult.Builder builder = BindingResult.newBuilder(); boolean found = false; String resourceName = META_INF_DEFAULTS + type.getCanonicalName() + ".properties"; logger.debug("searching for defaults in {}", resourceName); URL url = classLoader.getResource(resourceName); if (url != null) { Properties props; InputStream istr = null; try { istr = url.openStream(); props = new Properties(); props.load(istr); } catch (IOException e) { throw new ResolutionException("error reading " + resourceName, e); } finally { try { if (istr != null) { istr.close(); } } catch (IOException e) { logger.error("error closing {}: {}", resourceName, e); } } String providerName = props.getProperty("provider"); if (providerName != null) { try { logger.debug("found provider {} for {}", providerName, type); Class<?> clazz = classLoader.loadClass(providerName); Satisfaction sat = Satisfactions.providerType((Class<Provider<?>>) clazz.asSubclass(Provider.class)); if (!type.isAssignableFrom(sat.getErasedType())) { throw new ResolutionException(providerName + " does not provide " + type); } builder.setDesire(desire.restrict(sat)) .addFlag(BindingFlag.TERMINAL); found = true; } catch (ClassNotFoundException e) { throw new ResolutionException("cannot find default provider for " + type, e); } } String implName = props.getProperty("implementation"); if (implName != null) { try { logger.debug("found implementation {} for {}", implName, type); Class<?> clazz = classLoader.loadClass(implName); Satisfaction sat = Satisfactions.type(clazz); if (!type.isAssignableFrom(sat.getErasedType())) { throw new ResolutionException(providerName + " not compatible with " + type); } builder.setDesire(desire.restrict(sat)); found = true; } catch (ClassNotFoundException e) { throw new ResolutionException("cannot find default implementation for " + type, e); } } String skip = props.getProperty("skipIfUnusable"); if (skip != null && skip.trim().toLowerCase().equals("true")) { builder.addFlag(BindingFlag.SKIPPABLE); } if (found) { String policy = props.getProperty("cachePolicy", "NO_PREFERENCE"); builder.setCachePolicy(CachePolicy.valueOf(policy)); } } BindingResult result = found ? builder.build() : null; synchronized (metaInfCache) { metaInfCache.put(type, result); } return result; } }