package com.anjlab.ping.services; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TreeMap; import com.anjlab.ping.entities.Job; import com.anjlab.ping.entities.JobResult; import com.google.appengine.api.datastore.Key; public class Utils { public static final String EVERY_5_MINUTES = "every 5 minutes"; public static final String EVERY_15_MINUTES = "every 15 minutes"; public static final String EVERY_30_MINUTES = "every 30 minutes"; public static final String EVERY_1_HOURS = "every 1 hours"; public static final String EVERY_DAY_00_00 = "every day 00:00"; public static String getHttpCodesModel() { Map<Integer, String> codes = new TreeMap<Integer, String>(); codes.put(-100, "Informational"); codes.put(100, "Continue"); codes.put(101, "Switching Protocols"); codes.put(-200, "Successful"); codes.put(200, "OK"); codes.put(201, "Created"); codes.put(202, "Accepted"); codes.put(203, "Non-Authoritative Information"); codes.put(204, "No Content"); codes.put(205, "Reset Content"); codes.put(206, "Partial Content"); codes.put(-300, "Redirection"); codes.put(300, "Multiple Choices"); codes.put(301, "Moved Permanently"); codes.put(302, "Found"); codes.put(303, "See Other"); codes.put(304, "Not Modified"); codes.put(305, "Use Proxy"); codes.put(306, "(Unused)"); codes.put(307, "Temporary Redirect"); codes.put(-400, "Client Error"); codes.put(400, "Bad Request"); codes.put(401, "Unauthorized"); codes.put(402, "Payment Required"); codes.put(403, "Forbidden"); codes.put(404, "Not Found"); codes.put(405, "Method Not Allowed"); codes.put(406, "Not Acceptable"); codes.put(407, "Proxy Authentication Required"); codes.put(408, "Request Timeout"); codes.put(409, "Conflict"); codes.put(410, "Gone"); codes.put(411, "Length Required"); codes.put(412, "Precondition Failed"); codes.put(413, "Request Entity Too Large"); codes.put(414, "Request-URI Too Long"); codes.put(415, "Unsupported Media Type"); codes.put(416, "Requested Range Not Satisfiable"); codes.put(417, "Expectation Failed"); codes.put(-500, "Server Error"); codes.put(500, "Internal Server Error"); codes.put(501, "Not Implemented"); codes.put(502, "Bad Gateway"); codes.put(503, "Service Unavailable"); codes.put(504, "Gateway Timeout"); codes.put(505, "HTTP Version Not Supported"); StringBuffer sb = new StringBuffer(); for (Map.Entry<Integer, String> entry : codes.entrySet()) { if (sb.length() > 0) { sb.append(","); } sb.append(entry.getKey()); sb.append("="); if (entry.getKey() > 0) { sb.append(entry.getKey()); } else { sb.append(Math.abs(entry.getKey() / 100)); sb.append("xx"); } sb.append(" "); sb.append(entry.getValue()); } return sb.toString(); } private static String timeZoneModel; private static Map<String, String> timeZoneByCity = new HashMap<String, String>(); public static String getTimeZoneModel() { if (isNullOrEmpty(timeZoneModel)) { buildTimeZoneModel(); } return timeZoneModel; } private static void appendTimeZone(StringBuilder builder, String timeZoneId, String city) { if (builder.length() > 0) { builder.append(","); } builder.append(city); builder.append("=("); builder.append(timeZoneId); builder.append(") "); builder.append(city); timeZoneByCity.put(city, timeZoneId); } private static void buildTimeZoneModel() { StringBuilder builder = new StringBuilder(); appendTimeZone(builder, "GMT-11:00", "International Date Line West"); appendTimeZone(builder, "GMT-11:00", "Midway Island"); appendTimeZone(builder, "GMT-11:00", "Samoa"); appendTimeZone(builder, "GMT-10:00", "Hawaii"); appendTimeZone(builder, "GMT-09:00", "Alaska"); appendTimeZone(builder, "GMT-08:00", "Pacific Time (US & Canada)"); appendTimeZone(builder, "GMT-08:00", "Tijuana"); appendTimeZone(builder, "GMT-07:00", "Arizona"); appendTimeZone(builder, "GMT-07:00", "Chihuahua"); appendTimeZone(builder, "GMT-07:00", "Mazatlan"); appendTimeZone(builder, "GMT-07:00", "Mountain Time (US & Canada)"); appendTimeZone(builder, "GMT-06:00", "Central America"); appendTimeZone(builder, "GMT-06:00", "Central Time (US & Canada)"); appendTimeZone(builder, "GMT-06:00", "Guadalajara"); appendTimeZone(builder, "GMT-06:00", "Mexico City"); appendTimeZone(builder, "GMT-06:00", "Monterrey"); appendTimeZone(builder, "GMT-06:00", "Saskatchewan"); appendTimeZone(builder, "GMT-05:00", "Bogota"); appendTimeZone(builder, "GMT-05:00", "Eastern Time (US & Canada)"); appendTimeZone(builder, "GMT-05:00", "Indiana (East)"); appendTimeZone(builder, "GMT-05:00", "Lima"); appendTimeZone(builder, "GMT-05:00", "Quito"); appendTimeZone(builder, "GMT-04:00", "Atlantic Time (Canada)"); appendTimeZone(builder, "GMT-04:00", "Caracas"); appendTimeZone(builder, "GMT-04:00", "La Paz"); appendTimeZone(builder, "GMT-04:00", "Santiago"); appendTimeZone(builder, "GMT-03:30", "Newfoundland"); appendTimeZone(builder, "GMT-03:00", "Brasilia"); appendTimeZone(builder, "GMT-03:00", "Buenos Aires"); appendTimeZone(builder, "GMT-03:00", "Georgetown"); appendTimeZone(builder, "GMT-03:00", "Greenland"); appendTimeZone(builder, "GMT-02:00", "Mid-Atlantic"); appendTimeZone(builder, "GMT-01:00", "Azores"); appendTimeZone(builder, "GMT-01:00", "Cape Verde Is."); appendTimeZone(builder, "GMT+00:00", "Casablanca"); appendTimeZone(builder, "GMT+00:00", "Dublin"); appendTimeZone(builder, "GMT+00:00", "Edinburgh"); appendTimeZone(builder, "GMT+00:00", "Lisbon"); appendTimeZone(builder, "GMT+00:00", "London"); appendTimeZone(builder, "GMT+00:00", "Monrovia"); appendTimeZone(builder, "GMT+00:00", "UTC"); appendTimeZone(builder, "GMT+01:00", "Amsterdam"); appendTimeZone(builder, "GMT+01:00", "Belgrade"); appendTimeZone(builder, "GMT+01:00", "Berlin"); appendTimeZone(builder, "GMT+01:00", "Bern"); appendTimeZone(builder, "GMT+01:00", "Bratislava"); appendTimeZone(builder, "GMT+01:00", "Brussels"); appendTimeZone(builder, "GMT+01:00", "Budapest"); appendTimeZone(builder, "GMT+01:00", "Copenhagen"); appendTimeZone(builder, "GMT+01:00", "Ljubljana"); appendTimeZone(builder, "GMT+01:00", "Madrid"); appendTimeZone(builder, "GMT+01:00", "Paris"); appendTimeZone(builder, "GMT+01:00", "Prague"); appendTimeZone(builder, "GMT+01:00", "Rome"); appendTimeZone(builder, "GMT+01:00", "Sarajevo"); appendTimeZone(builder, "GMT+01:00", "Skopje"); appendTimeZone(builder, "GMT+01:00", "Stockholm"); appendTimeZone(builder, "GMT+01:00", "Vienna"); appendTimeZone(builder, "GMT+01:00", "Warsaw"); appendTimeZone(builder, "GMT+01:00", "West Central Africa"); appendTimeZone(builder, "GMT+01:00", "Zagreb"); appendTimeZone(builder, "GMT+02:00", "Athens"); appendTimeZone(builder, "GMT+02:00", "Bucharest"); appendTimeZone(builder, "GMT+02:00", "Cairo"); appendTimeZone(builder, "GMT+02:00", "Harare"); appendTimeZone(builder, "GMT+02:00", "Helsinki"); appendTimeZone(builder, "GMT+02:00", "Istanbul"); appendTimeZone(builder, "GMT+02:00", "Jerusalem"); appendTimeZone(builder, "GMT+02:00", "Kyev"); appendTimeZone(builder, "GMT+02:00", "Minsk"); appendTimeZone(builder, "GMT+02:00", "Pretoria"); appendTimeZone(builder, "GMT+02:00", "Riga"); appendTimeZone(builder, "GMT+02:00", "Sofia"); appendTimeZone(builder, "GMT+02:00", "Tallinn"); appendTimeZone(builder, "GMT+02:00", "Vilnius"); appendTimeZone(builder, "GMT+03:00", "Baghdad"); appendTimeZone(builder, "GMT+03:00", "Kuwait"); appendTimeZone(builder, "GMT+03:00", "Moscow"); appendTimeZone(builder, "GMT+03:00", "Nairobi"); appendTimeZone(builder, "GMT+03:00", "Riyadh"); appendTimeZone(builder, "GMT+03:00", "St. Petersburg"); appendTimeZone(builder, "GMT+03:00", "Volgograd"); appendTimeZone(builder, "GMT+03:30", "Tehran"); appendTimeZone(builder, "GMT+04:00", "Abu Dhabi"); appendTimeZone(builder, "GMT+04:00", "Baku"); appendTimeZone(builder, "GMT+04:00", "Muscat"); appendTimeZone(builder, "GMT+04:00", "Tbilisi"); appendTimeZone(builder, "GMT+04:00", "Yerevan"); appendTimeZone(builder, "GMT+04:30", "Kabul"); appendTimeZone(builder, "GMT+05:00", "Ekaterinburg"); appendTimeZone(builder, "GMT+05:00", "Islamabad"); appendTimeZone(builder, "GMT+05:00", "Karachi"); appendTimeZone(builder, "GMT+05:00", "Tashkent"); appendTimeZone(builder, "GMT+05:30", "Chennai"); appendTimeZone(builder, "GMT+05:30", "Kolkata"); appendTimeZone(builder, "GMT+05:30", "Mumbai"); appendTimeZone(builder, "GMT+05:30", "New Delhi"); appendTimeZone(builder, "GMT+05:45", "Kathmandu"); appendTimeZone(builder, "GMT+06:00", "Almaty"); appendTimeZone(builder, "GMT+06:00", "Astana"); appendTimeZone(builder, "GMT+06:00", "Dhaka"); appendTimeZone(builder, "GMT+06:00", "Novosibirsk"); appendTimeZone(builder, "GMT+06:00", "Sri Jayawardenepura"); appendTimeZone(builder, "GMT+06:30", "Rangoon"); appendTimeZone(builder, "GMT+07:00", "Bangkok"); appendTimeZone(builder, "GMT+07:00", "Hanoi"); appendTimeZone(builder, "GMT+07:00", "Jakarta"); appendTimeZone(builder, "GMT+07:00", "Krasnoyarsk"); appendTimeZone(builder, "GMT+08:00", "Beijing"); appendTimeZone(builder, "GMT+08:00", "Chongqing"); appendTimeZone(builder, "GMT+08:00", "Hong Kong"); appendTimeZone(builder, "GMT+08:00", "Irkutsk"); appendTimeZone(builder, "GMT+08:00", "Kuala Lumpur"); appendTimeZone(builder, "GMT+08:00", "Perth"); appendTimeZone(builder, "GMT+08:00", "Singapore"); appendTimeZone(builder, "GMT+08:00", "Taipei"); appendTimeZone(builder, "GMT+08:00", "Ulaan Bataar"); appendTimeZone(builder, "GMT+08:00", "Urumqi"); appendTimeZone(builder, "GMT+09:00", "Osaka"); appendTimeZone(builder, "GMT+09:00", "Sapporo"); appendTimeZone(builder, "GMT+09:00", "Seoul"); appendTimeZone(builder, "GMT+09:00", "Tokyo"); appendTimeZone(builder, "GMT+09:00", "Yakutsk"); appendTimeZone(builder, "GMT+09:30", "Adelaide"); appendTimeZone(builder, "GMT+09:30", "Darwin"); appendTimeZone(builder, "GMT+10:00", "Brisbane"); appendTimeZone(builder, "GMT+10:00", "Canberra"); appendTimeZone(builder, "GMT+10:00", "Guam"); appendTimeZone(builder, "GMT+10:00", "Hobart"); appendTimeZone(builder, "GMT+10:00", "Melbourne"); appendTimeZone(builder, "GMT+10:00", "Port Moresby"); appendTimeZone(builder, "GMT+10:00", "Sydney"); appendTimeZone(builder, "GMT+10:00", "Vladivostok"); appendTimeZone(builder, "GMT+11:00", "Magadan"); appendTimeZone(builder, "GMT+11:00", "New Caledonia"); appendTimeZone(builder, "GMT+11:00", "Solomon Is."); appendTimeZone(builder, "GMT+12:00", "Auckland"); appendTimeZone(builder, "GMT+12:00", "Fiji"); appendTimeZone(builder, "GMT+12:00", "Kamchatka"); appendTimeZone(builder, "GMT+12:00", "Marshall Is."); appendTimeZone(builder, "GMT+12:00", "Wellington"); appendTimeZone(builder, "GMT+13:00", "Nuku'alofa"); timeZoneModel = builder.toString(); } public static String getTimeZoneId(String city) { if (timeZoneByCity.size() == 0) { getTimeZoneModel(); } return timeZoneByCity.get(city); } private static final Map<String, Integer> cronModel = buildCronModel(); private static final String cronStringModel = join(",", cronModel.keySet()); public static String getCronStringModel() { return cronStringModel; } private static String join(String delimeter, Iterable<String> values) { StringBuilder builder = new StringBuilder(); for (String value : values) { if (builder.length() > 0) { builder.append(delimeter); } builder.append(value); } return builder.toString(); } /** * model Key is a cron string, Value is number of minutes in cron interval * @return */ private static Map<String, Integer> buildCronModel() { Map<String, Integer> result = new TreeMap<String, Integer>(); result.put(EVERY_DAY_00_00, 1440); result.put(EVERY_1_HOURS, 60); result.put(EVERY_30_MINUTES, 30); result.put(EVERY_15_MINUTES, 15); result.put(EVERY_5_MINUTES, 5); return result; } public static Integer getCronMinutes(String cronString) { return cronModel.containsKey(cronString) ? cronModel.get(cronString) : 0; } private static class Pair { public final long number; public final double decimals; public final String name; public Pair(long number, String name) { this(number, 0, name); } public Pair(long number, double decimals, String name) { this.number = number; this.name = name; this.decimals = decimals; } } public enum TimeGranularity { MAJOR_UNIT, MINUTE, DAY } public static String formatMinutesToWordsUpToMinutes(long numberOfMinutes) { return formatMinutesToWords(numberOfMinutes, TimeGranularity.MINUTE); } private static String formatMinutesToWords(long numberOfMinutes, TimeGranularity timeGranularity) { long numberOfMinutesInYear = 365 * 24 * 60; long numberOfMinutesInMonth = 30 * 24 * 60; long numberOfMinutesInDay = 24 * 60; long numberOfMinutesInHour = 60; long numberOfMinutesCopy = numberOfMinutes; long years = numberOfMinutes / numberOfMinutesInYear; numberOfMinutes -= years * numberOfMinutesInYear; long months = numberOfMinutes / numberOfMinutesInMonth; numberOfMinutes -= months * numberOfMinutesInMonth; long days = numberOfMinutes / numberOfMinutesInDay; numberOfMinutes -= days * numberOfMinutesInDay; long hours = numberOfMinutes / numberOfMinutesInHour; numberOfMinutes -= hours * numberOfMinutesInHour; long minutes = numberOfMinutes > 0 ? numberOfMinutes : 0; List<Pair> pairs = new ArrayList<Pair>(); if (timeGranularity == TimeGranularity.MAJOR_UNIT) { if (years != 0) { pairs.add(new Pair(years, 1.0 * (numberOfMinutesCopy - years * numberOfMinutesInYear) / numberOfMinutesInYear, " year")); } else if (months != 0) { pairs.add(new Pair(months, 1.0 * (numberOfMinutesCopy - months * numberOfMinutesInMonth) / numberOfMinutesInMonth, " month")); } else if (days != 0) { pairs.add(new Pair(days, 1.0 * (numberOfMinutesCopy - days * numberOfMinutesInDay) / numberOfMinutesInDay, " day")); } else if (hours != 0) { double decimals = 1.0 * (numberOfMinutesCopy - hours * numberOfMinutesInHour) / numberOfMinutesInHour; pairs.add(new Pair(hours + Math.round(decimals), " hour")); } else if (minutes != 0) { pairs.add(new Pair(minutes, " minute")); } } else { pairs.add(new Pair(years, " year")); pairs.add(new Pair(months, " month")); pairs.add(new Pair(days, " day")); if (timeGranularity == TimeGranularity.MINUTE) { pairs.add(new Pair(hours, " hour")); pairs.add(new Pair(minutes, " minute")); } } StringBuilder sb = new StringBuilder(); for (Pair pair : pairs) { if (pair.number > 0 || (pair.name.endsWith("minute") && sb.length() == 0)) { if (sb.length() > 0) { sb.append(" "); } sb.append(pair.number); if (pair.decimals > 0) { String decimals = String.format("%.1f", pair.decimals); sb.append(decimals.substring(1)); } sb.append(pair.name); appendIfPlural(sb, "s", pair.number); } } return sb.toString(); } private static void appendIfPlural(StringBuilder builder, String appendWhat, long number) { if (number == 0 || number > 1) { builder.append(appendWhat); } } public static String getHttpCodeDescription(int validatingHttpCode) { String[] descriptions = getHttpCodesModel().split(","); for (String description : descriptions) { String[] parts = description.split("="); if (parts[0].equals(validatingHttpCode + "")) { return parts[1]; } } return null; } public static Long[] createJobContext(Job job) { return createJobContext(job.getKey()); } public static Long[] createJobContext(Key jobKey) { return new Long[] { jobKey.getId() }; } public static boolean isNullOrEmpty(String s) { return s == null || s.length() == 0; } public static String getCSVExportFilename(Job job) { return getExportFilenameWithoutExtension(job) + ".csv"; } public static String getZipExportFilename(Job job) { return getExportFilenameWithoutExtension(job) + ".zip"; } private static Object getExportFilenameWithoutExtension(Job job) { return String.format("ping-service-%s", String.valueOf(job.getKey().getId())); } public static boolean isCronStringSupported(String cronString) { if (isNullOrEmpty(cronString)) { return false; } return cronModel.containsKey(cronString); } public static int getTimeInMinutes(int counter, String cronString) { return counter * getCronMinutes(cronString); } public static double calculateAvailabilityPercent(List<JobResult> results) { int recentSuccessCount = 0; for (JobResult result : results) { if (!result.isFailed()) { recentSuccessCount++; } } return Utils.calculatePercent(results.size(), recentSuccessCount); } public static double calculatePercent(int totalCount, int countOfInterest) { if (totalCount == 0) { return 0; } double percent = 100d * countOfInterest / totalCount; return percent; } public static String formatPercent(double value) { return String.format(Locale.ENGLISH, "%.3f", value) + "%"; } public static String formatPercentShort(double value) { return String.format(Locale.ENGLISH, "%.1f", value) + "%"; } public static String getTimeAgoUpToMinutes(Date timestamp) { if (timestamp == null) { return null; } long milliseconds = System.currentTimeMillis() - timestamp.getTime(); String timeAgo = formatMillisecondsToWordsUpToMinutes(milliseconds) + " ago"; return timeAgo; } public static String getTimeAgoUpToDays(Date timestamp) { if (timestamp == null) { return null; } long milliseconds = System.currentTimeMillis() - timestamp.getTime(); String timeAgo = formatMillisecondsToWordsUpToDays(milliseconds) + " ago"; return timeAgo; } public static String formatMillisecondsToWordsUpToMajorUnits(long totalTimeMillis) { return formatMillisecondsToWords(totalTimeMillis, TimeGranularity.MAJOR_UNIT); } public static String formatMillisecondsToWordsUpToMinutes(long totalTimeMillis) { return formatMillisecondsToWords(totalTimeMillis, TimeGranularity.MINUTE); } public static String formatMillisecondsToWordsUpToDays(long totalTimeMillis) { return formatMillisecondsToWords(totalTimeMillis, TimeGranularity.DAY); } private static String formatMillisecondsToWords(long totalTimeMillis, TimeGranularity timeGranularity) { String totalTimeFormatted; if (totalTimeMillis <= 0) { totalTimeFormatted = "A moment"; } else if (totalTimeMillis < getNumberOfMilliseconds(timeGranularity)) { totalTimeFormatted = "Less than a " + timeGranularity.name().toLowerCase(); } else { totalTimeFormatted = formatMinutesToWords((long)(totalTimeMillis / 1000 / 60), timeGranularity); } return totalTimeFormatted; } private static long getNumberOfMilliseconds(TimeGranularity timeGranularity) { switch (timeGranularity) { case MINUTE: return 1000 * 60L; case DAY: return 1000 * 60 * 60 * 24L; default: return 0; } } public static String formatTime(Date timestamp) { return timestamp != null ? Application.DATETIME_FORMAT.format(timestamp) : "N/A"; } public static boolean equals(Object a, Object b) { if (a == null && b != null) return false; if (b == null && a != null) return false; return a == b || a.equals(b); } public static boolean isCause(Throwable exception, Class<?> possibleCause) { return isCause(exception, possibleCause, null); } private static boolean isCause(Throwable exception, Class<?> possibleCause, Throwable topException) { if (exception == null) return false; if (exception == topException) return false; if (possibleCause.isAssignableFrom(exception.getClass())) return true; if (exception.getCause() == null) return false; if (possibleCause.isAssignableFrom(exception.getCause().getClass())) return true; return isCause(exception.getCause(), possibleCause, exception); } }