Java tutorial
/** * Copyright 2016 gromit.it * * Licensed 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 io.gromit.geolite2; import java.io.IOException; import java.net.InetAddress; import java.net.URL; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.zip.GZIPInputStream; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.maxmind.db.NoCache; import com.maxmind.db.NodeCache; import com.maxmind.db.Reader.FileMode; import com.maxmind.geoip2.DatabaseReader; import com.maxmind.geoip2.model.CityResponse; import io.gromit.geolite2.geonames.CityFinder; import io.gromit.geolite2.geonames.ContinentFinder; import io.gromit.geolite2.geonames.CountryFinder; import io.gromit.geolite2.geonames.SubdivisionFinder; import io.gromit.geolite2.geonames.TimeZoneFinder; import io.gromit.geolite2.model.City; import io.gromit.geolite2.model.Continent; import io.gromit.geolite2.model.Country; import io.gromit.geolite2.model.Subdivision; import io.gromit.geolite2.model.TimeZone; /** * The Class ScheduledDatabaseReader. */ public class GeoLocation { /** The logger. */ private static Logger logger = LoggerFactory.getLogger(GeoLocation.class); /** The Constant GEOLITE_FAIL_SAFE_URL. */ public static final String GEOLITE_FAIL_SAFE_URL = "io.gromit.geolite2.fail.safe.url"; /** The scheduled executor service. */ private ScheduledExecutorService scheduledExecutorService; /** The reader. */ private DatabaseReader databaseReader; /** The local m d5 checksum. */ private String localMD5Checksum; /** The md5 checksum url. */ private String md5ChecksumUrl = "http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.md5"; /** The database url. */ private String databaseUrl = "http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz"; /** The cache. */ private NodeCache cache = NoCache.getInstance(); /** The city finder. */ private CityFinder cityFinder = new CityFinder(); /** The continent finder. */ private ContinentFinder continentFinder = new ContinentFinder(); /** The country finder. */ private CountryFinder countryFinder = new CountryFinder(); /** The subdivision finder. */ private SubdivisionFinder subdivisionFinder = new SubdivisionFinder(); /** The time zone finder. */ private TimeZoneFinder timeZoneFinder = new TimeZoneFinder(); /** The loader listener. */ private LoaderListener loaderListener = LoaderListener.DEFAULT; /** * Instantiates a new scheduled database reader. */ public GeoLocation() { } /** * Cities url. * * @param citiesUrl the cities url * @return the geo location */ public GeoLocation citiesUrl(String citiesUrl) { this.cityFinder.citiesUrl(citiesUrl); return this; } /** * Countries url. * * @param countriesUrl the countries url * @return the geo location */ public GeoLocation countriesUrl(String countriesUrl) { this.countryFinder.countriesUrl(countriesUrl); return this; } /** * Time zones url. * * @param timeZonesUrl the time zones url * @return the geo location */ public GeoLocation timeZonesUrl(String timeZonesUrl) { this.timeZoneFinder.timeZonesUrl(timeZonesUrl); return this; } /** * Subdivision one url. * * @param subdivisionOneUrl the subdivision one url * @return the geo location */ public GeoLocation subdivisionOneUrl(String subdivisionOneUrl) { this.subdivisionFinder.subdivisionOneUrl(subdivisionOneUrl); return this; } /** * Subdivision two url. * * @param subdivisionTwoUrl the subdivision two url * @return the geo location */ public GeoLocation subdivisionTwoUrl(String subdivisionTwoUrl) { this.subdivisionFinder.subdivisionTwoUrl(subdivisionTwoUrl); return this; } /** * Md5 checksum url. * * @param md5ChecksumUrl * the md5 checksum url * @return the scheduled database reader */ public GeoLocation md5ChecksumUrl(String md5ChecksumUrl) { this.md5ChecksumUrl = md5ChecksumUrl; return this; } /** * Database url. * * @param databaseUrl * the database url * @return the scheduled database reader */ public GeoLocation databaseUrl(String databaseUrl) { this.databaseUrl = databaseUrl; return this; } /** * Cache. * * @param cache the cache * @return the scheduled database readers */ public GeoLocation cache(NodeCache cache) { this.cache = cache; return this; } /** * Loader listener. * * @param loaderListener the loader listener * @return the geo location */ public GeoLocation loaderListener(LoaderListener loaderListener) { this.loaderListener = loaderListener; this.cityFinder.loaderListener(loaderListener); this.countryFinder.loaderListener(loaderListener); this.subdivisionFinder.loaderListener(loaderListener); this.timeZoneFinder.loaderListener(loaderListener); return this; } public Map<String, Object> location(Double latitude, Double longitude) { City city = null; Country country = null; Continent continent = null; Subdivision one = null; Subdivision two = null; Map<String, Object> data = new LinkedHashMap<>(); data.put("latitude", latitude); data.put("longitude", longitude); city = this.cityFinder.find(longitude, latitude); if (city != null) { data.put("cityName", city.getName()); TimeZone timeZone = timeZoneFinder.find(city.getTimeZone()); if (timeZone != null) { HashMap<String, Object> timeZoneMap = new HashMap<>(); timeZoneMap.put("name", timeZone.getName()); timeZoneMap.put("dtsOffset", timeZone.getDtsOffset()); timeZoneMap.put("utcOffset", timeZone.getUtcOffset()); timeZoneMap.put("currentOffset", timeZone.getCurrentOffset()); timeZoneMap.put("changedAt", timeZone.getChangedAt()); data.put("timeZone", timeZoneMap); } one = subdivisionFinder.find(city.getCountryIsoCode(), city.getSubdivisionOne()); two = subdivisionFinder.find(city.getCountryIsoCode(), city.getSubdivisionOne(), city.getSubdivisionTwo()); country = countryFinder.find(city.getCountryIsoCode()); List<String> subdivisions = new ArrayList<>(); if (two != null) { subdivisions.add(two.getName()); } if (one != null) { subdivisions.add(one.getName()); } if (subdivisions != null && subdivisions.size() > 0) { data.put("subdivisions", subdivisions); } Map<String, Object> countryMap = new LinkedHashMap<>(); if (country != null) { countryMap.put("capital", country.getCapital()); countryMap.put("currencyCode", country.getCurrencyCode()); countryMap.put("currencyName", country.getCurrencyName()); countryMap.put("language", country.getLanguage()); countryMap.put("name", country.getName()); countryMap.put("phone", country.getPhone()); countryMap.put("iso", country.getIso()); data.put("country", countryMap); continent = continentFinder.find(country.getContinent()); if (continent != null) { HashMap<String, Object> continentMap = new HashMap<>(); continentMap.put("iso", continent.getIso()); continentMap.put("name", continent.getName()); data.put("continent", continentMap); } } } return data; } /** * Location. * * @param ip the ip * @return the map */ public Map<String, Object> location(String ip) { CityResponse cityResponse; try { cityResponse = this.databaseReader.city(InetAddress.getByName(ip)); } catch (UnknownHostException e) { throw new IllegalArgumentException(ip + " is not valid", e); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } if (cityResponse == null) { return null; } City city = null; Country country = null; Continent continent = null; Subdivision one = null; Subdivision two = null; Map<String, Object> data = new LinkedHashMap<>(); data.put("ip", ip); if (cityResponse.getLocation() != null) { data.put("latitude", cityResponse.getLocation().getLatitude()); data.put("longitude", cityResponse.getLocation().getLongitude()); } if (cityResponse.getCity() != null) { data.put("cityName", cityResponse.getCity().getName()); city = this.cityFinder.find(cityResponse.getCity().getGeoNameId()); } if (city == null && cityResponse.getLocation() != null) { city = this.cityFinder.find(cityResponse.getLocation().getLongitude(), cityResponse.getLocation().getLatitude()); } //if city does not match country, remove it if (city != null && cityResponse.getCountry() != null && !cityResponse.getCountry().getIsoCode().equals(city.getCountryIsoCode())) { city = null; } if (cityResponse.getCountry() != null) { country = countryFinder.find(cityResponse.getCountry().getGeoNameId()); } if (country == null && city != null) { country = countryFinder.find(city.getCountryIsoCode()); } if (continent == null && cityResponse.getContinent() != null) { continent = continentFinder.find(cityResponse.getContinent().getCode()); } if (continent == null && country != null) { continent = continentFinder.find(country.getContinent()); } if (city != null) { data.put("cityName", city.getName()); TimeZone timeZone = timeZoneFinder.find(city.getTimeZone()); if (timeZone != null) { HashMap<String, Object> timeZoneMap = new HashMap<>(); timeZoneMap.put("name", timeZone.getName()); timeZoneMap.put("dtsOffset", timeZone.getDtsOffset()); timeZoneMap.put("utcOffset", timeZone.getUtcOffset()); timeZoneMap.put("currentOffset", timeZone.getCurrentOffset()); timeZoneMap.put("changedAt", timeZone.getChangedAt()); data.put("timeZone", timeZoneMap); } one = subdivisionFinder.find(city.getCountryIsoCode(), city.getSubdivisionOne()); two = subdivisionFinder.find(city.getCountryIsoCode(), city.getSubdivisionOne(), city.getSubdivisionTwo()); } else if (cityResponse.getSubdivisions() != null && cityResponse.getSubdivisions().size() > 0) { one = subdivisionFinder.find(cityResponse.getSubdivisions().get(0).getGeoNameId()); if (cityResponse.getSubdivisions().size() > 1) { two = subdivisionFinder.find(cityResponse.getSubdivisions().get(1).getGeoNameId()); } } List<String> subdivisions = new ArrayList<>(); if (two != null) { subdivisions.add(two.getName()); } if (one != null) { subdivisions.add(one.getName()); } if (subdivisions != null && subdivisions.size() > 0) { data.put("subdivisions", subdivisions); } Map<String, Object> countryMap = new LinkedHashMap<>(); if (country != null) { countryMap.put("capital", country.getCapital()); countryMap.put("currencyCode", country.getCurrencyCode()); countryMap.put("currencyName", country.getCurrencyName()); countryMap.put("language", country.getLanguage()); countryMap.put("name", country.getName()); countryMap.put("phone", country.getPhone()); countryMap.put("iso", country.getIso()); } else if (cityResponse.getCountry() != null) { countryMap.put("name", cityResponse.getCountry().getName()); countryMap.put("iso", cityResponse.getCountry().getIsoCode()); } if (countryMap.size() > 0) { data.put("country", countryMap); } Map<String, Object> continentMap = new LinkedHashMap<>(); if (continent != null) { continentMap.put("iso", continent.getIso()); continentMap.put("name", continent.getName()); } else if (cityResponse.getContinent() != null) { continentMap.put("iso", cityResponse.getContinent().getCode()); continentMap.put("name", cityResponse.getContinent().getName()); } if (continentMap.size() > 0) { data.put("continent", continentMap); } return data; } /** * Start. * * @return the scheduled database reader * @throws IllegalStateException the illegal state exception */ public GeoLocation start() throws IllegalStateException { if (scheduledExecutorService != null) { throw new IllegalStateException("it is already started"); } readDatabase(); scheduledExecutorService = Executors.newScheduledThreadPool(1); scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { readDatabase(); } }, 1, 1, TimeUnit.DAYS); return this; } /** * Stop. * * @return the scheduled database reader * @throws IllegalStateException the illegal state exception */ public GeoLocation stop() throws IllegalStateException { if (scheduledExecutorService == null) { throw new IllegalStateException("it was never started"); } scheduledExecutorService.shutdown(); try { if (databaseReader != null) { this.databaseReader.close(); } } catch (IOException e) { logger.warn("error closing reader: {}", e.getMessage()); } return this; } /** * Read database. */ public void readDatabase() { try { readDatabase(databaseUrl); loaderListener.success(databaseUrl); } catch (Exception e) { loaderListener.failure(databaseUrl, e); logger.error("error loading from remote", e); if (StringUtils.isNotBlank(System.getProperty(GEOLITE_FAIL_SAFE_URL))) { readDatabase(System.getProperty(GEOLITE_FAIL_SAFE_URL)); } } } /** * Read database. * * @param databaseLocationUrl the database location url */ private void readDatabase(String databaseLocationUrl) { String onlineMD5Checksum = null; try { onlineMD5Checksum = IOUtils.toString(new URL(md5ChecksumUrl).openStream()).trim(); } catch (Exception e) { logger.error("could not read MD5 online: {}", e.getMessage()); return; } if (!onlineMD5Checksum.equals(localMD5Checksum)) { try { logger.info("UPDATING local database with online database"); DatabaseReader newReader = new DatabaseReader.Builder( new GZIPInputStream(new URL(databaseLocationUrl).openStream())) .locales(Collections.singletonList("en")).fileMode(FileMode.MEMORY).withCache(cache) .build(); if (databaseReader != null) { final DatabaseReader readerToClose = databaseReader; new Timer().schedule(new TimerTask() { @Override public void run() { try { readerToClose.close(); } catch (IOException e) { logger.warn("error closing reader: {}", e.getMessage()); } } }, 60 * 1000); } this.localMD5Checksum = onlineMD5Checksum; this.databaseReader = newReader; logger.info("UPDATED local database with online database"); } catch (Exception e) { logger.error("could not read Database online: {}", e.getMessage()); } } else { logger.info("local and online database are the same"); } this.cityFinder.readCities(); this.countryFinder.readCountries(); this.subdivisionFinder.readLevelOne(); this.subdivisionFinder.readLevelTwo(); this.timeZoneFinder.readTimeZones(); } }