/**
* Most of the code in the Qalingo project is copyrighted Hoteia and licensed
* under the Apache License Version 2.0 (release version 0.8.0)
* http://www.apache.org/licenses/LICENSE-2.0
*
* Copyright (c) Hoteia, 2012-2014
* http://www.hoteia.com - http://twitter.com/hoteia - contact@hoteia.com
*
*/
package org.hoteia.qalingo.core.service;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.hibernate.internal.util.SerializationHelper;
import org.hoteia.qalingo.core.dao.GeolocDao;
import org.hoteia.qalingo.core.domain.EngineSetting;
import org.hoteia.qalingo.core.domain.GeolocAddress;
import org.hoteia.qalingo.core.domain.GeolocCity;
import org.hoteia.qalingo.core.pojo.GeolocData;
import org.hoteia.qalingo.core.pojo.GeolocDataCity;
import org.hoteia.qalingo.core.pojo.GeolocDataCountry;
import org.hoteia.qalingo.core.util.CoreUtil;
import org.hoteia.qalingo.core.web.bean.geoloc.json.GoogleGeoCode;
import org.hoteia.qalingo.core.web.bean.geoloc.json.GoogleGeoCodeResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.maxmind.geoip2.DatabaseReader;
import com.maxmind.geoip2.exception.AddressNotFoundException;
import com.maxmind.geoip2.model.CityResponse;
import com.maxmind.geoip2.model.CountryResponse;
import com.maxmind.geoip2.record.City;
import com.maxmind.geoip2.record.Country;
@Service("geolocService")
@Transactional
public class GeolocService {
protected final Logger logger = LoggerFactory.getLogger(getClass());
protected final String GOOGLE_GEOCODING_GEO_CODE_OK = "OK";
protected final String GOOGLE_GEOCODING_GEO_CODE_ZERO_RESULTS = "ZERO_RESULTS";
protected final String GOOGLE_GEOCODING_GEO_CODE_OVER_QUERY_LIMIT = "OVER_QUERY_LIMIT";
protected final String GOOGLE_GEOCODING_GEO_CODE_REQUEST_DENIED = "REQUEST_DENIED";
protected final String GOOGLE_GEOCODING_GEO_CODE_INVALID_REQUEST = "INVALID_REQUEST";
protected final String GOOGLE_GEOCODING_GEO_CODE_UNKNOWN_ERROR = "UNKNOWN_ERROR";
@Autowired
protected EmailService emailService;
@Autowired
protected EngineSettingService engineSettingService;
@Autowired
protected GeolocDao geolocDao;
protected List<String> unknownValueList = new ArrayList<String>();
public GeolocService() {
unknownValueList.add("unknown"); // PRIVATE NAVIGATION VALUE : MOZILLA
}
// COMMON
public GeolocCity geolocByCityAndCountry(final String city, final String country){
GeolocCity geolocCity = null;
String formatedAddress = encodeGoogleAddress(null, null, city, country);
GoogleGeoCode geoCode = geolocGoogleWithAddress(formatedAddress);
if(geoCode != null && GOOGLE_GEOCODING_GEO_CODE_OVER_QUERY_LIMIT.equals(geoCode.getStatus())){
logger.error("API Geoloc returns message '" + geoCode.getStatus() + "': " + geoCode.getErrorMessage());
logger.error("Address encoded: '" + formatedAddress + "'");
engineSettingService.flagSettingGoogleGeolocationApiOverQuota();
return geolocCity;
}
if(geoCode != null) {
geolocCity = new GeolocCity();
geolocCity.setCity(city);
geolocCity.setCountry(country);
geolocCity.setJson(SerializationHelper.serialize(geoCode));
geolocCity.setLatitude(geoCode.getLatitude());
geolocCity.setLongitude(geoCode.getLongitude());
if(city == null){
// SANITY CHECK : DON'T SAVE A CITY AS NULL TOO MANY TIME
GeolocCity geolocCityCheck = geolocDao.getGeolocCityByCountryWithNullCity(country);
if(geolocCityCheck == null){
try {
geolocCity = geolocDao.saveOrUpdateGeolocCity(geolocCity);
} catch (Exception e) {
logger.error("Can't save GeolocCity: City: '" + geolocCity.getCity() + "', Country: '" + geolocCity.getCountry() + "'", e);
}
}
} else {
try {
geolocCity = geolocDao.saveOrUpdateGeolocCity(geolocCity);
} catch (Exception e) {
logger.error("Can't save GeolocCity: City: '" + geolocCity.getCity() + "', Country: '" + geolocCity.getCountry() + "'", e);
}
}
}
return geolocCity;
}
public GeolocAddress geolocByAddress(final String address, final String postalCode, final String city, final String country){
GeolocAddress geolocAddress = new GeolocAddress();
geolocAddress.setAddress(address);
geolocAddress.setPostalCode(postalCode);
geolocAddress.setCity(city);
geolocAddress.setCountry(country);
String formatedAddress = encodeGoogleAddress(address, postalCode, city, country);
GoogleGeoCode geoCode = geolocGoogleWithAddress(formatedAddress);
// SANITY CHECK
if(geoCode != null && GOOGLE_GEOCODING_GEO_CODE_OVER_QUERY_LIMIT.equals(geoCode.getStatus())){
logger.error("API Geoloc returns message '" + geoCode.getStatus() + "': " + geoCode.getErrorMessage());
logger.error("Address encoded: '" + formatedAddress + "'");
engineSettingService.flagSettingGoogleGeolocationApiOverQuota();
return geolocAddress;
}
if(geoCode != null){
geolocAddress.setLatitude(geoCode.getLatitude());
geolocAddress.setLongitude(geoCode.getLongitude());
// SANITY CHECK
if(!GOOGLE_GEOCODING_GEO_CODE_OK.equals(geoCode.getStatus())){
logger.error("API Geoloc returns message '" + geoCode.getStatus() + "': " + geoCode.getErrorMessage());
logger.error("Address encoded: '" + formatedAddress + "'");
engineSettingService.flagSettingGoogleGeolocationApiOverQuota();
return geolocAddress;
}
geolocAddress.setJson(SerializationHelper.serialize(geoCode));
geolocAddress.setFormatedAddress(formatedAddress);
geolocAddress = geolocDao.saveOrUpdateGeolocAddress(geolocAddress);
}
return geolocAddress;
}
public GeolocAddress geolocByLatitudeLongitude(final String latitude, final String longitude) {
GeolocAddress geolocAddress = null;
GoogleGeoCode geoCode = geolocGoogleWithLatitudeLongitude(latitude, longitude);
if(geoCode != null && GOOGLE_GEOCODING_GEO_CODE_OVER_QUERY_LIMIT.equals(geoCode.getStatus())){
logger.error("API Geoloc returns message '" + geoCode.getStatus() + "': " + geoCode.getErrorMessage());
logger.error("latitude: '" + latitude + "', longitude: '" + longitude + "'");
engineSettingService.flagSettingGoogleGeolocationApiOverQuota();
return geolocAddress;
}
if(geoCode != null && geoCode.getResults().size() > 0) {
GoogleGeoCodeResult googleGeoCodeResult = geoCode.getResults().get(0);
String formatedAdress = googleGeoCodeResult.getFormattedAddress();
formatedAdress = cleanGoogleAddress(formatedAdress);
geolocAddress = new GeolocAddress();
geolocAddress.setAddress(googleGeoCodeResult.getAddress());
geolocAddress.setPostalCode(googleGeoCodeResult.getPostalCode());
geolocAddress.setCity(googleGeoCodeResult.getCity());
geolocAddress.setCountry(googleGeoCodeResult.getCountryCode());
geolocAddress.setJson(SerializationHelper.serialize(geoCode));
geolocAddress.setFormatedAddress(formatedAdress);
geolocAddress.setLatitude(latitude);
geolocAddress.setLongitude(longitude);
// SANITY CHECK : DON'T SAVE AN ADDRESS WHICH ALREADY EXIST BUT WAS LOCATED WITH LAT/LONG DIFFERENT
Long rowCount = geolocDao.countGeolocAddressByFormatedAddress(formatedAdress);
if(rowCount != null && rowCount.intValue() == 0){
geolocAddress = geolocDao.saveOrUpdateGeolocAddress(geolocAddress);
}
}
return geolocAddress;
}
public GoogleGeoCode geolocGoogleWithAddress(final String formatedAddress){
GoogleGeoCode geoCode = null;
boolean googleGelocIsOverQuota;
try {
googleGelocIsOverQuota = engineSettingService.isGoogleGeolocationApiStillOverQuotas(new Date());
if (googleGelocIsOverQuota == false) {
String key = null;
try {
key = engineSettingService.getGoogleGeolocationApiKey();
} catch (Exception e) {
logger.error("Google Geolocation API Key is mandatory!", e);
}
if (key != null && StringUtils.isNotEmpty(key)) {
HttpPost httpPost = new HttpPost("https://maps.googleapis.com/maps/api/geocode/json?address=" + formatedAddress + "&key=" + key);
HttpClient httpClient = new DefaultHttpClient();
HttpResponse httpResponse = httpClient.execute(httpPost);
BufferedReader streamReader = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent()));
StringBuilder responseStrBuilder = new StringBuilder();
String inputStr;
while ((inputStr = streamReader.readLine()) != null) {
responseStrBuilder.append(inputStr);
}
String json = responseStrBuilder.toString();
ObjectMapper mapper = new ObjectMapper();
geoCode = mapper.readValue(json, GoogleGeoCode.class);
}
} else {
logger.warn("Google Geolocation API still over Quota! We can't use geolocation for this address: " + formatedAddress);
}
} catch (ClientProtocolException e) {
logger.error("", e);
} catch (IOException e) {
logger.error("", e);
} catch (IllegalStateException e) {
logger.error("", e);
} catch (ParseException e) {
logger.error("", e);
}
return geoCode;
}
public GoogleGeoCode geolocGoogleWithLatitudeLongitude(final String latitude, final String longitude){
GoogleGeoCode geoCode = null;
boolean googleGelocIsOverQuota;
try {
googleGelocIsOverQuota = engineSettingService.isGoogleGeolocationApiStillOverQuotas(new Date());
String paramLatLong = latitude.trim() + "," + longitude.trim();
if (!googleGelocIsOverQuota) {
String key = null;
try {
key = engineSettingService.getGoogleGeolocationApiKey();
} catch (Exception e) {
logger.error("Google Geolocation API Key is mandatory!", e);
}
if (key != null && StringUtils.isNotEmpty(key)) {
HttpPost request = new HttpPost("https://maps.googleapis.com/maps/api/geocode/json?latlng=" + paramLatLong + "&key=" + key);
HttpClient httpClient = new DefaultHttpClient();
HttpResponse response = httpClient.execute(request);
BufferedReader streamReader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
StringBuilder responseStrBuilder = new StringBuilder();
String inputStr;
while ((inputStr = streamReader.readLine()) != null) {
responseStrBuilder.append(inputStr);
}
String json = responseStrBuilder.toString();
ObjectMapper mapper = new ObjectMapper();
geoCode = mapper.readValue(json, GoogleGeoCode.class);
}
} else {
logger.warn("Google Geolocation API still over Quota! We can't use geolocation for this lat/long: " + paramLatLong);
}
} catch (ClientProtocolException e) {
logger.error("", e);
} catch (IOException e) {
logger.error("", e);
} catch (IllegalStateException e) {
logger.error("", e);
} catch (ParseException e) {
logger.error("", e);
}
return geoCode;
}
public String encodeGoogleAddress(final String address, final String postalCode, final String city, final String country) {
StringBuffer encode = new StringBuffer();
if(StringUtils.isNotEmpty(address)){
encode.append(cleanGoogleAddress(address.trim()));
encode.append(",");
}
if(StringUtils.isNotEmpty(postalCode)){
encode.append(cleanGoogleAddress(postalCode.trim()));
encode.append(",");
}
if(StringUtils.isNotEmpty(city)){
encode.append(cleanGoogleAddress(city.trim()));
encode.append(",");
}
if(StringUtils.isNotEmpty(country)){
encode.append(cleanGoogleAddress(country.trim()));
}
return encode.toString();
}
public String encodeAddress(final String address, final String postalCode, final String city, final String country) {
StringBuffer encode = new StringBuffer();
if(StringUtils.isNotEmpty(address)){
encode.append(cleanGoogleAddress(CoreUtil.replaceSpecificAlphabet(address.trim())));
encode.append(",");
}
if(StringUtils.isNotEmpty(postalCode)){
encode.append(cleanGoogleAddress(postalCode.trim()));
encode.append(",");
}
if(StringUtils.isNotEmpty(city)){
encode.append(cleanGoogleAddress(CoreUtil.replaceSpecificAlphabet(city.trim())));
encode.append(",");
}
if(StringUtils.isNotEmpty(country)){
encode.append(cleanGoogleAddress(country.trim()));
}
return encode.toString();
}
public String encodeAddressWithPhoneAsUniqueKey(final String address, final String postalCode, final String city, final String country, final String phone) {
StringBuffer encode = new StringBuffer();
if(StringUtils.isNotEmpty(address)){
encode.append(cleanGoogleAddress(CoreUtil.replaceSpecificAlphabet(address.trim())));
encode.append(",");
}
if(StringUtils.isNotEmpty(postalCode)){
encode.append(cleanGoogleAddress(postalCode.trim()));
encode.append(",");
}
if(StringUtils.isNotEmpty(city)){
encode.append(cleanGoogleAddress(CoreUtil.replaceSpecificAlphabet(city.trim())));
encode.append(",");
}
if(StringUtils.isNotEmpty(country)){
encode.append(cleanGoogleAddress(country.trim()));
}
if(StringUtils.isNotEmpty(phone)){
try {
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
PhoneNumber numberProto = phoneUtil.parse(phone, country);
boolean isValid = phoneUtil.isValidNumber(numberProto);
if (isValid) {
String formatedPhone = phoneUtil.format(numberProto, PhoneNumberFormat.E164);
if(!encode.toString().endsWith(",")){
encode.append(",");
}
encode.append(formatedPhone);
}
} catch (NumberParseException e) {
System.err.println("NumberParseException was thrown: " + e.toString());
}
}
return encode.toString();
}
protected String cleanGoogleAddress(String value){
return cleanEncodedAddress(value);
}
protected String cleanEncodedAddress(String value){
if(StringUtils.isNotEmpty(value)){
value = value.replace(",", "");
value = CoreUtil.replaceCharactersNotLetterOrDigit(value, "+");
}
return value;
}
// GEOLOC CITY
public GeolocCity getGeolocCityByCityAndCountry(final String city, final String country, Object... params) {
return geolocDao.getGeolocCityByCityAndCountry(city, country, params);
}
public GeolocCity getGeolocCityByCountryWithNullCity(final String country, Object... params) {
return geolocDao.getGeolocCityByCountryWithNullCity(country, params);
}
public GeolocCity saveOrUpdateGeolocCity(final GeolocCity geolocCity) {
return geolocDao.saveOrUpdateGeolocCity(geolocCity);
}
public void deleteGeolocCity(final GeolocCity geolocCity) {
geolocDao.deleteGeolocCity(geolocCity);
}
// GEOLOC ADDRESS
public GeolocAddress getGeolocAddressByFormatedAddress(final String formatedAddress, Object... params) {
return geolocDao.getGeolocAddressByFormatedAddress(formatedAddress, params);
}
public GeolocAddress getGeolocAddressByLatitudeAndLongitude(final String latitude, final String longitude, Object... params) {
return geolocDao.getGeolocAddressByLatitudeAndLongitude(latitude, longitude, params);
}
public Long countGeolocAddressByFormatedAddress(final String formatedAddress) {
return geolocDao.countGeolocAddressByFormatedAddress(formatedAddress);
}
public GeolocAddress saveOrUpdateGeolocAddress(final GeolocAddress geolocCity) {
return geolocDao.saveOrUpdateGeolocAddress(geolocCity);
}
public void deleteGeolocAddress(final GeolocAddress geolocCity) {
geolocDao.deleteGeolocAddress(geolocCity);
}
public GeolocData getGeolocData(final String remoteAddress) throws Exception {
GeolocData geolocData = null;
if(!CoreUtil.isLocalHostMode(remoteAddress)){
geolocData = new GeolocData();
final Country country = geolocAndGetCountry(remoteAddress);
geolocData.setRemoteAddress(remoteAddress);
if(country != null
&& StringUtils.isNotEmpty(country.getIsoCode())){
GeolocDataCountry geolocDataCountry = new GeolocDataCountry();
geolocDataCountry.setIsoCode(country.getIsoCode());
geolocDataCountry.setName(country.getName());
geolocData.setCountry(geolocDataCountry);
final City city = geolocAndGetCity(remoteAddress);
GeolocDataCity geolocDataCity = new GeolocDataCity();
geolocDataCity.setGeoNameId(city.getGeoNameId());
geolocDataCity.setName(city.getName());
geolocData.setCity(geolocDataCity);
}
}
return geolocData;
}
public String geolocAndGetCountryIsoCode(final String customerRemoteAddr) throws Exception {
final Country country = geolocAndGetCountry(customerRemoteAddr);
return country.getIsoCode();
}
public Country geolocAndGetCountry(final String customerRemoteAddr) throws Exception {
if(StringUtils.isNotEmpty(customerRemoteAddr)){
if(!unknownValueList.contains(customerRemoteAddr)){
try {
final InetAddress address = InetAddress.getByName(customerRemoteAddr);
final DatabaseReader databaseReader = new DatabaseReader.Builder(getCountryDataBase()).build();
final CountryResponse countryResponse = databaseReader.country(address);
if(countryResponse != null){
return countryResponse.getCountry();
}
} catch (AddressNotFoundException e) {
logger.warn("Geoloc country, can't find this address: '" + customerRemoteAddr + "'");
} catch (FileNotFoundException e) {
logger.error("Geoloc country, can't find database MaxMind", e);
} catch (Exception e) {
logger.error("Geoloc country, exception to find country with this address: '" + customerRemoteAddr + "'", e);
}
} else {
logger.debug("Geoloc country, can't find address (private navigation): '" + customerRemoteAddr + "'");
}
} else {
logger.debug("Geoloc country, can't find address, value is empty.");
}
return null;
}
public String geolocAndGetCityName(final String customerRemoteAddr) throws Exception {
final City city = geolocAndGetCity(customerRemoteAddr);
return city.getName();
}
public City geolocAndGetCity(final String customerRemoteAddr) throws Exception {
if(StringUtils.isNotEmpty(customerRemoteAddr)){
if(!unknownValueList.contains(customerRemoteAddr)){
try {
final InetAddress address = InetAddress.getByName(customerRemoteAddr);
final DatabaseReader databaseReader = new DatabaseReader.Builder(getCityDataBase()).build();
final CityResponse cityResponse = databaseReader.city(address);
if(cityResponse != null){
return cityResponse.getCity();
}
} catch (AddressNotFoundException e) {
logger.warn("Geoloc city, can't find this address:" + customerRemoteAddr);
} catch (FileNotFoundException e) {
logger.error("Geoloc city, can't find database MaxMind", e);
} catch (Exception e) {
logger.error("Geoloc city, can't find this city with this address:" + customerRemoteAddr, e);
}
} else {
logger.debug("Geoloc city, can't find address (private navigation): '" + customerRemoteAddr + "'");
}
} else {
logger.debug("Geoloc city, can't find address, value is empty.");
}
return null;
}
protected File getCityDataBase(){
EngineSetting engineSetting = engineSettingService.getSettingGeolocCityFilePath();
if(engineSetting != null){
final File database = new File(engineSetting.getDefaultValue());
return database;
}
return null;
}
protected File getCountryDataBase(){
EngineSetting engineSetting = engineSettingService.getSettingGeolocCountryFilePath();
if(engineSetting != null){
final File database = new File(engineSetting.getDefaultValue());
return database;
}
return null;
}
}