alibaba/java-dns-cache-manipulator

View on GitHub
library/src/main/java/com/alibaba/dcm/internal/InetAddressCacheUtilForOld.java

Summary

Maintainability
A
1 hr
Test Coverage
package com.alibaba.dcm.internal;

import com.alibaba.dcm.DnsCache;
import com.alibaba.dcm.DnsCacheEntry;
import edu.umd.cs.findbugs.annotations.ReturnValuesAreNonnullByDefault;
import org.jetbrains.annotations.ApiStatus;

import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.concurrent.GuardedBy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.alibaba.dcm.internal.InetAddressCacheUtilCommons.*;

/**
 * Util class to manipulate dns cache for old {@code JDK 8-}.
 * <p>
 * dns cache is {@link InetAddress.Cache#cache} in {@link InetAddress#addressCache}.
 * <p>
 * <b>Caution</b>: <br>
 * Manipulation on {@link InetAddress#addressCache} <strong>MUST</strong>
 * be guarded by {@link InetAddress#addressCache} to avoid multithreading problem,
 * you can see the implementation of {@link InetAddress} to confirm this
 * (<b><i>See Also</i></b> lists key code of {@link InetAddress} related to this point).
 *
 * @author Jerry Lee (oldratlee at gmail dot com)
 * @see InetAddress
 * @see InetAddress#addressCache
 * @see InetAddress.CacheEntry
 * @see InetAddress#cacheInitIfNeeded()
 * @see InetAddress#cacheAddresses(String, InetAddress[], boolean)
 */
@ParametersAreNonnullByDefault
@ReturnValuesAreNonnullByDefault
@ApiStatus.Internal
public final class InetAddressCacheUtilForOld {
    /**
     * Need convert host to lowercase, see {@link InetAddress#cacheAddresses(String, InetAddress[], boolean)}.
     */
    public static void setInetAddressCache(String host, String[] ips, long expireMillis)
            throws UnknownHostException, IllegalAccessException, InstantiationException,
            InvocationTargetException, ClassNotFoundException, NoSuchFieldException {
        host = host.toLowerCase();
        long expiration = expireMillis == NEVER_EXPIRATION ? NEVER_EXPIRATION : System.currentTimeMillis() + expireMillis;
        Object entry = newCacheEntry(host, ips, expiration);

        synchronized (getAddressCacheOfInetAddress()) {
            getCache().put(host, entry);
            getNegativeCache().remove(host);
        }
    }

    private static Object newCacheEntry(String host, String[] ips, long expiration)
            throws UnknownHostException, ClassNotFoundException, IllegalAccessException,
            InvocationTargetException, InstantiationException {
        // InetAddress.CacheEntry has only one constructor
        return getConstructorOfInetAddress$CacheEntry().newInstance(toInetAddressArray(host, ips), expiration);
    }

    /**
     * {@link InetAddress.CacheEntry#CacheEntry}
     */
    private static volatile Constructor<?> constructorOfInetAddress$CacheEntry = null;

    private static Constructor<?> getConstructorOfInetAddress$CacheEntry() throws ClassNotFoundException {
        if (constructorOfInetAddress$CacheEntry != null) return constructorOfInetAddress$CacheEntry;

        synchronized (InetAddressCacheUtilCommons.class) {
            // double check
            if (constructorOfInetAddress$CacheEntry != null) return constructorOfInetAddress$CacheEntry;

            final String className = "java.net.InetAddress$CacheEntry";
            final Class<?> clazz = Class.forName(className);

            // InetAddress.CacheEntry has only one constructor:
            // - for jdk 6, constructor signature is CacheEntry(Object address, long expiration)
            // - for jdk 7/8+, constructor signature is CacheEntry(InetAddress[] addresses, long expiration)
            //
            // code in jdk 6:
            //   https://hg.openjdk.java.net/jdk6/jdk6/jdk/file/8deef18bb749/src/share/classes/java/net/InetAddress.java#l739
            // code in jdk 7:
            //   https://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/4dd5e486620d/src/share/classes/java/net/InetAddress.java#l742
            // code in jdk 8:
            //   https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/45e4e636b757/src/share/classes/java/net/InetAddress.java#l748
            final Constructor<?> constructor = clazz.getDeclaredConstructors()[0];
            constructor.setAccessible(true);

            constructorOfInetAddress$CacheEntry = constructor;
            return constructor;
        }
    }

    public static void removeInetAddressCache(String host)
            throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        host = host.toLowerCase();

        synchronized (getAddressCacheOfInetAddress()) {
            getCache().remove(host);
            getNegativeCache().remove(host);
        }
    }

    /**
     * @return {@link InetAddress.Cache#cache} in {@link InetAddress#addressCache}
     */
    @GuardedBy("getAddressCacheOfInetAddress()")
    private static Map<String, Object> getCache()
            throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        return getCacheOfInetAddress$Cache0(getAddressCacheOfInetAddress());
    }

    /**
     * @return {@link InetAddress.Cache#cache} in {@link InetAddress#negativeCache}
     */
    @GuardedBy("getAddressCacheOfInetAddress()")
    private static Map<String, Object> getNegativeCache()
            throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        return getCacheOfInetAddress$Cache0(getNegativeCacheOfInetAddress());
    }


    /**
     * {@link InetAddress.Cache.cache}
     */
    private static volatile Field cacheMapFieldOfInetAddress$Cache = null;

    @SuppressWarnings("unchecked")
    private static Map<String, Object> getCacheOfInetAddress$Cache0(Object inetAddressCache)
            throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        if (cacheMapFieldOfInetAddress$Cache == null) {
            synchronized (InetAddressCacheUtilForOld.class) {
                if (cacheMapFieldOfInetAddress$Cache == null) { // double check
                    final Class<?> clazz = Class.forName("java.net.InetAddress$Cache");
                    final Field f = clazz.getDeclaredField("cache");
                    f.setAccessible(true);
                    cacheMapFieldOfInetAddress$Cache = f;
                }
            }
        }

        return (Map<String, Object>) cacheMapFieldOfInetAddress$Cache.get(inetAddressCache);
    }

    /**
     * @return {@link InetAddress#addressCache}
     */
    private static Object getAddressCacheOfInetAddress()
            throws NoSuchFieldException, IllegalAccessException {
        return getAddressCacheAndNegativeCacheOfInetAddress0()[0];
    }

    /**
     * @return {@link InetAddress#negativeCache}
     */
    private static Object getNegativeCacheOfInetAddress()
            throws NoSuchFieldException, IllegalAccessException {
        return getAddressCacheAndNegativeCacheOfInetAddress0()[1];
    }

    private static volatile Object[] ADDRESS_CACHE_AND_NEGATIVE_CACHE = null;

    /**
     * @return {@link InetAddress#addressCache} and {@link InetAddress#negativeCache}
     */
    private static Object[] getAddressCacheAndNegativeCacheOfInetAddress0()
            throws NoSuchFieldException, IllegalAccessException {
        if (ADDRESS_CACHE_AND_NEGATIVE_CACHE != null) return ADDRESS_CACHE_AND_NEGATIVE_CACHE;

        synchronized (InetAddressCacheUtilForOld.class) {
            // double check
            if (ADDRESS_CACHE_AND_NEGATIVE_CACHE != null) return ADDRESS_CACHE_AND_NEGATIVE_CACHE;

            final Field cacheField = InetAddress.class.getDeclaredField("addressCache");
            cacheField.setAccessible(true);

            final Field negativeCacheField = InetAddress.class.getDeclaredField("negativeCache");
            negativeCacheField.setAccessible(true);

            ADDRESS_CACHE_AND_NEGATIVE_CACHE = new Object[]{
                    cacheField.get(InetAddress.class),
                    negativeCacheField.get(InetAddress.class)
            };
            return ADDRESS_CACHE_AND_NEGATIVE_CACHE;
        }
    }

    @Nullable
    public static DnsCacheEntry getInetAddressCache(String host)
            throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        host = host.toLowerCase();

        final Object cacheEntry;
        synchronized (getAddressCacheOfInetAddress()) {
            cacheEntry = getCache().get(host);
        }

        if (null == cacheEntry) return null;

        final DnsCacheEntry dnsCacheEntry = inetAddress$CacheEntry2DnsCacheEntry(host, cacheEntry);
        if (isDnsCacheEntryExpired(dnsCacheEntry.getHost())) return null;

        return dnsCacheEntry;
    }

    private static boolean isDnsCacheEntryExpired(@Nullable String host) {
        return null == host || "0.0.0.0".equals(host);
    }

    public static DnsCache listInetAddressCache()
            throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        final Map<String, Object> cache;
        final Map<String, Object> negativeCache;
        synchronized (getAddressCacheOfInetAddress()) {
            cache = new HashMap<>(getCache());
            negativeCache = new HashMap<>(getNegativeCache());
        }

        return new DnsCache(convert(cache), convert(negativeCache));
    }

    private static List<DnsCacheEntry> convert(Map<String, Object> cache)
            throws IllegalAccessException, ClassNotFoundException, NoSuchFieldException {
        final List<DnsCacheEntry> ret = new ArrayList<>();
        for (Map.Entry<String, Object> entry : cache.entrySet()) {
            final String host = entry.getKey();
            if (isDnsCacheEntryExpired(host)) { // exclude expired entries!
                continue;
            }

            ret.add(inetAddress$CacheEntry2DnsCacheEntry(host, entry.getValue()));
        }
        return ret;
    }

    private static DnsCacheEntry inetAddress$CacheEntry2DnsCacheEntry(String host, Object entry)
            throws IllegalAccessException, ClassNotFoundException, NoSuchFieldException {
        initFieldsOfInetAddress$CacheEntry();

        final long expiration = expirationFieldOfInetAddress$CacheEntry.getLong(entry);

        final InetAddress[] addresses = (InetAddress[]) addressesFieldOfInetAddress$CacheEntry.get(entry);
        final String[] ips = getIpFromInetAddress(addresses);

        return new DnsCacheEntry(host, ips, expiration);
    }

    /**
     * {@link InetAddress.CacheEntry.expiration}
     */
    private static volatile Field expirationFieldOfInetAddress$CacheEntry = null;
    /**
     * {@link InetAddress.CacheEntry.addresses}
     */
    private static volatile Field addressesFieldOfInetAddress$CacheEntry = null;

    private static void initFieldsOfInetAddress$CacheEntry() throws ClassNotFoundException, NoSuchFieldException {
        if (expirationFieldOfInetAddress$CacheEntry != null && addressesFieldOfInetAddress$CacheEntry != null) return;

        synchronized (InetAddressCacheUtilForOld.class) {
            final Class<?> cacheEntryClass = Class.forName("java.net.InetAddress$CacheEntry");
            // double check
            if (expirationFieldOfInetAddress$CacheEntry != null && addressesFieldOfInetAddress$CacheEntry != null)
                return;

            // InetAddress.CacheEntry has 2 filed:
            // - for jdk 6, address and expiration
            // - for jdk 7+, addresses(*renamed*!) and expiration
            //
            // code in jdk 6:
            //   https://hg.openjdk.java.net/jdk6/jdk6/jdk/file/8deef18bb749/src/share/classes/java/net/InetAddress.java#l739
            // code in jdk 7:
            //   https://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/4dd5e486620d/src/share/classes/java/net/InetAddress.java#l742
            // code in jdk 8:
            //   https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/45e4e636b757/src/share/classes/java/net/InetAddress.java#l748

            Field expirationField = cacheEntryClass.getDeclaredField("expiration");
            expirationField.setAccessible(true);
            expirationFieldOfInetAddress$CacheEntry = expirationField;

            Field addressesField = cacheEntryClass.getDeclaredField("addresses");
            addressesField.setAccessible(true);
            addressesFieldOfInetAddress$CacheEntry = addressesField;
        }
    }

    public static void clearInetAddressCache()
            throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        synchronized (getAddressCacheOfInetAddress()) {
            getCache().clear();
            getNegativeCache().clear();
        }
    }

    private InetAddressCacheUtilForOld() {
    }
}