/*
* Copyright 2015-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* 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
*
* http://aws.amazon.com/apache2.0
*
* 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.
*/
using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using Amazon.Runtime;
using Amazon.MobileAnalytics.MobileAnalyticsManager;
using Amazon.MobileAnalytics;
using Amazon.MobileAnalytics.Model;
using ThirdParty.Json.LitJson;
using Amazon.Runtime.Internal.Util;
using Amazon.Runtime.Internal;
#if PCL || BCL45
using System.Threading.Tasks;
#endif
namespace Amazon.MobileAnalytics.MobileAnalyticsManager.Internal
{
///
/// Delivery client periodically sends events in local persistent storage to Mobile Analytics server.
/// Once the events is delivered successfully, those events would be deleted from local storage.
///
public partial class DeliveryClient : IDeliveryClient
{
private Logger _logger = Logger.GetLogger(typeof(DeliveryClient));
private object _deliveryLock = new object();
private bool _deliveryInProgress = false;
private readonly IDeliveryPolicyFactory _policyFactory;
private List _deliveryPolicies;
private IEventStore _eventStore;
private AmazonMobileAnalyticsClient _mobileAnalyticsLowLevelClient;
private ClientContext _clientContext;
private string _appID;
private MobileAnalyticsManagerConfig _maConfig;
private MobileAnalyticsManager _maManager;
private const int MAX_ALLOWED_SELECTS = 200;
///
/// Constructor of class.
///
/// Mobile Analytics Manager configuration.
/// An instance of ClientContext.
/// An instance of Credentials.
/// Region endpoint.
/// Mobile Analytics Manager instance.
public DeliveryClient(MobileAnalyticsManagerConfig maConfig, ClientContext clientContext, AWSCredentials credentials, RegionEndpoint regionEndPoint, MobileAnalyticsManager maManager) :
this(new DeliveryPolicyFactory(maConfig.AllowUseDataNetwork), maConfig, clientContext, credentials, regionEndPoint, maManager)
{
}
#if BCL35||UNITY
///
/// Enqueues the events for delivery. The event is stored in an instance of .
///
/// Event object.
public void EnqueueEventsForDelivery(Amazon.MobileAnalytics.Model.Event eventObject)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(delegate
{
EnqueueEventsHelper(eventObject);
}));
}
///
/// Attempts the delivery.
/// Delivery will fail if any of the policies IsAllowed() returns false.
/// The delivery are attmpted in batches of fixed size. To increase or decrease the size,
/// you can override MaxRequestSize in MobileAnalyticsManagerConfig.
///
public void AttemptDelivery()
{
lock (_deliveryLock)
{
if (_deliveryInProgress)
{
_logger.InfoFormat("Delivery already in progress, failing new delivery");
return;
}
_deliveryInProgress = true;
}
//validate all the policies before attempting the delivery
foreach (IDeliveryPolicy policy in _deliveryPolicies)
{
if (!policy.IsAllowed())
{
_logger.InfoFormat("Policy restriction: {0}", policy.GetType().Name);
lock (_deliveryLock)
{
_deliveryInProgress = false;
}
return;
}
}
List allEventList = _eventStore.GetEvents(_appID, MAX_ALLOWED_SELECTS);
if (allEventList.Count == 0)
{
_logger.InfoFormat("No Events to deliver.");
lock (_deliveryLock)
{
_deliveryInProgress = false;
}
return;
}
List submitEventsIdList = new List();
List submitEventsList = new List();
long submitEventsLength = 0L;
foreach (JsonData eventData in allEventList)
{
string eventString = (string)eventData["event"];
submitEventsLength += eventString.Length;
if (submitEventsLength < _maConfig.MaxRequestSize)
{
try {
Amazon.MobileAnalytics.Model.Event _analyticsEvent = JsonMapper.ToObject(eventString);
submitEventsList.Add(_analyticsEvent);
} catch (JsonException e) {
_logger.Error(e, "Could not load event from event store, discarding.");
}
submitEventsIdList.Add(eventData["id"].ToString());
}
else
{
SubmitEvents(submitEventsIdList, submitEventsList);
submitEventsIdList = new List();
submitEventsList = new List();
submitEventsLength = 0L;
}
}
if (submitEventsLength > 0)
SubmitEvents(submitEventsIdList, submitEventsList);
}
///
/// Submits a single batch of events to the service.
///
/// Row identifiers. The list of rowId, that is unique identifier of each event.
/// The list of events that need to be submitted.
private void SubmitEvents(List rowIds, List eventList)
{
PutEventsRequest putRequest = new PutEventsRequest();
putRequest.Events = eventList;
putRequest.ClientContext = Convert.ToBase64String(
System.Text.Encoding.UTF8.GetBytes(_clientContext.ToJsonString()));
putRequest.ClientContextEncoding = "base64";
_logger.DebugFormat("Client Context is : {0}", _clientContext.ToJsonString());
PutEventsResponse resp = null;
try
{
resp = _mobileAnalyticsLowLevelClient.PutEvents(putRequest);
}
catch (AmazonMobileAnalyticsException e)
{
_logger.Error(e, "An AmazonMobileAnalyticsException occurred while sending Amazon Mobile Analytics request: error code is {0} ; error type is {1} ; request id is {2} ; status code is {3} ; error message is {4}", e.ErrorCode, e.ErrorType, e.RequestId, e.StatusCode, e.Message);
// Delete events in any of the three error codes.
if (e.StatusCode == HttpStatusCode.BadRequest &&
(e.ErrorCode.Equals("ValidationException", StringComparison.CurrentCultureIgnoreCase) ||
e.ErrorCode.Equals("SerializationException", StringComparison.CurrentCultureIgnoreCase) ||
e.ErrorCode.Equals("BadRequestException", StringComparison.CurrentCultureIgnoreCase)))
{
MobileAnalyticsErrorEventArgs eventArgs = new MobileAnalyticsErrorEventArgs(this.GetType().Name, "Amazon Mobile Analytics Service returned an error.", e, eventList);
_maManager.OnRaiseErrorEvent(eventArgs);
_logger.InfoFormat("The error code is not retriable. Delete {0} events from local storage.", rowIds.Count);
_eventStore.DeleteEvent(rowIds);
}
else
{
MobileAnalyticsErrorEventArgs eventArgs = new MobileAnalyticsErrorEventArgs(this.GetType().Name, "Amazon Mobile Analytics Service returned an error.", e, new List());
_maManager.OnRaiseErrorEvent(eventArgs);
}
}
catch (AmazonServiceException e)
{
_logger.Error(e, "An AmazonServiceException occurred while sending Amazon Mobile Analytics request: error code is {0} ; error type is {1} ; request id is {2} ; status code is {3} ; error message is {4} ", e.ErrorCode, e.ErrorType, e.RequestId, e.StatusCode, e.Message);
MobileAnalyticsErrorEventArgs eventArgs = new MobileAnalyticsErrorEventArgs(this.GetType().Name, "Amazon Web Service returned an error.", e, new List());
_maManager.OnRaiseErrorEvent(eventArgs);
}
catch (Exception e)
{
_logger.Error(e, "An exception occurred while sending Amazon Mobile Analytics request.");
MobileAnalyticsErrorEventArgs eventArgs = new MobileAnalyticsErrorEventArgs(this.GetType().Name, "An exception occurred when sending request to Amazon Mobile Analytics.", e, new List());
_maManager.OnRaiseErrorEvent(eventArgs);
}
finally
{
if (resp != null && resp.HttpStatusCode == HttpStatusCode.Accepted)
{
_logger.InfoFormat("Mobile Analytics client successfully delivered {0} events to service. Delete those events from local storage.", rowIds.Count);
_eventStore.DeleteEvent(rowIds);
}
lock (_deliveryLock)
{
_deliveryInProgress = false;
}
}
}
#endif
#if PCL || BCL45
///
/// Enqueues the events for delivery. The event is stored in an instance of .
///
/// Event object.
public void EnqueueEventsForDelivery(Amazon.MobileAnalytics.Model.Event eventObject)
{
Task.Run(() =>
{
EnqueueEventsHelper(eventObject);
});
}
///
/// Attempts the delivery.
/// Delivery will fail if any of the policies IsAllowed() returns false.
/// The delivery are attmpted in batches of fixed size. To increase or decrease the size,
/// you can override MaxRequestSize in MobileAnalyticsManagerConfig.
///
public async Task AttemptDeliveryAsync()
{
lock (_deliveryLock)
{
if (_deliveryInProgress)
{
_logger.InfoFormat("Delivery already in progress, failing new delivery");
return;
}
_deliveryInProgress = true;
}
//validate all the policies before attempting the delivery
foreach (IDeliveryPolicy policy in _deliveryPolicies)
{
if (!policy.IsAllowed())
{
_logger.InfoFormat("Policy restriction: {0}", policy.GetType().Name);
lock (_deliveryLock)
{
_deliveryInProgress = false;
}
return;
}
}
List allEventList = _eventStore.GetEvents(_appID, MAX_ALLOWED_SELECTS);
if (allEventList == null || allEventList.Count == 0)
{
_logger.InfoFormat("No Events to deliver.");
lock (_deliveryLock)
{
_deliveryInProgress = false;
}
return;
}
List submitEventsIdList = new List();
List submitEventsList = new List();
long submitEventsLength = 0L;
foreach (JsonData eventData in allEventList)
{
string eventString = (string)eventData["event"];
submitEventsLength += eventString.Length;
if (submitEventsLength < _maConfig.MaxRequestSize)
{
Amazon.MobileAnalytics.Model.Event _analyticsEvent = JsonMapper.ToObject(eventString);
submitEventsIdList.Add(eventData["id"].ToString());
submitEventsList.Add(_analyticsEvent);
}
else
{
await SubmitEvents(submitEventsIdList, submitEventsList).ConfigureAwait(false);
submitEventsIdList = new List();
submitEventsList = new List();
submitEventsLength = 0L;
}
}
if (submitEventsLength > 0)
await SubmitEvents(submitEventsIdList, submitEventsList).ConfigureAwait(false);
}
///
/// Submits a single batch of events to the service.
///
/// Row identifiers. The list of rowId, that is unique identifier of each event.
/// The list of events that need to be submitted.
#if BCL35
private void SubmitEvents(List rowIds, List eventList)
#elif PCL || BCL45
private async Task SubmitEvents(List rowIds, List eventList)
#endif
{
PutEventsRequest putRequest = new PutEventsRequest();
putRequest.Events = eventList;
putRequest.ClientContext = Convert.ToBase64String(
System.Text.Encoding.UTF8.GetBytes(_clientContext.ToJsonString()));
putRequest.ClientContextEncoding = "base64";
_logger.DebugFormat("Client Context is : {0}", _clientContext.ToJsonString());
PutEventsResponse resp = null;
try
{
resp = await _mobileAnalyticsLowLevelClient.PutEventsAsync(putRequest).ConfigureAwait(false);
}
catch (AmazonMobileAnalyticsException e)
{
_logger.Error(e, "An AmazonMobileAnalyticsException occurred while sending Amazon Mobile Analytics request: error code is {0} ; error type is {1} ; request id is {2} ; status code is {3} ; error message is {4}", e.ErrorCode, e.ErrorType, e.RequestId, e.StatusCode, e.Message);
// Delete events in any of the three error codes.
if (e.StatusCode == HttpStatusCode.BadRequest &&
(e.ErrorCode.Equals("ValidationException", StringComparison.CurrentCultureIgnoreCase) ||
e.ErrorCode.Equals("SerializationException", StringComparison.CurrentCultureIgnoreCase) ||
e.ErrorCode.Equals("BadRequestException", StringComparison.CurrentCultureIgnoreCase)))
{
MobileAnalyticsErrorEventArgs eventArgs = new MobileAnalyticsErrorEventArgs(this.GetType().Name, "Amazon Mobile Analytics Service returned an error.", e, eventList);
_maManager.OnRaiseErrorEvent(eventArgs);
_logger.InfoFormat("The error code is not retriable. Delete {0} events from local storage.", rowIds.Count);
_eventStore.DeleteEvent(rowIds);
}
else
{
MobileAnalyticsErrorEventArgs eventArgs = new MobileAnalyticsErrorEventArgs(this.GetType().Name, "Amazon Mobile Analytics Service returned an error.", e, new List());
_maManager.OnRaiseErrorEvent(eventArgs);
}
}
catch (AmazonServiceException e)
{
_logger.Error(e, "An AmazonServiceException occurred while sending Amazon Mobile Analytics request: error code is {0} ; error type is {1} ; request id is {2} ; status code is {3} ; error message is {4} ", e.ErrorCode, e.ErrorType, e.RequestId, e.StatusCode, e.Message);
MobileAnalyticsErrorEventArgs eventArgs = new MobileAnalyticsErrorEventArgs(this.GetType().Name, "Amazon Web Service returned an error.", e, new List());
_maManager.OnRaiseErrorEvent(eventArgs);
}
catch (Exception e)
{
_logger.Error(e, "An exception occurred while sending Amazon Mobile Analytics request.");
MobileAnalyticsErrorEventArgs eventArgs = new MobileAnalyticsErrorEventArgs(this.GetType().Name, "An exception occurred when sending request to Amazon Mobile Analytics.", e, new List());
_maManager.OnRaiseErrorEvent(eventArgs);
}
finally
{
if (resp != null && resp.HttpStatusCode == HttpStatusCode.Accepted)
{
_logger.InfoFormat("Mobile Analytics client successfully delivered {0} events to service. Delete those events from local storage.", rowIds.Count);
_eventStore.DeleteEvent(rowIds);
}
lock (_deliveryLock)
{
_deliveryInProgress = false;
}
}
}
#endif
private void EnqueueEventsHelper(Amazon.MobileAnalytics.Model.Event eventObject)
{
string eventString = null;
try
{
eventString = JsonMapper.ToJson(eventObject);
}
catch (Exception e)
{
_logger.Error(e, "An exception occurred when converting low level client event to json string.");
List eventList = new List();
eventList.Add(eventObject);
MobileAnalyticsErrorEventArgs eventArgs = new MobileAnalyticsErrorEventArgs(this.GetType().Name, "An exception occurred when converting low level client event to json string.", e, eventList);
_maManager.OnRaiseErrorEvent(eventArgs);
}
if (null != eventString)
{
try
{
_eventStore.PutEvent(eventString, _appID);
}
catch (Exception e)
{
_logger.Error(e, "Event {0} was not stored.", eventObject.EventType);
MobileAnalyticsErrorEventArgs eventArgs = new MobileAnalyticsErrorEventArgs(this.GetType().Name, "An exception occurred when storing event into event store.", e, new List());
_maManager.OnRaiseErrorEvent(eventArgs);
}
_logger.DebugFormat("Event {0} is queued for delivery", eventObject.EventType);
}
}
#region dispose pattern implementation
///
/// Dispose pattern implementation
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Dispose pattern implementation
///
/// if disposing
protected virtual void Dispose(bool disposing)
{
if(disposing)
{
_eventStore.Dispose();
_mobileAnalyticsLowLevelClient.Dispose();
}
}
#endregion
}
}