• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc;
18 
19 import com.google.common.annotations.VisibleForTesting;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.Comparator;
23 import java.util.List;
24 import java.util.ServiceConfigurationError;
25 import java.util.ServiceLoader;
26 
27 final class ServiceProviders {
ServiceProviders()28   private ServiceProviders() {
29     // do not instantiate
30   }
31 
32   /**
33    * If this is not Android, returns the highest priority implementation of the class via
34    * {@link ServiceLoader}.
35    * If this is Android, returns an instance of the highest priority class in {@code hardcoded}.
36    */
load( Class<T> klass, Iterable<Class<?>> hardcoded, ClassLoader cl, PriorityAccessor<T> priorityAccessor)37   public static <T> T load(
38       Class<T> klass,
39       Iterable<Class<?>> hardcoded,
40       ClassLoader cl,
41       PriorityAccessor<T> priorityAccessor) {
42     List<T> candidates = loadAll(klass, hardcoded, cl, priorityAccessor);
43     if (candidates.isEmpty()) {
44       return null;
45     }
46     return candidates.get(0);
47   }
48 
49   /**
50    * If this is not Android, returns all available implementations discovered via
51    * {@link ServiceLoader}.
52    * If this is Android, returns all available implementations in {@code hardcoded}.
53    * The list is sorted in descending priority order.
54    */
loadAll( Class<T> klass, Iterable<Class<?>> hardcoded, ClassLoader cl, final PriorityAccessor<T> priorityAccessor)55   public static <T> List<T> loadAll(
56       Class<T> klass,
57       Iterable<Class<?>> hardcoded,
58       ClassLoader cl,
59       final PriorityAccessor<T> priorityAccessor) {
60     Iterable<T> candidates;
61     if (isAndroid(cl)) {
62       candidates = getCandidatesViaHardCoded(klass, hardcoded);
63     } else {
64       candidates = getCandidatesViaServiceLoader(klass, cl);
65     }
66     List<T> list = new ArrayList<>();
67     for (T current: candidates) {
68       if (!priorityAccessor.isAvailable(current)) {
69         continue;
70       }
71       list.add(current);
72     }
73 
74     // Sort descending based on priority.  If priorities are equal, compare the class names to
75     // get a reliable result.
76     Collections.sort(list, Collections.reverseOrder(new Comparator<T>() {
77       @Override
78       public int compare(T f1, T f2) {
79         int pd = priorityAccessor.getPriority(f1) - priorityAccessor.getPriority(f2);
80         if (pd != 0) {
81           return pd;
82         }
83         return f1.getClass().getName().compareTo(f2.getClass().getName());
84       }
85     }));
86     return Collections.unmodifiableList(list);
87   }
88 
89   /**
90    * Returns true if the {@link ClassLoader} is for android.
91    */
isAndroid(ClassLoader cl)92   static boolean isAndroid(ClassLoader cl) {
93     try {
94       // Specify a class loader instead of null because we may be running under Robolectric
95       Class.forName("android.app.Application", /*initialize=*/ false, cl);
96       return true;
97     } catch (Exception e) {
98       // If Application isn't loaded, it might as well not be Android.
99       return false;
100     }
101   }
102 
103   /**
104    * Loads service providers for the {@code klass} service using {@link ServiceLoader}.
105    */
106   @VisibleForTesting
getCandidatesViaServiceLoader(Class<T> klass, ClassLoader cl)107   public static <T> Iterable<T> getCandidatesViaServiceLoader(Class<T> klass, ClassLoader cl) {
108     Iterable<T> i = ServiceLoader.load(klass, cl);
109     // Attempt to load using the context class loader and ServiceLoader.
110     // This allows frameworks like http://aries.apache.org/modules/spi-fly.html to plug in.
111     if (!i.iterator().hasNext()) {
112       i = ServiceLoader.load(klass);
113     }
114     return i;
115   }
116 
117   /**
118    * Load providers from a hard-coded list. This avoids using getResource(), which has performance
119    * problems on Android (see https://github.com/grpc/grpc-java/issues/2037).
120    */
121   @VisibleForTesting
getCandidatesViaHardCoded(Class<T> klass, Iterable<Class<?>> hardcoded)122   static <T> Iterable<T> getCandidatesViaHardCoded(Class<T> klass, Iterable<Class<?>> hardcoded) {
123     List<T> list = new ArrayList<>();
124     for (Class<?> candidate : hardcoded) {
125       T t = createForHardCoded(klass, candidate);
126       if (t == null) {
127         continue;
128       }
129       list.add(t);
130     }
131     return list;
132   }
133 
createForHardCoded(Class<T> klass, Class<?> rawClass)134   private static <T> T createForHardCoded(Class<T> klass, Class<?> rawClass) {
135     try {
136       return rawClass.asSubclass(klass).getConstructor().newInstance();
137     } catch (ClassCastException ex) {
138       // Tools like Proguard that perform obfuscation rewrite strings only when the class they
139       // reference is known, as otherwise they wouldn't know its new name. This means some
140       // hard-coded Class.forNames() won't be rewritten. This can cause ClassCastException at
141       // runtime if the class ends up appearing on the classpath but that class is part of a
142       // separate copy of grpc. With tools like Maven Shade Plugin the class wouldn't be found at
143       // all and so would be skipped. We want to skip in this case as well.
144       return null;
145     } catch (Throwable t) {
146       throw new ServiceConfigurationError(
147           String.format("Provider %s could not be instantiated %s", rawClass.getName(), t), t);
148     }
149   }
150 
151   /**
152    * An interface that allows us to get priority information about a provider.
153    */
154   public interface PriorityAccessor<T> {
155     /**
156      * Checks this provider is available for use, taking the current environment into consideration.
157      * If {@code false}, no other methods are safe to be called.
158      */
isAvailable(T provider)159     boolean isAvailable(T provider);
160 
161     /**
162      * A priority, from 0 to 10 that this provider should be used, taking the current environment
163      * into consideration. 5 should be considered the default, and then tweaked based on environment
164      * detection. A priority of 0 does not imply that the provider wouldn't work; just that it
165      * should be last in line.
166      */
getPriority(T provider)167     int getPriority(T provider);
168   }
169 }
170