killbill-memoizeit

Merge branch 'integration' of git@github.com:ning/killbill

2/22/2012 12:46:33 AM

Details

diff --git a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
index d623338..1735062 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
@@ -36,14 +36,15 @@ public class AnalyticsListener
     }
 
     @Subscribe
-    public void handleSubscriptionTransitionChange(final SubscriptionTransition event) throws AccountApiException {
+    public void handleSubscriptionTransitionChange(final SubscriptionTransition event) throws AccountApiException
+    {
         switch (event.getTransitionType()) {
             case MIGRATE_ENTITLEMENT:
                 // TODO do nothing for now
-            break;
+                break;
             case CREATE:
                 bstRecorder.subscriptionCreated(event);
-            break;
+                break;
             case CANCEL:
                 bstRecorder.subscriptionCancelled(event);
                 break;
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransition.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransition.java
index 8da7ff0..6c3a393 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransition.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransition.java
@@ -18,6 +18,8 @@ package com.ning.billing.analytics;
 
 import org.joda.time.DateTime;
 
+import java.util.UUID;
+
 /**
  * Describe a state change between two BusinessSubscription
  * <p/>
@@ -25,6 +27,7 @@ import org.joda.time.DateTime;
  */
 public class BusinessSubscriptionTransition
 {
+    private final UUID id;
     private final String key;
     private final String accountKey;
     private final DateTime requestedTimestamp;
@@ -32,8 +35,11 @@ public class BusinessSubscriptionTransition
     private final BusinessSubscription previousSubscription;
     private final BusinessSubscription nextSubscription;
 
-    public BusinessSubscriptionTransition(final String key, final String accountKey, final DateTime requestedTimestamp, final BusinessSubscriptionEvent event, final BusinessSubscription previousSubscription, final BusinessSubscription nextsubscription)
+    public BusinessSubscriptionTransition(final UUID id, final String key, final String accountKey, final DateTime requestedTimestamp, final BusinessSubscriptionEvent event, final BusinessSubscription previousSubscription, final BusinessSubscription nextsubscription)
     {
+        if (id == null) {
+            throw new IllegalArgumentException("An event must have an id");
+        }
         if (key == null) {
             throw new IllegalArgumentException("An event must have an key");
         }
@@ -47,6 +53,7 @@ public class BusinessSubscriptionTransition
             throw new IllegalArgumentException("No event specified");
         }
 
+        this.id = id;
         this.key = key;
         this.accountKey = accountKey;
         this.requestedTimestamp = requestedTimestamp;
@@ -55,6 +62,11 @@ public class BusinessSubscriptionTransition
         this.nextSubscription = nextsubscription;
     }
 
+    public UUID getId()
+    {
+        return id;
+    }
+
     public BusinessSubscriptionEvent getEvent()
     {
         return event;
@@ -90,10 +102,11 @@ public class BusinessSubscriptionTransition
     {
         final StringBuilder sb = new StringBuilder();
         sb.append("BusinessSubscriptionTransition");
-        sb.append("{event=").append(event);
+        sb.append("{accountKey='").append(accountKey).append('\'');
+        sb.append(", id=").append(id);
         sb.append(", key='").append(key).append('\'');
-        sb.append(", accountKey='").append(accountKey).append('\'');
         sb.append(", requestedTimestamp=").append(requestedTimestamp);
+        sb.append(", event=").append(event);
         sb.append(", previousSubscription=").append(previousSubscription);
         sb.append(", nextSubscription=").append(nextSubscription);
         sb.append('}');
@@ -112,13 +125,16 @@ public class BusinessSubscriptionTransition
 
         final BusinessSubscriptionTransition that = (BusinessSubscriptionTransition) o;
 
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
         if (event != null ? !event.equals(that.event) : that.event != null) {
             return false;
         }
-        if (key != null ? !key.equals(that.key) : that.key != null) {
+        if (id != null ? !id.equals(that.id) : that.id != null) {
             return false;
         }
-        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+        if (key != null ? !key.equals(that.key) : that.key != null) {
             return false;
         }
         if (nextSubscription != null ? !nextSubscription.equals(that.nextSubscription) : that.nextSubscription != null) {
@@ -137,7 +153,8 @@ public class BusinessSubscriptionTransition
     @Override
     public int hashCode()
     {
-        int result = key != null ? key.hashCode() : 0;
+        int result = id != null ? id.hashCode() : 0;
+        result = 31 * result + (key != null ? key.hashCode() : 0);
         result = 31 * result + (accountKey != null ? accountKey.hashCode() : 0);
         result = 31 * result + (requestedTimestamp != null ? requestedTimestamp.hashCode() : 0);
         result = 31 * result + (event != null ? event.hashCode() : 0);
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java
index 0384fab..438850f 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java
@@ -30,6 +30,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.List;
+import java.util.UUID;
 
 public class BusinessSubscriptionTransitionRecorder
 {
@@ -47,37 +48,45 @@ public class BusinessSubscriptionTransitionRecorder
         this.accountApi = accountApi;
     }
 
-    public void subscriptionCreated(final SubscriptionTransition created) throws AccountApiException {
+    public void subscriptionCreated(final SubscriptionTransition created) throws AccountApiException
+    {
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCreated(created.getNextPlan());
         recordTransition(event, created);
     }
 
-    public void subscriptionCancelled(final SubscriptionTransition cancelled) throws AccountApiException {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(cancelled.getNextPlan());
+    public void subscriptionCancelled(final SubscriptionTransition cancelled) throws AccountApiException
+    {
+        // cancelled.getNextPlan() is null here - need to look at the previous one to create the correct event name
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(cancelled.getPreviousPlan());
         recordTransition(event, cancelled);
     }
 
-    public void subscriptionChanged(final SubscriptionTransition changed) throws AccountApiException {
+    public void subscriptionChanged(final SubscriptionTransition changed) throws AccountApiException
+    {
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionChanged(changed.getNextPlan());
         recordTransition(event, changed);
     }
 
-    public void subscriptionPaused(final SubscriptionTransition paused) throws AccountApiException {
+    public void subscriptionPaused(final SubscriptionTransition paused) throws AccountApiException
+    {
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPaused(paused.getNextPlan());
         recordTransition(event, paused);
     }
 
-    public void subscriptionResumed(final SubscriptionTransition resumed) throws AccountApiException {
+    public void subscriptionResumed(final SubscriptionTransition resumed) throws AccountApiException
+    {
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionResumed(resumed.getNextPlan());
         recordTransition(event, resumed);
     }
 
-    public void subscriptionPhaseChanged(final SubscriptionTransition phaseChanged) throws AccountApiException {
+    public void subscriptionPhaseChanged(final SubscriptionTransition phaseChanged) throws AccountApiException
+    {
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPhaseChanged(phaseChanged.getNextPlan(), phaseChanged.getNextState());
         recordTransition(event, phaseChanged);
     }
 
-    public void recordTransition(final BusinessSubscriptionEvent event, final SubscriptionTransition transition) throws AccountApiException {
+    public void recordTransition(final BusinessSubscriptionEvent event, final SubscriptionTransition transition) throws AccountApiException
+    {
         Currency currency = null;
         String transitionKey = null;
         String accountKey = null;
@@ -113,15 +122,24 @@ public class BusinessSubscriptionTransitionRecorder
         else {
             prevSubscription = new BusinessSubscription(transition.getPreviousPriceList(), transition.getPreviousPlan(), transition.getPreviousPhase(), currency, previousEffectiveTransitionTime, transition.getPreviousState(), transition.getSubscriptionId(), transition.getBundleId());
         }
-        final BusinessSubscription nextSubscription = new BusinessSubscription(transition.getNextPriceList(), transition.getNextPlan(), transition.getNextPhase(), currency, transition.getEffectiveTransitionTime(), transition.getNextState(), transition.getSubscriptionId(), transition.getBundleId());
+        final BusinessSubscription nextSubscription;
+
+        // next plan is null for CANCEL events
+        if (transition.getNextPlan() == null) {
+            nextSubscription = null;
+        }
+        else {
+            nextSubscription = new BusinessSubscription(transition.getNextPriceList(), transition.getNextPlan(), transition.getNextPhase(), currency, transition.getEffectiveTransitionTime(), transition.getNextState(), transition.getSubscriptionId(), transition.getBundleId());
+        }
 
-        record(transitionKey, accountKey, transition.getRequestedTransitionTime(), event, prevSubscription, nextSubscription);
+        record(transition.getId(), transitionKey, accountKey, transition.getRequestedTransitionTime(), event, prevSubscription, nextSubscription);
     }
 
     // Public for internal reasons
-    public void record(final String key, final String accountKey, final DateTime requestedDateTime, final BusinessSubscriptionEvent event, final BusinessSubscription prevSubscription, final BusinessSubscription nextSubscription)
+    public void record(final UUID id, final String key, final String accountKey, final DateTime requestedDateTime, final BusinessSubscriptionEvent event, final BusinessSubscription prevSubscription, final BusinessSubscription nextSubscription)
     {
         final BusinessSubscriptionTransition transition = new BusinessSubscriptionTransition(
+            id,
             key,
             accountKey,
             requestedDateTime,
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
index 374f296..b769e5a 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
@@ -43,6 +43,7 @@ public @interface BusinessSubscriptionTransitionBinder
             {
                 public void bind(final SQLStatement q, final BusinessSubscriptionTransitionBinder bind, final BusinessSubscriptionTransition arg)
                 {
+                    q.bind("event_id", arg.getId().toString());
                     q.bind("event_key", arg.getKey());
                     q.bind("account_key", arg.getAccountKey());
                     q.bind("requested_timestamp", arg.getRequestedTimestamp().getMillis());
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionMapper.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionMapper.java
index a31d104..ed41ab5 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionMapper.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionMapper.java
@@ -38,20 +38,20 @@ public class BusinessSubscriptionTransitionMapper implements ResultSetMapper<Bus
     public BusinessSubscriptionTransition map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException
     {
         BusinessSubscription prev = new BusinessSubscription(
-            r.getString(5), // productName
-            r.getString(6), // productType
-            r.getString(7) == null ? null : ProductCategory.valueOf(r.getString(7)), // productCategory
-            r.getString(8), // slug
-            r.getString(9),  // phase
-            r.getString(10),  // billing period
-            BigDecimal.valueOf(r.getDouble(11)), // price
-            r.getString(12), // priceList
-            BigDecimal.valueOf(r.getDouble(13)), // mrr
-            r.getString(14), // currency
-            r.getLong(15) == 0 ? null : new DateTime(r.getLong(15), DateTimeZone.UTC), // startDate
-            r.getString(16) == null ? null : SubscriptionState.valueOf(r.getString(16)), // state
-            r.getString(17) == null ? null : UUID.fromString(r.getString(17)), // subscriptionId
-            r.getString(18) == null ? null : UUID.fromString(r.getString(18)) //bundleId
+            r.getString(6), // productName
+            r.getString(7), // productType
+            r.getString(8) == null ? null : ProductCategory.valueOf(r.getString(8)), // productCategory
+            r.getString(9), // slug
+            r.getString(10),  // phase
+            r.getString(11),  // billing period
+            BigDecimal.valueOf(r.getDouble(12)), // price
+            r.getString(13), // priceList
+            BigDecimal.valueOf(r.getDouble(14)), // mrr
+            r.getString(15), // currency
+            r.getLong(16) == 0 ? null : new DateTime(r.getLong(16), DateTimeZone.UTC), // startDate
+            r.getString(17) == null ? null : SubscriptionState.valueOf(r.getString(17)), // state
+            r.getString(18) == null ? null : UUID.fromString(r.getString(18)), // subscriptionId
+            r.getString(19) == null ? null : UUID.fromString(r.getString(19)) //bundleId
         );
 
         // Avoid creating a dummy subscriptions with all null fields
@@ -60,20 +60,20 @@ public class BusinessSubscriptionTransitionMapper implements ResultSetMapper<Bus
         }
 
         BusinessSubscription next = new BusinessSubscription(
-            r.getString(19), // productName
-            r.getString(20), // productType
-            r.getString(21) == null ? null : ProductCategory.valueOf(r.getString(21)), // productCategory
-            r.getString(22), // slug8
-            r.getString(23),  // phase
-            r.getString(24),  // billing period
-            BigDecimal.valueOf(r.getDouble(25)), // price
-            r.getString(26), // priceList
-            BigDecimal.valueOf(r.getDouble(27)), // mrr
-            r.getString(28), // currency
-            r.getLong(29) == 0 ? null : new DateTime(r.getLong(29), DateTimeZone.UTC), // startDate
-            r.getString(30) == null ? null : SubscriptionState.valueOf(r.getString(30)), // state
-            r.getString(31) == null ? null : UUID.fromString(r.getString(31)), // subscriptionId
-            r.getString(32) == null ? null : UUID.fromString(r.getString(32)) //bundleId
+            r.getString(20), // productName
+            r.getString(21), // productType
+            r.getString(22) == null ? null : ProductCategory.valueOf(r.getString(22)), // productCategory
+            r.getString(23), // slug8
+            r.getString(24),  // phase
+            r.getString(25),  // billing period
+            BigDecimal.valueOf(r.getDouble(26)), // price
+            r.getString(27), // priceList
+            BigDecimal.valueOf(r.getDouble(28)), // mrr
+            r.getString(29), // currency
+            r.getLong(30) == 0 ? null : new DateTime(r.getLong(30), DateTimeZone.UTC), // startDate
+            r.getString(31) == null ? null : SubscriptionState.valueOf(r.getString(31)), // state
+            r.getString(32) == null ? null : UUID.fromString(r.getString(32)), // subscriptionId
+            r.getString(33) == null ? null : UUID.fromString(r.getString(33)) //bundleId
         );
 
         // Avoid creating a dummy subscriptions with all null fields
@@ -81,12 +81,13 @@ public class BusinessSubscriptionTransitionMapper implements ResultSetMapper<Bus
             next = null;
         }
 
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.valueOf(r.getString(4));
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.valueOf(r.getString(5));
 
         return new BusinessSubscriptionTransition(
-            r.getString(1),
+            UUID.fromString(r.getString(1)),
             r.getString(2),
-            new DateTime(r.getLong(3), DateTimeZone.UTC),
+            r.getString(3),
+            new DateTime(r.getLong(4), DateTimeZone.UTC),
             event,
             prev,
             next
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDao.sql.stg b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDao.sql.stg
index 1654b5b..de6076e 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDao.sql.stg
+++ b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDao.sql.stg
@@ -2,7 +2,8 @@ group BusinessSubscriptionTransition;
 
 getTransitions(event_key) ::= <<
   select
-    event_key
+    event_id
+  , event_key
   , account_key
   , requested_timestamp
   , event
@@ -41,8 +42,9 @@ getTransitions(event_key) ::= <<
 >>
 
 createTransition() ::= <<
-  insert into bst(
-    event_key
+  insert ignore into bst(
+    event_id
+  , event_key
   , account_key
   , requested_timestamp
   , event
@@ -75,7 +77,8 @@ createTransition() ::= <<
   , next_subscription_id
   , next_bundle_id
   ) values (
-    :event_key
+    :event_id
+  , :event_key
   , :account_key
   , :requested_timestamp
   , :event
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql b/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
index 49e48f0..6489b12 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
+++ b/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
@@ -1,6 +1,7 @@
 drop table if exists bst;
 create table bst (
-  event_key varchar(50) not null
+  event_id char(36) not null
+, event_key varchar(50) not null
 , account_key varchar(50) not null
 , requested_timestamp bigint not null
 , event varchar(50) not null
@@ -32,6 +33,7 @@ create table bst (
 , next_state varchar(32) default null
 , next_subscription_id varchar(100) default null
 , next_bundle_id varchar(100) default null
+, primary key(event_id)
 ) engine=innodb;
 create index bst_key_index on bst (event_key, requested_timestamp asc);
 
diff --git a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
index 6054529..d869e88 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
@@ -72,6 +72,7 @@ import static org.testng.Assert.fail;
 @Guice(modules = AnalyticsTestModule.class)
 public class TestAnalyticsService
 {
+    private static final UUID ID = UUID.randomUUID();
     private static final String KEY = "12345";
     private static final String ACCOUNT_KEY = "pierre-12345";
     private static final DefaultTagDefinition TAG_ONE = new DefaultTagDefinition("batch20", "something", "pierre", new DateTime(DateTimeZone.UTC));
@@ -165,7 +166,7 @@ public class TestAnalyticsService
         final String priceList = "something";
 
         transition = new SubscriptionTransitionData(
-            UUID.randomUUID(),
+            ID,
             subscriptionId,
             bundle.getId(),
             EntitlementEvent.EventType.API_USER,
@@ -182,6 +183,7 @@ public class TestAnalyticsService
             priceList
         );
         expectedTransition = new BusinessSubscriptionTransition(
+            ID,
             KEY,
             ACCOUNT_KEY,
             requestedTransitionTime,
diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java b/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
index c17cdd4..a9a5422 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
@@ -52,6 +52,7 @@ import java.util.UUID;
 
 public class TestAnalyticsDao
 {
+    private static final UUID EVENT_ID = UUID.randomUUID();
     private static final String EVENT_KEY = "12345";
     private static final String ACCOUNT_KEY = "pierre-143343-vcc";
 
@@ -84,7 +85,7 @@ public class TestAnalyticsDao
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(plan);
         final DateTime requestedTimestamp = new DateTime(DateTimeZone.UTC);
 
-        transition = new BusinessSubscriptionTransition(EVENT_KEY, ACCOUNT_KEY, requestedTimestamp, event, prevSubscription, nextSubscription);
+        transition = new BusinessSubscriptionTransition(EVENT_ID, EVENT_KEY, ACCOUNT_KEY, requestedTimestamp, event, prevSubscription, nextSubscription);
 
         final IDBI dbi = helper.getDBI();
         businessSubscriptionTransitionDao = dbi.onDemand(BusinessSubscriptionTransitionDao.class);
@@ -130,9 +131,50 @@ public class TestAnalyticsDao
     }
 
     @Test(groups = "slow")
+    public void testHandleDuplicatedEvents()
+    {
+        final BusinessSubscriptionTransition transitionWithNullPrev = new BusinessSubscriptionTransition(
+            transition.getId(),
+            transition.getKey(),
+            transition.getAccountKey(),
+            transition.getRequestedTimestamp(),
+            transition.getEvent(),
+            null,
+            transition.getNextSubscription()
+        );
+
+        businessSubscriptionTransitionDao.createTransition(transitionWithNullPrev);
+        List<BusinessSubscriptionTransition> transitions = businessSubscriptionTransitionDao.getTransitions(EVENT_KEY);
+        Assert.assertEquals(transitions.size(), 1);
+        Assert.assertEquals(transitions.get(0), transitionWithNullPrev);
+        // Try to add the same transition, with the same UUID - we should only store one though
+        businessSubscriptionTransitionDao.createTransition(transitionWithNullPrev);
+        transitions = businessSubscriptionTransitionDao.getTransitions(EVENT_KEY);
+        Assert.assertEquals(transitions.size(), 1);
+        Assert.assertEquals(transitions.get(0), transitionWithNullPrev);
+
+        // Try now to store a look-alike transition (same fields except UUID) - we should store it this time
+        final BusinessSubscriptionTransition secondTransitionWithNullPrev = new BusinessSubscriptionTransition(
+            UUID.randomUUID(),
+            transition.getKey(),
+            transition.getAccountKey(),
+            transition.getRequestedTimestamp(),
+            transition.getEvent(),
+            null,
+            transition.getNextSubscription()
+        );
+        businessSubscriptionTransitionDao.createTransition(secondTransitionWithNullPrev);
+        transitions = businessSubscriptionTransitionDao.getTransitions(EVENT_KEY);
+        Assert.assertEquals(transitions.size(), 2);
+        Assert.assertTrue(transitions.contains(transitionWithNullPrev));
+        Assert.assertTrue(transitions.contains(secondTransitionWithNullPrev));
+    }
+
+    @Test(groups = "slow")
     public void testTransitionsWithNullPrevSubscription()
     {
         final BusinessSubscriptionTransition transitionWithNullPrev = new BusinessSubscriptionTransition(
+            transition.getId(),
             transition.getKey(),
             transition.getAccountKey(),
             transition.getRequestedTimestamp(),
@@ -151,6 +193,7 @@ public class TestAnalyticsDao
     public void testTransitionsWithNullNextSubscription()
     {
         final BusinessSubscriptionTransition transitionWithNullNext = new BusinessSubscriptionTransition(
+            transition.getId(),
             transition.getKey(),
             transition.getAccountKey(),
             transition.getRequestedTimestamp(),
@@ -170,6 +213,7 @@ public class TestAnalyticsDao
     {
         final BusinessSubscription subscriptionWithNullFields = new BusinessSubscription(null, plan, phase, Currency.USD, null, null, null, null);
         final BusinessSubscriptionTransition transitionWithNullFields = new BusinessSubscriptionTransition(
+            transition.getId(),
             transition.getKey(),
             transition.getAccountKey(),
             transition.getRequestedTimestamp(),
@@ -189,6 +233,7 @@ public class TestAnalyticsDao
     {
         final BusinessSubscription subscriptionWithNullPlanAndPhase = new BusinessSubscription(null, null, null, Currency.USD, null, null, null, null);
         final BusinessSubscriptionTransition transitionWithNullPlanAndPhase = new BusinessSubscriptionTransition(
+            transition.getId(),
             transition.getKey(),
             transition.getAccountKey(),
             transition.getRequestedTimestamp(),
@@ -213,6 +258,7 @@ public class TestAnalyticsDao
     {
         final BusinessSubscription subscriptionWithNullPlan = new BusinessSubscription(null, null, phase, Currency.USD, null, null, null, null);
         final BusinessSubscriptionTransition transitionWithNullPlan = new BusinessSubscriptionTransition(
+            transition.getId(),
             transition.getKey(),
             transition.getAccountKey(),
             transition.getRequestedTimestamp(),
@@ -233,6 +279,7 @@ public class TestAnalyticsDao
     {
         final BusinessSubscription subscriptionWithNullPhase = new BusinessSubscription(null, plan, null, Currency.USD, null, null, null, null);
         final BusinessSubscriptionTransition transitionWithNullPhase = new BusinessSubscriptionTransition(
+            transition.getId(),
             transition.getKey(),
             transition.getAccountKey(),
             transition.getRequestedTimestamp(),
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
index ec6d9ae..a9a8293 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
@@ -17,8 +17,6 @@
 package com.ning.billing.analytics;
 
 
-import java.util.UUID;
-
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
@@ -30,13 +28,14 @@ import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.user.ApiEventType;
 import com.ning.billing.util.clock.ClockMock;
-
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import java.util.UUID;
+
 
 public class TestAnalyticsListener
 {
@@ -68,7 +67,7 @@ public class TestAnalyticsListener
         final DateTime effectiveTransitionTime = new DateTime(DateTimeZone.UTC);
         final DateTime requestedTransitionTime = new DateTime(DateTimeZone.UTC);
         final SubscriptionTransitionData firstTransition = createFirstSubscriptionTransition(requestedTransitionTime, effectiveTransitionTime);
-        final BusinessSubscriptionTransition firstBST = createExpectedFirstBST(requestedTransitionTime, effectiveTransitionTime);
+        final BusinessSubscriptionTransition firstBST = createExpectedFirstBST(firstTransition.getId(), requestedTransitionTime, effectiveTransitionTime);
         listener.handleSubscriptionTransitionChange(firstTransition);
         Assert.assertEquals(dao.getTransitions(KEY).size(), 1);
         Assert.assertEquals(dao.getTransitions(KEY).get(0), firstBST);
@@ -77,7 +76,7 @@ public class TestAnalyticsListener
         final DateTime effectivePauseTransitionTime = new DateTime(DateTimeZone.UTC);
         final DateTime requestedPauseTransitionTime = new DateTime(DateTimeZone.UTC);
         final SubscriptionTransitionData pausedSubscriptionTransition = createPauseSubscriptionTransition(effectivePauseTransitionTime, requestedPauseTransitionTime, firstTransition.getNextState());
-        final BusinessSubscriptionTransition pausedBST = createExpectedPausedBST(requestedPauseTransitionTime, effectivePauseTransitionTime, firstBST.getNextSubscription());
+        final BusinessSubscriptionTransition pausedBST = createExpectedPausedBST(pausedSubscriptionTransition.getId(), requestedPauseTransitionTime, effectivePauseTransitionTime, firstBST.getNextSubscription());
         listener.handleSubscriptionTransitionChange(pausedSubscriptionTransition);
         Assert.assertEquals(dao.getTransitions(KEY).size(), 2);
         Assert.assertEquals(dao.getTransitions(KEY).get(1), pausedBST);
@@ -86,7 +85,7 @@ public class TestAnalyticsListener
         final DateTime effectiveResumeTransitionTime = new DateTime(DateTimeZone.UTC);
         final DateTime requestedResumeTransitionTime = new DateTime(DateTimeZone.UTC);
         final SubscriptionTransitionData resumedSubscriptionTransition = createResumeSubscriptionTransition(requestedResumeTransitionTime, effectiveResumeTransitionTime, pausedSubscriptionTransition.getNextState());
-        final BusinessSubscriptionTransition resumedBST = createExpectedResumedBST(requestedResumeTransitionTime, effectiveResumeTransitionTime, pausedBST.getNextSubscription());
+        final BusinessSubscriptionTransition resumedBST = createExpectedResumedBST(resumedSubscriptionTransition.getId(), requestedResumeTransitionTime, effectiveResumeTransitionTime, pausedBST.getNextSubscription());
         listener.handleSubscriptionTransitionChange(resumedSubscriptionTransition);
         Assert.assertEquals(dao.getTransitions(KEY).size(), 3);
         Assert.assertEquals(dao.getTransitions(KEY).get(2), resumedBST);
@@ -94,41 +93,42 @@ public class TestAnalyticsListener
         // Cancel it
         final DateTime effectiveCancelTransitionTime = new DateTime(DateTimeZone.UTC);
         final DateTime requestedCancelTransitionTime = new DateTime(DateTimeZone.UTC);
-        listener.handleSubscriptionTransitionChange(createCancelSubscriptionTransition(requestedCancelTransitionTime, effectiveCancelTransitionTime, resumedSubscriptionTransition.getNextState()));
-        final BusinessSubscriptionTransition cancelledBST = createExpectedCancelledBST(requestedCancelTransitionTime, effectiveCancelTransitionTime, resumedBST.getNextSubscription());
+        final SubscriptionTransitionData cancelledSubscriptionTransition = createCancelSubscriptionTransition(requestedCancelTransitionTime, effectiveCancelTransitionTime, resumedSubscriptionTransition.getNextState());
+        final BusinessSubscriptionTransition cancelledBST = createExpectedCancelledBST(cancelledSubscriptionTransition.getId(), requestedCancelTransitionTime, effectiveCancelTransitionTime, resumedBST.getNextSubscription());
+        listener.handleSubscriptionTransitionChange(cancelledSubscriptionTransition);
         Assert.assertEquals(dao.getTransitions(KEY).size(), 4);
         Assert.assertEquals(dao.getTransitions(KEY).get(3), cancelledBST);
     }
 
-    private BusinessSubscriptionTransition createExpectedFirstBST(final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime)
+    private BusinessSubscriptionTransition createExpectedFirstBST(final UUID id, final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime)
     {
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCreated(plan);
         final Subscription.SubscriptionState subscriptionState = Subscription.SubscriptionState.ACTIVE;
-        return createExpectedBST(event, requestedTransitionTime, effectiveTransitionTime, null, subscriptionState);
+        return createExpectedBST(id, event, requestedTransitionTime, effectiveTransitionTime, null, subscriptionState);
     }
 
-    private BusinessSubscriptionTransition createExpectedPausedBST(final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final BusinessSubscription lastSubscription)
+    private BusinessSubscriptionTransition createExpectedPausedBST(final UUID id, final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final BusinessSubscription lastSubscription)
     {
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPaused(plan);
         final Subscription.SubscriptionState subscriptionState = Subscription.SubscriptionState.PAUSED;
-        return createExpectedBST(event, requestedTransitionTime, effectiveTransitionTime, lastSubscription, subscriptionState);
+        return createExpectedBST(id, event, requestedTransitionTime, effectiveTransitionTime, lastSubscription, subscriptionState);
     }
 
-    private BusinessSubscriptionTransition createExpectedResumedBST(final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final BusinessSubscription lastSubscription)
+    private BusinessSubscriptionTransition createExpectedResumedBST(final UUID id, final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final BusinessSubscription lastSubscription)
     {
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionResumed(plan);
         final Subscription.SubscriptionState nextState = Subscription.SubscriptionState.ACTIVE;
-        return createExpectedBST(event, requestedTransitionTime, effectiveTransitionTime, lastSubscription, nextState);
+        return createExpectedBST(id, event, requestedTransitionTime, effectiveTransitionTime, lastSubscription, nextState);
     }
 
-    private BusinessSubscriptionTransition createExpectedCancelledBST(final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final BusinessSubscription lastSubscription)
+    private BusinessSubscriptionTransition createExpectedCancelledBST(final UUID id, final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final BusinessSubscription lastSubscription)
     {
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(plan);
-        final Subscription.SubscriptionState nextState = Subscription.SubscriptionState.CANCELLED;
-        return createExpectedBST(event, requestedTransitionTime, effectiveTransitionTime, lastSubscription, nextState);
+        return createExpectedBST(id, event, requestedTransitionTime, effectiveTransitionTime, lastSubscription, null);
     }
 
     private BusinessSubscriptionTransition createExpectedBST(
+        final UUID eventId,
         final BusinessSubscriptionEvent eventType,
         final DateTime requestedTransitionTime,
         final DateTime effectiveTransitionTime,
@@ -137,12 +137,13 @@ public class TestAnalyticsListener
     )
     {
         return new BusinessSubscriptionTransition(
+            eventId,
             KEY,
             ACCOUNT_KEY,
             requestedTransitionTime,
             eventType,
             previousSubscription,
-            new BusinessSubscription(
+            nextState == null ? null : new BusinessSubscription(
                 null,
                 plan,
                 phase,
@@ -195,8 +196,24 @@ public class TestAnalyticsListener
     private SubscriptionTransitionData createCancelSubscriptionTransition(final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final Subscription.SubscriptionState previousState)
     {
         final ApiEventType eventType = ApiEventType.CANCEL;
-        final Subscription.SubscriptionState nextState = Subscription.SubscriptionState.CANCELLED;
-        return createSubscriptionTransition(eventType, requestedTransitionTime, effectiveTransitionTime, previousState, nextState);
+        // next state is null for canceled events
+        return new SubscriptionTransitionData(
+            UUID.randomUUID(),
+            subscriptionId,
+            bundleUUID,
+            EntitlementEvent.EventType.API_USER,
+            eventType,
+            requestedTransitionTime,
+            effectiveTransitionTime,
+            previousState,
+            plan,
+            phase,
+            priceList,
+            null,
+            null,
+            null,
+            null
+        );
     }
 
     private SubscriptionTransitionData createSubscriptionTransition(
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
index a8a954c..3b33bef 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
@@ -28,6 +28,8 @@ import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import java.util.UUID;
+
 import static com.ning.billing.catalog.api.Currency.USD;
 
 public class TestBusinessSubscriptionTransition
@@ -36,6 +38,7 @@ public class TestBusinessSubscriptionTransition
     private BusinessSubscription nextSubscription;
     private BusinessSubscriptionEvent event;
     private DateTime requestedTimestamp;
+    private UUID id;
     private String key;
     private String accountKey;
     private BusinessSubscriptionTransition transition;
@@ -53,9 +56,10 @@ public class TestBusinessSubscriptionTransition
         nextSubscription = new BusinessSubscription(nextISubscription, USD);
         event = BusinessSubscriptionEvent.subscriptionCancelled(prevISubscription.getCurrentPlan());
         requestedTimestamp = new DateTime(DateTimeZone.UTC);
+        id = UUID.randomUUID();
         key = "1234";
         accountKey = "pierre-1234";
-        transition = new BusinessSubscriptionTransition(key, accountKey, requestedTimestamp, event, prevSubscription, nextSubscription);
+        transition = new BusinessSubscriptionTransition(id, key, accountKey, requestedTimestamp, event, prevSubscription, nextSubscription);
     }
 
     @Test(groups = "fast")
@@ -76,22 +80,22 @@ public class TestBusinessSubscriptionTransition
 
         BusinessSubscriptionTransition otherTransition;
 
-        otherTransition = new BusinessSubscriptionTransition(key, accountKey, new DateTime(), event, prevSubscription, nextSubscription);
+        otherTransition = new BusinessSubscriptionTransition(id, key, accountKey, new DateTime(), event, prevSubscription, nextSubscription);
         Assert.assertTrue(!transition.equals(otherTransition));
 
-        otherTransition = new BusinessSubscriptionTransition("12345", accountKey, requestedTimestamp, event, prevSubscription, nextSubscription);
+        otherTransition = new BusinessSubscriptionTransition(id, "12345", accountKey, requestedTimestamp, event, prevSubscription, nextSubscription);
         Assert.assertTrue(!transition.equals(otherTransition));
 
-        otherTransition = new BusinessSubscriptionTransition(key, accountKey, requestedTimestamp, BusinessSubscriptionEvent.subscriptionPaused(null), prevSubscription, nextSubscription);
+        otherTransition = new BusinessSubscriptionTransition(id, key, accountKey, requestedTimestamp, BusinessSubscriptionEvent.subscriptionPaused(null), prevSubscription, nextSubscription);
         Assert.assertTrue(!transition.equals(otherTransition));
 
-        otherTransition = new BusinessSubscriptionTransition(key, accountKey, requestedTimestamp, event, prevSubscription, prevSubscription);
+        otherTransition = new BusinessSubscriptionTransition(id, key, accountKey, requestedTimestamp, event, prevSubscription, prevSubscription);
         Assert.assertTrue(!transition.equals(otherTransition));
 
-        otherTransition = new BusinessSubscriptionTransition(key, accountKey, requestedTimestamp, event, nextSubscription, nextSubscription);
+        otherTransition = new BusinessSubscriptionTransition(id, key, accountKey, requestedTimestamp, event, nextSubscription, nextSubscription);
         Assert.assertTrue(!transition.equals(otherTransition));
 
-        otherTransition = new BusinessSubscriptionTransition(key, accountKey, requestedTimestamp, event, nextSubscription, prevSubscription);
+        otherTransition = new BusinessSubscriptionTransition(id, key, accountKey, requestedTimestamp, event, nextSubscription, prevSubscription);
         Assert.assertTrue(!transition.equals(otherTransition));
     }
 
@@ -99,7 +103,15 @@ public class TestBusinessSubscriptionTransition
     public void testRejectInvalidTransitions() throws Exception
     {
         try {
-            new BusinessSubscriptionTransition(null, accountKey, requestedTimestamp, event, prevSubscription, nextSubscription);
+            new BusinessSubscriptionTransition(null, key, accountKey, requestedTimestamp, event, prevSubscription, nextSubscription);
+            Assert.fail();
+        }
+        catch (IllegalArgumentException e) {
+            Assert.assertTrue(true);
+        }
+
+        try {
+            new BusinessSubscriptionTransition(id, null, accountKey, requestedTimestamp, event, prevSubscription, nextSubscription);
             Assert.fail();
         }
         catch (IllegalArgumentException e) {
@@ -107,7 +119,7 @@ public class TestBusinessSubscriptionTransition
         }
 
         try {
-            new BusinessSubscriptionTransition(key, accountKey, null, event, prevSubscription, nextSubscription);
+            new BusinessSubscriptionTransition(id, key, accountKey, null, event, prevSubscription, nextSubscription);
             Assert.fail();
         }
         catch (IllegalArgumentException e) {
@@ -115,7 +127,7 @@ public class TestBusinessSubscriptionTransition
         }
 
         try {
-            new BusinessSubscriptionTransition(key, accountKey, requestedTimestamp, null, prevSubscription, nextSubscription);
+            new BusinessSubscriptionTransition(id, key, accountKey, requestedTimestamp, null, prevSubscription, nextSubscription);
             Assert.fail();
         }
         catch (IllegalArgumentException e) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
index 0f5375b..a2e6839 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
@@ -125,7 +125,7 @@ public class Engine implements EventListener, EntitlementService {
                 }
                 @Override
                 public long getDaoClaimTimeMs() {
-                    return config.getDaoMaxReadyEvents();
+                    return config.getDaoClaimTimeMs();
                 }
             });
         } catch (NotificationQueueAlreadyExists e) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
index 0dd5e3a..5107d71 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
@@ -48,15 +48,15 @@ public class DefaultNextBillingDateNotifier implements  NextBillingDateNotifier 
     private final EntitlementDao entitlementDao;
 
     private NotificationQueue nextBillingQueue;
-	private InvoiceListener listener;
+	private final InvoiceListener listener;
 
     @Inject
-	public DefaultNextBillingDateNotifier(NotificationQueueService notificationQueueService, 
+	public DefaultNextBillingDateNotifier(NotificationQueueService notificationQueueService,
 			InvoiceConfig config, EntitlementDao entitlementDao, InvoiceListener listener){
 		this.notificationQueueService = notificationQueueService;
 		this.config = config;
         this.entitlementDao = entitlementDao;
-        this.listener = listener; 
+        this.listener = listener;
 	}
 
     @Override
@@ -97,7 +97,7 @@ public class DefaultNextBillingDateNotifier implements  NextBillingDateNotifier 
                 }
                 @Override
                 public long getDaoClaimTimeMs() {
-                    return config.getDaoMaxReadyEvents();
+                    return config.getDaoClaimTimeMs();
                 }
             });
         } catch (NotificationQueueAlreadyExists e) {
@@ -121,5 +121,5 @@ public class DefaultNextBillingDateNotifier implements  NextBillingDateNotifier 
         listener.handleNextBillingDateEvent(subscriptionId, eventDateTime);
     }
 
- 
+
 }
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
index cc1ea28..a4f4c97 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
@@ -182,6 +182,11 @@ public abstract class NotificationQueueBase implements NotificationQueue {
         waitForNotificationStartCompletion();
     }
 
+    @Override
+    public String toString() {
+        return getFullQName();
+    }
+
     private void completedQueueStop() {
     	synchronized (this) {
     		stoppedComplete = true;