Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2019-2023 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright 2019-2023 Ping Identity Corporation
*
* 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.
*/
/*
* Copyright (C) 2019-2023 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.ldap.sdk;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import com.unboundid.util.Debug;
import com.unboundid.util.NotNull;
import com.unboundid.util.Nullable;
import com.unboundid.util.ObjectPair;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadLocalRandom;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
/**
* This class provides an implementation of a {@code NameResolver} that will
* cache lookups to potentially improve performance and provide a degree of
* resiliency against name service outages.
*/
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class CachingNameResolver
extends NameResolver
{
/**
* The default timeout that will be used if none is specified.
*/
private static final int DEFAULT_TIMEOUT_MILLIS = 3_600_000; // 1 hour
// A cached version of the address of the local host system.
@NotNull private final AtomicReference>
localHostAddress;
// A cached version of the loopback address.
@NotNull private final AtomicReference>
loopbackAddress;
// A map that associates IP addresses with their canonical host names. The
// key will be the IP address, and the value will be an object pair that
// associates the time that the cache record expires with the cached canonical
// host name for the IP address.
@NotNull private final Map>
addressToNameMap;
// A map that associates host names with the set of all associated IP
// addresses. The key will be an all-lowercase representation of the host
// name, and the value will be an object pair that associates the time that
// the cache record expires with the cached set of IP addresses for the host
// name.
@NotNull private final Map>
nameToAddressMap;
// The length of time, in milliseconds, that a cached record should be
// considered valid.
private final long timeoutMillis;
/**
* Creates a new instance of this caching name resolver that will use a
* default timeout.
*/
public CachingNameResolver()
{
this(DEFAULT_TIMEOUT_MILLIS);
}
/**
* Creates a new instance of this caching name resolver that will use the
* specified timeout.
*
* @param timeoutMillis The length of time, in milliseconds, that cache
* records should be considered valid. It must be
* greater than zero. If a record has been in the
* cache for less than this period of time, then the
* cached record will be used instead of making a name
* service call. If a record has been in the cache
* for longer than this period of time, then the
* cached record will only be used if it is not
* possible to get an updated version of the record
* from the name service.
*/
public CachingNameResolver(final int timeoutMillis)
{
this.timeoutMillis = timeoutMillis;
localHostAddress = new AtomicReference<>();
loopbackAddress = new AtomicReference<>();
addressToNameMap = new ConcurrentHashMap<>(20);
nameToAddressMap = new ConcurrentHashMap<>(20);
}
/**
* Retrieves the length of time, in milliseconds, that cache records should
* be considered valid. If a record has been in the cache for less than this
* period fo time, then the cached record will be used instead of making a
* name service call. If a record has been in the cache for longer than this
* period of time, then the cached record will only be used if it is not
* possible to get an updated version of the record from the name service.
*
* @return The length of time, in milliseconds, that cache records should be
* considered valid.
*/
public int getTimeoutMillis()
{
return (int) timeoutMillis;
}
/**
* {@inheritDoc}
*/
@Override()
@NotNull()
public InetAddress getByName(@Nullable final String host)
throws UnknownHostException, SecurityException
{
// Use the getAllByNameInternal method to get all addresses associated with
// the provided name. If there's only one name associated with the address,
// then return that name. If there are multiple names, then return one at
// random.
final InetAddress[] addresses = getAllByNameInternal(host);
if (addresses.length == 1)
{
return addresses[0];
}
return addresses[ThreadLocalRandom.get().nextInt(addresses.length)];
}
/**
* {@inheritDoc}
*/
@Override()
@NotNull()
public InetAddress[] getAllByName(@Nullable final String host)
throws UnknownHostException, SecurityException
{
// Create a defensive copy of the address array so that the caller cannot
// alter the original.
final InetAddress[] addresses = getAllByNameInternal(host);
return Arrays.copyOf(addresses, addresses.length);
}
/**
* Retrieves an array of {@code InetAddress} objects that encapsulate all
* known IP addresses associated with the provided host name.
*
* @param host The host name for which to retrieve the corresponding
* {@code InetAddress} objects. It can be a resolvable name or
* a textual representation of an IP address. If the provided
* name is the textual representation of an IPv6 address, then
* it can use either the form described in RFC 2373 or RFC 2732,
* or it can be an IPv6 scoped address. If it is {@code null},
* then the returned address should represent an address of the
* loopback interface.
*
* @return An array of {@code InetAddress} objects that encapsulate all known
* IP addresses associated with the provided host name.
*
* @throws UnknownHostException If the provided name cannot be resolved to
* its corresponding IP addresses.
*
* @throws SecurityException If a security manager prevents the name
* resolution attempt.
*/
@NotNull()
public InetAddress[] getAllByNameInternal(@Nullable final String host)
throws UnknownHostException, SecurityException
{
// Get an all-lowercase representation of the provided host name. Note that
// the provided host name can be null, so we need to handle that possibility
// as well.
final String lowerHost;
if (host == null)
{
lowerHost = "";
}
else
{
lowerHost = StaticUtils.toLowerCase(host);
}
// Get the appropriate record from the cache. If there isn't a cached
// then do perform a name service lookup and cache the result before
// returning it.
final ObjectPair cachedRecord =
nameToAddressMap.get(lowerHost);
if (cachedRecord == null)
{
return lookUpAndCache(host, lowerHost);
}
// If the cached record is not expired, then return its set of addresses.
if (System.currentTimeMillis() <= cachedRecord.getFirst())
{
return cachedRecord.getSecond();
}
// The cached record is expired. Try to get a new record from the name
// service, and if that attempt succeeds, then cache the result before
// returning it. If the name service lookup fails, then fall back to using
// the cached addresses even though they're expired.
try
{
return lookUpAndCache(host, lowerHost);
}
catch (final Exception e)
{
Debug.debugException(e);
return cachedRecord.getSecond();
}
}
/**
* Performs a name service lookup to retrieve all addresses for the provided
* name. If the lookup succeeds, then cache the result before returning it.
*
* @param host The host name for which to retrieve the corresponding
* {@code InetAddress} objects. It can be a resolvable
* name or a textual representation of an IP address. If
* the provided name is the textual representation of an
* IPv6 address, then it can use either the form described
* in RFC 2373 or RFC 2732, or it can be an IPv6 scoped
* address. If it is {@code null}, then the returned
* address should represent an address of the loopback
* interface.
* @param lowerHost An all-lowercase representation of the provided host
* name, or an empty string if the provided host name is
* {@code null}. This will be the key under which the
* record will be stored in the cache.
*
* @return An array of {@code InetAddress} objects that represent all
* addresses for the provided name.
*
* @throws UnknownHostException If the provided name cannot be resolved to
* its corresponding IP addresses.
*
* @throws SecurityException If a security manager prevents the name
* resolution attempt.
*/
@NotNull()
private InetAddress[] lookUpAndCache(@Nullable final String host,
@NotNull final String lowerHost)
throws UnknownHostException, SecurityException
{
final InetAddress[] addresses = InetAddress.getAllByName(host);
final long cacheRecordExpirationTime =
System.currentTimeMillis() + timeoutMillis;
final ObjectPair cacheRecord =
new ObjectPair<>(cacheRecordExpirationTime, addresses);
nameToAddressMap.put(lowerHost, cacheRecord);
return addresses;
}
/**
* {@inheritDoc}
*/
@Override()
@NotNull()
public String getHostName(@NotNull final InetAddress inetAddress)
{
// The default InetAddress.getHostName() method has the potential to perform
// a name service lookup, which we want to avoid if at all possible.
// However, if the provided inet address has a name associated with it, then
// we'll want to use it. Fortunately, we can tell if the provided address
// has a name associated with it by looking at the toString method, which is
// defined in the specification to be "hostName/ipAddress" if there is a
// host name, or just "/ipAddress" if there is no associated host name and a
// name service lookup would be required. So look at the string
// representation to extract the host name if it's available, but then fall
// back to using the canonical name otherwise.
final String stringRepresentation = String.valueOf(inetAddress);
final int lastSlashPos = stringRepresentation.lastIndexOf('/');
if (lastSlashPos > 0)
{
return stringRepresentation.substring(0, lastSlashPos);
}
return getCanonicalHostName(inetAddress);
}
/**
* {@inheritDoc}
*/
@Override()
@NotNull()
public String getCanonicalHostName(@NotNull final InetAddress inetAddress)
{
// Get the appropriate record from the cache. If there isn't a cached
// then do perform a name service lookup and cache the result before
// returning it.
final ObjectPair cachedRecord =
addressToNameMap.get(inetAddress);
if (cachedRecord == null)
{
return lookUpAndCache(inetAddress, null);
}
// If the cached record is not expired, then return its canonical host name.
if (System.currentTimeMillis() <= cachedRecord.getFirst())
{
return cachedRecord.getSecond();
}
// The cached record is expired. Try to get a new record from the name
// service, and if that attempt succeeds, then cache the result before
// returning it. If the name service lookup fails, then fall back to using
// the cached canonical host name even though it's expired.
return lookUpAndCache(inetAddress, cachedRecord.getSecond());
}
/**
* Performs a name service lookup to retrieve the canonical host name for the
* provided {@code InetAddress} object. If the lookup succeeds, then cache
* the result before returning it. If the lookup fails (which will be
* indicated by the returned name matching the textual representation of the
* IP address for the provided {@code InetAddress} object) and the provided
* cached result is not {@code null}, then the cached name will be returned,
* but the cache will not be updated.
*
* @param inetAddress The address to use when performing the name service
* lookup to retrieve the canonical name. It must not be
* {@code null}.
* @param cachedName The cached name to be returned if the name service
* lookup fails. It may be {@code null} if there is no
* cached name for the provided address.
*
* @return The canonical host name resulting from the name service lookup,
* the cached name if the lookup failed and the cached name was
* non-{@code null}, or a textual representation of the IP address as
* a last resort.
*/
@NotNull()
private String lookUpAndCache(@NotNull final InetAddress inetAddress,
@Nullable final String cachedName)
{
final String canonicalHostName = inetAddress.getCanonicalHostName();
if (canonicalHostName.equals(inetAddress.getHostAddress()))
{
// The name that we got back is a textual representation of the IP
// address. This suggests that either the canonical lookup failed because
// of a problem while communicating with the name service, or that the
// IP address is not mapped to a name. If a cached name was provided,
// then we'll return that. Otherwise, we'll fall back to returning the
// textual address. In either case, we won't alter the cache.
if (cachedName == null)
{
return canonicalHostName;
}
else
{
return cachedName;
}
}
else
{
// The name service lookup succeeded, so cache the result before returning
// it.
final long cacheRecordExpirationTime =
System.currentTimeMillis() + timeoutMillis;
final ObjectPair cacheRecord =
new ObjectPair<>(cacheRecordExpirationTime, canonicalHostName);
addressToNameMap.put(inetAddress, cacheRecord);
return canonicalHostName;
}
}
/**
* {@inheritDoc}
*/
@Override()
@NotNull()
public InetAddress getLocalHost()
throws UnknownHostException, SecurityException
{
// If we don't have a cached version of the local host address, then
// make a name service call to resolve it and store it in the cache before
// returning it.
final ObjectPair cachedAddress = localHostAddress.get();
if (cachedAddress == null)
{
final InetAddress localHost = InetAddress.getLocalHost();
final long expirationTime =
System.currentTimeMillis() + timeoutMillis;
localHostAddress.set(new ObjectPair(expirationTime,
localHost));
return localHost;
}
// If the cached address has not yet expired, then use the cached address.
final long cachedRecordExpirationTime = cachedAddress.getFirst();
if (System.currentTimeMillis() <= cachedRecordExpirationTime)
{
return cachedAddress.getSecond();
}
// The cached address is expired. Make a name service call to get it again
// and cache that result if we can. If the name service lookup fails, then
// return the cached version even though it's expired.
try
{
final InetAddress localHost = InetAddress.getLocalHost();
final long expirationTime =
System.currentTimeMillis() + timeoutMillis;
localHostAddress.set(new ObjectPair(expirationTime,
localHost));
return localHost;
}
catch (final Exception e)
{
Debug.debugException(e);
return cachedAddress.getSecond();
}
}
/**
* {@inheritDoc}
*/
@Override()
@NotNull()
public InetAddress getLoopbackAddress()
{
// If we don't have a cached version of the loopback address, then make a
// name service call to resolve it and store it in the cache before
// returning it.
final ObjectPair cachedAddress = loopbackAddress.get();
if (cachedAddress == null)
{
final InetAddress address = InetAddress.getLoopbackAddress();
final long expirationTime =
System.currentTimeMillis() + timeoutMillis;
loopbackAddress.set(new ObjectPair(expirationTime,
address));
return address;
}
// If the cached address has not yet expired, then use the cached address.
final long cachedRecordExpirationTime = cachedAddress.getFirst();
if (System.currentTimeMillis() <= cachedRecordExpirationTime)
{
return cachedAddress.getSecond();
}
// The cached address is expired. Make a name service call to get it again
// and cache that result if we can. If the name service lookup fails, then
// return the cached version even though it's expired.
try
{
final InetAddress address = InetAddress.getLoopbackAddress();
final long expirationTime =
System.currentTimeMillis() + timeoutMillis;
loopbackAddress.set(new ObjectPair(expirationTime,
address));
return address;
}
catch (final Exception e)
{
Debug.debugException(e);
return cachedAddress.getSecond();
}
}
/**
* Clears all information from the name resolver cache.
*/
public void clearCache()
{
localHostAddress.set(null);
loopbackAddress.set(null);
addressToNameMap.clear();
nameToAddressMap.clear();
}
/**
* Retrieves a handle to the map used to cache address-to-name lookups. This
* method should only be used for unit testing.
*
* @return A handle to the address-to-name map.
*/
@NotNull()
Map> getAddressToNameMap()
{
return addressToNameMap;
}
/**
* Retrieves a handle to the map used to cache name-to-address lookups. This
* method should only be used for unit testing.
*
* @return A handle to the name-to-address map.
*/
@NotNull()
Map> getNameToAddressMap()
{
return nameToAddressMap;
}
/**
* Retrieves a handle to the {@code AtomicReference} used to cache the local
* host address. This should only be used for testing.
*
* @return A handle to the {@code AtomicReference} used to cache the local
* host address.
*/
@NotNull()
AtomicReference> getLocalHostAddressReference()
{
return localHostAddress;
}
/**
* Retrieves a handle to the {@code AtomicReference} used to cache the
* loopback address. This should only be used for testing.
*
* @return A handle to the {@code AtomicReference} used to cache the
* loopback address.
*/
@NotNull()
AtomicReference> getLoopbackAddressReference()
{
return loopbackAddress;
}
/**
* {@inheritDoc}
*/
@Override()
public void toString(@NotNull final StringBuilder buffer)
{
buffer.append("CachingNameResolver(timeoutMillis=");
buffer.append(timeoutMillis);
buffer.append(')');
}
}