package org.fluxtream.core.services.impl; import com.maxmind.geoip.Location; import com.maxmind.geoip.LookupService; import net.sf.json.JSONObject; import org.apache.commons.codec.binary.Base64; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; import org.fluxtream.core.Configuration; import org.fluxtream.core.aspects.FlxLogger; import org.fluxtream.core.auth.AuthHelper; import org.fluxtream.core.auth.FlxUserDetails; import org.fluxtream.core.connectors.Connector; import org.fluxtream.core.connectors.OAuth2Helper; import org.fluxtream.core.connectors.location.LocationFacet; import org.fluxtream.core.domain.*; import org.fluxtream.core.services.*; import org.fluxtream.core.utils.*; import org.json.JSONArray; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.security.access.annotation.Secured; import org.springframework.security.authentication.encoding.ShaPasswordEncoder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.persistence.EntityManager; import javax.persistence.LockModeType; import javax.persistence.PersistenceContext; import javax.persistence.Query; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.*; @Service @Transactional(readOnly=true) public class GuestServiceImpl implements GuestService, DisposableBean { static FlxLogger logger = FlxLogger.getLogger(GuestServiceImpl.class); @Autowired BodyTrackHelper bodyTrackHelper; @Autowired Configuration env; @PersistenceContext EntityManager em; @Qualifier("apiDataServiceImpl") @Autowired ApiDataService apiDataService; @Qualifier("metadataServiceImpl") @Autowired MetadataService metadataService; @Qualifier("connectorUpdateServiceImpl") @Autowired ConnectorUpdateService connectorUpdateService; @Autowired OAuth2Helper oAuth2Helper; @Autowired @Qualifier("AsyncWorker") ThreadPoolTaskExecutor executor; @Autowired BeanFactory beanFactory; @Autowired SystemService systemService; @Autowired SettingsService settingsService; @Autowired BuddiesService buddiesService; LookupService geoIpLookupService; private final RandomString randomString = new RandomString(64); private UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { final Guest guest = JPAUtils.findUnique(em, Guest.class, "guest.byUsername", username); if (guest == null) return null; return new FlxUserDetails(guest); } public UserDetails loadUserByEmail(String email) throws UsernameNotFoundException { final Guest guest = JPAUtils.findUnique(em, Guest.class, "guest.byEmail", email); if (guest == null) return null; return new FlxUserDetails(guest); } @Transactional(readOnly = false) public Guest createGuest(String username, String firstname, String lastname, String password, String email, Guest.RegistrationMethod registrationMethod, final String appId) throws UsernameAlreadyTakenException, ExistingEmailException { if (loadUserByUsername(username) != null) throw new UsernameAlreadyTakenException(username + " is already taken"); if (loadUserByEmail(email) != null) throw new ExistingEmailException(email + " is already used"); Guest guest = new Guest(); guest.username = username; guest.email = email; guest.firstname = firstname; guest.lastname = lastname; guest.registrationMethod = registrationMethod; guest.setRoles("ROLE_USER"); if (password!=null) setPassword(guest, password); if (appId!=null) { // enforce API registration method guest.registrationMethod = Guest.RegistrationMethod.REGISTRATION_METHOD_API; guest.appId = appId; } em.persist(guest); return guest; } private void setPassword(Guest guest, String password) { ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(); String salt = randomString.nextString(); guest.salt = salt; if (guest.registrationMethod == Guest.RegistrationMethod.REGISTRATION_METHOD_FACEBOOK) guest.registrationMethod = Guest.RegistrationMethod.REGISTRATION_METHOD_FACEBOOK_WITH_PASSWORD; guest.password = passwordEncoder.encodePassword(password, salt); } @Override @Transactional(readOnly = false) public void setPassword(long guestId, String password) { Guest guest = getGuestById(guestId); setPassword(guest, password); em.persist(guest); } @Override @Transactional(readOnly=false) public ApiKey createApiKey(final long guestId, final Connector connector) { ApiKey apiKey = new ApiKey(); apiKey.setGuestId(guestId); apiKey.setConnector(connector); em.persist(apiKey); populateApiKey(apiKey.getId()); return apiKey; } @Override @Transactional(readOnly=false) public void populateApiKey(final long apiKeyId) { ConnectorInfo connectorInfo = null; ApiKey apiKey = getApiKey(apiKeyId); try { connectorInfo = systemService.getConnectorInfo(apiKey.getConnector().getName()); } catch (Throwable e) {} if (connectorInfo == null || !connectorInfo.enabled) throw new RuntimeException("This connector is not enabled!"); final String[] apiKeyAttributesKeys = connectorInfo.getApiKeyAttributesKeys(); if(apiKeyAttributesKeys!=null) { for (String key : apiKeyAttributesKeys) { if (env.get(key)==null) throw new RuntimeException("No value was found for key :" + key + ". Cannot create apiKey"); setApiKeyAttribute(apiKey, key, env.get(key)); } } } @Override public Set<String> getParseInstallations(long guestId) { final GuestDetails details = JPAUtils.findUnique(em, GuestDetails.class, "guestDetails.byGuestId", guestId); if (details!=null) return details.getInstallations(); return null; } @Override @Transactional(readOnly=false) public void addParseInstallation(long guestId, String parseInstallationId) { GuestDetails details = JPAUtils.findUnique(em, GuestDetails.class, "guestDetails.byGuestId", guestId); if (details==null) { details = new GuestDetails(guestId); } details.addInstallation(parseInstallationId); em.persist(details); } @Override @Transactional(readOnly=false) public GuestDetails getGuestDetails(long guestId) { GuestDetails details = JPAUtils.findUnique(em, GuestDetails.class, "guestDetails.byGuestId", guestId); if (details==null) details = new GuestDetails(guestId); em.persist(details); return details; } @Override public List<String> getDeviceIds(long guestId) { Query query = em.createNativeQuery("SELECT DISTINCT refreshToken FROM AuthorizationToken where guestId=?"); query.setParameter(1, guestId); final List<String> resultList = query.getResultList(); final List<String> deviceIds = new ArrayList<String>(); for (String s : resultList) { if (s!=null) deviceIds.add(s); } return deviceIds; } @Override @Transactional(readOnly=false) public void deleteConnectorProfile(final ApiKey apiKey) { Class<? extends AbstractUserProfile> userProfileClass = apiKey.getConnector() .userProfileClass(); if (userProfileClass != null && userProfileClass != AbstractUserProfile.class) { Query deleteProfileQuery = em.createQuery("DELETE FROM " + userProfileClass.getName() + " WHERE apiKeyId=" + apiKey.getId()); deleteProfileQuery.executeUpdate(); } } @Override public Guest getGuest(String username) { return JPAUtils.findUnique(em, Guest.class, "guest.byUsername", username); } @Override public boolean isUsernameAvailable(String username) { final Guest guest = JPAUtils.findUnique(em, Guest.class, "guest.byUsername", username); return guest == null; } @Override public Guest getGuestById(long id) { return em.find(Guest.class, id); } @Override @Transactional(readOnly = false) public ApiKey setApiKeyAttribute(ApiKey ak, String key, String value) { ApiKey apiKey = em.find(ApiKey.class, ak.getId(), LockModeType.PESSIMISTIC_WRITE); // apiKey could be null, for example if the connector // was already deleted. In this case just return // null if(apiKey==null) { return null; } // At this point we know that apiKey exists and // is non-null apiKey.removeAttribute(key); ApiKeyAttribute attr = new ApiKeyAttribute(); attr.attributeKey = key; attr.setAttributeValue(value, env); em.persist(attr); apiKey.setAttribute(attr); em.merge(apiKey); return apiKey; } @Override public Map<String, String> getApiKeyAttributes(final long apiKeyId) { ApiKey apiKey = em.find(ApiKey.class, apiKeyId); final Map<String, String> attributes = apiKey.getAttributes(env); return attributes; } @Override @Transactional(readOnly = false) public void removeApiKey(long apiKeyId) { ApiKey apiKey = em.find(ApiKey.class, apiKeyId); // apiKey could be null, for example if the connector // was already deleted. In this case just return if(apiKey==null) { return; } final String refreshTokenRemoveURL = apiKey.getAttributeValue("refreshTokenRemoveURL", env); // Revoke refresh token might throw. If it does we still want to remove the apiKeys from // the DB which is why we put it in a try/finally block try { if (refreshTokenRemoveURL !=null) oAuth2Helper.revokeRefreshToken(apiKey.getGuestId(), apiKey.getConnector(), refreshTokenRemoveURL); } finally { // cleanup the data asynchrously in order not to block the user's flow ApiDataCleanupWorker worker = beanFactory.getBean(ApiDataCleanupWorker.class); worker.setApiKey(apiKey); executor.execute(worker); } } @Override @Deprecated public String getApiKeyAttribute(ApiKey ak, String key) { ApiKey apiKey = em.find(ApiKey.class, ak.getId()); // apiKey could be null, for example if the connector // was deleted. In this case return null if(apiKey!=null) { return apiKey.getAttributeValue(key, env); } else { return null; } } @Override public ApiKey getApiKey(final long apiKeyId) { return em.find(ApiKey.class, apiKeyId); } @Override public List<ApiKey> getApiKeys(long guestId) { // rawKeys includes all the keys in the apiKeys table for a given guest. // However, it's potentially the case that this could include keys which // do not currently map to valid entries in the Connector table. // We can test for that condition by checking if key.getConnector() returns // null. Include only the keys which return non-null in goodKeys and return. List<ApiKey> rawKeys = JPAUtils.find(em, ApiKey.class, "apiKeys.all", guestId); List<ApiKey> goodKeys = new ArrayList<ApiKey>(); for (ApiKey key : rawKeys){ if(key.getConnector()!=null) { goodKeys.add(key); } } return(goodKeys); } @Override public boolean hasApiKey(long guestId, Connector api) { assert api!=null : "api must not be null"; ApiKey apiKey = JPAUtils.findUnique(em, ApiKey.class, "apiKey.byApi", guestId, api.value()); return (apiKey != null); } @Override public List<ApiKey> getApiKeys(long guestId, Connector api) { List<ApiKey> apiKeys = JPAUtils.find(em, ApiKey.class, "apiKey.byApi", guestId, api.value()); Collections.sort(apiKeys, new Comparator<ApiKey>() { @Override public int compare(ApiKey o1, ApiKey o2) { return (int)(o2.getId()-o1.getId()); } }); return apiKeys; } @Override @Transactional(readOnly=false) public void setApiKeyStatus(final long apiKeyId, final ApiKey.Status status, final String stackTrace, final String reason) { final ApiKey apiKey = getApiKey(apiKeyId); if (apiKey!=null) { apiKey.status = status; if (status== ApiKey.Status.STATUS_UP) { apiKey.stackTrace = null; apiKey.reason = null; } else { if (stackTrace != null) apiKey.stackTrace = stackTrace; if (reason != null) apiKey.reason = reason; } em.persist(apiKey); } } @Override @Transactional(readOnly=false) public void setApiKeyToSynching(final long apiKeyId, final boolean synching) { final ApiKey apiKey = getApiKey(apiKeyId); if (apiKey!=null) { apiKey.synching = synching; em.persist(apiKey); } } @Override @Deprecated public ApiKey getApiKey(long guestId, Connector api) { List<ApiKey> apiKeys = getApiKeys(guestId, api); return apiKeys.size()>0 ? apiKeys.get(0) : null; } @Override @Transactional(readOnly=false) public void removeApiKeys(final long guestId, final Connector connector) { final List<ApiKey> apiKeys = getApiKeys(guestId, connector); for (ApiKey apiKey : apiKeys) removeApiKey(apiKey.getId()); } @Override @Transactional(readOnly = false) public void eraseGuestInfo(long id) throws Exception { Guest guest = getGuestById(id); if (guest == null) return; if (guest.registrationMethod==Guest.RegistrationMethod.REGISTRATION_METHOD_FACEBOOK|| guest.registrationMethod==Guest.RegistrationMethod.REGISTRATION_METHOD_FACEBOOK_WITH_PASSWORD) { revokeFacebookPermissions(guest); } JPAUtils.execute(em, "updateWorkerTasks.delete.all", guest.getId()); em.remove(guest); List<ApiKey> apiKeys = getApiKeys(guest.getId()); for (ApiKey key : apiKeys) { if(key!=null && key.getConnector()!=null) { apiDataService.eraseApiData(key, true); } } JPAUtils.execute(em, "addresses.delete.all", guest.getId()); JPAUtils.execute(em, "notifications.delete.all", guest.getId()); JPAUtils.execute(em, "settings.delete.all", guest.getId()); JPAUtils.execute(em, "location.delete.all", guest.getId()); JPAUtils.execute(em, "visitedCities.delete.all", guest.getId()); JPAUtils.execute(em, "updateWorkerTasks.delete.all", guest.getId()); JPAUtils.execute(em, "tags.delete.all", guest.getId()); JPAUtils.execute(em, "notifications.delete.all", guest.getId()); buddiesService.removeAllSharedChannels(guest.getId()); buddiesService.removeAllSharedConnectors(guest.getId()); final List<TrustedBuddy> coachingBuddies = JPAUtils.find(em, TrustedBuddy.class, "trustedBuddies.byGuestId", guest.getId()); for (TrustedBuddy trustedBuddy : coachingBuddies) em.remove(trustedBuddy); JPAUtils.execute(em, "channelMapping.delete.all", guest.getId()); JPAUtils.execute(em, "connectorFilterState.delete.all", guest.getId()); JPAUtils.execute(em, "channelStyle.delete.all", guest.getId()); JPAUtils.execute(em, "grapherView.delete.all", guest.getId()); JPAUtils.execute(em, "widgetSettings.delete.all", guest.getId()); JPAUtils.execute(em, "dashboards.delete.all", guest.getId()); try { String couchUsername = URLEncoder.encode(AuthHelper.getGuest().username, "UTF-8"); JSONObject couchUser = getCouchUser(couchUsername); if (couchUser.has("error")) { if (couchUser.getString("reason").equals("missing")) { logger.info("there is no couchdb user name " + guest.username); return; } } else { deleteCouchUser(couchUser); deleteCouchDB(couchUser.getString("name"), "self_report_db_observations_"); deleteCouchDB(couchUser.getString("name"), "self_report_db_topics_"); deleteCouchDB(couchUser.getString("name"), "self_report_db_deleted_observations_"); deleteCouchDB(couchUser.getString("name"), "self_report_db_deleted_topics_"); } } catch (Exception e) { logger.warn("There was an error trying to delete couch info for user " + guest.username); } } private void deleteCouchDB(String couchUsername, String dbName) throws IOException { HttpClient client = new DefaultHttpClient(); final String couchdbHost = env.get("couchdb.host"); final String couchdbPort = env.get("couchdb.port"); final String couchdbAdminLogin = env.get("couchdb.admin_login"); final String couchdbAdminPasword = env.get("couchdb.admin_password"); String userPassword = couchdbAdminLogin + ":" + couchdbAdminPasword; byte[] encodedCredentials = Base64.encodeBase64(userPassword.getBytes()); final String request = String.format("http://%s:%s/%s", couchdbHost, couchdbPort, dbName+couchUsername); HttpDelete delete = new HttpDelete(request); delete.addHeader("Authorization", "Basic " + new String(encodedCredentials)); delete.addHeader("Content-Type", "application/json"); delete.addHeader("Accept", "application/json"); client.execute(delete); } private void deleteCouchUser(JSONObject couchUser) throws Exception { String revision = null; String username = couchUser.getString("name"); if (couchUser.has("_rev")) revision = couchUser.getString("_rev"); else throw new Exception("Couldn't get revision for user: " + username); HttpClient client = new DefaultHttpClient(); int status = 0; final String couchdbHost = env.get("couchdb.host"); final String couchdbPort = env.get("couchdb.port"); final String couchdbAdminLogin = env.get("couchdb.admin_login"); final String couchdbAdminPasword = env.get("couchdb.admin_password"); String userPassword = couchdbAdminLogin + ":" + couchdbAdminPasword; byte[] encodedCredentials = Base64.encodeBase64(userPassword.getBytes()); final String request = String.format("http://%s:%s/_users/org.couchdb.user:%s?rev=%s", couchdbHost, couchdbPort, username, revision); HttpDelete delete = new HttpDelete(request); delete.addHeader("Authorization", "Basic " + new String(encodedCredentials)); delete.addHeader("Content-Type", "application/json"); delete.addHeader("Accept", "application/json"); HttpResponse response = client.execute(delete); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_CONFLICT) { throw new Exception("There was a conflict trying to delete user"); } } private JSONObject getCouchUser(String username) throws IOException { HttpClient client = new DefaultHttpClient(); final String couchdbHost = env.get("couchdb.host"); final String couchdbPort = env.get("couchdb.port"); final String couchdbAdminLogin = env.get("couchdb.admin_login"); final String couchdbAdminPasword = env.get("couchdb.admin_password"); String userPassword = couchdbAdminLogin + ":" + couchdbAdminPasword; byte[] encodedCredentials = Base64.encodeBase64(userPassword.getBytes()); final String request = String.format("http://%s:%s/_users/org.couchdb.user:%s", couchdbHost, couchdbPort, username); HttpGet get = new HttpGet(request); get.addHeader("Authorization", "Basic " + new String(encodedCredentials)); get.addHeader("Content-Type", "application/json"); get.addHeader("Accept", "application/json"); HttpResponse response = client.execute(get); String jsonString = EntityUtils.toString(response.getEntity()); return JSONObject.fromObject(jsonString); } private void revokeFacebookPermissions(final Guest guest) { ApiKey facebookApiKey = getApiKey(guest.getId(), Connector.getConnector("facebook")); final String meString = getApiKeyAttribute(facebookApiKey, "me"); final String accessToken = getApiKeyAttribute(facebookApiKey, "accessToken"); JSONObject meJSON = JSONObject.fromObject(meString); String id = meJSON.getString("id"); HttpClient client = new DefaultHttpClient(); try { final String uri = String.format("https://graph.facebook.com/%s/permissions?access_token=%s", id, accessToken); HttpDelete delete = new HttpDelete(uri); client.execute(delete); } catch (IOException e) { e.printStackTrace(); } finally { client.getConnectionManager().shutdown(); } } @Override @Transactional(readOnly = false) @Secured("ROLE_ADMIN") public void eraseGuestInfo(String username) throws Exception { Guest guest = getGuest(username); eraseGuestInfo(guest.getId()); } @Override public List<Guest> getAllGuests() { List<Guest> all = JPAUtils.find(em, Guest.class, "guests.all", (Object[]) null); List<Guest> result = new ArrayList<Guest>(); for (Guest guest : all) result.add(guest); return result; } @Override @Transactional(readOnly = false) @Secured("ROLE_ADMIN") public void addRole(long guestId, String role) { Guest guest = getGuestById(guestId); if (guest.hasRole(role)) return; List<String> userRoles = guest.getUserRoles(); userRoles.add(role); persistUserRoles(guest, userRoles); } @Override @Transactional(readOnly = false) @Secured("ROLE_ADMIN") public void removeRole(long guestId, String role) { Guest guest = getGuestById(guestId); if (!guest.hasRole(role)) return; List<String> userRoles = guest.getUserRoles(); userRoles.remove(role); persistUserRoles(guest, userRoles); } private void persistUserRoles(Guest guest, List<String> userRoles) { StringBuffer roles = new StringBuffer(); for (int i = 0; i < userRoles.size(); i++) { if (i > 0) roles.append(","); roles.append(userRoles.get(i)); } guest.setRoles(roles.toString()); em.persist(guest); } @Override @Transactional(readOnly=false) public void setApiKeySettings(final long apiKeyId, final Object settings) { ApiKey apiKey = getApiKey(apiKeyId); apiKey.setSettings(settings); em.persist(apiKey); } @Override @Transactional(readOnly=false) public void removeApiKeyAttribute(final long apiKeyId, final String key) { ApiKey apiKey = getApiKey(apiKeyId); apiKey.removeAttribute(key); em.persist(apiKey); } @Override @Transactional(readOnly=false) public void setAutoLoginToken(final long guestId, final String s) { Guest guest = getGuestById(guestId); guest.autoLoginToken = s; guest.autoLoginTokenTimestamp = System.currentTimeMillis(); em.persist(guest); } @Override public boolean checkPassword(final long guestId, final String currentPassword) { Guest guest = getGuestById(guestId); ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(); String password = passwordEncoder.encodePassword(currentPassword, guest.salt); return password.equals(guest.password); } @Override public ResetPasswordToken getToken(String token) { return JPAUtils.findUnique(em, ResetPasswordToken.class, "passwordToken.byToken", token); } @Override @Transactional(readOnly = false) public ResetPasswordToken createToken(long guestId) { ResetPasswordToken pToken = new ResetPasswordToken(); pToken.guestId = guestId; pToken.token = randomString.nextString(); pToken.ts = System.currentTimeMillis(); em.persist(pToken); return pToken; } @Override public Guest getGuestByEmail(String email) { return JPAUtils.findUnique(em, Guest.class, "guest.byEmail", email); } @Override public void deleteToken(String token) { ResetPasswordToken ptoken = JPAUtils.findUnique(em, ResetPasswordToken.class, "passwordToken.byToken", token); em.remove(ptoken); } @Override public void checkIn(long guestId, String ipAddress) throws IOException { if (SecurityUtils.isStealth()) return; if (geoIpLookupService == null) { String dbLocation = env.get("geoIpDb.location"); geoIpLookupService = new LookupService(dbLocation, LookupService.GEOIP_MEMORY_CACHE); } LocationFacet locationFacet = new LocationFacet(1); long time = System.currentTimeMillis(); locationFacet.guestId = guestId; locationFacet.timestampMs = time; locationFacet.start = time; locationFacet.end = time; locationFacet.guestId = guestId; // Set both api and apiKeyId fields to zero since this location is not coming from a connector locationFacet.api = 0; locationFacet.apiKeyId = 0L; Location ipLocation = null; try { ipLocation = geoIpLookupService.getLocation(ipAddress); } catch (Throwable t) { StringBuilder sb = new StringBuilder("module=web component=guestServiceImpl action=checkIn") .append(" guestId=").append(guestId).append(" message=" + t.getMessage()); logger.info(sb.toString()); } if (ipLocation != null) { locationFacet.accuracy = 7000; locationFacet.latitude = ipLocation.latitude; locationFacet.longitude = ipLocation.longitude; locationFacet.source = LocationFacet.Source.GEO_IP_DB; apiDataService.addGuestLocation(guestId, locationFacet); } else if (env.get("environment").equals("local")) { try{ locationFacet.accuracy = 7000; locationFacet.latitude = env.getFloat("defaultLocation.latitude"); locationFacet.longitude = env.getFloat("defaultLocation.longitude"); locationFacet.source = LocationFacet.Source.OTHER; apiDataService.addGuestLocation(guestId, locationFacet); } catch (Exception ignored){ } } else if (env.get("ip2location.apiKey")!=null) { String ip2locationKey = env.get("ip2location.apiKey"); String jsonString; try { jsonString = HttpUtils.fetch("http://api.ipinfodb.com/v3/ip-city/?key=" + ip2locationKey + "&ip=" + ipAddress + "&format=json"); } catch (UnexpectedHttpResponseCodeException e) { // simply log the error and don't persist anything to the guest location table logger.warn(String.format("ip2location http error; code is %s, message is '%s'", e.getHttpResponseCode(), e.getHttpResponseMessage())); return; } JSONObject json = JSONObject.fromObject(jsonString); String latitude = json.getString("latitude"); String longitude = json.getString("longitude"); locationFacet.latitude = Float.valueOf(latitude); locationFacet.longitude = Float.valueOf(longitude); if (latitude != null && longitude != null) { float lat = Float.valueOf(latitude); float lon = Float.valueOf(longitude); locationFacet.accuracy = 7000; locationFacet.latitude = lat; locationFacet.longitude = lon; locationFacet.source = LocationFacet.Source.IP_TO_LOCATION; apiDataService.addGuestLocation(guestId, locationFacet); } } } @Override public void destroy() throws Exception { executor.shutdown(); } }