View Javadoc

1   /*
2    * Grapht, an open source dependency injector.
3    * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
4    * Copyright 2010-2014 Regents of the University of Minnesota
5    *
6    * This program is free software; you can redistribute it and/or modify
7    * it under the terms of the GNU Lesser General Public License as
8    * published by the Free Software Foundation; either version 2.1 of the
9    * License, or (at your option) any later version.
10   *
11   * This program is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14   * details.
15   *
16   * You should have received a copy of the GNU General Public License along with
17   * this program; if not, write to the Free Software Foundation, Inc., 51
18   * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19   */
20  package org.grouplens.grapht.solver;
21  
22  import org.grouplens.grapht.CachePolicy;
23  import org.grouplens.grapht.ResolutionException;
24  import org.grouplens.grapht.annotation.*;
25  import org.grouplens.grapht.reflect.Desire;
26  import org.grouplens.grapht.reflect.Qualifiers;
27  import org.grouplens.grapht.reflect.Satisfaction;
28  import org.grouplens.grapht.reflect.Satisfactions;
29  import org.grouplens.grapht.util.Preconditions;
30  import org.grouplens.grapht.util.Types;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  import javax.inject.Provider;
35  import java.io.IOException;
36  import java.io.InputStream;
37  import java.lang.annotation.Annotation;
38  import java.net.URL;
39  import java.util.HashMap;
40  import java.util.Map;
41  import java.util.Properties;
42  
43  /**
44   * A binding function that looks for {@link DefaultImplementation} or
45   * {@link DefaultProvider} on the desired type or the qualifier. For constants,
46   * it will also check for {@link DefaultDouble}, {@link DefaultInteger},
47   * {@link DefaultBoolean}, and {@link DefaultString}.
48   * 
49   * @author <a href="http://grouplens.org">GroupLens Research</a>
50   */
51  public class DefaultDesireBindingFunction implements BindingFunction {
52      private static final String META_INF_DEFAULTS = "META-INF/grapht/defaults/";
53      private final Logger logger = LoggerFactory.getLogger(DefaultDesireBindingFunction.class);
54      private final ClassLoader classLoader;
55  
56      private final Map<Class<?>, BindingResult> metaInfCache =
57              new HashMap<Class<?>, BindingResult>();
58      
59      DefaultDesireBindingFunction(ClassLoader loader) {
60          Preconditions.notNull("spi", loader);
61          classLoader = loader;
62      }
63  
64      public static DefaultDesireBindingFunction create(ClassLoader loader) {
65          if (loader == null) {
66              loader = Thread.currentThread().getContextClassLoader();
67          }
68          if (loader == null) {
69              loader = DefaultDesireBindingFunction.class.getClassLoader();
70          }
71          return new DefaultDesireBindingFunction(loader);
72      }
73  
74      public static DefaultDesireBindingFunction create() {
75          return create(null);
76      }
77      
78      @Override
79      public BindingResult bind(InjectionContext context, DesireChain dchain) throws ResolutionException {
80          Desire desire = dchain.getCurrentDesire();
81          BindingResult result = null;
82  
83          Annotation qualifier = desire.getInjectionPoint().getQualifier();
84  
85          // Only use qualifier defaults if this is the first desire 
86          // (i.e. the desire that declared any qualifier)
87          // REVIEW If it is not the first desire, can a qualifier exist?
88          if (dchain.getPreviousDesires().isEmpty() && qualifier != null) {
89              Class<? extends Annotation> annotType = qualifier.annotationType();
90              annotType = Qualifiers.resolveAliases(annotType);
91  
92              result = getDefaultValue(desire, annotType);
93              if (result == null) {
94                  result = getAnnotatedDefault(desire, annotType);
95              }
96  
97              // if the qualifier does not allow fall-through, we're done
98              if (!annotType.isAnnotationPresent(AllowUnqualifiedMatch.class)) {
99                  return result;
100             }
101         }
102 
103         // Now check the desired type for @DefaultImplementation or @DefaultProvider if the type
104         // source has not been disabled.
105         if (result == null) {
106             result = getAnnotatedDefault(desire, desire.getDesiredType());
107         }
108 
109         // Last-ditch, try to get a default from META-INF
110         if (result == null) {
111             result = getMetaInfDefault(desire, desire.getDesiredType());
112         }
113         
114         // There are no annotations on the {@link Qualifier} or the type that indicate a
115         // default binding or value, or the defaults have been disabled,
116         // so we return null
117         return result;
118     }
119 
120     /**
121      * Get a default value (double, integer, string, etc.).
122      * @param desire The desire to satisfy.
123      * @param type The class to scan for annotations.
124      * @return The binding result, or {@code null} if there are no relevant annotations.
125      */
126     private BindingResult getDefaultValue(Desire desire, Class<?> type) {
127         // FIXME Check whether the annotation type is actually relevant for the desire
128         BindingResult.Builder bld = null;
129         DefaultDouble dfltDouble = type.getAnnotation(DefaultDouble.class);
130         if (dfltDouble != null) {
131             bld = BindingResult.newBuilder()
132                                .setDesire(desire.restrict(Satisfactions.instance(dfltDouble.value())));
133         }
134         DefaultInteger dfltInt = type.getAnnotation(DefaultInteger.class);
135         if (dfltInt != null) {
136             bld = BindingResult.newBuilder().setDesire(desire.restrict(Satisfactions.instance(dfltInt.value())));
137         }
138         DefaultBoolean dfltBool = type.getAnnotation(DefaultBoolean.class);
139         if (dfltBool != null) {
140             bld = BindingResult.newBuilder().setDesire(desire.restrict(Satisfactions.instance(dfltBool.value())));
141         }
142         DefaultString dfltStr = type.getAnnotation(DefaultString.class);
143         if (dfltStr != null) {
144             bld = BindingResult.newBuilder().setDesire(desire.restrict(Satisfactions.instance(dfltStr.value())));
145         }
146         if (bld != null) {
147             return bld.setCachePolicy(CachePolicy.NO_PREFERENCE)
148                       .addFlag(BindingFlag.TERMINAL)
149                       .build();
150         } else {
151             return null;
152         }
153     }
154 
155     /**
156      * Get the default from annotations on the class, if present.
157      *
158      * @param type The type to scan for annotations.
159      * @return A binding result, or {@code null} if no usable annotations are present.
160      */
161     private BindingResult getAnnotatedDefault(Desire desire, Class<?> type) {
162         DefaultProvider provider = type.getAnnotation(DefaultProvider.class);
163         BindingResult.Builder brb = null;
164         if (provider != null) {
165             brb = BindingResult.newBuilder()
166                                .setDesire(desire.restrict(Satisfactions.providerType(provider.value())))
167                                .setCachePolicy(provider.cachePolicy())
168                                .addFlag(BindingFlag.TERMINAL);
169             if (provider.skipIfUnusable()) {
170                 brb.addFlag(BindingFlag.SKIPPABLE);
171             }
172         }
173 
174         DefaultImplementation impl = type.getAnnotation(DefaultImplementation.class);
175         if (impl != null) {
176             brb = BindingResult.newBuilder()
177                                .setCachePolicy(impl.cachePolicy());
178             if (Types.isInstantiable(impl.value())) {
179                 brb.setDesire(desire.restrict(Satisfactions.type(impl.value())));
180             } else {
181                 brb.setDesire(desire.restrict(impl.value()));
182             }
183             if (impl.skipIfUnusable()) {
184                 brb.addFlag(BindingFlag.SKIPPABLE);
185             }
186         }
187 
188         DefaultNull dnull = type.getAnnotation(DefaultNull.class);
189         if (dnull != null) {
190             brb = BindingResult.newBuilder()
191                                .setDesire(desire.restrict(Satisfactions.nullOfType(desire.getDesiredType())))
192                                .setCachePolicy(CachePolicy.NO_PREFERENCE)
193                                .addFlag(BindingFlag.TERMINAL);
194         }
195 
196         return brb != null ? brb.build() : null;
197     }
198 
199     @SuppressWarnings("unchecked")
200     private BindingResult getMetaInfDefault(Desire desire, Class<?> type) throws ResolutionException {
201         synchronized (metaInfCache) {
202             if (metaInfCache.containsKey(type)) {
203                 return metaInfCache.get(type);
204             }
205         }
206 
207         BindingResult.Builder builder = BindingResult.newBuilder();
208         boolean found = false;
209         String resourceName = META_INF_DEFAULTS + type.getCanonicalName() + ".properties";
210         logger.debug("searching for defaults in {}", resourceName);
211         URL url = classLoader.getResource(resourceName);
212 
213         if (url != null) {
214             Properties props;
215             InputStream istr = null;
216             try {
217                 istr = url.openStream();
218                 props = new Properties();
219                 props.load(istr);
220             } catch (IOException e) {
221                 throw new ResolutionException("error reading " + resourceName, e);
222             } finally {
223                 try {
224                     if (istr != null) {
225                         istr.close();
226                     }
227                 } catch (IOException e) {
228                     logger.error("error closing {}: {}", resourceName, e);
229                 }
230             }
231 
232             String providerName = props.getProperty("provider");
233             if (providerName != null) {
234                 try {
235                     logger.debug("found provider {} for {}", providerName, type);
236                     Class<?> clazz = classLoader.loadClass(providerName);
237                     Satisfaction sat = Satisfactions.providerType((Class<Provider<?>>) clazz.asSubclass(Provider.class));
238                     if (!type.isAssignableFrom(sat.getErasedType())) {
239                         throw new ResolutionException(providerName + " does not provide " + type);
240                     }
241                     builder.setDesire(desire.restrict(sat))
242                            .addFlag(BindingFlag.TERMINAL);
243                     found = true;
244                 } catch (ClassNotFoundException e) {
245                     throw new ResolutionException("cannot find default provider for " + type, e);
246                 }
247             }
248 
249             String implName = props.getProperty("implementation");
250             if (implName != null) {
251                 try {
252                     logger.debug("found implementation {} for {}", implName, type);
253                     Class<?> clazz = classLoader.loadClass(implName);
254                     Satisfaction sat = Satisfactions.type(clazz);
255                     if (!type.isAssignableFrom(sat.getErasedType())) {
256                         throw new ResolutionException(providerName + " not compatible with " + type);
257                     }
258                     builder.setDesire(desire.restrict(sat));
259                     found = true;
260                 } catch (ClassNotFoundException e) {
261                     throw new ResolutionException("cannot find default implementation for " + type, e);
262                 }
263             }
264 
265             String skip = props.getProperty("skipIfUnusable");
266             if (skip != null && skip.trim().toLowerCase().equals("true")) {
267                 builder.addFlag(BindingFlag.SKIPPABLE);
268             }
269 
270             if (found) {
271                 String policy = props.getProperty("cachePolicy", "NO_PREFERENCE");
272                 builder.setCachePolicy(CachePolicy.valueOf(policy));
273             }
274         }
275 
276         BindingResult result = found ? builder.build() : null;
277         synchronized (metaInfCache) {
278             metaInfCache.put(type, result);
279         }
280         return result;
281     }
282 }