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