com.couchbase.client.core.deps.io.grpc.ServiceProviders Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core-io Show documentation
Show all versions of core-io Show documentation
The official Couchbase JVM Core IO Library
/*
* Copyright 2017 The gRPC Authors
*
* 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.couchbase.client.core.deps.io.grpc;
import com.couchbase.client.core.deps.com.google.common.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
final class ServiceProviders {
private ServiceProviders() {
// do not instantiate
}
/**
* If this is not Android, returns the highest priority implementation of the class via
* {@link ServiceLoader}.
* If this is Android, returns an instance of the highest priority class in {@code hardcoded}.
*/
public static T load(
Class klass,
Iterable> hardcoded,
ClassLoader cl,
PriorityAccessor priorityAccessor) {
List candidates = loadAll(klass, hardcoded, cl, priorityAccessor);
if (candidates.isEmpty()) {
return null;
}
return candidates.get(0);
}
/**
* If this is not Android, returns all available implementations discovered via
* {@link ServiceLoader}.
* If this is Android, returns all available implementations in {@code hardcoded}.
* The list is sorted in descending priority order.
*/
public static List loadAll(
Class klass,
Iterable> hardcoded,
ClassLoader cl,
final PriorityAccessor priorityAccessor) {
Iterable candidates;
if (isAndroid(cl)) {
candidates = getCandidatesViaHardCoded(klass, hardcoded);
} else {
candidates = getCandidatesViaServiceLoader(klass, cl);
}
List list = new ArrayList<>();
for (T current: candidates) {
if (!priorityAccessor.isAvailable(current)) {
continue;
}
list.add(current);
}
// Sort descending based on priority. If priorities are equal, compare the class names to
// get a reliable result.
Collections.sort(list, Collections.reverseOrder(new Comparator() {
@Override
public int compare(T f1, T f2) {
int pd = priorityAccessor.getPriority(f1) - priorityAccessor.getPriority(f2);
if (pd != 0) {
return pd;
}
return f1.getClass().getName().compareTo(f2.getClass().getName());
}
}));
return Collections.unmodifiableList(list);
}
/**
* Returns true if the {@link ClassLoader} is for android.
*/
static boolean isAndroid(ClassLoader cl) {
try {
// Specify a class loader instead of null because we may be running under Robolectric
Class.forName("android.app.Application", /*initialize=*/ false, cl);
return true;
} catch (Exception e) {
// If Application isn't loaded, it might as well not be Android.
return false;
}
}
/**
* Loads service providers for the {@code klass} service using {@link ServiceLoader}.
*/
@VisibleForTesting
public static Iterable getCandidatesViaServiceLoader(Class klass, ClassLoader cl) {
Iterable i = ServiceLoader.load(klass, cl);
// Attempt to load using the context class loader and ServiceLoader.
// This allows frameworks like http://aries.apache.org/modules/spi-fly.html to plug in.
if (!i.iterator().hasNext()) {
i = ServiceLoader.load(klass);
}
return i;
}
/**
* Load providers from a hard-coded list. This avoids using getResource(), which has performance
* problems on Android (see https://github.com/grpc/grpc-java/issues/2037).
*/
@VisibleForTesting
static Iterable getCandidatesViaHardCoded(Class klass, Iterable> hardcoded) {
List list = new ArrayList<>();
for (Class candidate : hardcoded) {
T t = createForHardCoded(klass, candidate);
if (t == null) {
continue;
}
list.add(t);
}
return list;
}
private static T createForHardCoded(Class klass, Class rawClass) {
try {
return rawClass.asSubclass(klass).getConstructor().newInstance();
} catch (ClassCastException ex) {
// Tools like Proguard that perform obfuscation rewrite strings only when the class they
// reference is known, as otherwise they wouldn't know its new name. This means some
// hard-coded Class.forNames() won't be rewritten. This can cause ClassCastException at
// runtime if the class ends up appearing on the classpath but that class is part of a
// separate copy of grpc. With tools like Maven Shade Plugin the class wouldn't be found at
// all and so would be skipped. We want to skip in this case as well.
return null;
} catch (Throwable t) {
throw new ServiceConfigurationError(
String.format("Provider %s could not be instantiated %s", rawClass.getName(), t), t);
}
}
/**
* An interface that allows us to get priority information about a provider.
*/
public interface PriorityAccessor {
/**
* Checks this provider is available for use, taking the current environment into consideration.
* If {@code false}, no other methods are safe to be called.
*/
boolean isAvailable(T provider);
/**
* A priority, from 0 to 10 that this provider should be used, taking the current environment
* into consideration. 5 should be considered the default, and then tweaked based on environment
* detection. A priority of 0 does not imply that the provider wouldn't work; just that it
* should be last in line.
*/
int getPriority(T provider);
}
}