diff --git a/clickstream/src/main/java/software/aws/solution/clickstream/client/EventRecorder.java b/clickstream/src/main/java/software/aws/solution/clickstream/client/EventRecorder.java index a5bc1f1..cef0362 100644 --- a/clickstream/src/main/java/software/aws/solution/clickstream/client/EventRecorder.java +++ b/clickstream/src/main/java/software/aws/solution/clickstream/client/EventRecorder.java @@ -29,6 +29,7 @@ import java.util.Locale; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -101,6 +102,7 @@ public Uri recordEvent(@NonNull final AnalyticsEvent event) { } } else { LOG.error(String.format("Error to save event with EventType: %s", event.getEventType())); + sendEventImmediately(event); } return uri; } @@ -207,5 +209,21 @@ String[] getBatchOfEvents(final Cursor cursor) { return new String[] {eventBuilder.toString(), lastEventId}; } + + /** + * Method for send event immediately when event saved fail. + * @param event AnalyticsEvent + */ + public void sendEventImmediately(AnalyticsEvent event) { + Runnable task = () -> { + NetRequest.uploadEvents("[" + event.toJSONObject().toString() + "]", + clickstreamContext.getClickstreamConfiguration(), + bundleSequenceId); + bundleSequenceId += 1; + clickstreamContext.getSystem().getPreferences() + .putInt(KEY_BUNDLE_SEQUENCE_ID_PREF, bundleSequenceId); + }; + Executors.newSingleThreadExecutor().execute(task); + } } diff --git a/clickstream/src/main/java/software/aws/solution/clickstream/client/db/ClickstreamDBUtil.java b/clickstream/src/main/java/software/aws/solution/clickstream/client/db/ClickstreamDBUtil.java index 40114b4..7f64b7b 100644 --- a/clickstream/src/main/java/software/aws/solution/clickstream/client/db/ClickstreamDBUtil.java +++ b/clickstream/src/main/java/software/aws/solution/clickstream/client/db/ClickstreamDBUtil.java @@ -18,14 +18,20 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.database.SQLException; import android.net.Uri; +import com.amazonaws.logging.Log; +import com.amazonaws.logging.LogFactory; import software.aws.solution.clickstream.client.AnalyticsEvent; +import software.aws.solution.clickstream.client.EventRecorder; /** * Clickstream Database Util. */ public class ClickstreamDBUtil { + private static final Log LOG = LogFactory.getLog(EventRecorder.class); + /** * ClickstreamDBBase is a basic helper for accessing the database. */ @@ -58,7 +64,13 @@ public void closeDB() { * @return An Uri of the record inserted. */ public Uri saveEvent(final AnalyticsEvent event) { - return clickstreamDBBase.insert(clickstreamDBBase.getContentUri(), generateContentValuesFromEvent(event)); + Uri uri = null; + try { + uri = clickstreamDBBase.insert(clickstreamDBBase.getContentUri(), generateContentValuesFromEvent(event)); + } catch (SQLException error) { + LOG.warn("SQLException: " + error.getMessage()); + } + return uri; } private ContentValues generateContentValuesFromEvent(final AnalyticsEvent event) { diff --git a/clickstream/src/test/java/software/aws/solution/clickstream/EventRecorderTest.java b/clickstream/src/test/java/software/aws/solution/clickstream/EventRecorderTest.java index 93d067f..32d5618 100644 --- a/clickstream/src/test/java/software/aws/solution/clickstream/EventRecorderTest.java +++ b/clickstream/src/test/java/software/aws/solution/clickstream/EventRecorderTest.java @@ -69,6 +69,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) @@ -567,6 +568,23 @@ public void testRecordEventRequestUploadTimestamp() throws Exception { assertTrue(requestResult); } + /** + * test save event failed and send event immediately. + * + * @throws Exception exception. + */ + @Test + public void testSaveEventFailedAndWillSendEventImmediately() throws Exception { + dbUtil = mock(ClickstreamDBUtil.class); + ReflectUtil.modifyFiled(eventRecorder, "dbUtil", dbUtil); + when(dbUtil.saveEvent(event)).thenReturn(null); + eventRecorder.recordEvent(event); + verify(log).error("Error to save event with EventType: testEvent"); + Thread.sleep(1500); + int bundleSequenceId = (int) ReflectUtil.getFiled(eventRecorder, "bundleSequenceId"); + assertTrue(bundleSequenceId > 1); + } + /** * common method to set request path. * diff --git a/clickstream/src/test/java/software/aws/solution/clickstream/db/DBUtilTest.java b/clickstream/src/test/java/software/aws/solution/clickstream/db/DBUtilTest.java index 7735ae5..ff0a441 100644 --- a/clickstream/src/test/java/software/aws/solution/clickstream/db/DBUtilTest.java +++ b/clickstream/src/test/java/software/aws/solution/clickstream/db/DBUtilTest.java @@ -15,7 +15,9 @@ package software.aws.solution.clickstream.db; +import android.content.ContentValues; import android.database.Cursor; +import android.database.SQLException; import android.net.Uri; import androidx.test.core.app.ApplicationProvider; @@ -28,13 +30,19 @@ import org.robolectric.annotation.Config; import software.aws.solution.clickstream.AnalyticsEventTest; import software.aws.solution.clickstream.client.AnalyticsEvent; +import software.aws.solution.clickstream.client.db.ClickstreamDBBase; import software.aws.solution.clickstream.client.db.ClickstreamDBUtil; +import software.aws.solution.clickstream.util.ReflectUtil; import java.util.Objects; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE, sdk = 26) @@ -63,6 +71,21 @@ public void testInsertSingleEvent() { assertNotEquals(idInserted, 0); } + /** + * test insert single event failed. + * + * @throws Exception exception. + */ + @Test + public void testInsertSingleEventFailed() throws Exception { + ClickstreamDBBase clickstreamDBBase = mock(ClickstreamDBBase.class); + ReflectUtil.modifyFiled(dbUtil, "clickstreamDBBase", clickstreamDBBase); + doThrow(new SQLException("Mocked SQLException")).when(clickstreamDBBase).insert(any(Uri.class), any( + ContentValues.class)); + Uri uri = dbUtil.saveEvent(analyticsEvent); + assertNull(uri); + } + /** * test query all. */