7414 |
12 Oct 17 |
nicklas |
1 |
/** |
7414 |
12 Oct 17 |
nicklas |
$Id $ |
7414 |
12 Oct 17 |
nicklas |
3 |
|
7414 |
12 Oct 17 |
nicklas |
Copyright (C) 2017 Nicklas Nordborg |
7414 |
12 Oct 17 |
nicklas |
5 |
|
7414 |
12 Oct 17 |
nicklas |
This file is part of BASE - BioArray Software Environment. |
7414 |
12 Oct 17 |
nicklas |
Available at http://base.thep.lu.se/ |
7414 |
12 Oct 17 |
nicklas |
8 |
|
7414 |
12 Oct 17 |
nicklas |
BASE is free software; you can redistribute it and/or |
7414 |
12 Oct 17 |
nicklas |
modify it under the terms of the GNU General Public License |
7414 |
12 Oct 17 |
nicklas |
as published by the Free Software Foundation; either version 3 |
7414 |
12 Oct 17 |
nicklas |
of the License, or (at your option) any later version. |
7414 |
12 Oct 17 |
nicklas |
13 |
|
7414 |
12 Oct 17 |
nicklas |
BASE is distributed in the hope that it will be useful, |
7414 |
12 Oct 17 |
nicklas |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
7414 |
12 Oct 17 |
nicklas |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
7414 |
12 Oct 17 |
nicklas |
GNU General Public License for more details. |
7414 |
12 Oct 17 |
nicklas |
18 |
|
7414 |
12 Oct 17 |
nicklas |
You should have received a copy of the GNU General Public License |
7414 |
12 Oct 17 |
nicklas |
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 |
Utility class for geographic location. |
7414 |
12 Oct 17 |
nicklas |
48 |
|
7414 |
12 Oct 17 |
nicklas |
@author nicklas |
7414 |
12 Oct 17 |
nicklas |
@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 |
Try to convert the given ip address to a location. This will |
7414 |
12 Oct 17 |
nicklas |
query the 'freegeoip' server specified in base.config. The default |
7414 |
12 Oct 17 |
nicklas |
is ''. If this has been disabled or if the location is unknown, |
7414 |
12 Oct 17 |
nicklas |
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 |
Get an URL that opens a map marking the given position. A |
7414 |
12 Oct 17 |
nicklas |
link template must have been configured in base.config (geolocation.maptemplate). |
7414 |
12 Oct 17 |
nicklas |
Use {LAT} and {LONG} as placeholders for the latitude |
7414 |
12 Oct 17 |
nicklas |
and longitude. |
7414 |
12 Oct 17 |
nicklas |
101 |
|
7414 |
12 Oct 17 |
nicklas |
@param latitude The latitude of the location |
7414 |
12 Oct 17 |
nicklas |
@param longitude The longitude of the location |
7414 |
12 Oct 17 |
nicklas |
@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 |
Convert JSON data from freegeoip to a GeoLocation instance. |
7415 |
13 Oct 17 |
nicklas |
The JSON must contain at least one of "city", "region_name" or |
7415 |
13 Oct 17 |
nicklas |
"country_name". |
7415 |
13 Oct 17 |
nicklas |
@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 |
We only accept "200" status response, "application/json" content type |
7415 |
13 Oct 17 |
nicklas |
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 |
} |