src/core/net/sf/basedb/util/GeoLocation.java

Code
Comments
Other
Rev Date Author Line
7414 12 Oct 17 nicklas 1 /**
7414 12 Oct 17 nicklas 2   $Id $
7414 12 Oct 17 nicklas 3
7414 12 Oct 17 nicklas 4   Copyright (C) 2017 Nicklas Nordborg
7414 12 Oct 17 nicklas 5
7414 12 Oct 17 nicklas 6   This file is part of BASE - BioArray Software Environment.
7414 12 Oct 17 nicklas 7   Available at http://base.thep.lu.se/
7414 12 Oct 17 nicklas 8
7414 12 Oct 17 nicklas 9   BASE is free software; you can redistribute it and/or
7414 12 Oct 17 nicklas 10   modify it under the terms of the GNU General Public License
7414 12 Oct 17 nicklas 11   as published by the Free Software Foundation; either version 3
7414 12 Oct 17 nicklas 12   of the License, or (at your option) any later version.
7414 12 Oct 17 nicklas 13
7414 12 Oct 17 nicklas 14   BASE is distributed in the hope that it will be useful,
7414 12 Oct 17 nicklas 15   but WITHOUT ANY WARRANTY; without even the implied warranty of
7414 12 Oct 17 nicklas 16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
7414 12 Oct 17 nicklas 17   GNU General Public License for more details.
7414 12 Oct 17 nicklas 18
7414 12 Oct 17 nicklas 19   You should have received a copy of the GNU General Public License
7414 12 Oct 17 nicklas 20   along with BASE. If not, see <http://www.gnu.org/licenses/>.
7414 12 Oct 17 nicklas 21 */
7414 12 Oct 17 nicklas 22 package net.sf.basedb.util;
7414 12 Oct 17 nicklas 23
7415 13 Oct 17 nicklas 24 import java.io.InputStream;
7415 13 Oct 17 nicklas 25 import java.io.InputStreamReader;
7415 13 Oct 17 nicklas 26 import java.io.StringWriter;
7415 13 Oct 17 nicklas 27 import java.net.URI;
7415 13 Oct 17 nicklas 28 import java.net.URLEncoder;
7714 21 May 19 nicklas 29 import java.nio.charset.StandardCharsets;
7415 13 Oct 17 nicklas 30
7415 13 Oct 17 nicklas 31 import org.apache.commons.validator.routines.InetAddressValidator;
7415 13 Oct 17 nicklas 32 import org.apache.http.Header;
7415 13 Oct 17 nicklas 33 import org.apache.http.HttpEntity;
7415 13 Oct 17 nicklas 34 import org.apache.http.HttpResponse;
7415 13 Oct 17 nicklas 35 import org.apache.http.HttpStatus;
7415 13 Oct 17 nicklas 36 import org.apache.http.StatusLine;
7415 13 Oct 17 nicklas 37 import org.json.simple.JSONObject;
7415 13 Oct 17 nicklas 38 import org.json.simple.parser.JSONParser;
7415 13 Oct 17 nicklas 39
7415 13 Oct 17 nicklas 40 import net.sf.basedb.core.AbsoluteProgressReporter;
7414 12 Oct 17 nicklas 41 import net.sf.basedb.core.Config;
7415 13 Oct 17 nicklas 42 import net.sf.basedb.core.InvalidDataException;
7415 13 Oct 17 nicklas 43 import net.sf.basedb.util.filter.Filter;
7415 13 Oct 17 nicklas 44 import net.sf.basedb.util.uri.http.HttpConnectionManager;
7414 12 Oct 17 nicklas 45
7414 12 Oct 17 nicklas 46 /**
7414 12 Oct 17 nicklas 47   Utility class for geographic location.
7414 12 Oct 17 nicklas 48
7414 12 Oct 17 nicklas 49   @author nicklas
7414 12 Oct 17 nicklas 50   @since 3.12
7414 12 Oct 17 nicklas 51 */
7414 12 Oct 17 nicklas 52 public class GeoLocation
7414 12 Oct 17 nicklas 53 {
7414 12 Oct 17 nicklas 54
7414 12 Oct 17 nicklas 55   /**
7414 12 Oct 17 nicklas 56     Try to convert the given ip address to a location. This will
7414 12 Oct 17 nicklas 57     query the 'freegeoip' server specified in base.config. The default
7414 12 Oct 17 nicklas 58     is ''. If this has been disabled or if the location is unknown,
7414 12 Oct 17 nicklas 59     null is returned.
7414 12 Oct 17 nicklas 60   */
7414 12 Oct 17 nicklas 61   public static GeoLocation getLocationFromIp(String ip)
7414 12 Oct 17 nicklas 62   {
7415 13 Oct 17 nicklas 63     if (!InetAddressValidator.getInstance().isValid(ip))
7415 13 Oct 17 nicklas 64     {
7415 13 Oct 17 nicklas 65       throw new InvalidDataException("Invalid ip address: " + ip);
7415 13 Oct 17 nicklas 66     }
7415 13 Oct 17 nicklas 67     
7415 13 Oct 17 nicklas 68     String url = Config.getString("geolocation.freegeoip");
7415 13 Oct 17 nicklas 69     if (url == null) return null;
7415 13 Oct 17 nicklas 70     
7714 21 May 19 nicklas 71     url = url.replace("{IP}", URLEncoder.encode(ip, StandardCharsets.UTF_8));
7415 13 Oct 17 nicklas 72     
7415 13 Oct 17 nicklas 73     JSONObject jsonLoc = null;
7415 13 Oct 17 nicklas 74     InputStream in = null;
7415 13 Oct 17 nicklas 75     try
7415 13 Oct 17 nicklas 76     {
7415 13 Oct 17 nicklas 77       ResponseFilter filter = new ResponseFilter();
7415 13 Oct 17 nicklas 78       HttpConnectionManager http = new HttpConnectionManager(new URI(url), null);
7415 13 Oct 17 nicklas 79       http.setResponseFilter(filter);
7415 13 Oct 17 nicklas 80       http.setTimeout(3); // Do not allow more than 3 seconds!
7415 13 Oct 17 nicklas 81       in = http.getInputStream();
7415 13 Oct 17 nicklas 82       StringWriter sout = new StringWriter();
7714 21 May 19 nicklas 83       FileUtil.copy(new InputStreamReader(in, StandardCharsets.UTF_8), sout, filter);
7415 13 Oct 17 nicklas 84       jsonLoc = (JSONObject)new JSONParser().parse(sout.toString());
7415 13 Oct 17 nicklas 85       in.close();
7415 13 Oct 17 nicklas 86     }
7415 13 Oct 17 nicklas 87     catch (Exception ex)
7415 13 Oct 17 nicklas 88     {
7415 13 Oct 17 nicklas 89       FileUtil.close(in);
7415 13 Oct 17 nicklas 90       throw new RuntimeException(url, ex);
7415 13 Oct 17 nicklas 91     }
7415 13 Oct 17 nicklas 92     
7415 13 Oct 17 nicklas 93     return fromJson(jsonLoc);
7414 12 Oct 17 nicklas 94   }
7414 12 Oct 17 nicklas 95   
7414 12 Oct 17 nicklas 96   /**
7414 12 Oct 17 nicklas 97     Get an URL that opens a map marking the given position. A
7414 12 Oct 17 nicklas 98     link template must have been configured in base.config (geolocation.maptemplate).
7414 12 Oct 17 nicklas 99     Use {LAT} and {LONG} as placeholders for the latitude
7414 12 Oct 17 nicklas 100     and longitude.
7414 12 Oct 17 nicklas 101
7414 12 Oct 17 nicklas 102     @param latitude The latitude of the location
7414 12 Oct 17 nicklas 103     @param longitude The longitude of the location
7414 12 Oct 17 nicklas 104     @return A link or null if either of the parameters is null
7414 12 Oct 17 nicklas 105   */
7414 12 Oct 17 nicklas 106   public static String getURLToMap(Float latitude, Float longitude)
7414 12 Oct 17 nicklas 107   {
7414 12 Oct 17 nicklas 108     if (latitude == null || longitude == null) return null;
7414 12 Oct 17 nicklas 109     String template = Config.getString("geolocation.maptemplate");
7414 12 Oct 17 nicklas 110     if (template == null) return null;
7414 12 Oct 17 nicklas 111     String url = template.replace("{LAT}", latitude.toString()).replace("{LONG}", longitude.toString());
7414 12 Oct 17 nicklas 112     return url;
7414 12 Oct 17 nicklas 113   }
7414 12 Oct 17 nicklas 114   
7415 13 Oct 17 nicklas 115   /**
7415 13 Oct 17 nicklas 116     Convert JSON data from freegeoip to a GeoLocation instance.
7415 13 Oct 17 nicklas 117     The JSON must contain at least one of "city", "region_name" or 
7415 13 Oct 17 nicklas 118     "country_name".
7415 13 Oct 17 nicklas 119     @return A location object or null if there is no data
7415 13 Oct 17 nicklas 120   */
7415 13 Oct 17 nicklas 121   public static GeoLocation fromJson(JSONObject json)
7415 13 Oct 17 nicklas 122   {
7415 13 Oct 17 nicklas 123     String city = Values.getStringOrNull((String)json.get("city"));
7415 13 Oct 17 nicklas 124     String region = Values.getStringOrNull((String)json.get("region_name"));
7415 13 Oct 17 nicklas 125     String country = Values.getStringOrNull((String)json.get("country_name"));
7415 13 Oct 17 nicklas 126     if (city == null && region == null && country == null) return null;
7415 13 Oct 17 nicklas 127     
7415 13 Oct 17 nicklas 128     GeoLocation loc = new GeoLocation();
7415 13 Oct 17 nicklas 129     loc.setCity(city);
7415 13 Oct 17 nicklas 130     loc.setRegion(region);
7415 13 Oct 17 nicklas 131     loc.setCountry(country);
7415 13 Oct 17 nicklas 132     Number latitude = (Number)json.get("latitude");
7415 13 Oct 17 nicklas 133     Number longitude = (Number)json.get("longitude");
7415 13 Oct 17 nicklas 134     if (latitude != null) loc.setLatitude(latitude.floatValue());
7415 13 Oct 17 nicklas 135     if (longitude != null) loc.setLongitude(longitude.floatValue());
7415 13 Oct 17 nicklas 136     return loc;
7415 13 Oct 17 nicklas 137   }
7415 13 Oct 17 nicklas 138     
7414 12 Oct 17 nicklas 139   private String city;
7414 12 Oct 17 nicklas 140   private String region;
7414 12 Oct 17 nicklas 141   private String country;
7414 12 Oct 17 nicklas 142   
7414 12 Oct 17 nicklas 143   private Float latitude;
7414 12 Oct 17 nicklas 144   private Float longitude;
7414 12 Oct 17 nicklas 145   
7414 12 Oct 17 nicklas 146   public GeoLocation()
7414 12 Oct 17 nicklas 147   {}
7414 12 Oct 17 nicklas 148   
7414 12 Oct 17 nicklas 149   public String getCity() 
7414 12 Oct 17 nicklas 150   {
7414 12 Oct 17 nicklas 151     return city;
7414 12 Oct 17 nicklas 152   }
7414 12 Oct 17 nicklas 153
7414 12 Oct 17 nicklas 154   public void setCity(String city)
7414 12 Oct 17 nicklas 155   {
7414 12 Oct 17 nicklas 156     this.city = city;
7414 12 Oct 17 nicklas 157   }
7414 12 Oct 17 nicklas 158
7414 12 Oct 17 nicklas 159   public String getRegion() 
7414 12 Oct 17 nicklas 160   {
7414 12 Oct 17 nicklas 161     return region;
7414 12 Oct 17 nicklas 162   }
7414 12 Oct 17 nicklas 163
7414 12 Oct 17 nicklas 164   public void setRegion(String region) 
7414 12 Oct 17 nicklas 165   {
7414 12 Oct 17 nicklas 166     this.region = region;
7414 12 Oct 17 nicklas 167   }
7414 12 Oct 17 nicklas 168
7414 12 Oct 17 nicklas 169   public String getCountry() 
7414 12 Oct 17 nicklas 170   {
7414 12 Oct 17 nicklas 171     return country;
7414 12 Oct 17 nicklas 172   }
7414 12 Oct 17 nicklas 173
7414 12 Oct 17 nicklas 174   public void setCountry(String country) 
7414 12 Oct 17 nicklas 175   {
7414 12 Oct 17 nicklas 176     this.country = country;
7414 12 Oct 17 nicklas 177   }
7414 12 Oct 17 nicklas 178
7414 12 Oct 17 nicklas 179   public Float getLatitude() 
7414 12 Oct 17 nicklas 180   {
7414 12 Oct 17 nicklas 181     return latitude;
7414 12 Oct 17 nicklas 182   }
7414 12 Oct 17 nicklas 183
7414 12 Oct 17 nicklas 184   public void setLatitude(Float latitude) 
7414 12 Oct 17 nicklas 185   {
7414 12 Oct 17 nicklas 186     this.latitude = latitude;
7414 12 Oct 17 nicklas 187   }
7414 12 Oct 17 nicklas 188
7414 12 Oct 17 nicklas 189   public Float getLongitude() 
7414 12 Oct 17 nicklas 190   {
7414 12 Oct 17 nicklas 191     return longitude;
7414 12 Oct 17 nicklas 192   }
7414 12 Oct 17 nicklas 193
7414 12 Oct 17 nicklas 194   public void setLongitude(Float longitude) 
7414 12 Oct 17 nicklas 195   {
7414 12 Oct 17 nicklas 196     this.longitude = longitude;
7414 12 Oct 17 nicklas 197   }
7414 12 Oct 17 nicklas 198
7414 12 Oct 17 nicklas 199   @Override
7414 12 Oct 17 nicklas 200   public String toString()
7414 12 Oct 17 nicklas 201   {
7414 12 Oct 17 nicklas 202     StringBuilder sb = new StringBuilder();
7414 12 Oct 17 nicklas 203     append(sb, city);
7414 12 Oct 17 nicklas 204     append(sb, region);
7414 12 Oct 17 nicklas 205     append(sb, country);
7414 12 Oct 17 nicklas 206     return sb.length() == 0 ? null : sb.toString();
7414 12 Oct 17 nicklas 207   }
7414 12 Oct 17 nicklas 208   
7414 12 Oct 17 nicklas 209   private void append(StringBuilder sb, String s)
7414 12 Oct 17 nicklas 210   {
7414 12 Oct 17 nicklas 211     if (s == null) return;
7414 12 Oct 17 nicklas 212     if (sb.length() > 0) sb.append(", ");
7414 12 Oct 17 nicklas 213     sb.append(s);
7414 12 Oct 17 nicklas 214   }
7415 13 Oct 17 nicklas 215   
7415 13 Oct 17 nicklas 216   /**
7415 13 Oct 17 nicklas 217     We only accept "200" status response, "application/json" content type
7415 13 Oct 17 nicklas 218     and not more than 1K bytes (should be a lot less under normal operations).
7415 13 Oct 17 nicklas 219   */
7415 13 Oct 17 nicklas 220   static class ResponseFilter
7415 13 Oct 17 nicklas 221     implements Filter<HttpResponse>, AbsoluteProgressReporter
7415 13 Oct 17 nicklas 222   {
7415 13 Oct 17 nicklas 223
7415 13 Oct 17 nicklas 224     @Override
7415 13 Oct 17 nicklas 225     public boolean evaluate(HttpResponse response) 
7415 13 Oct 17 nicklas 226     {
7415 13 Oct 17 nicklas 227       StatusLine status = response.getStatusLine();
7415 13 Oct 17 nicklas 228       if (status != null && status.getStatusCode() != HttpStatus.SC_OK)
7415 13 Oct 17 nicklas 229       {
7415 13 Oct 17 nicklas 230         throw new RuntimeException(status.toString());
7415 13 Oct 17 nicklas 231       }
7415 13 Oct 17 nicklas 232       
7415 13 Oct 17 nicklas 233       HttpEntity entity = response.getEntity();
7494 04 Jun 18 nicklas 234       Header contentTypeHeader = entity.getContentType();
7494 04 Jun 18 nicklas 235       String contentType = contentTypeHeader != null ? contentTypeHeader.getValue() : null;
7494 04 Jun 18 nicklas 236       if (contentType != null && !contentType.contains("application/json"))
7415 13 Oct 17 nicklas 237       {
7494 04 Jun 18 nicklas 238         throw new RuntimeException("Unexpected content type: "+contentType);
7415 13 Oct 17 nicklas 239       }
7415 13 Oct 17 nicklas 240       
7415 13 Oct 17 nicklas 241       long size = entity.getContentLength();
7415 13 Oct 17 nicklas 242       if (size > 1000)
7415 13 Oct 17 nicklas 243       {
7415 13 Oct 17 nicklas 244         throw new RuntimeException("Too much data: " + size);
7415 13 Oct 17 nicklas 245       }
7415 13 Oct 17 nicklas 246       
7415 13 Oct 17 nicklas 247       return true;
7415 13 Oct 17 nicklas 248     }
7415 13 Oct 17 nicklas 249
7415 13 Oct 17 nicklas 250     @Override
7415 13 Oct 17 nicklas 251     public void display(int percent, String message) 
7415 13 Oct 17 nicklas 252     {}
7415 13 Oct 17 nicklas 253
7415 13 Oct 17 nicklas 254     @Override
7415 13 Oct 17 nicklas 255     public void append(String message) 
7415 13 Oct 17 nicklas 256     {}
7415 13 Oct 17 nicklas 257
7415 13 Oct 17 nicklas 258     @Override
7415 13 Oct 17 nicklas 259     public void displayAbsolute(long completed, String message) 
7415 13 Oct 17 nicklas 260     {
7415 13 Oct 17 nicklas 261       if (completed > 1000)
7415 13 Oct 17 nicklas 262       {
7415 13 Oct 17 nicklas 263         throw new RuntimeException("Too much data: " + completed);
7415 13 Oct 17 nicklas 264       }
7415 13 Oct 17 nicklas 265     }
7415 13 Oct 17 nicklas 266     
7415 13 Oct 17 nicklas 267   }
7414 12 Oct 17 nicklas 268 }