package com.workshare.msnos.core.geo; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.annotation.JsonProperty; import com.maxmind.geoip2.model.OmniResponse; import com.maxmind.geoip2.record.AbstractNamedRecord; import com.maxmind.geoip2.record.City; import com.maxmind.geoip2.record.Continent; import com.maxmind.geoip2.record.Country; import com.maxmind.geoip2.record.Subdivision; import com.workshare.msnos.core.geo.Location.Place.Type; import com.workshare.msnos.core.protocols.ip.Endpoint; import com.workshare.msnos.soup.json.Json; public class Location { private static final Logger log = LoggerFactory.getLogger(Location.class); public static Location UNKNOWN = new Location(Place.NOWHERE, Place.NOWHERE, Place.NOWHERE, Place.NOWHERE); public class Match { private final int value; private final Location source; private final Location target; public Match(Location source, Location target) { this.source = source; this.target = target; this.value = calculateValue(source, target); } private int calculateValue(Location source, Location target) { int total = 0; total += source.continent.equals(target.continent) ? 1 : 0; total += source.country.equals(target.country) ? 2 : 0; total += source.region.equals(target.region) ? 4 : 0; total += source.city.equals(target.city) ? 8 : 0; return total; } public Location getSource() { return source; } public Location getTarget() { return target; } public int value() { return value; } public String toString() { return "match: "+Integer.toString(value); } } public static class GPS { @JsonProperty("lat") private final Double latitude; @JsonProperty("lon") private final Double longitude; @JsonProperty("acc") private final Integer accuracy; public GPS(com.maxmind.geoip2.record.Location maxlocation) { this.latitude = maxlocation.getLatitude(); this.longitude = maxlocation.getLongitude(); this.accuracy = maxlocation.getAccuracyRadius(); } public Double getLatitude() { return latitude; } public Double getLongitude() { return longitude; } public Integer getAccuracy() { return accuracy; } } public static class Place { public enum Type { CONTINENT, COUNTRY, REGION, CITY } public static Location.Place NOWHERE = new Location.Place(Type.CONTINENT, "Nowhere", "NN") { @Override public boolean equals(Object obj) { return false; } @Override public String toString() { return "{\"type\":\"UNKNOWN\"}"; } }; private final Type type; private final String name; private final String code; public Place(Type type, String name, String code) { if (type == null || code == null || name == null) throw new IllegalArgumentException("No constructor parameter can be null!"); this.type = type; this.code = code; this.name = name; } public Type getType() { return type; } public String getName() { return name; } public String getCode() { return code; } @Override public String toString() { return Json.toJsonString(this); } @Override public int hashCode() { final int prime = 17; int result = 1; result = prime * result + code.hashCode(); result = prime * result + type.hashCode(); return result; } @Override public boolean equals(Object obj) { try { Place other = (Place) obj; return type == other.type && code.equals(other.code); } catch (Exception ignore) { return false; } } } private final Place continent; private final Place country; private final Place region; private final Place city; private final int precision; private final GPS gps; public Location(OmniResponse response) { this( makeContinent(response.getContinent()), makeCountry(response.getCountry()), makeRegion(response.getMostSpecificSubdivision()), makeCity(response.getCity()), makeGps(response) ); } public Location(Place continent, Place country, Place region, Place city) { this(continent, country, region, city, null); } public Location(Place continent, Place country, Place region, Place city, GPS gps) { this.continent = parseNull(continent); this.country = parseNull(country); this.region = parseNull(region); this.city = parseNull(city); this.precision = computePrecision(); this.gps = gps; } private Place parseNull(Place place) { return place == null ? Place.NOWHERE : place; } private int computePrecision() { int total = 0; total += (continent != Place.NOWHERE ? 1 : 0); total += (country != Place.NOWHERE ? 2 : 0); total += (region != Place.NOWHERE ? 4 : 0); total += (city != Place.NOWHERE ? 8 : 0); return total; } public Place getContinent() { return continent; } public Place getCountry() { return country; } public Place getRegion() { return region; } public Place getCity() { return city; } public GPS getGPS() { return gps; } public int getPrecision() { return precision; } public Match match(Location other) { return new Match(this, other == null ? UNKNOWN : other); } @Override public boolean equals(Object obj) { try { Location other = (Location) obj; return equals(continent, other.continent) && equals(country, other.country) && equals(region, other.region) && equals(city, other.city); } catch (Exception ignore) { return false; } } @Override public int hashCode() { int result = continent != null ? continent.hashCode() : 0; result = 31 * result + (country != null ? country.hashCode() : 0); result = 31 * result + (region != null ? region.hashCode() : 0); result = 31 * result + (city != null ? city.hashCode() : 0); result = 31 * result + precision; return result; } private boolean equals(Place alfa, Place beta) { return alfa == beta || alfa.equals(beta); } @Override public String toString() { StringBuilder sb = new StringBuilder(); if (continent != Place.NOWHERE) appendPlaceForToString(sb, continent.getName()); if (country != Place.NOWHERE) appendPlaceForToString(sb, country.getName()); if (region != Place.NOWHERE) appendPlaceForToString(sb, region.getName()); if (city != Place.NOWHERE) appendPlaceForToString(sb, city.getName()); if (sb.length() == 0) sb.append("unknown"); return sb.toString(); } private void appendPlaceForToString(StringBuilder sb, String name) { if (sb.length() > 0) { sb.append(", "); } sb.append(name); } public static Location computeMostPreciseLocation(final Set<Endpoint> endpoints) { log.debug("Computing location for endpoints {}", endpoints); Location res = UNKNOWN; if (endpoints != null) for (Endpoint endpoint : endpoints) { final String hostString = endpoint.getNetwork().getHostString(); Location loc = computeLocation(hostString); log.debug("Location for endpoints {}: [{}]", hostString, loc); if (loc.getPrecision() > res.getPrecision()) res = loc; } log.debug("Location is: ", res); return res; } public static Location computeLocation(final String hostString) { return LocationFactory.DEFAULT.make(hostString); } private static boolean isValid(final AbstractNamedRecord r) { return r != null && r.getGeoNameId() != null; } private static Place makeContinent(final Continent where) { return isValid(where) ? new Place(Place.Type.CONTINENT, where.getName(), where.getCode()) : null; } private static Place makeCountry(final Country where) { return isValid(where) ? new Place(Place.Type.COUNTRY, where.getName(), where.getIsoCode()) : null; } private static Place makeRegion(final Subdivision where) { if (!isValid(where)) return null; String code = where.getIsoCode(); if (code == null) code = where.getGeoNameId().toString(); return newPlace(Place.Type.REGION, where.getName(), code); } private static GPS makeGps(OmniResponse response) { com.maxmind.geoip2.record.Location location = response.getLocation(); return (location == null ? null : new GPS(location)); } private static Place makeCity(final City where) { return isValid(where) ? new Place(Place.Type.CITY, where.getName(), where.getGeoNameId().toString()) : null; } private static Place newPlace(Type aRegion, String aName, String aIsoCode) { try { return new Place(aRegion, aName, aIsoCode); } catch (Throwable any) { return null; } } }