package net.lightbody.bmp.proxy.dns; import com.google.common.collect.ImmutableList; import java.net.InetAddress; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * An {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver} that applies the AdvancedHostResolver methods to multiple implementations. Methods * are applied to the resolvers in the order specified when the ChainedHostResolver is constructed. AdvancedHostResolver methods that modify the * resolver are guaranteed to complete atomically over all resolvers. For example, if one thread makes a call to * {@link #resolve(String)} while another thread is remapping hosts using * {@link #remapHost(String, String)}, the call to {@link #resolve(String)} is guaranteed to * apply the newly-remapped hosts to <i>all</i> resolvers managed by this ChainedHostResolver, or to <i>no</i> resolvers, but the call to * {@link #resolve(String)} will never result in the host name remappings applied only to "some" of the chained resolvers. * <p/> * For getter methods (all read-only methods except {@link #resolve(String)}), the ChainedHostResolver returns results from the first chained resolver. * <p/> * The atomic write methods specified by AdvancedHostResolver are: * <ul> * <li>{@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#remapHost(String, String)}</li> * <li>{@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#remapHosts(java.util.Map)}</li> * <li>{@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#removeHostRemapping(String)}</li> * <li>{@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#clearHostRemappings()}</li> * <li>{@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#setNegativeDNSCacheTimeout(int, java.util.concurrent.TimeUnit)}</li> * <li>{@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#setPositiveDNSCacheTimeout(int, java.util.concurrent.TimeUnit)}</li> * <li>{@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver#clearDNSCache()}</li> * </ul> */ public class ChainedHostResolver implements AdvancedHostResolver { private final List<? extends AdvancedHostResolver> resolvers; private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private final Lock readLock = readWriteLock.readLock(); private final Lock writeLock = readWriteLock.writeLock(); /** * Creates a ChainedHostResolver that applies {@link net.lightbody.bmp.proxy.dns.AdvancedHostResolver} methods to the specified resolvers * in the order specified by the collection's iterator. * * @param resolvers resolvers to invoke, in the order specified by the collection's iterator */ public ChainedHostResolver(Collection<? extends AdvancedHostResolver> resolvers) { if (resolvers == null) { this.resolvers = Collections.emptyList(); } else { this.resolvers = ImmutableList.copyOf(resolvers); } } /** * Returns the resolvers used by this ChainedHostResolver. The iterator of the collection is guaranteed to return the resolvers in the order * in which they are queried. * * @return resolvers used by this ChainedHostResolver */ public Collection<? extends AdvancedHostResolver> getResolvers() { return ImmutableList.copyOf(resolvers); } @Override public void remapHosts(Map<String, String> hostRemappings) { writeLock.lock(); try { for (AdvancedHostResolver resolver : resolvers) { resolver.remapHosts(hostRemappings); } } finally { writeLock.unlock(); } } @Override public void remapHost(String originalHost, String remappedHost) { writeLock.lock(); try { for (AdvancedHostResolver resolver : resolvers) { resolver.remapHost(originalHost, remappedHost); } } finally { writeLock.unlock(); } } @Override public void removeHostRemapping(String originalHost) { writeLock.lock(); try { for (AdvancedHostResolver resolver : resolvers) { resolver.removeHostRemapping(originalHost); } } finally { writeLock.unlock(); } } @Override public void clearHostRemappings() { writeLock.lock(); try { for (AdvancedHostResolver resolver : resolvers) { resolver.clearHostRemappings(); } } finally { writeLock.unlock(); } } @Override public Map<String, String> getHostRemappings() { readLock.lock(); try { if (resolvers.isEmpty()) { return Collections.emptyMap(); } else { return resolvers.get(0).getHostRemappings(); } } finally { readLock.unlock(); } } @Override public Collection<String> getOriginalHostnames(String remappedHost) { readLock.lock(); try { if (resolvers.isEmpty()) { return Collections.emptyList(); } else { return resolvers.get(0).getOriginalHostnames(remappedHost); } } finally { readLock.unlock(); } } @Override public void clearDNSCache() { writeLock.lock(); try { for (AdvancedHostResolver resolver : resolvers) { resolver.clearDNSCache(); } } finally { writeLock.unlock(); } } @Override public void setPositiveDNSCacheTimeout(int timeout, TimeUnit timeUnit) { writeLock.lock(); try { for (AdvancedHostResolver resolver : resolvers) { resolver.setPositiveDNSCacheTimeout(timeout, timeUnit); } } finally { writeLock.unlock(); } } @Override public void setNegativeDNSCacheTimeout(int timeout, TimeUnit timeUnit) { writeLock.lock(); try { for (AdvancedHostResolver resolver : resolvers) { resolver.setNegativeDNSCacheTimeout(timeout, timeUnit); } } finally { writeLock.unlock(); } } @Override public Collection<InetAddress> resolve(String host) { readLock.lock(); try { // attempt to resolve the host using all resolvers. returns the results from the first successful resolution. for (AdvancedHostResolver resolver : resolvers) { Collection<InetAddress> results = resolver.resolve(host); if (!results.isEmpty()) { return results; } } // no resolvers returned results return Collections.emptyList(); } finally { readLock.unlock(); } } }