/** * Copyright 2016-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. * <p> * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * <p> * http://aws.amazon.com/apache2.0 * <p> * or in the "license" file accompanying this file. This file 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 com.amazonaws.mobileconnectors.pinpoint.targeting; import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonServiceException; import com.amazonaws.mobileconnectors.pinpoint.PinpointManager; import com.amazonaws.mobileconnectors.pinpoint.internal.core.PinpointContext; import com.amazonaws.mobileconnectors.pinpoint.targeting.endpointProfile.EndpointProfile; import com.amazonaws.services.pinpoint.model.EndpointDemographic; import com.amazonaws.services.pinpoint.model.EndpointLocation; import com.amazonaws.services.pinpoint.model.EndpointRequest; import com.amazonaws.services.pinpoint.model.UpdateEndpointRequest; import com.amazonaws.services.pinpoint.model.EndpointUser; import com.amazonaws.util.DateUtils; import com.amazonaws.util.VersionInfoUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import static com.amazonaws.mobileconnectors.pinpoint.internal.core.util.Preconditions.checkNotNull; public class TargetingClient { private static final String USER_AGENT = PinpointManager.class.getName() + "/" + VersionInfoUtils.getVersion(); private static final Log log = LogFactory.getLog(TargetingClient.class); private final static int MAX_EVENT_OPERATIONS = 1000; private final static String CUSTOM_ATTRIBUTES_KEY = "ENDPOINT_PROFILE_CUSTOM_ATTRIBUTES"; private final static String CUSTOM_METRICS_KEY = "ENDPOINT_PROFILE_CUSTOM_METRICS"; private final PinpointContext context; private final Map<String, java.util.List<String>> globalAttributes; private final Map<String, Double> globalMetrics; private final ExecutorService endpointRunnableQueue; //For testing public TargetingClient(final PinpointContext context, ThreadPoolExecutor executor) { checkNotNull(context, "A valid pinpointContext must be provided"); this.endpointRunnableQueue = executor; this.context = context; globalAttributes = loadAttributes(); globalMetrics = loadMetrics(); } public TargetingClient(final PinpointContext context) { this(context, new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(MAX_EVENT_OPERATIONS), new ThreadPoolExecutor.DiscardPolicy())); } /** * Returns the device endpoint profile. * TargetingClient attributes and Metrics are added to the endpoint profile. * @returns the current device endpoint profile */ public EndpointProfile currentEndpoint() { EndpointProfile endpointProfile = new EndpointProfile(this.context); // Add global attributes. if (!this.globalAttributes.isEmpty()) { for (Map.Entry<String, java.util.List<String>> pair : globalAttributes.entrySet()) { endpointProfile.addAttribute(pair.getKey(), pair.getValue()); } } if (!this.globalMetrics.isEmpty()) { for (Map.Entry<String, Double> pair : globalMetrics.entrySet()) { endpointProfile.addMetric(pair.getKey(), pair.getValue()); } } return endpointProfile; } /** * Register the current endpoint with the Pinpoint service. * TargetingClient attributes and Metrics are added to the endpoint profile. */ public void updateEndpointProfile() { this.executeUpdate(this.currentEndpoint()); } /** * Register the provided endpoint with the Pinpoint service. * TargetingClient attributes and Metrics are added to the endpoint profile. */ public void updateEndpointProfile(EndpointProfile endpointProfile) { // Add global attributes. if (!this.globalAttributes.isEmpty()) { for (Map.Entry<String, java.util.List<String>> pair : globalAttributes.entrySet()) { endpointProfile.addAttribute(pair.getKey(), pair.getValue()); } } if (!this.globalMetrics.isEmpty()) { for (Map.Entry<String, Double> pair : globalMetrics.entrySet()) { endpointProfile.addMetric(pair.getKey(), pair.getValue()); } } this.executeUpdate(endpointProfile); } private void executeUpdate(EndpointProfile endpointProfile) { EndpointDemographic demographic = new EndpointDemographic().withAppVersion(endpointProfile.getDemographic().getAppVersion()) .withLocale(endpointProfile.getDemographic().getLocale().getISO3Country()) .withTimezone(endpointProfile.getDemographic().getTimezone()) .withMake(endpointProfile.getDemographic().getMake()) .withModel(endpointProfile.getDemographic().getModel()) .withPlatform(endpointProfile.getDemographic().getPlatform()) .withPlatformVersion(endpointProfile.getDemographic().getPlatformVersion()); EndpointLocation location = new EndpointLocation() .withLatitude(endpointProfile.getLocation().getLatitude()) .withLongitude(endpointProfile.getLocation().getLongitude()) .withPostalCode(endpointProfile.getLocation().getPostalCode()) .withCity(endpointProfile.getLocation().getCity()) .withRegion(endpointProfile.getLocation().getRegion()) .withCountry(endpointProfile.getLocation().getCountry()); EndpointUser user = new EndpointUser(); user.setUserId(endpointProfile.getUser().getUserId()); final EndpointRequest endpointRequest = new EndpointRequest(). withChannelType(endpointProfile.getChannelType()). withAddress(endpointProfile.getAddress()). withLocation(location). withDemographic(demographic). withEffectiveDate(DateUtils.formatISO8601Date(new Date(endpointProfile.getEffectiveDate()))). withOptOut(endpointProfile.getOptOut()). withAttributes(endpointProfile.getAllAttributes()). withMetrics(endpointProfile.getAllMetrics()). withUser(user); final UpdateEndpointRequest updateEndpointRequest = new UpdateEndpointRequest(). withApplicationId(endpointProfile.getApplicationId()). withEndpointId(endpointProfile.getEndpointId()). withEndpointRequest(endpointRequest); updateEndpointRequest.getRequestClientOptions().appendUserAgent(USER_AGENT); endpointRunnableQueue.execute(new Runnable() { @Override public void run() { try { log.info(String.format("Updating EndpointProfile : %s", updateEndpointRequest.toString())); context.getPinpointServiceClient().updateEndpoint(updateEndpointRequest); log.info(String.format("EndpointProfile updated successfully: %s", updateEndpointRequest.toString())); } catch (AmazonServiceException e) { log.error("AmazonServiceException occurred during endpoint update:", e); } catch (AmazonClientException e) { log.info("AmazonClientException occurred during endpoint update:", e); } } }); } private void saveAttributes() { JSONObject jsonObject = new JSONObject(globalAttributes); String jsonString = jsonObject.toString(); this.context.getSystem().getPreferences().putString(CUSTOM_ATTRIBUTES_KEY, jsonString); } private Map<String, java.util.List<String>> loadAttributes() { Map<String, java.util.List<String>> outputMap = new ConcurrentHashMap<String, java.util.List<String>>(); String jsonString = this.context.getSystem().getPreferences().getString(CUSTOM_ATTRIBUTES_KEY, null); if (jsonString == null || jsonString.equalsIgnoreCase("")) { return outputMap; } try { JSONObject jsonObject = new JSONObject(jsonString); Iterator<String> keysItr = jsonObject.keys(); while (keysItr.hasNext()) { String key = keysItr.next(); JSONArray jsonArray = jsonObject.getJSONArray(key); List<String> listValues = new ArrayList<String>(); for (int i =0; i < jsonArray.length(); i++) { listValues.add(jsonArray.getString(i)); } outputMap.put(key, listValues); } } catch (JSONException e) { e.printStackTrace(); } return outputMap; } private void saveMetrics() { JSONObject jsonObject = new JSONObject(globalMetrics); String jsonString = jsonObject.toString(); this.context.getSystem().getPreferences().putString(CUSTOM_METRICS_KEY, jsonString); } private Map<String, Double> loadMetrics() { Map<String, Double> outputMap = new ConcurrentHashMap<String, Double>(); String jsonString = this.context.getSystem().getPreferences().getString(CUSTOM_METRICS_KEY, null); if (jsonString == null || jsonString.equalsIgnoreCase("")) { return outputMap; } try { JSONObject jsonObject = new JSONObject(jsonString); Iterator<String> keysItr = jsonObject.keys(); while (keysItr.hasNext()) { String key = keysItr.next(); Double value = jsonObject.getDouble(key); outputMap.put(key, value); } } catch (JSONException e) { e.printStackTrace(); } return outputMap; } /** * Adds the specified attribute to the current endpoint profile generated by this client. * Note: The maximum allowed attributes/metrics is 20. Attempts to add more may be ignored * * @param attributeName the name of the attribute to add * @param attributeValues the value of the attribute */ public void addAttribute(final String attributeName, final java.util.List<String> attributeValues) { if (attributeName == null) { log.debug("Null attribute name provided to addGlobalAttribute"); return; } if (attributeValues == null) { log.debug("Null attribute values provided to addGlobalAttribute. attribute name:" + attributeName); return; } globalAttributes.put(attributeName, attributeValues); saveAttributes(); } /** * Removes the specified attribute. All subsequently created events will no * longer have this global attribute. from the current endpoint profile generated by this client. * * @param attributeName the name of the attribute to remove */ public void removeAttribute(final String attributeName) { if (attributeName == null) { log.warn("Null attribute name provided to removeGlobalAttribute"); return; } globalAttributes.remove(attributeName); saveAttributes(); } /** * Adds the specified metric to the current endpoint profile generated by this client. Note: The * maximum allowed attributes and metrics on an endpoint update is 20. Attempts * to add more may be ignored * * @param metricName the name of the metric to add * @param metricValue the value of the metric */ public void addMetric(String metricName, Double metricValue) { if (metricName == null) { log.warn("Null metric name provided to addGlobalMetric"); return; } if (metricValue == null) { log.warn("Null metric value provided to addGlobalMetric. metric name:" + metricName); return; } globalMetrics.put(metricName, metricValue); saveMetrics(); } /** * Removes the specified metric from the current endpoint profile generated by this client. * * @param metricName the name of the metric to remove */ public void removeMetric(String metricName) { if (metricName == null) { log.warn("Null metric name provided to removeGlobalMetric"); return; } globalMetrics.remove(metricName); saveMetrics(); } }