/*
* Copyright 2015 Amazon Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://aws.amazon.com/apache2.0
*
* 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.eclipse.core.mobileanalytics.batchclient.internal;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.eclipse.core.mobileanalytics.batchclient.MobileAnalyticsBatchClient;
import com.amazonaws.handlers.AsyncHandler;
import com.amazonaws.services.mobileanalytics.AmazonMobileAnalyticsAsync;
import com.amazonaws.services.mobileanalytics.AmazonMobileAnalyticsAsyncClient;
import com.amazonaws.services.mobileanalytics.model.Event;
import com.amazonaws.services.mobileanalytics.model.PutEventsRequest;
import com.amazonaws.services.mobileanalytics.model.PutEventsResult;
/**
* An implementation of MobileAnalyticsBatchClient which uses a bounded queue
* for caching incoming events and a single-threaded service client for sending
* out event batches. It also guarantees that the events are sent in the same
* order as they are accepted by the client.
*/
public class MobileAnalyticsBatchClientImpl implements MobileAnalyticsBatchClient {
private static final int MIN_EVENT_BATCH_SIZE = 20;
private static final int MAX_QUEUE_SIZE = 500;
/**
* Mobile Analytics async client with a single background thread
*/
private final AmazonMobileAnalyticsAsync mobileAnalytics;
/**
* The x-amz-client-context header string to be included in every PutEvents
* request
*/
private final String clientContextString;
/**
* For caching incoming events for batching
*/
private final EventQueue eventQueue = new EventQueue();
/**
* To keep track of the on-going putEvents request and make sure only one
* request can be made at a time.
*/
private final AtomicBoolean isSendingPutEventsRequest = new AtomicBoolean(false);
public MobileAnalyticsBatchClientImpl(
AWSCredentialsProvider credentialsProvider,
String clientContextString) {
this.mobileAnalytics = new AmazonMobileAnalyticsAsyncClient(
credentialsProvider, Executors.newFixedThreadPool(1));
this.clientContextString = clientContextString;
}
public void putEvent(Event event) {
// we don't lock the queue when accepting incoming event, and the
// queue size is only a rough estimate.
int queueSize = eventQueue.size();
// keep the queue bounded
if (queueSize >= MAX_QUEUE_SIZE) {
tryDispatchAllEventsAsync();
return;
}
eventQueue.addToTail(event);
if (queueSize >= MIN_EVENT_BATCH_SIZE) {
tryDispatchAllEventsAsync();
}
}
public void flush() {
tryDispatchAllEventsAsync();
}
/**
* To make sure the order of the analytic events is preserved, this method
* call will immediately return if there is an ongoing PutEvents call.
*/
private void tryDispatchAllEventsAsync() {
boolean contentionDetected = this.isSendingPutEventsRequest
.getAndSet(true);
if (!contentionDetected) {
dispatchAllEventsAsync();
}
}
/**
* Only one thread can call this method at a time
*/
private void dispatchAllEventsAsync() {
final List<Event> eventsBatch = this.eventQueue.pollAllQueuedEvents();
mobileAnalytics.putEventsAsync(
new PutEventsRequest().withClientContext(clientContextString)
.withEvents(eventsBatch),
new AsyncHandler<PutEventsRequest, PutEventsResult>() {
public void onSuccess(PutEventsRequest arg0, PutEventsResult arg1) {
markRequestDone();
}
public void onError(Exception arg0) {
restoreEventsQueue(eventsBatch);
markRequestDone();
}
private void restoreEventsQueue(List<Event> failedBatch) {
MobileAnalyticsBatchClientImpl.this.eventQueue
.addToHead(failedBatch);
}
private void markRequestDone() {
MobileAnalyticsBatchClientImpl.this.isSendingPutEventsRequest
.set(false);
}
});
}
}