com.deviceatlas.cloud.deviceidentification.service.DeviceIdentificatorService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of deviceatlas-cloud-java-client Show documentation
Show all versions of deviceatlas-cloud-java-client Show documentation
DeviceAtlas is the world's fastest, most accurate device detection solution
providing real-time information on all mobile and other devices accessing the web.
The newest version!
/*
* The MIT License (MIT)
*
* Copyright (c) 2016 Afilias Technologies Ltd
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom
* the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.deviceatlas.cloud.deviceidentification.service;
import com.deviceatlas.cloud.deviceidentification.cacheprovider.CacheException;
import com.deviceatlas.cloud.deviceidentification.client.ClientException;
import com.deviceatlas.cloud.deviceidentification.client.ClientConstants;
import com.deviceatlas.cloud.deviceidentification.client.Result;
import com.deviceatlas.cloud.deviceidentification.client.HeaderConstants;
import com.deviceatlas.cloud.deviceidentification.client.Properties;
import com.deviceatlas.cloud.deviceidentification.utils.StringUtils;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Enumeration;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DeviceIdentificatorService {
private EndPointService endPointService;
private String licenceKey = null;
private boolean useClientCookie = true;
private boolean sendExtraHeaders = false;
/// A list of http-headers to be sent to the DeviceAtlas Cloud. This headers
/// are used for device detection, specially if a third party browser or a proxy
/// changes the original user-agent.
private static final String[] ESSENTIAL_HEADERS = {
"x-profile",
"x-wap-profile",
"x-att-deviceid",
"accept",
"accept-language",
};
// A list of http-headers which may contain the original user-agent.
// this headers are sent to DeviceAtlas Cloud beside.
private static final String[] ESSENTIAL_USER_AGENT_HEADERS = {
"x-device-user-agent",
"x-original-user-agent",
"x-operamini-phone-ua",
"x-skyfire-phone",
"x-bolt-phone-ua",
"device-stock-ua",
"x-ucbrowser-ua",
"x-ucbrowser-device-ua",
"x-ucbrowser-device",
"x-puffin-ua",
};
// An array of additional http-headers to be sent to the DeviceAtlas Cloud.
// This headers are not sent by default. This headers can be used for
// carrier detection and geoip.
private static final String[] EXTRA_HEADERS = {
"client-ip",
"x-forwarded-for",
"x-forwarded",
"forwarded-for",
"forwarded",
"proxy-client-ip",
"wl-proxy-client-ip",
};
private static final Logger LOGGER = LoggerFactory.getLogger(DeviceIdentificatorService.class);
public DeviceIdentificatorService(EndPointService endPointService) {
this.endPointService = endPointService;
}
/**
* Gets the licence key in the identificator's level
*
* @return String
*/
public String getLicenceKey() {
return licenceKey;
}
/**
* Sets the licence key in the identificator's level
*
* @param licenceKey
*/
public void setLicenceKey(String licenceKey) {
this.licenceKey = licenceKey;
this.endPointService.setLicenceKey(this.licenceKey);
}
/**
* Getter for the useClientCookie setting. Defaults to true. If TRUE then if
* device data which is created by the DeviceAtlas client side component
* (JS library) exists it will be used
*
* @return The sendExtraHeaders setting. Defaults to false
*/
public boolean getUseClientCookie() {
return useClientCookie;
}
/**
* Setter for the useClientCookie setting. Defaults to true. If TRUE then if
* device data which is created by the DeviceAtlas client side component
* (JS library) exists it will be used
*
* @param useClientCookie true = use device data which is created by the
* DeviceAtlas client side component if exists
*/
public void setUseClientCookie(boolean useClientCookie) {
this.useClientCookie = useClientCookie;
}
/**
* Getter for the sendExtraHeaders setting. Defaults to false. If this
* TRUE then extra client headers are sent with each request to the
* service. If this is FALSE then only select headers essential for
* detection are sent.
*
* @return The sendExtraHeaders setting. Defaults to false.
*/
public boolean getSendExtraHeaders() {
return sendExtraHeaders;
}
/**
* Setter for the sendExtraHeaders setting. Defaults to false. If this
* TRUE then extra client headers are sent with each request to the
* service. If this is FALSE then only select headers essential for
* detection are sent.
*
* @param sendExtraHeaders TRUE if to send extra headers, FALSE to just
* send essential headers.
*/
public void setSendExtraHeaders(boolean sendExtraHeaders) {
this.sendExtraHeaders = sendExtraHeaders;
}
/**
* Get device data from DeviceAtlas Cloud service.
* If using cache is not turned off the device data will be cached after each
* cloud call, if cached data exists for a device it will be used over the cloud.
* If device data provided by "DeviceAtlas Client Side Component" exists in
* a cookie then cloud data will be merged with the cookie data.
*
* @param request The HttpServletRequest request object
* @return A Map of device data as {
* Client.KEY_USERAGENT: "UA",
* Client.KEY_SOURCE: "data source",
* Client.KEY_PROPERTIES: {"propertyName": "PropertyVal",},
* } Note that Client.KEY_PROPERTIES will not exists for not detectable data
* and on failures.
* @throws ClientException When any part of detection or the API fails.
* @deprecated
*/
@Deprecated
public Map getDeviceData(HttpServletRequest request) throws ClientException {
Map headers = prepareHeadersForServletRequest(request);
return getDeviceDataByHeaders(headers);
}
/**
* Get device data from DeviceAtlas Cloud service.
* If using cache is not turned off the device data will be cached after each
* cloud call, if cached data exists for a device it will be used over the cloud.
* NOTE: it is recommended to use getDeviceDataByUserAgent() to get device data
* when passing a user-agent string. Because "getDeviceData(String)" is
* an overload for "getDeviceData(HttpServletRequest)" so the JavaEE lib would be
* required when compiling your project. But If you use
* "getDeviceDataByUserAgent(String)" instead, the JavaEE will not be required.
*
* @param userAgent User-agent string
* @return A Map of device data as {
* Client.KEY_USERAGENT: "UA",
* Client.KEY_SOURCE: "data source",
* Client.KEY_PROPERTIES: {"propertyName": "PropertyVal",},
* } Note that Client.KEY_PROPERTIES will not exists for not detectable data
* and on failures.
* @throws ClientException When any part of detection or the API fails.
* @deprecated
*/
@Deprecated
public Map getDeviceData(String userAgent) throws ClientException {
return getDeviceDataByUserAgent(userAgent);
}
/**
* Get device data from DeviceAtlas Cloud service.
* If using cache is not turned off the device data will be cached after each
* cloud call, if cached data exists for a device it will be used over the cloud.
* NOTE: it is recommended to use getDeviceDataByHeaders() to get device data
* when passing a Map of headers. Because {@code getDeviceData(Map)}
* is an overload for "getDeviceData(HttpServletRequest)" the JavaEE lib would be
* required when compiling your project. But if you use
* {@code getDeviceDataByHeaders(Map)} instead, the JavaEE will not be required.
*
* @param headers A Map of http headers {"header-name": "header-value",}
* @return A Map of device data as {
* Client.KEY_USERAGENT: "UA",
* Client.KEY_SOURCE: "data source",
* Client.KEY_PROPERTIES: {"propertyName": "PropertyVal",},
* } Note that Client.KEY_PROPERTIES will not exists for not detectable data
* and on failures.
* @throws ClientException When any part of detection or the API fails.
* @deprecated
*/
@Deprecated
public Map getDeviceData(Map headers) throws ClientException {
return getDeviceDataByHeaders(headers);
}
/**
* Get device data from DeviceAtlas Cloud service.
* If using cache is not turned off the device data will be cached after each
* cloud call, if cached data exists for a device it will be used over the cloud.
*
* @param userAgent User-agent string
* @return A Map of device data as {
* Client.KEY_USERAGENT: "UA",
* Client.KEY_SOURCE: "data source",
* Client.KEY_PROPERTIES: {"propertyName": "PropertyVal",},
* } Note that Client.KEY_PROPERTIES will not exists for not detectable data
* and on failures.
* @throws ClientException When any part of detection or the API fails.
* @deprecated
*/
@Deprecated
public Map getDeviceDataByUserAgent(String userAgent) throws ClientException {
Map headers = new HashMap();
headers.put("user-agent", userAgent);
return getDeviceDataByHeaders(headers);
}
/**
* Get device data from DeviceAtlas Cloud service.
* If using cache is not turned off the device data will be cached after each
* cloud call, if cached data exists for a device it will be used over the cloud.
*
* @param headers A Map of http headers {"header-name": "header-value",}
* @return A Map of device data as {
* Client.KEY_USERAGENT: "UA",
* Client.KEY_SOURCE: "data source",
* Client.KEY_PROPERTIES: {"propertyName": "PropertyVal",},
* } Note that Client.KEY_PROPERTIES will not exists for not detectable data
* and on failures.
*
* @throws ClientException When any part of detection or the API fails.
* @deprecated
*/
@Deprecated
public Map getDeviceDataByHeaders(Map headers) throws ClientException {
String userAgent;
String cookie;
Map tmpHeaders;
Map dataRet = prepareDataHeaders(headers);
userAgent = (String)dataRet.get(HeaderConstants.UA_HEADER.toString());
cookie = (String)dataRet.get(HeaderConstants.COOKIE_HEADER.toString());
tmpHeaders = (Map)dataRet.get(ClientConstants.CLOUD_SERVICE_RESULT.toString());
Map results = new HashMap ();
results.put(ClientConstants.KEY_USERAGENT.toString(), userAgent);
try {
setCacheData(results, userAgent, cookie, tmpHeaders);
} catch (ClientException ex) {
throw new ClientException(
"There was a problem getting/setting the device properties: \"" + ex.getMessage() + "\"",
ex
);
}
return results;
}
/**
* Get device data from DeviceAtlas Cloud service.
* If using cache is not turned off the device data will be cached after each
* cloud call, if cached data exists for a device it will be used over the cloud.
* If device data provided by "DeviceAtlas Client Side Component" exists in
* a cookie then cloud data will be merged with the cookie data.
*
* @param request The HttpServletRequest request object
* @return Result
* @throws ClientException When any part of detection or the API fails.
*/
public Result getResult(HttpServletRequest request) throws ClientException {
Map headers = prepareHeadersForServletRequest(request);
return getResultByHeaders(headers);
}
/**
* Get device data from DeviceAtlas Cloud service.
* If using cache is not turned off the device data will be cached after each
* cloud call, if cached data exists for a device it will be used over the cloud.
* NOTE: it is recommended to use getDeviceDataByUserAgent() to get device data
* when passing a user-agent string. Because "getDeviceData(String)" is
* an overload for "getDeviceData(HttpServletRequest)" so the JavaEE lib would be
* required when compiling your project. But If you use
* "getDeviceDataByUserAgent(String)" instead, the JavaEE will not be required.
*
* @param userAgent User-agent string
* @return Result
* @throws ClientException When any part of detection or the API fails.
*/
public Result getResult(String userAgent) throws ClientException {
return getResultByUserAgent(userAgent);
}
/**
* Get device data from DeviceAtlas Cloud service.
* If using cache is not turned off the device data will be cached after each
* cloud call, if cached data exists for a device it will be used over the cloud.
* NOTE: it is recommended to use getDeviceDataByHeaders() to get device data
* when passing a Map of headers. Because {@code getDeviceData(Map)}
* is an overload for "getDeviceData(HttpServletRequest)" the JavaEE lib would be
* required when compiling your project. But if you use
* {@code getDeviceDataByHeaders(Map)} instead, the JavaEE will not be required.
*
* @param headers A Map of http headers {"header-name": "header-value",}
* @return Result
* @throws ClientException When any part of detection or the API fails.
*/
public Result getResult(Map headers) throws ClientException {
return getResultByHeaders(headers);
}
/**
* Get device data from DeviceAtlas Cloud service.
* If using cache is not turned off the device data will be cached after each
* cloud call, if cached data exists for a device it will be used over the cloud.
*
* @param userAgent User-agent string
* @return Result
* @throws ClientException When any part of detection or the API fails.
*/
public Result getResultByUserAgent(String userAgent) throws ClientException {
Map headers = new HashMap();
headers.put("user-agent", userAgent);
return getResultByHeaders(headers);
}
/**
* Get device data from DeviceAtlas Cloud service.
* If using cache is not turned off the device data will be cached after each
* cloud call, if cached data exists for a device it will be used over the cloud.
*
* @param headers A Map of http headers {"header-name": "header-value",}
* @return A Map of device data as {
* Client.KEY_USERAGENT: "UA",
* Client.KEY_SOURCE: "data source",
* Client.KEY_PROPERTIES: {"propertyName": "PropertyVal",},
* } Note that Client.KEY_PROPERTIES will not exists for not detectable data
* and on failures.
*
* @throws ClientException When any part of detection or the API fails.
*/
public Result getResultByHeaders(Map headers) throws ClientException {
String userAgent;
String cookie;
Map tmpHeaders;
Map dataRet = prepareDataHeaders(headers);
userAgent = (String)dataRet.get(HeaderConstants.UA_HEADER.toString());
cookie = (String)dataRet.get(HeaderConstants.COOKIE_HEADER.toString());
tmpHeaders = (Map)dataRet.get(ClientConstants.CLOUD_SERVICE_RESULT.toString());
Result eResponse = new Result();
eResponse.setHeaders(headers);
try {
setCacheData(eResponse, userAgent, cookie, tmpHeaders);
} catch (ClientException ex) {
throw new ClientException(
"There was a problem getting/setting the device properties: \"" + ex.getMessage() + "\"",
ex
);
}
return eResponse;
}
/**
* Treats the headers from a servlet request
*
* @param request
* @return Map
*/
public Map prepareHeadersForServletRequest(HttpServletRequest request) {
// this is the only spot to use JavaEE > > >
Map headers = new HashMap();
Enumeration e = request.getHeaderNames();
Cookie [] cookies;
while (e.hasMoreElements()) {
String header = (String)e.nextElement();
String headerVal = request.getHeader(header);
headers.put(header, headerVal);
}
if (useClientCookie && (cookies = request.getCookies()) != null) {
for (int i = 0, l = cookies.length; i < l; i++) {
Cookie cookie = cookies[i];
if (cookie.getName().equals(ClientConstants.CLIENT_COOKIE_NAME.toString())) {
headers.put(ClientConstants.CLIENT_COOKIE_NAME.toString(), cookie.getValue());
}
}
}
headers.put(HeaderConstants.REMOTE_ADDR.toString(), request.getRemoteAddr());
// < < < JavaEE
return headers;
}
/**
* Treats the various headers, creates the user agent and cookie
*
* @param headers
* @return Map
*/
public Map prepareDataHeaders(Map headers) {
Map tmpHeaders;
Map dataRet = new HashMap();
endPointService.setCloudUrl(null);
String userAgent;
String cookie = null;
endPointService.setRankingStatus(null);
// get user agent
tmpHeaders = StringUtils.normaliseKeys(headers);
userAgent = tmpHeaders.get(HeaderConstants.UA_HEADER.toString());
if (userAgent == null) {
userAgent = "";
}
cookie = extractCookieValue(tmpHeaders);
// fetch device data
endPointService.setCalledServers(new ArrayList());
dataRet.put(HeaderConstants.UA_HEADER.toString(), userAgent);
dataRet.put(HeaderConstants.COOKIE_HEADER.toString(), cookie);
dataRet.put(ClientConstants.CLOUD_SERVICE_RESULT.toString(), headers);
return dataRet;
}
/**
* Returns the DA cookie value from the headers list
*
* @param headers
* @return cookie
*/
public String extractCookieValue(Map headers) {
String cookie = null;
// get client side component cookie value
if (useClientCookie && headers != null &&
(cookie = headers.get(ClientConstants.CLIENT_COOKIE_NAME.toString())) != null) {
return cookie;
}
if (useClientCookie && headers != null &&
(cookie = headers.get(HeaderConstants.COOKIE_HEADER.toString())) != null) {
String[] cookies = cookie.split(";");
for (String c : cookies) {
c = c.trim();
if (c.startsWith(ClientConstants.CLIENT_COOKIE_NAME.toString())) {
cookie = c.replace(ClientConstants.CLIENT_COOKIE_NAME.toString(), "").replace("=", "");
}
}
}
return cookie;
}
/**
* Sets the cloud service data to the cache's layer
*
* @param results
* @param userAgent
* @param cookie
* @param headers
*/
public void setCacheData(Object results, String userAgent, String cookie, Map headers) throws ClientException {
String cacheKey = getCacheKey(userAgent, cookie, headers);
String source = ClientConstants.SOURCE_NONE.toString();
Map data = null;
try {
if (endPointService.getCacheService().getUseCache() && (data = endPointService.getCacheService().getCacheProvider().get(cacheKey)) != null) {
source = ClientConstants.SOURCE_CACHE.toString();
}
if (data == null) {
headers = prepareHeaders(headers);
// add the client side component cookie
if (cookie != null) {
headers.put(HeaderConstants.CLIENT_COOKIE_HEADER.toString(), cookie);
}
data = endPointService.getCloudService(userAgent, headers);
source = ClientConstants.SOURCE_CLOUD.toString();
// put device data into cache
if (endPointService.getCacheService().getUseCache()) {
endPointService.getCacheService().getCacheProvider().set(cacheKey, data);
}
}
} catch (CacheException ex) {
LOGGER.error("setCacheData", ex);
}
if (results instanceof Map) {
((Map)results).put(ClientConstants.KEY_SOURCE.toString(), source);
if (data != null) {
((Map)results).put(ClientConstants.KEY_PROPERTIES.toString(), data);
}
} else if (results instanceof Result) {
((Result)results).setSource(source);
if (data != null) {
Properties properties = new Properties();
properties.putMap(data);
((Result)results).setProperties(properties);
}
} else {
throw new ClientException("Invalid results type");
}
}
/**
* Get a key for caching device data
*/
private String getCacheKey(String userAgent, String cookie, Map headers) throws ClientException {
StringBuilder sb = new StringBuilder(userAgent);
if (headers != null) {
// cache key - combination of user agent and JS created cookie
for (byte i=0; i < ESSENTIAL_USER_AGENT_HEADERS.length; i++) {
String val = headers.get(ESSENTIAL_USER_AGENT_HEADERS[i]);
if (val != null) {
sb.append(val);
}
}
// opera headers
for (Map.Entry entry : headers.entrySet()) {
String header = entry.getKey();
String val = entry.getValue();
if (header.contains(ClientConstants.OPERA_HEADER_IDENTIFIER.toString())) {
sb.append(val);
}
}
}
if (cookie != null) {
sb.append(cookie);
}
return StringUtils.md5(sb.toString());
}
/**
* Treats the Essential headers
*
* @param headers
* @param newHeaders
*/
public void prepareEssentialHeaders(Map headers, Map newHeaders) {
// add essential headers which are useful for device detection
for (byte i=0; i < ESSENTIAL_HEADERS.length; i++) {
String headerVal = headers.get(ESSENTIAL_HEADERS[i]);
if (headerVal != null) {
newHeaders.put(ESSENTIAL_HEADERS[i], headerVal);
}
}
}
/**
* Treats the Extra user agent headers
*
* @param headers
* @param newHeaders
*/
public void prepareEssentialUserAgentHeaders(Map headers, Map newHeaders) {
// add special headers these can contain device info
for (byte i=0; i < ESSENTIAL_USER_AGENT_HEADERS.length; i++) {
String headerVal = headers.get(ESSENTIAL_USER_AGENT_HEADERS[i]);
if (headerVal != null) {
newHeaders.put(ESSENTIAL_USER_AGENT_HEADERS[i], headerVal);
}
}
}
/**
* Treats the Opera identifier header
*
* @param headers
* @param newHeaders
*/
public void prepareOperaHeader(Map headers, Map newHeaders) {
// opera headers
for (Map.Entry entry : headers.entrySet()) {
String header = entry.getKey();
if (header.contains(ClientConstants.OPERA_HEADER_IDENTIFIER.toString())) {
newHeaders.put(header, entry.getValue());
}
}
}
/**
* Treats the extra headers
*
* @param headers
* @param newHeaders
*/
public void prepareExtraHeaders(Map headers, Map newHeaders) {
// add optional headers
if (sendExtraHeaders) {
for (byte i=0; i < EXTRA_HEADERS.length; i++) {
String headerVal = headers.get(EXTRA_HEADERS[i]);
if (headerVal != null) {
newHeaders.put(EXTRA_HEADERS[i], headerVal);
}
}
// add the remote IP address
String remoteAddrVal = headers.get(HeaderConstants.REMOTE_ADDR.toString());
if (remoteAddrVal != null)
{
newHeaders.put(HeaderConstants.REMOTE_ADDR.toString(), remoteAddrVal);
}
}
}
/**
* Extract headers to be send to the cloud service from a header Map object.
* @param headers headers
* @return Map
*/
public Map prepareHeaders(Map headers) {
Map newHeaders = new HashMap();
prepareEssentialHeaders(headers, newHeaders);
prepareEssentialUserAgentHeaders(headers, newHeaders);
prepareOperaHeader(headers, newHeaders);
prepareExtraHeaders(headers, newHeaders);
return newHeaders;
}
}