1 /* 2 * Copyright 2019 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 static com.google.common.base.Preconditions.checkArgument; 20 21 import com.google.common.annotations.VisibleForTesting; 22 import com.google.common.collect.ImmutableMap; 23 import java.net.URI; 24 import java.util.ArrayList; 25 import java.util.Collections; 26 import java.util.HashMap; 27 import java.util.LinkedHashSet; 28 import java.util.List; 29 import java.util.Locale; 30 import java.util.Map; 31 import java.util.logging.Level; 32 import java.util.logging.Logger; 33 import javax.annotation.Nullable; 34 import javax.annotation.concurrent.GuardedBy; 35 import javax.annotation.concurrent.ThreadSafe; 36 37 /** 38 * Registry of {@link NameResolverProvider}s. The {@link #getDefaultRegistry default instance} 39 * loads providers at runtime through the Java service provider mechanism. 40 * 41 * @since 1.21.0 42 */ 43 @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4159") 44 @ThreadSafe 45 public final class NameResolverRegistry { 46 private static final Logger logger = Logger.getLogger(NameResolverRegistry.class.getName()); 47 private static NameResolverRegistry instance; 48 49 private final NameResolver.Factory factory = new NameResolverFactory(); 50 private static final String UNKNOWN_SCHEME = "unknown"; 51 @GuardedBy("this") 52 private String defaultScheme = UNKNOWN_SCHEME; 53 54 @GuardedBy("this") 55 private final LinkedHashSet<NameResolverProvider> allProviders = new LinkedHashSet<>(); 56 /** Generated from {@code allProviders}. Is mapping from scheme key to the highest priority 57 * {@link NameResolverProvider}. Is replaced instead of mutating. */ 58 @GuardedBy("this") 59 private ImmutableMap<String, NameResolverProvider> effectiveProviders = ImmutableMap.of(); 60 61 62 /** 63 * Register a provider. 64 * 65 * <p>If the provider's {@link NameResolverProvider#isAvailable isAvailable()} returns 66 * {@code false}, this method will throw {@link IllegalArgumentException}. 67 * 68 * <p>Providers will be used in priority order. In case of ties, providers are used in 69 * registration order. 70 */ register(NameResolverProvider provider)71 public synchronized void register(NameResolverProvider provider) { 72 addProvider(provider); 73 refreshProviders(); 74 } 75 addProvider(NameResolverProvider provider)76 private synchronized void addProvider(NameResolverProvider provider) { 77 checkArgument(provider.isAvailable(), "isAvailable() returned false"); 78 allProviders.add(provider); 79 } 80 81 /** 82 * Deregisters a provider. No-op if the provider is not in the registry. 83 * 84 * @param provider the provider that was added to the register via {@link #register}. 85 */ deregister(NameResolverProvider provider)86 public synchronized void deregister(NameResolverProvider provider) { 87 allProviders.remove(provider); 88 refreshProviders(); 89 } 90 refreshProviders()91 private synchronized void refreshProviders() { 92 Map<String, NameResolverProvider> refreshedProviders = new HashMap<>(); 93 int maxPriority = Integer.MIN_VALUE; 94 String refreshedDefaultScheme = UNKNOWN_SCHEME; 95 // We prefer first-registered providers 96 for (NameResolverProvider provider : allProviders) { 97 String scheme = provider.getScheme(); 98 NameResolverProvider existing = refreshedProviders.get(scheme); 99 if (existing == null || existing.priority() < provider.priority()) { 100 refreshedProviders.put(scheme, provider); 101 } 102 if (maxPriority < provider.priority()) { 103 maxPriority = provider.priority(); 104 refreshedDefaultScheme = provider.getScheme(); 105 } 106 } 107 effectiveProviders = ImmutableMap.copyOf(refreshedProviders); 108 defaultScheme = refreshedDefaultScheme; 109 } 110 111 /** 112 * Returns the default registry that loads providers via the Java service loader mechanism. 113 */ getDefaultRegistry()114 public static synchronized NameResolverRegistry getDefaultRegistry() { 115 if (instance == null) { 116 List<NameResolverProvider> providerList = ServiceProviders.loadAll( 117 NameResolverProvider.class, 118 getHardCodedClasses(), 119 NameResolverProvider.class.getClassLoader(), 120 new NameResolverPriorityAccessor()); 121 if (providerList.isEmpty()) { 122 logger.warning("No NameResolverProviders found via ServiceLoader, including for DNS. This " 123 + "is probably due to a broken build. If using ProGuard, check your configuration"); 124 } 125 instance = new NameResolverRegistry(); 126 for (NameResolverProvider provider : providerList) { 127 logger.fine("Service loader found " + provider); 128 instance.addProvider(provider); 129 } 130 instance.refreshProviders(); 131 } 132 return instance; 133 } 134 135 /** 136 * Returns effective providers map from scheme to the highest priority NameResolverProvider of 137 * that scheme. 138 */ 139 @VisibleForTesting providers()140 synchronized Map<String, NameResolverProvider> providers() { 141 return effectiveProviders; 142 } 143 asFactory()144 public NameResolver.Factory asFactory() { 145 return factory; 146 } 147 148 @VisibleForTesting getHardCodedClasses()149 static List<Class<?>> getHardCodedClasses() { 150 // Class.forName(String) is used to remove the need for ProGuard configuration. Note that 151 // ProGuard does not detect usages of Class.forName(String, boolean, ClassLoader): 152 // https://sourceforge.net/p/proguard/bugs/418/ 153 ArrayList<Class<?>> list = new ArrayList<>(); 154 try { 155 list.add(Class.forName("io.grpc.internal.DnsNameResolverProvider")); 156 } catch (ClassNotFoundException e) { 157 logger.log(Level.FINE, "Unable to find DNS NameResolver", e); 158 } 159 return Collections.unmodifiableList(list); 160 } 161 162 private final class NameResolverFactory extends NameResolver.Factory { 163 @Override 164 @Nullable newNameResolver(URI targetUri, NameResolver.Args args)165 public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) { 166 String scheme = targetUri.getScheme(); 167 if (scheme == null) { 168 return null; 169 } 170 NameResolverProvider provider = providers().get(scheme.toLowerCase(Locale.US)); 171 return provider == null ? null : provider.newNameResolver(targetUri, args); 172 } 173 174 @Override getDefaultScheme()175 public String getDefaultScheme() { 176 synchronized (NameResolverRegistry.this) { 177 return defaultScheme; 178 } 179 } 180 } 181 182 private static final class NameResolverPriorityAccessor 183 implements ServiceProviders.PriorityAccessor<NameResolverProvider> { 184 @Override isAvailable(NameResolverProvider provider)185 public boolean isAvailable(NameResolverProvider provider) { 186 return provider.isAvailable(); 187 } 188 189 @Override getPriority(NameResolverProvider provider)190 public int getPriority(NameResolverProvider provider) { 191 return provider.priority(); 192 } 193 } 194 } 195