blob: 30ac9242b76e8785ca78006d296307260d4ce26a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.rya.export.client.conf;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ntp.NTPUDPClient;
import org.apache.commons.net.ntp.TimeInfo;
import org.apache.log4j.Logger;
import org.mortbay.jetty.HttpMethods;
import com.google.common.net.HttpHeaders;
/**
* Utility methods for time.
*/
public final class TimeUtils {
private static final Logger log = Logger.getLogger(TimeUtils.class);
/**
* The default host name of the time server to use.
* List of time servers: http://tf.nist.gov/tf-cgi/servers.cgi
* Do not query time server more than once every 4 seconds.
*/
public static final String DEFAULT_TIME_SERVER_HOST = "time.nist.gov";
private static final int NTP_SERVER_TIMEOUT_MS = 15000;
/**
* Queries the default NTP Server for the time.
* Do not query time server more than once every 4 seconds.
* @return the NTP server {@link Date} or {@code null}.
* @throws IOException
*/
public static Date getDefaultNtpServerDate() throws IOException {
return getNtpServerDate(DEFAULT_TIME_SERVER_HOST);
}
/**
* Queries the specified NTP Server for the time.
* Do not query time server more than once every 4 seconds.
* @param timeServerHost the time server host name.
* @return the NTP server {@link Date} or {@code null}.
* @throws IOException
*/
public static Date getNtpServerDate(final String timeServerHost) throws IOException {
try {
TimeInfo timeInfo = null;
final NTPUDPClient timeClient = new NTPUDPClient();
timeClient.setDefaultTimeout(NTP_SERVER_TIMEOUT_MS);
final InetAddress inetAddress = InetAddress.getByName(timeServerHost);
if (inetAddress != null) {
timeInfo = timeClient.getTime(inetAddress);
if (timeInfo != null) {
// TODO: which time to use?
final long serverTime = timeInfo.getMessage().getTransmitTimeStamp().getTime();
//long serverTime = timeInfo.getReturnTime();
final Date ntpDate = new Date(serverTime);
return ntpDate;
}
}
} catch (final IOException e) {
throw new IOException("Unable to get NTP server time.", e);
}
return null;
}
/**
* Gets the remote machine's system time by checking the DATE field in the header
* from a HTTP HEAD method response.
* @param urlString the URL string of the remote machine's web server to connect to.
* @return the remote machine's system {@link Date} or {@code null}.
* @throws IOException
* @throws ParseException
*/
public static Date getRemoteMachineDate(final String urlString) throws IOException, ParseException {
Date remoteDate = null;
HttpURLConnection conn = null;
try {
final URL url = new URL(urlString);
// Set up the initial connection
conn = (HttpURLConnection)url.openConnection();
// Use HEAD instead of GET so content isn't returned.
conn.setRequestMethod(HttpMethods.HEAD);
conn.setDoOutput(false);
conn.setReadTimeout(10000);
conn.connect();
final Map<String, List<String>> header = conn.getHeaderFields();
for (final String key : header.keySet()) {
if (key != null && HttpHeaders.DATE.equals(key)) {
final List<String> data = header.get(key);
final String dateString = data.get(0);
final SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z");
remoteDate = sdf.parse(dateString);
break;
}
}
} finally {
// Close the connection
if (conn != null) {
conn.disconnect();
}
}
return remoteDate;
}
/**
* Gets the time difference between the 2 specified times from the NTP server time and the machine system time.
* @param ntpDate the {@link Date} from the NTP server host.
* @param machineDate the {@link Date} from the machine (either local or remote).
* @param isMachineLocal {@code true} if the {@code machineDate} from a local machine. {@code false}
* if it's from a remote machine.
* @return the difference between the NTP server time and the machine's system time. A positive value
* indicates that the machine's system time is ahead of the time server. A negative value indicates that
* the machine's system time is behind of the time server.
*/
public static Long getTimeDifference(final Date ntpDate, final Date machineDate, final boolean isMachineLocal) {
Long diff = null;
if (ntpDate != null && machineDate != null) {
log.info("NTP Server Time: " + ntpDate);
final String machineLabel = isMachineLocal ? "Local" : "Remote";
log.info(machineLabel + " Machine Time: " + machineDate);
diff = machineDate.getTime() - ntpDate.getTime();
final boolean isAhead = diff > 0;
final String durationBreakdown = TimeUtils.getDurationBreakdown(diff, false);
log.info(machineLabel + " Machine time is " + (isAhead ? "ahead of" : "behind") + " NTP server time by " + durationBreakdown + ".");
}
return diff;
}
/**
* Gets the time difference between the NTP server and the local machine system time.
* @param timeServerHost the time server host name.
* @return the difference between the NTP server time and the local machine's system time. A positive value
* indicates that the local machine's system time is ahead of the time server. A negative value indicates that
* the local machine's system time is behind of the time server.
* @throws IOException
*/
public static Long getNtpServerAndLocalMachineTimeDifference(final String timeServerHost) throws IOException {
log.info("Getting NTP Server time from " + timeServerHost + "...");
final Date ntpDate = getNtpServerDate(timeServerHost);
Long diff = null;
if (ntpDate != null) {
log.info("Getting Local Machine time...");
final Date machineDate = new Date();
diff = getTimeDifference(ntpDate, machineDate, true);
}
return diff;
}
/**
* Gets the time difference between the NTP server and the remote machine system time.
* @param timeServerHost the time server host name.
* @param remoteMachineUrlString the URL string of the remote machine's web server to connect to.
* @return the difference between the NTP server time and the remote machine's system time. A positive value
* indicates that the remote machine's system time is ahead the time server. A negative value indicates that
* the remote machine's system time is behind the time server.
* @throws ParseException
* @throws IOException
*/
public static Long getNtpServerAndRemoteMachineTimeDifference(final String timeServerHost, final String remoteMachineUrlString) throws IOException, ParseException {
log.info("Getting NTP Server time from " + timeServerHost + "...");
final Date ntpDate = getNtpServerDate(timeServerHost);
Long diff = null;
if (ntpDate != null) {
log.info("Getting Remote Machine time from " + remoteMachineUrlString + "...");
final Date machineDate = getRemoteMachineDate(remoteMachineUrlString);
diff = getTimeDifference(ntpDate, machineDate, false);
}
return diff;
}
/**
* Gets the time difference between the NTP server and the machine system time (either locally or remotely depending on the URL).
* @param timeServerHost the time server host name.
* @param machineUrlString the URL string of the machine's web server to connect to. The machine might be
* local or remote.
* @return the difference between the NTP server time and the machine's system time. A positive value
* indicates that the machine's system time is ahead of the time server. A negative value indicates that
* the machine's system time is behind the time server.
* @throws ParseException
* @throws IOException
*/
public static Long getNtpServerAndMachineTimeDifference(final String timeServerHost, final String machineUrlString) throws IOException, ParseException {
final boolean isUrlLocalMachine = isUrlLocalMachine(machineUrlString);
Long machineTimeOffset;
if (isUrlLocalMachine) {
machineTimeOffset = getNtpServerAndLocalMachineTimeDifference(timeServerHost);
} else {
machineTimeOffset = getNtpServerAndRemoteMachineTimeDifference(timeServerHost, machineUrlString);
}
return machineTimeOffset;
}
/**
* Gets the machine system time (either locally or remotely depending on the URL).
* @param urlString the URL string of the machine to check.
* @return the machine's system time.
* @throws IOException
* @throws ParseException
*/
public static Date getMachineDate(final String urlString) throws IOException, ParseException {
final boolean isMachineLocal = isUrlLocalMachine(urlString);
Date machineDate;
if (isMachineLocal) {
// Get local system machine time
machineDate = new Date();
} else {
// Get remote machine time from HTTP HEAD response. Check hosted server web page on machine for time.
machineDate = getRemoteMachineDate(urlString);
}
return machineDate;
}
/**
* Checks to see if the URL provided is hosted on the local machine or not.
* @param urlString the URL string to check.
* @return {@code true} if the URL is hosted on the local machine. {@code false}
* if it's on a remote machine.
* @throws UnknownHostException
* @throws MalformedURLException
*/
public static boolean isUrlLocalMachine(final String urlString) throws UnknownHostException, MalformedURLException {
final String localAddress = InetAddress.getLocalHost().getHostAddress();
final String requestAddress = InetAddress.getByName(new URL(urlString).getHost()).getHostAddress();
return localAddress != null && requestAddress != null && localAddress.equals(requestAddress);
}
/**
* Convert a millisecond duration to a string format.
* @param durationMs A duration to convert to a string form.
* @return A string of the form "X Days Y Hours Z Minutes A Seconds B Milliseconds".
*/
public static String getDurationBreakdown(final long durationMs) {
return getDurationBreakdown(durationMs, true);
}
/**
* Convert a millisecond duration to a string format.
* @param durationMs A duration to convert to a string form.
* @param showSign {@code true} to show if the duration is positive or negative. {@code false}
* to not display the sign.
* @return A string of the form "X Days Y Hours Z Minutes A Seconds B Milliseconds".
*/
public static String getDurationBreakdown(final long durationMs, final boolean showSign) {
long tempDurationMs = Math.abs(durationMs);
final long days = TimeUnit.MILLISECONDS.toDays(tempDurationMs);
tempDurationMs -= TimeUnit.DAYS.toMillis(days);
final long hours = TimeUnit.MILLISECONDS.toHours(tempDurationMs);
tempDurationMs -= TimeUnit.HOURS.toMillis(hours);
final long minutes = TimeUnit.MILLISECONDS.toMinutes(tempDurationMs);
tempDurationMs -= TimeUnit.MINUTES.toMillis(minutes);
final long seconds = TimeUnit.MILLISECONDS.toSeconds(tempDurationMs);
tempDurationMs -= TimeUnit.SECONDS.toMillis(seconds);
final long milliseconds = TimeUnit.MILLISECONDS.toMillis(tempDurationMs);
final StringBuilder sb = new StringBuilder();
if (tempDurationMs != 0 && showSign) {
sb.append(tempDurationMs > 0 ? "+" : "-");
}
if (days > 0) {
sb.append(days);
sb.append(days == 1 ? " Day " : " Days ");
}
if (hours > 0) {
sb.append(hours);
sb.append(hours == 1 ? " Hour " : " Hours ");
}
if (minutes > 0) {
sb.append(minutes);
sb.append(minutes == 1 ? " Minute " : " Minutes ");
}
if (seconds > 0) {
sb.append(seconds);
sb.append(seconds == 1 ? " Second " : " Seconds " );
}
if (milliseconds > 0 || (!showSign && sb.length() == 0) || (showSign && sb.length() == 1)) {
// At least show the milliseconds if nothing else has been shown so far
sb.append(milliseconds);
sb.append(milliseconds == 1 ? " Millisecond" : " Milliseconds");
}
return StringUtils.trim(sb.toString());
}
/**
* Checks if a date is before another date or if they are equal.
* @param date1 the first {@link Date}.
* @param date2 the second {@link Date}.
* @return {@code true} if {@code date1} is before or equal to {@code date2}. {@code false} otherwise.
*/
public static boolean dateBeforeInclusive(final Date date1, final Date date2) {
return !date1.after(date2);
}
/**
* Checks if a date is after another date or if they are equal.
* @param date1 the first {@link Date}.
* @param date2 the second {@link Date}.
* @return {@code true} if {@code date1} is after or equal to {@code date2}. {@code false} otherwise.
*/
public static boolean dateAfterInclusive(final Date date1, final Date date2) {
return !date1.before(date2);
}
}