/*
Copyright (C) 2001, 2006 United States Government
as represented by the Administrator of the
National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind.util;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.Configuration;
import java.net.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.*;
import java.io.IOException;
/**
* Provides tracking of per-host network availability. Host computers that fail network requests can be logged to this
* class' tracking list. When a host has been logged a specified number of times, it is marked as unreachable. Users can
* query instances of this class to determine whether a host has been marked as unreachable.
* <p/>
* Users are expected to invoke this class' {@link #logUnavailableHost(java.net.URL)} method when an attempt to contact
* a host fails. Each invocation increments the failure count by one. When the count exceeds the attempt limit, the host
* is marked as unreachable. When attempts to contact the host <em>are</em> successful, users should invoke this class'
* {@link #logAvailableHost(java.net.URL)} method to clear its status.
* <p/>
* A host may become reachable at a time subsequent to its being logged. To detect this, this class will mark a host as
* not unreachable after a specifiable interval of time. If the host is once more logged as unavailable, its entry will
* return to the unavailable state. This cycle continues indefinitely.
* <p/>
* Methods are also provided to determine whether the public network can be reached and whether the NASA World Wind
* servers cab be reached.
*
* @author tag
* @version $Id: BasicNetworkStatus.java 3843 2007-12-09 01:53:20Z tgaskins $
*/
public class BasicNetworkStatus extends AVListImpl implements NetworkStatus
{
private static final long DEFAULT_TRY_AGAIN_INTERVAL = (long) 60e3; // seconds
private static final int DEFAULT_ATTEMPT_LIMIT = 10; // number of unavailable events to declare host unavailable
private static final String[] networkTestSites = new String[]
{"www.nasa.gov", "worldwind.arc.nasa.gov", "google.com", "microsoft.com", "yahoo.com"};
private static final long NETWORK_STATUS_REPORT_INTERVAL = (long) 60e3;
private static class HostInfo
{
private final long tryAgainInterval;
private final int attemptLimit;
private AtomicInteger logCount = new AtomicInteger();
private AtomicLong lastLogTime = new AtomicLong();
private HostInfo(int attemptLimit, long tryAgainInterval)
{
this.lastLogTime.set(System.currentTimeMillis());
this.logCount.set(1);
this.tryAgainInterval = tryAgainInterval;
this.attemptLimit = attemptLimit;
}
private boolean isUnavailable()
{
return this.logCount.get() >= this.attemptLimit;
}
private boolean isTimeToTryAgain()
{
return System.currentTimeMillis() - this.lastLogTime.get() >= this.tryAgainInterval;
}
}
private ConcurrentHashMap<String, HostInfo> hostMap = new ConcurrentHashMap<String, HostInfo>();
private AtomicLong tryAgainInterval = new AtomicLong(DEFAULT_TRY_AGAIN_INTERVAL);
private AtomicInteger attemptLimit = new AtomicInteger(DEFAULT_ATTEMPT_LIMIT);
// Fields for determining overall network status.
private boolean offlineMode;
private AtomicLong lastUnavailableLogTime = new AtomicLong(System.currentTimeMillis());
private AtomicLong lastAvailableLogTime = new AtomicLong(System.currentTimeMillis() + 1);
private AtomicLong lastNetworkCheckTime = new AtomicLong(System.currentTimeMillis());
private AtomicLong lastNetworkStatusReportTime = new AtomicLong(0);
private AtomicBoolean lastNetworkUnavailableResult = new AtomicBoolean(false);
public BasicNetworkStatus()
{
String oms = Configuration.getStringValue(AVKey.OFFLINE_MODE, "false");
this.offlineMode = oms.startsWith("t") || oms.startsWith("T");
}
/**
* Indicates whether World Wind will attempt to connect to the network to retrieve data or for other reasons.
*
* @return <code>true</code> if World Wind is in off-line mode, <code>false</code> if not.
*/
public boolean isOfflineMode()
{
return offlineMode;
}
/**
* Indicate whether World Wind should attempt to connect to the network to retrieve data or for other reasons.
* The default value for this attribute is <code>false</code>, indicating that the network should be used.
*
* @param offlineMode <code>true</code> if World Wind should use the network, <code>false</code> otherwise
*/
public void setOfflineMode(boolean offlineMode)
{
this.offlineMode = offlineMode;
}
/**
* Set the number of times a host must be logged as unavailable before it is marked unavailable in this class.
*
* @param limit the number of log-unavailability invocations necessary to consider the host unreachable.
* @throws IllegalArgumentException if the limit is less than 1.
*/
public void setAttemptLimit(int limit)
{
if (limit < 1)
{
String message = Logging.getMessage("NetworkStatus.InvalidAttemptLimit");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.attemptLimit.set(limit);
}
/**
* Set the length of time to wait until a host is marked as not unreachable subsequent to its being marked
* unreachable.
*
* @param interval The length of time, in milliseconds, to wait to unmark a host as unreachable.
* @throws IllegalArgumentException if the interval is less than 0.
*/
public void setTryAgainInterval(long interval)
{
if (interval < 0)
{
String message = Logging.getMessage("NetworkStatus.InvalidTryAgainInterval");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.tryAgainInterval.set(interval);
}
/**
* Returns the number of times a host must be logged as unavailable before it is marked unavailable in this class.
*
* @return the limit.
*/
public int getAttemptLimit()
{
return this.attemptLimit.get();
}
/**
* Returns the length of time to wait until a host is marked as not unreachable subsequent to its being marked
* unreachable.
*
* @return the interval, in milliseconds.
*/
public long getTryAgainInterval()
{
return this.tryAgainInterval.get();
}
/**
* Log a host as unavailable. Each invocation increments the host's attempt count. When the count equals or exceeds
* the attempt limit, the host is marked as unavailable.
*
* @param url a url containing the host to mark as unavailable.
*/
public void logUnavailableHost(URL url)
{
if (this.offlineMode)
return;
if (url == null)
{
String message = Logging.getMessage("nullValue.URLIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
String hostName = url.getHost();
if (this.hostMap.containsKey(hostName))
{
HostInfo hi = this.hostMap.get(hostName);
if (!hi.isUnavailable())
hi.logCount.incrementAndGet();
hi.lastLogTime.set(System.currentTimeMillis());
}
else
{
HostInfo hi = new HostInfo(this.attemptLimit.get(), this.tryAgainInterval.get());
this.hostMap.put(hostName, hi);
}
this.lastUnavailableLogTime.set(System.currentTimeMillis());
}
/**
* Log a host as available. Each invocation causes the host to no longer be marked as unavailable. Its
* unavailability count is effectively set to 0.
*
* @param url a url containing the host to mark as available.
*/
public void logAvailableHost(URL url)
{
if (this.offlineMode)
return;
if (url == null)
{
String message = Logging.getMessage("nullValue.URLIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
String hostName = url.getHost();
if (this.hostMap.containsKey(hostName))
this.hostMap.remove(hostName);
this.lastAvailableLogTime.set(System.currentTimeMillis());
}
/**
* Indicates whether the host has been marked as unavailable. To be marked unavailable a host's attempt count must
* exceed the specified attempt limit.
*
* @param url a url containing the host to check for availability.
* @return true if the host is marked as unavailable, otherwise false.
*/
public boolean isHostUnavailable(URL url)
{
if (this.offlineMode)
return true;
if (url == null)
{
String message = Logging.getMessage("nullValue.URLIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
String hostName = url.getHost();
if (!this.hostMap.containsKey(hostName))
return false;
HostInfo hi = this.hostMap.get(hostName);
if (hi.isTimeToTryAgain())
{
this.removeKey(hostName);
return false;
}
return hi.isUnavailable();
}
/**
* Indicates whether a public network can be reached or has been reached in the previous five seconds.
*
* @return false if the network can be reached or has been reached in the previous five seconds, otherwise true.
*/
public boolean isNetworkUnavailable()
{
return this.offlineMode || this.isNetworkUnavailable(5000L);
}
/**
* Indicates whether a public network can be reached or has been reached in a specified previous amount of time.
*
* @param checkInterval the number of milliseconds in the past used to determine whether the server was avaialble
* recently.
* @return false if the network can be reached or has been reached in a specified time, otherwise true.
*/
public boolean isNetworkUnavailable(long checkInterval)
{
if (this.offlineMode)
return true;
// If there's been success since failure, network assumed to be reachable.
if (this.lastAvailableLogTime.get() > this.lastUnavailableLogTime.get())
{
this.lastNetworkUnavailableResult.set(false);
return this.lastNetworkUnavailableResult.get();
}
long now = System.currentTimeMillis();
// If there's been success recently, network assumed to be reachable.
if (!this.lastNetworkUnavailableResult.get() && now - this.lastAvailableLogTime.get() < checkInterval)
{
return this.lastNetworkUnavailableResult.get();
}
// If query comes too soon after an earlier one that addressed the network, return the earlier result.
if (now - this.lastNetworkCheckTime.get() < checkInterval)
{
return this.lastNetworkUnavailableResult.get();
}
this.lastNetworkCheckTime.set(now);
if (!this.isWorlWindServerUnavailable())
{
this.lastNetworkUnavailableResult.set(false); // network not unreachable
return this.lastNetworkUnavailableResult.get();
}
for (String testHost : networkTestSites)
{
if (this.isHostReachable(testHost))
{
{
this.lastNetworkUnavailableResult.set(false); // network not unreachable
return this.lastNetworkUnavailableResult.get();
}
}
}
if (now - this.lastNetworkStatusReportTime.get() > NETWORK_STATUS_REPORT_INTERVAL)
{
this.lastNetworkStatusReportTime.set(now);
String message = Logging.getMessage("NetworkStatus.NetworkUnreachable");
Logging.logger().info(message);
}
this.lastNetworkUnavailableResult.set(true); // if no successful contact then network is unreachable
return this.lastNetworkUnavailableResult.get();
}
/**
* Indicates whether the NASA World Wind servers can be reached.
*
* @return false if the servers can be reached, otherwise true.
*/
public boolean isWorlWindServerUnavailable()
{
return this.offlineMode || !this.isHostReachable("worldwind.arc.nasa.gov");
}
private boolean isHostReachable(String hostName)
{
try
{
// Assume host is unreachable if we can't get its dns entry without getting an exception
//noinspection ResultOfMethodCallIgnored
InetAddress.getByName(hostName);
}
catch (UnknownHostException e)
{
String message = Logging.getMessage("NetworkStatus.UnreachableTestHost", hostName);
Logging.logger().fine(message);
return false;
}
catch (Exception e)
{
String message = Logging.getMessage("NetworkStatus.ExceptionTestingHost", hostName);
Logging.logger().info(message);
return false;
}
// Was able to get internet address, but host still might not be reachable because the address might have been
// cached earlier when it was available. So need to try something else.
URLConnection connection = null;
try
{
URL url = new URL("http://" + hostName);
Proxy proxy = WWIO.configureProxy();
if (proxy != null)
connection = url.openConnection(proxy);
else
connection = url.openConnection();
connection.setConnectTimeout(2000);
String ct = connection.getContentType();
if (ct != null)
return true;
}
catch (IOException e)
{
String message = Logging.getMessage("NetworkStatus.ExceptionTestingHost", hostName);
Logging.logger().info(message);
}
finally
{
if (connection != null && connection instanceof HttpURLConnection)
((HttpURLConnection) connection).disconnect();
}
return false;
}
//
// public static void main(String[] args)
// {
// try
// {
// NetworkStatus ns = new BasicNetworkStatus();
// boolean tf = ns.isWorlWindServerUnavailable();
// tf = ns.isNetworkUnavailable();
// }
// catch (Exception e)
// {
// e.printStackTrace();
// }
// }
}