killbill-uncached

Changes

account/pom.xml 2(+1 -1)

analytics/pom.xml 22(+21 -1)

api/pom.xml 2(+1 -1)

beatrix/pom.xml 2(+1 -1)

catalog/pom.xml 2(+1 -1)

invoice/pom.xml 2(+1 -1)

payment/pom.xml 2(+1 -1)

pom.xml 45(+29 -16)

util/pom.xml 2(+1 -1)

Details

account/pom.xml 2(+1 -1)

diff --git a/account/pom.xml b/account/pom.xml
index d388d5e..11823a4 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.0.14-SNAPSHOT</version>
+        <version>0.0.15-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-account</artifactId>
diff --git a/account/src/main/java/com/ning/billing/account/api/EntityBase.java b/account/src/main/java/com/ning/billing/account/api/EntityBase.java
index da67bc8..f4283d5 100644
--- a/account/src/main/java/com/ning/billing/account/api/EntityBase.java
+++ b/account/src/main/java/com/ning/billing/account/api/EntityBase.java
@@ -48,11 +48,6 @@ public abstract class EntityBase implements IEntity, IPersistable {
     }
 
     @Override
-    public String getIdAsString() {
-        return id.toString();
-    }
-
-    @Override
     public boolean isNew() {
         return this.isNew;
     }
diff --git a/account/src/main/java/com/ning/billing/account/glue/AccountModule.java b/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
index 62b66e1..dc7d349 100644
--- a/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
+++ b/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
@@ -17,7 +17,6 @@
 package com.ning.billing.account.glue;
 
 import com.google.inject.AbstractModule;
-import com.ning.billing.account.api.AccountService;
 import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.account.api.IAccountService;
 import com.ning.billing.account.api.IAccountUserApi;
@@ -48,21 +47,26 @@ public class AccountModule extends AbstractModule {
         bind(IAccountUserApi.class).to(AccountUserApi.class).asEagerSingleton();
     }
 
-    private void installAccountService() {
-        bind(IAccountService.class).to(AccountService.class).asEagerSingleton();
-    }
+//    private void installAccountService() {
+//        bind(IAccountService.class).to(AccountService.class).asEagerSingleton();
+//    }
 
     private void installFieldStore() {
         bind(IFieldStoreDao.class).to(FieldStoreDao.class).asEagerSingleton();
     }
 
+    protected void installInjectorMagic() {
+        bind(InjectorMagic.class).asEagerSingleton();
+    }
+
     @Override
     protected void configure() {
         installConfig();
         installAccountCore();
         installAccountDao();
         installAccountUserApi();
-        installAccountService();
+//        installAccountService();
         installFieldStore();
+        installInjectorMagic();
     }
 }

analytics/pom.xml 22(+21 -1)

diff --git a/analytics/pom.xml b/analytics/pom.xml
index 3755178..f5cc263 100644
--- a/analytics/pom.xml
+++ b/analytics/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.0.14-SNAPSHOT</version>
+        <version>0.0.15-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-analytics</artifactId>
@@ -74,6 +74,26 @@
         </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-account</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-catalog</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-entitlement</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
             <artifactId>killbill-util</artifactId>
             <type>test-jar</type>
             <scope>test</scope>
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 564db0e..415a002 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
@@ -18,149 +18,51 @@ package com.ning.billing.analytics;
 
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
-import com.ning.billing.account.api.IAccount;
-import com.ning.billing.account.api.IAccountUserApi;
-import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionDao;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.entitlement.api.user.IEntitlementUserApi;
-import com.ning.billing.entitlement.api.user.ISubscriptionBundle;
 import com.ning.billing.entitlement.api.user.ISubscriptionTransition;
 
-import org.joda.time.DateTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.List;
-
 public class AnalyticsListener
 {
-    private static final Logger log = LoggerFactory.getLogger(AnalyticsListener.class);
-
-    private final BusinessSubscriptionTransitionDao dao;
-    private final IEntitlementUserApi entitlementApi;
-    private final IAccountUserApi accountApi;
+    private final BusinessSubscriptionTransitionRecorder bstRecorder;
+    private final BusinessAccountRecorder bacRecorder;
 
     @Inject
-    public AnalyticsListener(final BusinessSubscriptionTransitionDao dao, final IEntitlementUserApi entitlementApi, final IAccountUserApi accountApi)
+    public AnalyticsListener(final BusinessSubscriptionTransitionRecorder bstRecorder, final BusinessAccountRecorder bacRecorder)
     {
-        this.dao = dao;
-        this.entitlementApi = entitlementApi;
-        this.accountApi = accountApi;
+        this.bstRecorder = bstRecorder;
+        this.bacRecorder = bacRecorder;
     }
 
-    /*
-     * Disable until we fix IRS to allow for two instances (One for bilr proxy, or for killbill)
-     * @Subscribe
-     */
-    public void handleNotificationChange(ISubscriptionTransition event) {
-        switch (event.getTransitionType()) {
-        case CREATE:
-            subscriptionCreated(event);
-            break;
-        case CANCEL:
-            subscriptionCancelled(event);
-            break;
-        case CHANGE:
-            subscriptionChanged(event);
-            break;
-        case PAUSE:
-            subscriptionPaused(event);
-            break;
-        case RESUME:
-            subscriptionResumed(event);
-            break;
-        case UNCANCEL:
-            break;
-        case PHASE:
-            subscriptionPhaseChanged(event);
-            break;
-        default:
-            throw new RuntimeException("Unexpected event type " + event.getRequestedTransitionTime());
-        }
-    }
-
-    public void subscriptionCreated(final ISubscriptionTransition created)
+    @Subscribe
+    public void handleSubscriptionTransitionChange(final ISubscriptionTransition event)
     {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCreated(created.getNextPlan());
-        recordTransition(event, created);
-    }
-
-    public void subscriptionCancelled(final ISubscriptionTransition cancelled)
-    {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(cancelled.getNextPlan());
-        recordTransition(event, cancelled);
-    }
-
-    public void subscriptionChanged(final ISubscriptionTransition changed)
-    {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionChanged(changed.getNextPlan());
-        recordTransition(event, changed);
-    }
-
-    public void subscriptionPaused(final ISubscriptionTransition paused)
-    {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPaused(paused.getNextPlan());
-        recordTransition(event, paused);
-    }
-
-    public void subscriptionResumed(final ISubscriptionTransition resumed)
-    {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionResumed(resumed.getNextPlan());
-        recordTransition(event, resumed);
-    }
-
-    public void subscriptionPhaseChanged(final ISubscriptionTransition phaseChanged)
-    {
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPhaseChanged(phaseChanged.getNextPlan(), phaseChanged.getNextState());
-        recordTransition(event, phaseChanged);
-    }
-
-    private void recordTransition(final BusinessSubscriptionEvent event, final ISubscriptionTransition transition)
-    {
-        Currency currency = null;
-        String transitionKey = null;
-
-        // Retrieve key and currency via the bundle
-        final ISubscriptionBundle bundle = entitlementApi.getBundleFromId(transition.getBundleId());
-        if (bundle != null) {
-            transitionKey = bundle.getKey();
-
-            final IAccount account = accountApi.getAccountById(bundle.getAccountId());
-            if (account != null) {
-                currency = account.getCurrency();
-            }
-        }
-
-        // The ISubscriptionTransition interface gives us all the prev/next information we need but the start date
-        // of the previous plan. We need to retrieve it from our own transitions table
-        DateTime previousEffectiveTransitionTime = null;
-        final List<BusinessSubscriptionTransition> transitions = dao.getTransitions(transitionKey);
-        if (transitions != null) {
-            final BusinessSubscriptionTransition lastTransition = transitions.get(transitions.size() - 1);
-            if (lastTransition != null && lastTransition.getNextSubscription() != null) {
-                previousEffectiveTransitionTime = lastTransition.getNextSubscription().getStartDate();
-            }
+        switch (event.getTransitionType()) {
+            case CREATE:
+                bstRecorder.subscriptionCreated(event);
+                break;
+            case CANCEL:
+                bstRecorder.subscriptionCancelled(event);
+                break;
+            case CHANGE:
+                bstRecorder.subscriptionChanged(event);
+                break;
+            case PAUSE:
+                bstRecorder.subscriptionPaused(event);
+                break;
+            case RESUME:
+                bstRecorder.subscriptionResumed(event);
+                break;
+            case UNCANCEL:
+                break;
+            case PHASE:
+                bstRecorder.subscriptionPhaseChanged(event);
+                break;
+            default:
+                throw new RuntimeException("Unexpected event type " + event.getTransitionType());
         }
-
-        // TODO Support currency changes
-        final BusinessSubscription prevSubscription = new BusinessSubscription(transition.getPreviousPlan(), transition.getPreviousPhase(), currency, previousEffectiveTransitionTime, transition.getPreviousState(), transition.getSubscriptionId(), transition.getBundleId());
-        final BusinessSubscription nextSubscription = new BusinessSubscription(transition.getNextPlan(), transition.getNextPhase(), currency, transition.getEffectiveTransitionTime(), transition.getNextState(), transition.getSubscriptionId(), transition.getBundleId());
-
-        recordTransition(transitionKey, transition.getRequestedTransitionTime(), event, prevSubscription, nextSubscription);
     }
 
-    // Public for now for internal reasons
-    public void recordTransition(final String key, final DateTime requestedDateTime, final BusinessSubscriptionEvent event, final BusinessSubscription prevSubscription, final BusinessSubscription nextSubscription)
+    @Subscribe
+    public void handleAccountChange(final Object event)
     {
-        final BusinessSubscriptionTransition transition = new BusinessSubscriptionTransition(
-            key,
-            requestedDateTime,
-            event,
-            prevSubscription,
-            nextSubscription
-        );
-
-        log.info(transition.getEvent() + " " + transition);
-        dao.createTransition(transition);
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/api/AnalyticsService.java b/analytics/src/main/java/com/ning/billing/analytics/api/AnalyticsService.java
new file mode 100644
index 0000000..216ff9b
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/api/AnalyticsService.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.ning.billing.analytics.api;
+
+import com.google.inject.Inject;
+import com.ning.billing.analytics.AnalyticsListener;
+import com.ning.billing.lifecycle.LyfecycleHandlerType;
+import com.ning.billing.util.eventbus.IEventBus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AnalyticsService implements IAnalyticsService
+{
+    private static final Logger log = LoggerFactory.getLogger(AnalyticsService.class);
+
+    private static final String ANALYTICS_SERVICE = "analytics-service";
+
+    private final AnalyticsListener listener;
+    private final IEventBus eventBus;
+
+    @Inject
+    public AnalyticsService(final AnalyticsListener listener, final IEventBus eventBus)
+    {
+        this.listener = listener;
+        this.eventBus = eventBus;
+    }
+
+    @Override
+    public String getName()
+    {
+        return ANALYTICS_SERVICE;
+    }
+
+    @LyfecycleHandlerType(LyfecycleHandlerType.LyfecycleLevel.REGISTER_EVENTS)
+    public void registerForNotifications()
+    {
+        try {
+            eventBus.register(listener);
+        }
+        catch (IEventBus.EventBusException e) {
+            log.error("Unable to register to the EventBus!", e);
+        }
+    }
+}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
new file mode 100644
index 0000000..7c5a6da
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.ning.billing.analytics;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.IAccount;
+import com.ning.billing.account.api.IAccountUserApi;
+import com.ning.billing.analytics.dao.BusinessAccountDao;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BusinessAccountRecorder
+{
+    private static final Logger log = LoggerFactory.getLogger(BusinessAccountRecorder.class);
+
+    private final BusinessAccountDao dao;
+    private final IAccountUserApi accountApi;
+
+    @Inject
+    public BusinessAccountRecorder(final BusinessAccountDao dao, final IAccountUserApi accountApi)
+    {
+        this.dao = dao;
+        this.accountApi = accountApi;
+    }
+
+    public void subscriptionCreated(final IAccount created)
+    {
+    }
+
+    public void subscriptionUpdated(final IAccount updated)
+    {
+    }
+}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
index dc33209..ae73e4d 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
@@ -17,13 +17,7 @@
 package com.ning.billing.analytics;
 
 import com.ning.billing.analytics.utils.Rounder;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.catalog.api.IDuration;
-import com.ning.billing.catalog.api.IPlan;
-import com.ning.billing.catalog.api.IPlanPhase;
-import com.ning.billing.catalog.api.IProduct;
-import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.catalog.api.TimeUnit;
+import com.ning.billing.catalog.api.*;
 import com.ning.billing.entitlement.api.user.ISubscription;
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
@@ -53,6 +47,7 @@ public class BusinessSubscription
     private final String phase;
     private final String billingPeriod;
     private final BigDecimal price;
+    private final String priceList;
     private final BigDecimal mrr;
     private final String currency;
     private final DateTime startDate;
@@ -60,7 +55,7 @@ public class BusinessSubscription
     private final UUID subscriptionId;
     private final UUID bundleId;
 
-    public BusinessSubscription(final String productName, final String productType, final ProductCategory productCategory, final String slug, final String phase, final String billingPeriod, final BigDecimal price, final BigDecimal mrr, final String currency, final DateTime startDate, final SubscriptionState state, final UUID subscriptionId, final UUID bundleId)
+    public BusinessSubscription(final String productName, final String productType, final ProductCategory productCategory, final String slug, final String phase, final String billingPeriod, final BigDecimal price, final String priceList, final BigDecimal mrr, final String currency, final DateTime startDate, final SubscriptionState state, final UUID subscriptionId, final UUID bundleId)
     {
         this.productName = productName;
         this.productType = productType;
@@ -69,6 +64,7 @@ public class BusinessSubscription
         this.phase = phase;
         this.billingPeriod = billingPeriod;
         this.price = price;
+        this.priceList = priceList;
         this.mrr = mrr;
         this.currency = currency;
         this.startDate = startDate;
@@ -88,11 +84,13 @@ public class BusinessSubscription
      */
     BusinessSubscription(final ISubscription subscription, final Currency currency)
     {
-        this(subscription.getCurrentPlan(), subscription.getCurrentPhase(), currency, subscription.getStartDate(), subscription.getState(), subscription.getId(), subscription.getBundleId());
+        this(subscription.getCurrentPriceList(), subscription.getCurrentPlan(), subscription.getCurrentPhase(), currency, subscription.getStartDate(), subscription.getState(), subscription.getId(), subscription.getBundleId());
     }
 
-    public BusinessSubscription(final IPlan currentPlan, final IPlanPhase currentPhase, final Currency currency, final DateTime startDate, final SubscriptionState state, final UUID subscriptionId, final UUID bundleId)
+    public BusinessSubscription(final String priceList, final IPlan currentPlan, final IPlanPhase currentPhase, final Currency currency, final DateTime startDate, final SubscriptionState state, final UUID subscriptionId, final UUID bundleId)
     {
+        this.priceList = priceList;
+
         // Record plan information
         if (currentPlan != null && currentPlan.getProduct() != null) {
             final IProduct product = currentPlan.getProduct();
@@ -113,10 +111,15 @@ public class BusinessSubscription
 
             if (currentPhase.getPhaseType() != null) {
                 phase = currentPhase.getPhaseType().toString();
-                billingPeriod = currentPhase.getBillingPeriod().toString();
             }
             else {
                 phase = null;
+            }
+
+            if (currentPhase.getBillingPeriod() != null) {
+                billingPeriod = currentPhase.getBillingPeriod().toString();
+            }
+            else {
                 billingPeriod = null;
             }
 
@@ -125,16 +128,16 @@ public class BusinessSubscription
                 mrr = getMrrFromISubscription(currentPhase.getDuration(), price);
             }
             else {
-                price = null;
-                mrr = null;
+                price = BigDecimal.ZERO;
+                mrr = BigDecimal.ZERO;
             }
         }
         else {
             slug = null;
             phase = null;
             billingPeriod = null;
-            price = null;
-            mrr = null;
+            price = BigDecimal.ZERO;
+            mrr = BigDecimal.ZERO;
         }
 
         if (currency != null) {
@@ -185,6 +188,11 @@ public class BusinessSubscription
         return price;
     }
 
+    public String getPriceList()
+    {
+        return priceList;
+    }
+
     public double getRoundedPrice()
     {
         return Rounder.round(price);
@@ -228,7 +236,7 @@ public class BusinessSubscription
     static BigDecimal getMrrFromISubscription(final IDuration duration, final BigDecimal price)
     {
         if (duration == null || duration.getUnit() == null || duration.getNumber() == 0) {
-            return null;
+            return BigDecimal.ZERO;
         }
 
         if (duration.getUnit().equals(TimeUnit.UNLIMITED)) {
@@ -261,6 +269,7 @@ public class BusinessSubscription
         sb.append(", slug='").append(slug).append('\'');
         sb.append(", phase='").append(phase).append('\'');
         sb.append(", price=").append(price);
+        sb.append(", priceList=").append(priceList);
         sb.append(", mrr=").append(mrr);
         sb.append(", currency='").append(currency).append('\'');
         sb.append(", startDate=").append(startDate);
@@ -301,6 +310,9 @@ public class BusinessSubscription
         if (price != null ? !(Rounder.round(price) == Rounder.round(that.price)) : that.price != null) {
             return false;
         }
+        if (priceList != null ? !priceList.equals(that.priceList) : that.priceList != null) {
+            return false;
+        }
         if (productCategory != null ? !productCategory.equals(that.productCategory) : that.productCategory != null) {
             return false;
         }
@@ -335,6 +347,7 @@ public class BusinessSubscription
         result = 31 * result + (slug != null ? slug.hashCode() : 0);
         result = 31 * result + (phase != null ? phase.hashCode() : 0);
         result = 31 * result + (price != null ? price.hashCode() : 0);
+        result = 31 * result + (priceList != null ? priceList.hashCode() : 0);
         result = 31 * result + (mrr != null ? mrr.hashCode() : 0);
         result = 31 * result + (currency != null ? currency.hashCode() : 0);
         result = 31 * result + (startDate != null ? startDate.hashCode() : 0);
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 b37e45a..8da7ff0 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransition.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransition.java
@@ -26,16 +26,20 @@ import org.joda.time.DateTime;
 public class BusinessSubscriptionTransition
 {
     private final String key;
+    private final String accountKey;
     private final DateTime requestedTimestamp;
     private final BusinessSubscriptionEvent event;
     private final BusinessSubscription previousSubscription;
     private final BusinessSubscription nextSubscription;
 
-    public BusinessSubscriptionTransition(final String key, final DateTime requestedTimestamp, final BusinessSubscriptionEvent event, final BusinessSubscription previousSubscription, final BusinessSubscription nextsubscription)
+    public BusinessSubscriptionTransition(final String key, final String accountKey, final DateTime requestedTimestamp, final BusinessSubscriptionEvent event, final BusinessSubscription previousSubscription, final BusinessSubscription nextsubscription)
     {
         if (key == null) {
             throw new IllegalArgumentException("An event must have an key");
         }
+        if (accountKey == null) {
+            throw new IllegalArgumentException("An event must have an account key");
+        }
         if (requestedTimestamp == null) {
             throw new IllegalArgumentException("An event must have a requestedTimestamp");
         }
@@ -44,6 +48,7 @@ public class BusinessSubscriptionTransition
         }
 
         this.key = key;
+        this.accountKey = accountKey;
         this.requestedTimestamp = requestedTimestamp;
         this.event = event;
         this.previousSubscription = previousSubscription;
@@ -60,6 +65,11 @@ public class BusinessSubscriptionTransition
         return key;
     }
 
+    public String getAccountKey()
+    {
+        return accountKey;
+    }
+
     public BusinessSubscription getNextSubscription()
     {
         return nextSubscription;
@@ -82,6 +92,7 @@ public class BusinessSubscriptionTransition
         sb.append("BusinessSubscriptionTransition");
         sb.append("{event=").append(event);
         sb.append(", key='").append(key).append('\'');
+        sb.append(", accountKey='").append(accountKey).append('\'');
         sb.append(", requestedTimestamp=").append(requestedTimestamp);
         sb.append(", previousSubscription=").append(previousSubscription);
         sb.append(", nextSubscription=").append(nextSubscription);
@@ -107,6 +118,9 @@ public class BusinessSubscriptionTransition
         if (key != null ? !key.equals(that.key) : that.key != null) {
             return false;
         }
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
         if (nextSubscription != null ? !nextSubscription.equals(that.nextSubscription) : that.nextSubscription != null) {
             return false;
         }
@@ -124,6 +138,7 @@ public class BusinessSubscriptionTransition
     public int hashCode()
     {
         int 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);
         result = 31 * result + (previousSubscription != null ? previousSubscription.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
new file mode 100644
index 0000000..5828331
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.ning.billing.analytics;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.IAccount;
+import com.ning.billing.account.api.IAccountUserApi;
+import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionDao;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.entitlement.api.user.IEntitlementUserApi;
+import com.ning.billing.entitlement.api.user.ISubscriptionBundle;
+import com.ning.billing.entitlement.api.user.ISubscriptionTransition;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+public class BusinessSubscriptionTransitionRecorder
+{
+    private static final Logger log = LoggerFactory.getLogger(BusinessSubscriptionTransitionRecorder.class);
+
+    private final BusinessSubscriptionTransitionDao dao;
+    private final IEntitlementUserApi entitlementApi;
+    private final IAccountUserApi accountApi;
+
+    @Inject
+    public BusinessSubscriptionTransitionRecorder(final BusinessSubscriptionTransitionDao dao, final IEntitlementUserApi entitlementApi, final IAccountUserApi accountApi)
+    {
+        this.dao = dao;
+        this.entitlementApi = entitlementApi;
+        this.accountApi = accountApi;
+    }
+
+    public void subscriptionCreated(final ISubscriptionTransition created)
+    {
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCreated(created.getNextPlan());
+        recordTransition(event, created);
+    }
+
+    public void subscriptionCancelled(final ISubscriptionTransition cancelled)
+    {
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(cancelled.getNextPlan());
+        recordTransition(event, cancelled);
+    }
+
+    public void subscriptionChanged(final ISubscriptionTransition changed)
+    {
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionChanged(changed.getNextPlan());
+        recordTransition(event, changed);
+    }
+
+    public void subscriptionPaused(final ISubscriptionTransition paused)
+    {
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPaused(paused.getNextPlan());
+        recordTransition(event, paused);
+    }
+
+    public void subscriptionResumed(final ISubscriptionTransition resumed)
+    {
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionResumed(resumed.getNextPlan());
+        recordTransition(event, resumed);
+    }
+
+    public void subscriptionPhaseChanged(final ISubscriptionTransition phaseChanged)
+    {
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPhaseChanged(phaseChanged.getNextPlan(), phaseChanged.getNextState());
+        recordTransition(event, phaseChanged);
+    }
+
+    public void recordTransition(final BusinessSubscriptionEvent event, final ISubscriptionTransition transition)
+    {
+        Currency currency = null;
+        String transitionKey = null;
+        String accountKey = null;
+
+        // Retrieve key and currency via the bundle
+        final ISubscriptionBundle bundle = entitlementApi.getBundleFromId(transition.getBundleId());
+        if (bundle != null) {
+            transitionKey = bundle.getKey();
+
+            final IAccount account = accountApi.getAccountById(bundle.getAccountId());
+            if (account != null) {
+                accountKey = account.getExternalKey();
+                currency = account.getCurrency();
+            }
+        }
+
+        // The ISubscriptionTransition interface gives us all the prev/next information we need but the start date
+        // of the previous plan. We need to retrieve it from our own transitions table
+        DateTime previousEffectiveTransitionTime = null;
+        final List<BusinessSubscriptionTransition> transitions = dao.getTransitions(transitionKey);
+        if (transitions != null && transitions.size() > 0) {
+            final BusinessSubscriptionTransition lastTransition = transitions.get(transitions.size() - 1);
+            if (lastTransition != null && lastTransition.getNextSubscription() != null) {
+                previousEffectiveTransitionTime = lastTransition.getNextSubscription().getStartDate();
+            }
+        }
+
+        // TODO Support currency changes
+        final BusinessSubscription prevSubscription;
+        if (previousEffectiveTransitionTime == null) {
+            prevSubscription = null;
+        }
+        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());
+
+        record(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)
+    {
+        final BusinessSubscriptionTransition transition = new BusinessSubscriptionTransition(
+            key,
+            accountKey,
+            requestedDateTime,
+            event,
+            prevSubscription,
+            nextSubscription
+        );
+
+        log.info(transition.getEvent() + " " + transition);
+        dao.createTransition(transition);
+    }
+}
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 ccf2a2b..9db1b75 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
@@ -23,11 +23,7 @@ import org.skife.jdbi.v2.sqlobject.Binder;
 import org.skife.jdbi.v2.sqlobject.BinderFactory;
 import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
 
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import java.lang.annotation.*;
 import java.sql.Types;
 
 @BindingAnnotation(BusinessSubscriptionTransitionBinder.BstBinderFactory.class)
@@ -44,6 +40,7 @@ public @interface BusinessSubscriptionTransitionBinder
                 public void bind(final SQLStatement q, final BusinessSubscriptionTransitionBinder bind, final BusinessSubscriptionTransition arg)
                 {
                     q.bind("event_key", arg.getKey());
+                    q.bind("account_key", arg.getAccountKey());
                     q.bind("requested_timestamp", arg.getRequestedTimestamp().getMillis());
                     q.bind("event", arg.getEvent().toString());
 
@@ -56,6 +53,7 @@ public @interface BusinessSubscriptionTransitionBinder
                         q.bindNull("prev_phase", Types.VARCHAR);
                         q.bindNull("prev_billing_period", Types.VARCHAR);
                         q.bindNull("prev_price", Types.NUMERIC);
+                        q.bindNull("prev_price_list", Types.VARCHAR);
                         q.bindNull("prev_mrr", Types.NUMERIC);
                         q.bindNull("prev_currency", Types.VARCHAR);
                         q.bindNull("prev_start_date", Types.BIGINT);
@@ -76,6 +74,7 @@ public @interface BusinessSubscriptionTransitionBinder
                         q.bind("prev_phase", previousSubscription.getPhase());
                         q.bind("prev_billing_period", previousSubscription.getBillingPeriod());
                         q.bind("prev_price", previousSubscription.getRoundedPrice());
+                        q.bind("prev_price_list", previousSubscription.getPriceList());
                         q.bind("prev_mrr", previousSubscription.getRoundedMrr());
                         q.bind("prev_currency", previousSubscription.getCurrency());
                         if (previousSubscription.getStartDate() == null) {
@@ -113,6 +112,7 @@ public @interface BusinessSubscriptionTransitionBinder
                         q.bindNull("next_phase", Types.VARCHAR);
                         q.bindNull("next_billing_period", Types.VARCHAR);
                         q.bindNull("next_price", Types.NUMERIC);
+                        q.bindNull("next_price_list", Types.VARCHAR);
                         q.bindNull("next_mrr", Types.NUMERIC);
                         q.bindNull("next_currency", Types.VARCHAR);
                         q.bindNull("next_start_date", Types.BIGINT);
@@ -133,6 +133,7 @@ public @interface BusinessSubscriptionTransitionBinder
                         q.bind("next_phase", nextSubscription.getPhase());
                         q.bind("next_billing_period", nextSubscription.getBillingPeriod());
                         q.bind("next_price", nextSubscription.getRoundedPrice());
+                        q.bind("next_price_list", nextSubscription.getPriceList());
                         q.bind("next_mrr", nextSubscription.getRoundedMrr());
                         q.bind("next_currency", nextSubscription.getCurrency());
                         if (nextSubscription.getStartDate() == null) {
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 55aedb9..fb5e641 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,19 +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(4), // productName
-            r.getString(5), // productType
-            r.getString(6) == null ? null : ProductCategory.valueOf(r.getString(6)), // productCategory
-            r.getString(7), // slug
-            r.getString(8),  // phase
-            r.getString(9),  // billing period
-            BigDecimal.valueOf(r.getDouble(10)), // price
-            BigDecimal.valueOf(r.getDouble(11)), // mrr
-            r.getString(12), // currency
-            r.getLong(13) == 0 ? null : new DateTime(r.getLong(13), DateTimeZone.UTC), // startDate
-            r.getString(14) == null ? null : SubscriptionState.valueOf(r.getString(14)), // state
-            r.getString(15) == null ? null : UUID.fromString(r.getString(15)), // subscriptionId
-            r.getString(16) == null ? null : UUID.fromString(r.getString(16)) //bundleId
+            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
         );
 
         // Avoid creating a dummy subscriptions with all null fields
@@ -59,19 +60,20 @@ public class BusinessSubscriptionTransitionMapper implements ResultSetMapper<Bus
         }
 
         BusinessSubscription next = new BusinessSubscription(
-            r.getString(17), // productName
-            r.getString(18), // productType
-            r.getString(19) == null ? null : ProductCategory.valueOf(r.getString(19)), // productCategory
-            r.getString(20), // slug8
-            r.getString(21),  // phase
-            r.getString(22),  // billing period
-            BigDecimal.valueOf(r.getDouble(23)), // price
-            BigDecimal.valueOf(r.getDouble(24)), // mrr
-            r.getString(25), // currency
-            r.getLong(26) == 0 ? null : new DateTime(r.getLong(26), DateTimeZone.UTC), // startDate
-            r.getString(27) == null ? null : SubscriptionState.valueOf(r.getString(27)), // state
-            r.getString(28) == null ? null : UUID.fromString(r.getString(28)), // subscriptionId
-            r.getString(29) == null ? null : UUID.fromString(r.getString(29)) //bundleId
+            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
         );
 
         // Avoid creating a dummy subscriptions with all null fields
@@ -79,11 +81,12 @@ public class BusinessSubscriptionTransitionMapper implements ResultSetMapper<Bus
             next = null;
         }
 
-        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.valueOf(r.getString(3));
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.valueOf(r.getString(4));
 
         return new BusinessSubscriptionTransition(
             r.getString(1),
-            new DateTime(r.getLong(2), DateTimeZone.UTC),
+            r.getString(2),
+            new DateTime(r.getLong(3), DateTimeZone.UTC),
             event,
             prev,
             next
diff --git a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
index 7c74808..59fc392 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
@@ -18,6 +18,11 @@ package com.ning.billing.analytics.setup;
 
 
 import com.google.inject.AbstractModule;
+import com.ning.billing.analytics.AnalyticsListener;
+import com.ning.billing.analytics.BusinessAccountRecorder;
+import com.ning.billing.analytics.BusinessSubscriptionTransitionRecorder;
+import com.ning.billing.analytics.api.AnalyticsService;
+import com.ning.billing.analytics.api.IAnalyticsService;
 import com.ning.billing.analytics.dao.BusinessAccountDao;
 import com.ning.billing.analytics.dao.BusinessAccountDaoProvider;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionDao;
@@ -30,5 +35,11 @@ public class AnalyticsModule extends AbstractModule
     {
         bind(BusinessSubscriptionTransitionDao.class).toProvider(BusinessSubscriptionTransitionDaoProvider.class).asEagerSingleton();
         bind(BusinessAccountDao.class).toProvider(BusinessAccountDaoProvider.class).asEagerSingleton();
+
+        bind(BusinessSubscriptionTransitionRecorder.class).asEagerSingleton();
+        bind(BusinessAccountRecorder.class).asEagerSingleton();
+        bind(AnalyticsListener.class).asEagerSingleton();
+
+        bind(IAnalyticsService.class).to(AnalyticsService.class).asEagerSingleton();
     }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/utils/Rounder.java b/analytics/src/main/java/com/ning/billing/analytics/utils/Rounder.java
index 0f0adef..ab5728d 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/utils/Rounder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/utils/Rounder.java
@@ -22,6 +22,11 @@ public class Rounder
 {
     public static final int SCALE = 4;
 
+    // Static only
+    private Rounder()
+    {
+    }
+
     public static double round(final BigDecimal decimal)
     {
         if (decimal == null) {
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 11e160a..1654b5b 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
@@ -3,6 +3,7 @@ group BusinessSubscriptionTransition;
 getTransitions(event_key) ::= <<
   select
     event_key
+  , account_key
   , requested_timestamp
   , event
   , prev_product_name
@@ -12,6 +13,7 @@ getTransitions(event_key) ::= <<
   , prev_phase
   , prev_billing_period
   , prev_price
+  , prev_price_list
   , prev_mrr
   , prev_currency
   , prev_start_date
@@ -25,6 +27,7 @@ getTransitions(event_key) ::= <<
   , next_phase
   , next_billing_period
   , next_price
+  , next_price_list
   , next_mrr
   , next_currency
   , next_start_date
@@ -40,6 +43,7 @@ getTransitions(event_key) ::= <<
 createTransition() ::= <<
   insert into bst(
     event_key
+  , account_key
   , requested_timestamp
   , event
   , prev_product_name
@@ -49,6 +53,7 @@ createTransition() ::= <<
   , prev_phase
   , prev_billing_period
   , prev_price
+  , prev_price_list
   , prev_mrr
   , prev_currency
   , prev_start_date
@@ -62,6 +67,7 @@ createTransition() ::= <<
   , next_phase
   , next_billing_period
   , next_price
+  , next_price_list
   , next_mrr
   , next_currency
   , next_start_date
@@ -70,6 +76,7 @@ createTransition() ::= <<
   , next_bundle_id
   ) values (
     :event_key
+  , :account_key
   , :requested_timestamp
   , :event
   , :prev_product_name
@@ -79,6 +86,7 @@ createTransition() ::= <<
   , :prev_phase
   , :prev_billing_period
   , :prev_price
+  , :prev_price_list
   , :prev_mrr
   , :prev_currency
   , :prev_start_date
@@ -92,6 +100,7 @@ createTransition() ::= <<
   , :next_phase
   , :next_billing_period
   , :next_price
+  , :next_price_list
   , :next_mrr
   , :next_currency
   , :next_start_date
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 908715a..49e48f0 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
+, account_key varchar(50) not null
 , requested_timestamp bigint not null
 , event varchar(50) not null
 , prev_product_name varchar(32) default null
@@ -10,6 +11,7 @@ create table bst (
 , prev_phase varchar(32) default null
 , prev_billing_period varchar(32) default null
 , prev_price numeric(10, 4) default 0
+, prev_price_list varchar(32) default null
 , prev_mrr numeric(10, 4) default 0
 , prev_currency varchar(32) default null
 , prev_start_date bigint default null
@@ -23,6 +25,7 @@ create table bst (
 , next_phase varchar(32) default null
 , next_billing_period varchar(32) default null
 , next_price numeric(10, 4) default 0
+, next_price_list varchar(32) default null
 , next_mrr numeric(10, 4) default 0
 , next_currency varchar(32) default null
 , next_start_date bigint default null
diff --git a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
new file mode 100644
index 0000000..03e102d
--- /dev/null
+++ b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.ning.billing.analytics;
+
+import com.ning.billing.account.glue.AccountModule;
+import com.ning.billing.analytics.setup.AnalyticsModule;
+import com.ning.billing.catalog.glue.CatalogModule;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.glue.EntitlementModule;
+import com.ning.billing.util.glue.EventBusModule;
+import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
+
+public class AnalyticsTestModule extends AnalyticsModule
+{
+    @Override
+    protected void configure()
+    {
+        super.configure();
+
+        // Need to configure a few more things for the EventBus
+        install(new AccountModule());
+        install(new CatalogModule());
+        install(new EventBusModule());
+        install(new EntitlementModule());
+
+        // Install the Dao layer
+        final MysqlTestingHelper helper = new MysqlTestingHelper();
+        bind(MysqlTestingHelper.class).toInstance(helper);
+        final DBI dbi = helper.getDBI();
+        bind(IDBI.class).toInstance(dbi);
+        bind(DBI.class).toInstance(dbi);
+    }
+}
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
new file mode 100644
index 0000000..08e5724
--- /dev/null
+++ b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.ning.billing.analytics.api;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.IAccount;
+import com.ning.billing.account.api.IAccountUserApi;
+import com.ning.billing.analytics.*;
+import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionDao;
+import com.ning.billing.catalog.api.*;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.user.*;
+import com.ning.billing.entitlement.events.IEvent;
+import com.ning.billing.entitlement.events.user.ApiEventType;
+import com.ning.billing.util.eventbus.IEventBus;
+import org.apache.commons.io.IOUtils;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.UUID;
+
+@Guice(modules = AnalyticsTestModule.class)
+public class TestAnalyticsService
+{
+    private static final String KEY = "1234";
+    private static final String ACCOUNT_KEY = "pierre-1234";
+
+    @Inject
+    private IAccountUserApi accountApi;
+
+    @Inject
+    private IEntitlementUserApi entitlementApi;
+
+    @Inject
+    private AnalyticsService service;
+
+    @Inject
+    private IEventBus bus;
+
+    @Inject
+    private BusinessSubscriptionTransitionDao dao;
+
+    @Inject
+    private MysqlTestingHelper helper;
+
+    private ISubscriptionTransition transition;
+    private BusinessSubscriptionTransition expectedTransition;
+
+    @BeforeClass(alwaysRun = true)
+    public void startMysql() throws IOException, ClassNotFoundException, SQLException, EntitlementUserApiException
+    {
+        final String analyticsDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/analytics/ddl.sql"));
+        // For bundles
+        final String accountDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
+        final String entitlementDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+
+        helper.startMysql();
+        helper.initDb(analyticsDdl);
+        helper.initDb(accountDdl);
+        helper.initDb(entitlementDdl);
+
+        // We need a bundle to retrieve the event key
+        final MockAccount account = new MockAccount(UUID.randomUUID(), ACCOUNT_KEY, Currency.USD);
+        final IAccount storedAccount = accountApi.createAccount(account);
+        final ISubscriptionBundle bundle = entitlementApi.createBundleForAccount(storedAccount, KEY);
+
+        // Verify we correctly initialized the account subsystem
+        Assert.assertNotNull(bundle);
+        Assert.assertEquals(bundle.getKey(), KEY);
+
+        // Create a subscription transition
+        final IProduct product = new MockProduct("platinium", "subscription", ProductCategory.BASE);
+        final IPlan plan = new MockPlan("platinum-monthly", product);
+        final IPlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
+        final UUID subscriptionId = UUID.randomUUID();
+        final DateTime effectiveTransitionTime = new DateTime(DateTimeZone.UTC);
+        final DateTime requestedTransitionTime = new DateTime(DateTimeZone.UTC);
+        final String priceList = "something";
+        transition = new SubscriptionTransition(
+            subscriptionId,
+            bundle.getId(),
+            IEvent.EventType.API_USER,
+            ApiEventType.CREATE,
+            requestedTransitionTime,
+            effectiveTransitionTime,
+            null,
+            null,
+            null,
+            null,
+            ISubscription.SubscriptionState.ACTIVE,
+            plan,
+            phase,
+            priceList
+        );
+        expectedTransition = new BusinessSubscriptionTransition(
+            KEY,
+            ACCOUNT_KEY,
+            requestedTransitionTime,
+            BusinessSubscriptionEvent.subscriptionCreated(plan),
+            null,
+            new BusinessSubscription(priceList, plan, phase, null, effectiveTransitionTime, ISubscription.SubscriptionState.ACTIVE, subscriptionId, bundle.getId())
+        );
+    }
+
+    @AfterClass(alwaysRun = true)
+    public void stopMysql()
+    {
+        helper.stopMysql();
+    }
+
+    @Test(groups = "slow")
+    public void testRegisterForNotifications() throws Exception
+    {
+        // Make sure the service has been instantiated
+        Assert.assertEquals(service.getName(), "analytics-service");
+
+        // Test the bus and make sure we can register our service
+        try {
+            bus.start();
+            service.registerForNotifications();
+        }
+        catch (Throwable t) {
+            Assert.fail("Unable to start the bus or service! " + t);
+        }
+
+        // Send an event to the bus and make sure our Dao got it
+        bus.post(transition);
+        Thread.sleep(1000);
+        Assert.assertEquals(dao.getTransitions(KEY).size(), 1);
+        Assert.assertEquals(dao.getTransitions(KEY).get(0), expectedTransition);
+
+        // Test the shutdown sequence
+        try {
+            bus.stop();
+        }
+        catch (Throwable t) {
+            Assert.fail("Unable to stop the bus!");
+        }
+    }
+}
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 d575ee2..9fe916e 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
@@ -16,21 +16,9 @@
 
 package com.ning.billing.analytics.dao;
 
-import com.ning.billing.analytics.BusinessAccount;
-import com.ning.billing.analytics.BusinessSubscription;
-import com.ning.billing.analytics.BusinessSubscriptionEvent;
-import com.ning.billing.analytics.BusinessSubscriptionTransition;
-import com.ning.billing.analytics.MockDuration;
-import com.ning.billing.analytics.MockPhase;
-import com.ning.billing.analytics.MockPlan;
-import com.ning.billing.analytics.MockProduct;
+import com.ning.billing.analytics.*;
 import com.ning.billing.analytics.utils.Rounder;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.catalog.api.IPlan;
-import com.ning.billing.catalog.api.IPlanPhase;
-import com.ning.billing.catalog.api.IProduct;
-import com.ning.billing.catalog.api.PhaseType;
-import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.catalog.api.*;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.entitlement.api.user.ISubscription;
 import org.apache.commons.io.IOUtils;
@@ -40,6 +28,7 @@ import org.skife.jdbi.v2.IDBI;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import java.io.IOException;
@@ -55,6 +44,9 @@ public class TestAnalyticsDao
     private static final String ACCOUNT_KEY = "pierre-143343-vcc";
 
     private final MysqlTestingHelper helper = new MysqlTestingHelper();
+    private final IProduct product = new MockProduct("platinium", "subscription", ProductCategory.BASE);
+    private final IPlan plan = new MockPlan("platinum-monthly", product);
+    private final IPlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
 
     private BusinessSubscriptionTransitionDao businessSubscriptionTransitionDao;
     private BusinessSubscriptionTransition transition;
@@ -75,16 +67,12 @@ public class TestAnalyticsDao
 
     private void setupBusinessSubscriptionTransition()
     {
-        final IProduct product = new MockProduct("platinium", "subscription", ProductCategory.BASE);
-        final IPlan plan = new MockPlan("platinum-monthly", product);
-        final IPlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
-
-        final BusinessSubscription prevSubscription = new BusinessSubscription(plan, phase, Currency.USD, new DateTime(DateTimeZone.UTC), ISubscription.SubscriptionState.ACTIVE, UUID.randomUUID(), UUID.randomUUID());
-        final BusinessSubscription nextSubscription = new BusinessSubscription(plan, phase, Currency.USD, new DateTime(DateTimeZone.UTC), ISubscription.SubscriptionState.CANCELLED, UUID.randomUUID(), UUID.randomUUID());
+        final BusinessSubscription prevSubscription = new BusinessSubscription(null, plan, phase, Currency.USD, new DateTime(DateTimeZone.UTC), ISubscription.SubscriptionState.ACTIVE, UUID.randomUUID(), UUID.randomUUID());
+        final BusinessSubscription nextSubscription = new BusinessSubscription(null, plan, phase, Currency.USD, new DateTime(DateTimeZone.UTC), ISubscription.SubscriptionState.CANCELLED, UUID.randomUUID(), UUID.randomUUID());
         final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(plan);
         final DateTime requestedTimestamp = new DateTime(DateTimeZone.UTC);
 
-        transition = new BusinessSubscriptionTransition(EVENT_KEY, requestedTimestamp, event, prevSubscription, nextSubscription);
+        transition = new BusinessSubscriptionTransition(EVENT_KEY, ACCOUNT_KEY, requestedTimestamp, event, prevSubscription, nextSubscription);
 
         final IDBI dbi = helper.getDBI();
         businessSubscriptionTransitionDao = dbi.onDemand(BusinessSubscriptionTransitionDao.class);
@@ -123,6 +111,137 @@ public class TestAnalyticsDao
         helper.stopMysql();
     }
 
+    @BeforeMethod
+    public void cleanup() throws Exception
+    {
+        helper.cleanupTable("bst");
+    }
+
+    @Test(groups = "slow")
+    public void testTransitionsWithNullPrevSubscription()
+    {
+        final BusinessSubscriptionTransition transitionWithNullPrev = new BusinessSubscriptionTransition(
+            transition.getKey(),
+            transition.getAccountKey(),
+            transition.getRequestedTimestamp(),
+            transition.getEvent(),
+            null,
+            transition.getNextSubscription()
+        );
+        businessSubscriptionTransitionDao.createTransition(transitionWithNullPrev);
+
+        final List<BusinessSubscriptionTransition> transitions = businessSubscriptionTransitionDao.getTransitions(EVENT_KEY);
+        Assert.assertEquals(transitions.size(), 1);
+        Assert.assertEquals(transitions.get(0), transitionWithNullPrev);
+    }
+
+    @Test(groups = "slow")
+    public void testTransitionsWithNullNextSubscription()
+    {
+        final BusinessSubscriptionTransition transitionWithNullNext = new BusinessSubscriptionTransition(
+            transition.getKey(),
+            transition.getAccountKey(),
+            transition.getRequestedTimestamp(),
+            transition.getEvent(),
+            transition.getPreviousSubscription(),
+            null
+        );
+        businessSubscriptionTransitionDao.createTransition(transitionWithNullNext);
+
+        final List<BusinessSubscriptionTransition> transitions = businessSubscriptionTransitionDao.getTransitions(EVENT_KEY);
+        Assert.assertEquals(transitions.size(), 1);
+        Assert.assertEquals(transitions.get(0), transitionWithNullNext);
+    }
+
+    @Test(groups = "slow")
+    public void testTransitionsWithNullFieldsInSubscription()
+    {
+        final BusinessSubscription subscriptionWithNullFields = new BusinessSubscription(null, plan, phase, Currency.USD, null, null, null, null);
+        final BusinessSubscriptionTransition transitionWithNullFields = new BusinessSubscriptionTransition(
+            transition.getKey(),
+            transition.getAccountKey(),
+            transition.getRequestedTimestamp(),
+            transition.getEvent(),
+            subscriptionWithNullFields,
+            subscriptionWithNullFields
+        );
+        businessSubscriptionTransitionDao.createTransition(transitionWithNullFields);
+
+        final List<BusinessSubscriptionTransition> transitions = businessSubscriptionTransitionDao.getTransitions(EVENT_KEY);
+        Assert.assertEquals(transitions.size(), 1);
+        Assert.assertEquals(transitions.get(0), transitionWithNullFields);
+    }
+
+    @Test(groups = "slow")
+    public void testTransitionsWithNullPlanAndPhase() throws Exception
+    {
+        final BusinessSubscription subscriptionWithNullPlanAndPhase = new BusinessSubscription(null, null, null, Currency.USD, null, null, null, null);
+        final BusinessSubscriptionTransition transitionWithNullPlanAndPhase = new BusinessSubscriptionTransition(
+            transition.getKey(),
+            transition.getAccountKey(),
+            transition.getRequestedTimestamp(),
+            transition.getEvent(),
+            subscriptionWithNullPlanAndPhase,
+            subscriptionWithNullPlanAndPhase
+        );
+        businessSubscriptionTransitionDao.createTransition(transitionWithNullPlanAndPhase);
+
+        final List<BusinessSubscriptionTransition> transitions = businessSubscriptionTransitionDao.getTransitions(EVENT_KEY);
+        Assert.assertEquals(transitions.size(), 1);
+        Assert.assertEquals(transitions.get(0).getKey(), transition.getKey());
+        Assert.assertEquals(transitions.get(0).getRequestedTimestamp(), transition.getRequestedTimestamp());
+        Assert.assertEquals(transitions.get(0).getEvent(), transition.getEvent());
+        // Null Plan and Phase doesn't make sense so we turn the subscription into a null
+        Assert.assertNull(transitions.get(0).getPreviousSubscription());
+        Assert.assertNull(transitions.get(0).getNextSubscription());
+    }
+
+    @Test(groups = "slow")
+    public void testTransitionsWithNullPlan() throws Exception
+    {
+        final BusinessSubscription subscriptionWithNullPlan = new BusinessSubscription(null, null, phase, Currency.USD, null, null, null, null);
+        final BusinessSubscriptionTransition transitionWithNullPlan = new BusinessSubscriptionTransition(
+            transition.getKey(),
+            transition.getAccountKey(),
+            transition.getRequestedTimestamp(),
+            transition.getEvent(),
+            subscriptionWithNullPlan,
+            subscriptionWithNullPlan
+        );
+        businessSubscriptionTransitionDao.createTransition(transitionWithNullPlan);
+
+        final List<BusinessSubscriptionTransition> transitions = businessSubscriptionTransitionDao.getTransitions(EVENT_KEY);
+        Assert.assertEquals(transitions.size(), 1);
+        // Null Plan but Phase - we don't turn the subscription into a null
+        Assert.assertEquals(transitions.get(0), transitionWithNullPlan);
+    }
+
+    @Test(groups = "slow")
+    public void testTransitionsWithNullPhase() throws Exception
+    {
+        final BusinessSubscription subscriptionWithNullPhase = new BusinessSubscription(null, plan, null, Currency.USD, null, null, null, null);
+        final BusinessSubscriptionTransition transitionWithNullPhase = new BusinessSubscriptionTransition(
+            transition.getKey(),
+            transition.getAccountKey(),
+            transition.getRequestedTimestamp(),
+            transition.getEvent(),
+            subscriptionWithNullPhase,
+            subscriptionWithNullPhase
+        );
+        businessSubscriptionTransitionDao.createTransition(transitionWithNullPhase);
+
+        final List<BusinessSubscriptionTransition> transitions = businessSubscriptionTransitionDao.getTransitions(EVENT_KEY);
+        Assert.assertEquals(transitions.size(), 1);
+        Assert.assertEquals(transitions.get(0).getKey(), transition.getKey());
+        Assert.assertEquals(transitions.get(0).getRequestedTimestamp(), transition.getRequestedTimestamp());
+        Assert.assertEquals(transitions.get(0).getEvent(), transition.getEvent());
+
+        // Null Phase but Plan - we don't turn the subscription into a null, however price and mrr are both set to 0 (not null)
+        final BusinessSubscription blankSubscription = new BusinessSubscription(null, plan, new MockPhase(null, null, null, 0.0), Currency.USD, null, null, null, null);
+        Assert.assertEquals(transitions.get(0).getPreviousSubscription(), blankSubscription);
+        Assert.assertEquals(transitions.get(0).getNextSubscription(), blankSubscription);
+    }
+
     @Test(groups = "slow")
     public void testCreateAndRetrieveTransitions()
     {
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
new file mode 100644
index 0000000..d9f4009
--- /dev/null
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.ning.billing.analytics;
+
+import com.ning.billing.account.api.IAccount;
+import com.ning.billing.catalog.api.Currency;
+
+import java.util.UUID;
+
+public class MockAccount implements IAccount
+{
+    private final UUID id;
+    private final String accountKey;
+    private final Currency currency;
+
+    public MockAccount(final UUID id, final String accountKey, final Currency currency)
+    {
+        this.id = id;
+        this.accountKey = accountKey;
+        this.currency = currency;
+    }
+
+    @Override
+    public String getName()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getEmail()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getPhone()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getExternalKey()
+    {
+        return accountKey;
+    }
+
+    @Override
+    public int getBillCycleDay()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Currency getCurrency()
+    {
+        return currency;
+    }
+
+    @Override
+    public UUID getId()
+    {
+        return id;
+    }
+
+    @Override
+    public boolean isNew() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setAsSaved() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockBusinessSubscriptionTransitionDao.java b/analytics/src/test/java/com/ning/billing/analytics/MockBusinessSubscriptionTransitionDao.java
new file mode 100644
index 0000000..d7e36b4
--- /dev/null
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockBusinessSubscriptionTransitionDao.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.ning.billing.analytics;
+
+import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionBinder;
+import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionDao;
+import org.skife.jdbi.v2.sqlobject.Bind;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class MockBusinessSubscriptionTransitionDao implements BusinessSubscriptionTransitionDao
+{
+    private final Map<String, List<BusinessSubscriptionTransition>> content = new HashMap<String, List<BusinessSubscriptionTransition>>();
+
+    @Override
+    public List<BusinessSubscriptionTransition> getTransitions(@Bind("event_key") final String key)
+    {
+        return content.get(key);
+    }
+
+    @Override
+    public int createTransition(@BusinessSubscriptionTransitionBinder final BusinessSubscriptionTransition transition)
+    {
+        if (content.get(transition.getKey()) == null) {
+            content.put(transition.getKey(), new ArrayList<BusinessSubscriptionTransition>());
+        }
+        content.get(transition.getKey()).add(transition);
+        return 1;
+    }
+
+    @Override
+    public void test()
+    {
+    }
+}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java b/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
new file mode 100644
index 0000000..8c78cde
--- /dev/null
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.ning.billing.analytics;
+
+import com.ning.billing.account.api.IAccount;
+import com.ning.billing.account.api.IAccountData;
+import com.ning.billing.account.api.IAccountUserApi;
+import com.ning.billing.catalog.api.Currency;
+
+import java.util.List;
+import java.util.UUID;
+
+public class MockIAccountUserApi implements IAccountUserApi
+{
+    private final MockAccount account;
+
+    public MockIAccountUserApi(final String accountKey, final Currency currency)
+    {
+        account = new MockAccount(UUID.randomUUID(), accountKey, currency);
+    }
+
+    @Override
+    public IAccount createAccount(final IAccountData data)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public IAccount getAccountByKey(final String key)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public IAccount getAccountById(final UUID uid)
+    {
+        return account;
+    }
+
+    @Override
+    public List<IAccount> getAccounts()
+    {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockIEntitlementUserApi.java b/analytics/src/test/java/com/ning/billing/analytics/MockIEntitlementUserApi.java
new file mode 100644
index 0000000..1aabb67
--- /dev/null
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockIEntitlementUserApi.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.ning.billing.analytics;
+
+import com.ning.billing.account.api.IAccount;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.IEntitlementUserApi;
+import com.ning.billing.entitlement.api.user.ISubscription;
+import com.ning.billing.entitlement.api.user.ISubscriptionBundle;
+import org.joda.time.DateTime;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+public class MockIEntitlementUserApi implements IEntitlementUserApi
+{
+    private final Map<UUID, String> subscriptionBundles = new HashMap<UUID, String>();
+
+    public MockIEntitlementUserApi(final UUID bundleUUID, final String key)
+    {
+        subscriptionBundles.put(bundleUUID, key);
+    }
+
+    @Override
+    public ISubscriptionBundle getBundleFromId(final UUID id)
+    {
+        final String key = subscriptionBundles.get(id);
+        if (key == null) {
+            return null;
+        }
+
+        return new ISubscriptionBundle()
+        {
+            @Override
+            public UUID getAccountId()
+            {
+                return UUID.randomUUID();
+            }
+
+            @Override
+            public UUID getId()
+            {
+                return id;
+            }
+
+            @Override
+            public DateTime getStartDate()
+            {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public String getKey()
+            {
+                return key;
+            }
+
+            @Override
+            public void setPrivate(final String name, final String value)
+            {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public String getPrivate(final String name)
+            {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    @Override
+    public ISubscription getSubscriptionFromId(final UUID id)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<ISubscriptionBundle> getBundlesForAccount(final UUID accountId)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<ISubscription> getSubscriptionsForBundle(final UUID bundleId)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ISubscriptionBundle createBundleForAccount(final IAccount account, final String bundleKey) throws EntitlementUserApiException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ISubscription createSubscription(final UUID bundleId, final String productName, final BillingPeriod term, final String planSet, final DateTime requestedDate) throws EntitlementUserApiException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<ISubscription> getSubscriptionsForKey(String bundleKey) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java b/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
index 4912957..10d1256 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
@@ -16,18 +16,11 @@
 
 package com.ning.billing.analytics;
 
+import com.ning.billing.catalog.api.*;
+
 import java.math.BigDecimal;
 import java.util.Date;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.catalog.api.IDuration;
-import com.ning.billing.catalog.api.IInternationalPrice;
-import com.ning.billing.catalog.api.IPlan;
-import com.ning.billing.catalog.api.IPlanPhase;
-import com.ning.billing.catalog.api.IPrice;
-import com.ning.billing.catalog.api.PhaseType;
-
 public class MockPhase implements IPlanPhase
 {
     private final PhaseType cohort;
@@ -60,10 +53,11 @@ public class MockPhase implements IPlanPhase
                 return BigDecimal.valueOf(price);
             }
 
-			@Override
-			public Date getEffectiveDateForExistingSubscriptons() {
-				return new Date();
-			}
+            @Override
+            public Date getEffectiveDateForExistingSubscriptons()
+            {
+                return new Date();
+            }
         };
     }
 
@@ -83,24 +77,30 @@ public class MockPhase implements IPlanPhase
             {
                 return BigDecimal.valueOf(price);
             }
-            
-			@Override
-			public Date getEffectiveDateForExistingSubscriptons() {
-				return new Date();
-			}
+
+            @Override
+            public Date getEffectiveDateForExistingSubscriptons()
+            {
+                return new Date();
+            }
         };
     }
 
     @Override
     public BillingPeriod getBillingPeriod()
     {
-        return BillingPeriod.MONTHLY;
+        return null;
     }
 
     @Override
     public String getName()
     {
-        return plan.getName() + "-" + cohort;
+        if (plan == null) {
+            return null;
+        }
+        else {
+            return plan.getName() + "-" + cohort;
+        }
     }
 
     @Override
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockProduct.java b/analytics/src/test/java/com/ning/billing/analytics/MockProduct.java
index 958de9a..57e0b5b 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockProduct.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockProduct.java
@@ -50,15 +50,15 @@ public class MockProduct implements IProduct
         return category;
     }
 
-	@Override
-	public IProduct[] getAvailable() {
-		// TODO Auto-generated method stub
-		return null;
-	}
+    @Override
+    public IProduct[] getAvailable()
+    {
+        return null;
+    }
 
-	@Override
-	public IProduct[] getIncluded() {
-		// TODO Auto-generated method stub
-		return null;
-	}
+    @Override
+    public IProduct[] getIncluded()
+    {
+        return null;
+    }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
index e208852..a756447 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
@@ -124,6 +124,11 @@ public class MockSubscription implements ISubscription
     @Override
     public String getCurrentPriceList()
     {
-        throw new UnsupportedOperationException();
+        return null;
+    }
+
+    @Override
+    public DateTime getEndDate() {
+        return null;
     }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
new file mode 100644
index 0000000..1aa9435
--- /dev/null
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.ning.billing.analytics;
+
+import com.ning.billing.catalog.api.*;
+import com.ning.billing.entitlement.api.user.ISubscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.events.IEvent;
+import com.ning.billing.entitlement.events.user.ApiEventType;
+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
+{
+    private static final String KEY = "1234";
+    private static final String ACCOUNT_KEY = "pierre-1234";
+    private final Currency CURRENCY = Currency.BRL;
+
+    private final MockBusinessSubscriptionTransitionDao dao = new MockBusinessSubscriptionTransitionDao();
+    private final UUID subscriptionId = UUID.randomUUID();
+    private final UUID bundleUUID = UUID.randomUUID();
+    private final IProduct product = new MockProduct("platinium", "subscription", ProductCategory.BASE);
+    private final IPlan plan = new MockPlan("platinum-monthly", product);
+    private final IPlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
+    private final String priceList = null;
+
+    private AnalyticsListener listener;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws Exception
+    {
+        final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(dao, new MockIEntitlementUserApi(bundleUUID, KEY), new MockIAccountUserApi(ACCOUNT_KEY, CURRENCY));
+        listener = new AnalyticsListener(recorder, null);
+    }
+
+    @Test(groups = "fast")
+    public void testSubscriptionLifecycle() throws Exception
+    {
+        // Create a subscription
+        final DateTime effectiveTransitionTime = new DateTime(DateTimeZone.UTC);
+        final DateTime requestedTransitionTime = new DateTime(DateTimeZone.UTC);
+        final SubscriptionTransition firstTransition = createFirstSubscriptionTransition(requestedTransitionTime, effectiveTransitionTime);
+        final BusinessSubscriptionTransition firstBST = createExpectedFirstBST(requestedTransitionTime, effectiveTransitionTime);
+        listener.handleSubscriptionTransitionChange(firstTransition);
+        Assert.assertEquals(dao.getTransitions(KEY).size(), 1);
+        Assert.assertEquals(dao.getTransitions(KEY).get(0), firstBST);
+
+        // Pause it
+        final DateTime effectivePauseTransitionTime = new DateTime(DateTimeZone.UTC);
+        final DateTime requestedPauseTransitionTime = new DateTime(DateTimeZone.UTC);
+        final SubscriptionTransition pausedSubscriptionTransition = createPauseSubscriptionTransition(effectivePauseTransitionTime, requestedPauseTransitionTime, firstTransition.getNextState());
+        final BusinessSubscriptionTransition pausedBST = createExpectedPausedBST(requestedPauseTransitionTime, effectivePauseTransitionTime, firstBST.getNextSubscription());
+        listener.handleSubscriptionTransitionChange(pausedSubscriptionTransition);
+        Assert.assertEquals(dao.getTransitions(KEY).size(), 2);
+        Assert.assertEquals(dao.getTransitions(KEY).get(1), pausedBST);
+
+        // Un-Pause it
+        final DateTime effectiveResumeTransitionTime = new DateTime(DateTimeZone.UTC);
+        final DateTime requestedResumeTransitionTime = new DateTime(DateTimeZone.UTC);
+        final SubscriptionTransition resumedSubscriptionTransition = createResumeSubscriptionTransition(requestedResumeTransitionTime, effectiveResumeTransitionTime, pausedSubscriptionTransition.getNextState());
+        final BusinessSubscriptionTransition resumedBST = createExpectedResumedBST(requestedResumeTransitionTime, effectiveResumeTransitionTime, pausedBST.getNextSubscription());
+        listener.handleSubscriptionTransitionChange(resumedSubscriptionTransition);
+        Assert.assertEquals(dao.getTransitions(KEY).size(), 3);
+        Assert.assertEquals(dao.getTransitions(KEY).get(2), resumedBST);
+
+        // 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());
+        Assert.assertEquals(dao.getTransitions(KEY).size(), 4);
+        Assert.assertEquals(dao.getTransitions(KEY).get(3), cancelledBST);
+    }
+
+    private BusinessSubscriptionTransition createExpectedFirstBST(final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime)
+    {
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCreated(plan);
+        final ISubscription.SubscriptionState subscriptionState = ISubscription.SubscriptionState.ACTIVE;
+        return createExpectedBST(event, requestedTransitionTime, effectiveTransitionTime, null, subscriptionState);
+    }
+
+    private BusinessSubscriptionTransition createExpectedPausedBST(final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final BusinessSubscription lastSubscription)
+    {
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPaused(plan);
+        final ISubscription.SubscriptionState subscriptionState = ISubscription.SubscriptionState.PAUSED;
+        return createExpectedBST(event, requestedTransitionTime, effectiveTransitionTime, lastSubscription, subscriptionState);
+    }
+
+    private BusinessSubscriptionTransition createExpectedResumedBST(final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final BusinessSubscription lastSubscription)
+    {
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionResumed(plan);
+        final ISubscription.SubscriptionState nextState = ISubscription.SubscriptionState.ACTIVE;
+        return createExpectedBST(event, requestedTransitionTime, effectiveTransitionTime, lastSubscription, nextState);
+    }
+
+    private BusinessSubscriptionTransition createExpectedCancelledBST(final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final BusinessSubscription lastSubscription)
+    {
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(plan);
+        final ISubscription.SubscriptionState nextState = ISubscription.SubscriptionState.CANCELLED;
+        return createExpectedBST(event, requestedTransitionTime, effectiveTransitionTime, lastSubscription, nextState);
+    }
+
+    private BusinessSubscriptionTransition createExpectedBST(
+        final BusinessSubscriptionEvent eventType,
+        final DateTime requestedTransitionTime,
+        final DateTime effectiveTransitionTime,
+        final BusinessSubscription previousSubscription,
+        final ISubscription.SubscriptionState nextState
+    )
+    {
+        return new BusinessSubscriptionTransition(
+            KEY,
+            ACCOUNT_KEY,
+            requestedTransitionTime,
+            eventType,
+            previousSubscription,
+            new BusinessSubscription(
+                null,
+                plan,
+                phase,
+                CURRENCY,
+                effectiveTransitionTime,
+                nextState,
+                subscriptionId,
+                bundleUUID
+            )
+        );
+    }
+
+    private SubscriptionTransition createFirstSubscriptionTransition(final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime)
+    {
+        final ApiEventType eventType = ApiEventType.CREATE;
+        final ISubscription.SubscriptionState nextState = ISubscription.SubscriptionState.ACTIVE;
+        return new SubscriptionTransition(
+            subscriptionId,
+            bundleUUID,
+            IEvent.EventType.API_USER,
+            eventType,
+            requestedTransitionTime,
+            effectiveTransitionTime,
+            null,
+            null,
+            null,
+            null,
+            nextState,
+            plan,
+            phase,
+            priceList
+        );
+    }
+
+    private SubscriptionTransition createPauseSubscriptionTransition(final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final ISubscription.SubscriptionState previousState)
+    {
+        final ApiEventType eventType = ApiEventType.PAUSE;
+        final ISubscription.SubscriptionState nextState = ISubscription.SubscriptionState.PAUSED;
+        return createSubscriptionTransition(eventType, requestedTransitionTime, effectiveTransitionTime, previousState, nextState);
+    }
+
+    private SubscriptionTransition createResumeSubscriptionTransition(final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final ISubscription.SubscriptionState previousState)
+    {
+        final ApiEventType eventType = ApiEventType.RESUME;
+        final ISubscription.SubscriptionState nextState = ISubscription.SubscriptionState.ACTIVE;
+        return createSubscriptionTransition(eventType, requestedTransitionTime, effectiveTransitionTime, previousState, nextState);
+    }
+
+    private SubscriptionTransition createCancelSubscriptionTransition(final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final ISubscription.SubscriptionState previousState)
+    {
+        final ApiEventType eventType = ApiEventType.CANCEL;
+        final ISubscription.SubscriptionState nextState = ISubscription.SubscriptionState.CANCELLED;
+        return createSubscriptionTransition(eventType, requestedTransitionTime, effectiveTransitionTime, previousState, nextState);
+    }
+
+    private SubscriptionTransition createSubscriptionTransition(
+        final ApiEventType eventType,
+        final DateTime requestedTransitionTime,
+        final DateTime effectiveTransitionTime,
+        final ISubscription.SubscriptionState previousState,
+        final ISubscription.SubscriptionState nextState
+    )
+    {
+        return new SubscriptionTransition(
+            subscriptionId,
+            bundleUUID,
+            IEvent.EventType.API_USER,
+            eventType,
+            requestedTransitionTime,
+            effectiveTransitionTime,
+            previousState,
+            plan,
+            phase,
+            priceList,
+            nextState,
+            plan,
+            phase,
+            priceList
+        );
+    }
+}
\ No newline at end of file
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
index f185a2a..92cb1de 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
@@ -16,12 +16,7 @@
 
 package com.ning.billing.analytics;
 
-import com.ning.billing.catalog.api.IDuration;
-import com.ning.billing.catalog.api.IPlan;
-import com.ning.billing.catalog.api.IPlanPhase;
-import com.ning.billing.catalog.api.IProduct;
-import com.ning.billing.catalog.api.PhaseType;
-import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.catalog.api.*;
 import com.ning.billing.entitlement.api.user.ISubscription;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
@@ -84,7 +79,7 @@ public class TestBusinessSubscription
         Assert.assertEquals(subscription.getRoundedMrr(), 0.0);
         Assert.assertEquals(subscription.getSlug(), phase.getName());
         Assert.assertEquals(subscription.getPhase(), phase.getPhaseType().toString());
-        Assert.assertEquals(subscription.getBillingPeriod(), phase.getBillingPeriod().toString());
+        Assert.assertEquals(subscription.getBillingPeriod(), phase.getBillingPeriod());
         Assert.assertEquals(subscription.getPrice(), phase.getRecurringPrice().getPrice(null));
         Assert.assertEquals(subscription.getProductCategory(), product.getCategory());
         Assert.assertEquals(subscription.getProductName(), product.getName());
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 d45a6d4..61fd399 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
@@ -16,11 +16,7 @@
 
 package com.ning.billing.analytics;
 
-import com.ning.billing.catalog.api.IPlan;
-import com.ning.billing.catalog.api.IPlanPhase;
-import com.ning.billing.catalog.api.IProduct;
-import com.ning.billing.catalog.api.PhaseType;
-import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.catalog.api.*;
 import com.ning.billing.entitlement.api.user.ISubscription;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
@@ -28,9 +24,7 @@ import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import java.util.Currency;
-
-import static com.ning.billing.catalog.api.Currency.*;
+import static com.ning.billing.catalog.api.Currency.USD;
 
 public class TestBusinessSubscriptionTransition
 {
@@ -39,6 +33,7 @@ public class TestBusinessSubscriptionTransition
     private BusinessSubscriptionEvent event;
     private DateTime requestedTimestamp;
     private String key;
+    private String accountKey;
     private BusinessSubscriptionTransition transition;
 
     @BeforeMethod(alwaysRun = true)
@@ -55,7 +50,8 @@ public class TestBusinessSubscriptionTransition
         event = BusinessSubscriptionEvent.subscriptionCancelled(prevISubscription.getCurrentPlan());
         requestedTimestamp = new DateTime(DateTimeZone.UTC);
         key = "1234";
-        transition = new BusinessSubscriptionTransition(key, requestedTimestamp, event, prevSubscription, nextSubscription);
+        accountKey = "pierre-1234";
+        transition = new BusinessSubscriptionTransition(key, accountKey, requestedTimestamp, event, prevSubscription, nextSubscription);
     }
 
     @Test(groups = "fast")
@@ -76,22 +72,22 @@ public class TestBusinessSubscriptionTransition
 
         BusinessSubscriptionTransition otherTransition;
 
-        otherTransition = new BusinessSubscriptionTransition(key, new DateTime(), event, prevSubscription, nextSubscription);
+        otherTransition = new BusinessSubscriptionTransition(key, accountKey, new DateTime(), event, prevSubscription, nextSubscription);
         Assert.assertTrue(!transition.equals(otherTransition));
 
-        otherTransition = new BusinessSubscriptionTransition("12345", requestedTimestamp, event, prevSubscription, nextSubscription);
+        otherTransition = new BusinessSubscriptionTransition("12345", accountKey, requestedTimestamp, event, prevSubscription, nextSubscription);
         Assert.assertTrue(!transition.equals(otherTransition));
 
-        otherTransition = new BusinessSubscriptionTransition(key, requestedTimestamp, BusinessSubscriptionEvent.subscriptionPaused(null), prevSubscription, nextSubscription);
+        otherTransition = new BusinessSubscriptionTransition(key, accountKey, requestedTimestamp, BusinessSubscriptionEvent.subscriptionPaused(null), prevSubscription, nextSubscription);
         Assert.assertTrue(!transition.equals(otherTransition));
 
-        otherTransition = new BusinessSubscriptionTransition(key, requestedTimestamp, event, prevSubscription, prevSubscription);
+        otherTransition = new BusinessSubscriptionTransition(key, accountKey, requestedTimestamp, event, prevSubscription, prevSubscription);
         Assert.assertTrue(!transition.equals(otherTransition));
 
-        otherTransition = new BusinessSubscriptionTransition(key, requestedTimestamp, event, nextSubscription, nextSubscription);
+        otherTransition = new BusinessSubscriptionTransition(key, accountKey, requestedTimestamp, event, nextSubscription, nextSubscription);
         Assert.assertTrue(!transition.equals(otherTransition));
 
-        otherTransition = new BusinessSubscriptionTransition(key, requestedTimestamp, event, nextSubscription, prevSubscription);
+        otherTransition = new BusinessSubscriptionTransition(key, accountKey, requestedTimestamp, event, nextSubscription, prevSubscription);
         Assert.assertTrue(!transition.equals(otherTransition));
     }
 
@@ -99,7 +95,7 @@ public class TestBusinessSubscriptionTransition
     public void testRejectInvalidTransitions() throws Exception
     {
         try {
-            new BusinessSubscriptionTransition(null, requestedTimestamp, event, prevSubscription, nextSubscription);
+            new BusinessSubscriptionTransition(null, accountKey, requestedTimestamp, event, prevSubscription, nextSubscription);
             Assert.fail();
         }
         catch (IllegalArgumentException e) {
@@ -107,7 +103,7 @@ public class TestBusinessSubscriptionTransition
         }
 
         try {
-            new BusinessSubscriptionTransition(key, null, event, prevSubscription, nextSubscription);
+            new BusinessSubscriptionTransition(key, accountKey, null, event, prevSubscription, nextSubscription);
             Assert.fail();
         }
         catch (IllegalArgumentException e) {
@@ -115,7 +111,7 @@ public class TestBusinessSubscriptionTransition
         }
 
         try {
-            new BusinessSubscriptionTransition(key, requestedTimestamp, null, prevSubscription, nextSubscription);
+            new BusinessSubscriptionTransition(key, accountKey, requestedTimestamp, null, prevSubscription, nextSubscription);
             Assert.fail();
         }
         catch (IllegalArgumentException e) {
diff --git a/analytics/src/test/java/com/ning/billing/analytics/utils/TestRounder.java b/analytics/src/test/java/com/ning/billing/analytics/utils/TestRounder.java
new file mode 100644
index 0000000..b3414d7
--- /dev/null
+++ b/analytics/src/test/java/com/ning/billing/analytics/utils/TestRounder.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.ning.billing.analytics.utils;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.math.BigDecimal;
+
+public class TestRounder
+{
+    @Test(groups = "fast")
+    public void testRound() throws Exception
+    {
+        Assert.assertEquals(Rounder.round(null), 0.0);
+        Assert.assertEquals(Rounder.round(BigDecimal.ZERO), 0.0);
+        Assert.assertEquals(Rounder.round(BigDecimal.ONE), 1.0);
+        Assert.assertEquals(Rounder.round(BigDecimal.TEN), 10.0);
+        Assert.assertEquals(Rounder.round(BigDecimal.valueOf(1.33333)), 1.3333);
+        Assert.assertEquals(Rounder.round(BigDecimal.valueOf(4444.33333)), 4444.3333);
+        Assert.assertEquals(Rounder.round(BigDecimal.valueOf(10.11111)), 10.1111);
+        Assert.assertEquals(Rounder.round(BigDecimal.valueOf(10.11115)), 10.1112);
+        Assert.assertEquals(Rounder.round(BigDecimal.valueOf(10.11116)), 10.1112);
+    }
+}

api/pom.xml 2(+1 -1)

diff --git a/api/pom.xml b/api/pom.xml
index 5385aad..6e07c7b 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.0.14-SNAPSHOT</version>
+        <version>0.0.15-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-api</artifactId>
diff --git a/api/src/main/java/com/ning/billing/account/api/IEntity.java b/api/src/main/java/com/ning/billing/account/api/IEntity.java
index 175033d..1797ea1 100644
--- a/api/src/main/java/com/ning/billing/account/api/IEntity.java
+++ b/api/src/main/java/com/ning/billing/account/api/IEntity.java
@@ -21,8 +21,6 @@ import java.util.UUID;
 public interface IEntity {
     public UUID getId();
 
-    public String getIdAsString();
-
     public boolean isNew();
 
     public void setAsSaved();
diff --git a/api/src/main/java/com/ning/billing/analytics/api/IAnalyticsService.java b/api/src/main/java/com/ning/billing/analytics/api/IAnalyticsService.java
new file mode 100644
index 0000000..2a5d0c5
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/analytics/api/IAnalyticsService.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.ning.billing.analytics.api;
+
+import com.ning.billing.lifecycle.IService;
+
+public interface IAnalyticsService extends IService {
+}
diff --git a/api/src/main/java/com/ning/billing/BillingExceptionBase.java b/api/src/main/java/com/ning/billing/BillingExceptionBase.java
index 486e5da..3a458fc 100644
--- a/api/src/main/java/com/ning/billing/BillingExceptionBase.java
+++ b/api/src/main/java/com/ning/billing/BillingExceptionBase.java
@@ -30,6 +30,12 @@ public class BillingExceptionBase extends Exception {
     private final int code;
     private final String formattedMsg;
 
+    public BillingExceptionBase(Throwable cause, int code, final String msg) {
+        this.formattedMsg = msg;
+        this.code = code;
+        this.cause = cause;
+    }
+
     public BillingExceptionBase(Throwable cause, ErrorCode code, final Object... args) {
         String tmp = null;
         try {
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/test/IEntitlementTestApi.java b/api/src/main/java/com/ning/billing/entitlement/api/test/IEntitlementTestApi.java
index fbaa68e..0473cd9 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/test/IEntitlementTestApi.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/test/IEntitlementTestApi.java
@@ -16,8 +16,11 @@
 
 package com.ning.billing.entitlement.api.test;
 
+import java.util.UUID;
+
+
 public interface IEntitlementTestApi {
 
-    public void doProcessReadyEvents();
+    public void doProcessReadyEvents(UUID [] subscriptionsIds, Boolean recursive, Boolean oneEventOnly);
 
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApiException.java b/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApiException.java
index c4b8c42..693938b 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApiException.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApiException.java
@@ -18,9 +18,15 @@ package com.ning.billing.entitlement.api.user;
 
 import com.ning.billing.BillingExceptionBase;
 import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.CatalogApiException;
 
 public class EntitlementUserApiException extends BillingExceptionBase {
 
+    private static final long serialVersionUID = 19083233L;
+
+    public EntitlementUserApiException(CatalogApiException e) {
+        super(e, e.getCode(), e.getMessage());
+    }
     public EntitlementUserApiException(Throwable e, ErrorCode code, Object...args) {
         super(e, code, args);
     }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/IEntitlementUserApi.java b/api/src/main/java/com/ning/billing/entitlement/api/user/IEntitlementUserApi.java
index f45f450..f53e8bb 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/IEntitlementUserApi.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/IEntitlementUserApi.java
@@ -16,13 +16,12 @@
 
 package com.ning.billing.entitlement.api.user;
 
-import java.util.List;
-import java.util.UUID;
-
-import org.joda.time.DateTime;
-
 import com.ning.billing.account.api.IAccount;
 import com.ning.billing.catalog.api.BillingPeriod;
+import org.joda.time.DateTime;
+
+import java.util.List;
+import java.util.UUID;
 
 
 public interface IEntitlementUserApi {
@@ -35,6 +34,8 @@ public interface IEntitlementUserApi {
 
     public List<ISubscription> getSubscriptionsForBundle(UUID bundleId);
 
+    public List<ISubscription> getSubscriptionsForKey(String bundleKey);
+
     public ISubscriptionBundle createBundleForAccount(IAccount account, String bundleKey)
         throws EntitlementUserApiException;
 
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/ISubscription.java b/api/src/main/java/com/ning/billing/entitlement/api/user/ISubscription.java
index 574de30..4b6c43e 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/ISubscription.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/ISubscription.java
@@ -16,15 +16,12 @@
 
 package com.ning.billing.entitlement.api.user;
 
-import java.util.UUID;
-
-import com.ning.billing.account.api.IAccount;
-import org.joda.time.DateTime;
-
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.IPlan;
 import com.ning.billing.catalog.api.IPlanPhase;
-import com.ning.billing.catalog.api.ActionPolicy;
+import org.joda.time.DateTime;
+
+import java.util.UUID;
 
 
 public interface ISubscription extends IPrivateFields {
@@ -59,6 +56,8 @@ public interface ISubscription extends IPrivateFields {
 
     public DateTime getStartDate();
 
+    public DateTime getEndDate();
+
     public IPlan getCurrentPlan();
 
     public String getCurrentPriceList();

beatrix/pom.xml 2(+1 -1)

diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 3571e7d..7c90e56 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.0.14-SNAPSHOT</version>
+        <version>0.0.15-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-beatrix</artifactId>

catalog/pom.xml 2(+1 -1)

diff --git a/catalog/pom.xml b/catalog/pom.xml
index 5643272..125ebdb 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.0.14-SNAPSHOT</version>
+        <version>0.0.15-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-catalog</artifactId>
diff --git a/catalog/src/main/java/com/ning/billing/catalog/InternationalPrice.java b/catalog/src/main/java/com/ning/billing/catalog/InternationalPrice.java
index 63cf388..ec88cfa 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/InternationalPrice.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/InternationalPrice.java
@@ -16,26 +16,27 @@
 
 package com.ning.billing.catalog;
 
-import java.math.BigDecimal;
-import java.util.Date;
-
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlElement;
-
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.IInternationalPrice;
 import com.ning.billing.catalog.api.IPrice;
 import com.ning.billing.util.config.ValidatingConfig;
 import com.ning.billing.util.config.ValidationErrors;
 
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import java.math.BigDecimal;
+import java.net.URI;
+import java.util.Date;
+
 @XmlAccessorType(XmlAccessType.NONE)
 public class InternationalPrice extends ValidatingConfig<Catalog> implements IInternationalPrice {
+	private static Price[] zeroPrice;
 
 	//TODO MDW Validation - effectiveDateForExistingSubscriptons > catalog effectiveDate 
 	@XmlElement(required=false)
 	private Date effectiveDateForExistingSubscriptons;
-	
+
 	//TODO: Must have a price point for every configured currency
 	//TODO: No prices is a zero cost plan
 	@XmlElement(name="price")
@@ -71,6 +72,17 @@ public class InternationalPrice extends ValidatingConfig<Catalog> implements IIn
 		return new BigDecimal(0);
 	}
 
+	protected void setEffectiveDateForExistingSubscriptons(
+			Date effectiveDateForExistingSubscriptons) {
+		this.effectiveDateForExistingSubscriptons = effectiveDateForExistingSubscriptons;
+	}
+
+	protected InternationalPrice setPrices(Price[] prices) {
+		this.prices = prices;
+		return this;
+	}
+
+
 	@Override
 	public ValidationErrors validate(Catalog catalog, ValidationErrors errors)  {
 		if(prices.length == 0) return errors;
@@ -92,15 +104,26 @@ public class InternationalPrice extends ValidatingConfig<Catalog> implements IIn
 		}
 		return false;
 	}
-	
-	protected void setEffectiveDateForExistingSubscriptons(
-			Date effectiveDateForExistingSubscriptons) {
-		this.effectiveDateForExistingSubscriptons = effectiveDateForExistingSubscriptons;
+
+	@Override
+	public void initialize(Catalog root, URI uri) {
+		if(prices == null) {
+			prices = getZeroPrice(root);
+		}
+		super.initialize(root, uri);
 	}
 
-	protected InternationalPrice setPrices(Price[] prices) {
-		this.prices = prices;
-		return this;
+	private synchronized Price[] getZeroPrice(Catalog root) {
+		if(zeroPrice == null) {
+			Currency[] currencies = root.getSupportedCurrencies();
+			zeroPrice = new Price[currencies.length];
+			for(int i = 0; i < currencies.length; i++) {
+				zeroPrice[i] = new Price();
+				zeroPrice[i].setCurrency(currencies[i]);
+				zeroPrice[i].setValue(new BigDecimal(0));
+			}
+		}
+		return zeroPrice;
 	}
 
 }
diff --git a/catalog/src/main/java/com/ning/billing/catalog/Plan.java b/catalog/src/main/java/com/ning/billing/catalog/Plan.java
index 9a994f0..8ad7151 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/Plan.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/Plan.java
@@ -16,18 +16,6 @@
 
 package com.ning.billing.catalog;
 
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Iterator;
-
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlAttribute;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElementWrapper;
-import javax.xml.bind.annotation.XmlID;
-import javax.xml.bind.annotation.XmlIDREF;
-
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.IPlan;
 import com.ning.billing.catalog.api.IPlanPhase;
@@ -35,6 +23,11 @@ import com.ning.billing.catalog.api.IProduct;
 import com.ning.billing.util.config.ValidatingConfig;
 import com.ning.billing.util.config.ValidationErrors;
 
+import javax.xml.bind.annotation.*;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Iterator;
+
 @XmlAccessorType(XmlAccessType.NONE)
 public class Plan extends ValidatingConfig<Catalog> implements IPlan {
 
@@ -119,10 +112,12 @@ public class Plan extends ValidatingConfig<Catalog> implements IPlan {
 		super.initialize(catalog, sourceURI);
 		if(finalPhase != null) {
 			finalPhase.setPlan(this);
+			finalPhase.initialize(catalog, sourceURI);
 		}
 		if(initialPhases != null) {
 			for(PlanPhase p : initialPhases) {
 				p.setPlan(this);
+				p.initialize(catalog, sourceURI);
 			}
 		}
 	}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/PlanPhase.java b/catalog/src/main/java/com/ning/billing/catalog/PlanPhase.java
index 851ef12..4f1d087 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/PlanPhase.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/PlanPhase.java
@@ -16,20 +16,16 @@
 
 package com.ning.billing.catalog;
 
+import com.ning.billing.catalog.api.*;
+import com.ning.billing.util.config.ValidatingConfig;
+import com.ning.billing.util.config.ValidationError;
+import com.ning.billing.util.config.ValidationErrors;
+
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlElement;
-
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.catalog.api.IDuration;
-import com.ning.billing.catalog.api.IInternationalPrice;
-import com.ning.billing.catalog.api.IPlan;
-import com.ning.billing.catalog.api.IPlanPhase;
-import com.ning.billing.catalog.api.PhaseType;
-import com.ning.billing.util.config.ValidatingConfig;
-import com.ning.billing.util.config.ValidationError;
-import com.ning.billing.util.config.ValidationErrors;
+import java.net.URI;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class PlanPhase extends ValidatingConfig<Catalog> implements IPlanPhase {
@@ -138,6 +134,13 @@ public class PlanPhase extends ValidatingConfig<Catalog> implements IPlanPhase {
 		return errors;
 
 	}
+	
+
+	@Override
+	public void initialize(Catalog root, URI uri) {
+		if (fixedPrice != null) { fixedPrice.initialize(root, uri);  }	
+		if (recurringPrice != null) { recurringPrice.initialize(root, uri); }
+	}
 
 	protected PlanPhase setFixedPrice(InternationalPrice price) {
 		this.fixedPrice = price;
diff --git a/catalog/src/main/java/com/ning/billing/catalog/Price.java b/catalog/src/main/java/com/ning/billing/catalog/Price.java
index 369b9ac..57d9112 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/Price.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/Price.java
@@ -16,17 +16,16 @@
 
 package com.ning.billing.catalog;
 
-import java.math.BigDecimal;
-
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlElement;
-
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.IPrice;
 import com.ning.billing.util.config.ValidatingConfig;
 import com.ning.billing.util.config.ValidationErrors;
 
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import java.math.BigDecimal;
+
 @XmlAccessorType(XmlAccessType.NONE)
 public class Price extends ValidatingConfig<Catalog> implements IPrice {
 	@XmlElement(required=true)
@@ -51,7 +50,7 @@ public class Price extends ValidatingConfig<Catalog> implements IPrice {
 		return value;
 	}
 	
-	public Price setCurrency(Currency currency) {
+	protected Price setCurrency(Currency currency) {
 		this.currency = currency;
 		return this;
 	}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestInternationalPrice.java b/catalog/src/test/java/com/ning/billing/catalog/TestInternationalPrice.java
new file mode 100644
index 0000000..cfa959c
--- /dev/null
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestInternationalPrice.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you 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://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.ning.billing.catalog;
+
+import com.ning.billing.catalog.api.Currency;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.math.BigDecimal;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+public class TestInternationalPrice {
+  @Test
+  public void testZeroValue() throws URISyntaxException {
+	  Catalog c = new MockCatalog();
+	  c.setSupportedCurrencies(new Currency[]{Currency.GBP, Currency.EUR, Currency.USD, Currency.BRL, Currency.MXN});
+	  InternationalPrice p0 =  new MockInternationalPrice();
+	  p0.setPrices(null);
+	  p0.initialize(c, new URI("foo:bar"));
+	  InternationalPrice p1 =  new MockInternationalPrice();
+	  p1.setPrices(new Price[] {
+			  new Price().setCurrency(Currency.GBP).setValue(new BigDecimal(1)),
+			  new Price().setCurrency(Currency.EUR).setValue(new BigDecimal(1)),
+			  new Price().setCurrency(Currency.USD).setValue(new BigDecimal(1)),
+			  new Price().setCurrency(Currency.BRL).setValue(new BigDecimal(1)),
+			  new Price().setCurrency(Currency.MXN).setValue(new BigDecimal(1)),		  
+	  });
+	  p1.initialize(c, new URI("foo:bar"));
+
+	  Assert.assertEquals(p0.getPrice(Currency.GBP), new BigDecimal(0));
+	  Assert.assertEquals(p0.getPrice(Currency.EUR), new BigDecimal(0));
+	  Assert.assertEquals(p0.getPrice(Currency.USD), new BigDecimal(0));
+	  Assert.assertEquals(p0.getPrice(Currency.BRL), new BigDecimal(0));
+	  Assert.assertEquals(p0.getPrice(Currency.MXN), new BigDecimal(0));
+	  
+	  Assert.assertEquals(p1.getPrice(Currency.GBP), new BigDecimal(1));
+	  Assert.assertEquals(p1.getPrice(Currency.EUR), new BigDecimal(1));
+	  Assert.assertEquals(p1.getPrice(Currency.USD), new BigDecimal(1));
+	  Assert.assertEquals(p1.getPrice(Currency.BRL), new BigDecimal(1));
+	  Assert.assertEquals(p1.getPrice(Currency.MXN), new BigDecimal(1));
+	  
+  }
+}
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 7483255..6bd2ee7 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.0.14-SNAPSHOT</version>
+        <version>0.0.15-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-entitlement</artifactId>
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/alignment/PlanAligner.java b/entitlement/src/main/java/com/ning/billing/entitlement/alignment/PlanAligner.java
index 01ec2c0..fdcbb9b 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/alignment/PlanAligner.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/alignment/PlanAligner.java
@@ -16,24 +16,16 @@
 
 package com.ning.billing.entitlement.alignment;
 
-import java.util.LinkedList;
-import java.util.List;
-
-import org.joda.time.DateTime;
-
 import com.google.inject.Inject;
-import com.ning.billing.catalog.api.ICatalog;
-import com.ning.billing.catalog.api.ICatalogService;
-import com.ning.billing.catalog.api.IDuration;
-import com.ning.billing.catalog.api.IPlan;
-import com.ning.billing.catalog.api.IPlanPhase;
-import com.ning.billing.catalog.api.PlanAlignmentChange;
-import com.ning.billing.catalog.api.PlanAlignmentCreate;
-import com.ning.billing.catalog.api.PlanPhaseSpecifier;
-import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.*;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.util.clock.Clock;
+import org.joda.time.DateTime;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
 
 public class PlanAligner implements IPlanAligner {
 
@@ -152,6 +144,11 @@ public class PlanAligner implements IPlanAligner {
     private List<TimedPhase> getPhaseAlignments(Subscription subscription, IPlan plan,
             DateTime effectiveDate, DateTime planStartDate) {
 
+        // The plan can be null with the nasty endpoint from test API.
+        if (plan == null) {
+            return Collections.emptyList();
+        }
+
         List<TimedPhase> result = new LinkedList<IPlanAligner.TimedPhase>();
 
         DateTime curPhaseStart = planStartDate;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/test/EntitlementTestApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/test/EntitlementTestApi.java
index 61cee03..2f8c9ab 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/test/EntitlementTestApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/test/EntitlementTestApi.java
@@ -16,12 +16,13 @@
 
 package com.ning.billing.entitlement.api.test;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import com.google.inject.Inject;
 import com.ning.billing.config.IEntitlementConfig;
 import com.ning.billing.entitlement.engine.core.IApiEventProcessor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.UUID;
 
 public class EntitlementTestApi implements IEntitlementTestApi {
 
@@ -37,10 +38,10 @@ public class EntitlementTestApi implements IEntitlementTestApi {
     }
 
     @Override
-    public void doProcessReadyEvents() {
+    public void doProcessReadyEvents(UUID [] subscriptionsIds, Boolean recursive, Boolean oneEventOnly) {
         if (config.isEventProcessingOff()) {
             log.warn("Running event processing loop");
-            apiEventProcessor.processAllReadyEvents();
+            apiEventProcessor.processAllReadyEvents(subscriptionsIds, recursive, oneEventOnly);
         }
     }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java
index 93fe561..d841e0f 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java
@@ -16,32 +16,25 @@
 
 package com.ning.billing.entitlement.api.user;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import org.joda.time.DateTime;
-
 import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.IAccount;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.catalog.api.ICatalogService;
-import com.ning.billing.catalog.api.IPlan;
-import com.ning.billing.catalog.api.IPlanPhase;
-import com.ning.billing.catalog.api.IPriceListSet;
+import com.ning.billing.catalog.api.*;
 import com.ning.billing.entitlement.alignment.IPlanAligner;
 import com.ning.billing.entitlement.alignment.IPlanAligner.TimedPhase;
-import com.ning.billing.entitlement.api.user.ISubscription;
-import com.ning.billing.entitlement.api.user.ISubscriptionBundle;
-import com.ning.billing.entitlement.api.user.IEntitlementUserApi;
 import com.ning.billing.entitlement.engine.dao.IEntitlementDao;
 import com.ning.billing.entitlement.events.IEvent;
 import com.ning.billing.entitlement.events.phase.IPhaseEvent;
 import com.ning.billing.entitlement.events.phase.PhaseEvent;
 import com.ning.billing.entitlement.events.user.ApiEventCreate;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
+import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.IClock;
+import org.joda.time.DateTime;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
 
 public class EntitlementUserApi implements IEntitlementUserApi {
 
@@ -76,6 +69,12 @@ public class EntitlementUserApi implements IEntitlementUserApi {
     }
 
     @Override
+    public List<ISubscription> getSubscriptionsForKey(String bundleKey) {
+        return dao.getSubscriptionsForKey(bundleKey);
+    }
+
+
+    @Override
     public List<ISubscription> getSubscriptionsForBundle(UUID bundleId) {
         return dao.getSubscriptions(bundleId);
     }
@@ -92,8 +91,8 @@ public class EntitlementUserApi implements IEntitlementUserApi {
             BillingPeriod term, String priceList, DateTime requestedDate) throws EntitlementUserApiException {
 
         String realPriceList = (priceList == null) ? IPriceListSet.DEFAULT_PRICELIST_NAME : priceList;
-
         DateTime now = clock.getUTCNow();
+        requestedDate = (requestedDate != null) ? Clock.truncateMs(requestedDate) : null;
         if (requestedDate != null && requestedDate.isAfter(now)) {
             throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_DATE, requestedDate.toString());
         }
@@ -158,6 +157,4 @@ public class EntitlementUserApi implements IEntitlementUserApi {
         // STEPH Also update startDate for bundle ?
         return dao.createSubscription(subscription, events);
     }
-
-
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
index afcf053..c5b707f 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
@@ -16,41 +16,24 @@
 
 package com.ning.billing.entitlement.api.user;
 
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.UUID;
-
 import com.ning.billing.ErrorCode;
-import com.ning.billing.account.api.IAccount;
-import org.joda.time.DateTime;
-
-import com.ning.billing.catalog.api.ActionPolicy;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.catalog.api.ICatalog;
-import com.ning.billing.catalog.api.IPlan;
-import com.ning.billing.catalog.api.IPlanPhase;
-import com.ning.billing.catalog.api.PlanPhaseSpecifier;
-import com.ning.billing.catalog.api.PlanSpecifier;
-import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.catalog.api.*;
 import com.ning.billing.entitlement.alignment.IPlanAligner;
 import com.ning.billing.entitlement.alignment.IPlanAligner.TimedPhase;
-import com.ning.billing.entitlement.engine.core.Engine;
 import com.ning.billing.entitlement.engine.dao.IEntitlementDao;
 import com.ning.billing.entitlement.events.IEvent;
 import com.ning.billing.entitlement.events.IEvent.EventType;
 import com.ning.billing.entitlement.events.phase.IPhaseEvent;
 import com.ning.billing.entitlement.events.phase.PhaseEvent;
-import com.ning.billing.entitlement.events.user.ApiEventCancel;
-import com.ning.billing.entitlement.events.user.ApiEventChange;
-import com.ning.billing.entitlement.events.user.ApiEventType;
-import com.ning.billing.entitlement.events.user.ApiEventUncancel;
-import com.ning.billing.entitlement.events.user.IApiEvent;
+import com.ning.billing.entitlement.events.user.*;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.entitlement.glue.InjectorMagic;
+import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.IClock;
+import org.joda.time.DateTime;
+
+import java.lang.reflect.Field;
+import java.util.*;
 
 public class Subscription extends PrivateFields  implements ISubscription {
 
@@ -206,6 +189,16 @@ public class Subscription extends PrivateFields  implements ISubscription {
 
 
     @Override
+    public DateTime getEndDate() {
+        ISubscriptionTransition latestTransition = getLatestTranstion();
+        if (latestTransition.getNextState() == SubscriptionState.CANCELLED) {
+            return latestTransition.getEffectiveTransitionTime();
+        }
+        return null;
+    }
+
+
+    @Override
     public void cancel(DateTime requestedDate, boolean eot) throws EntitlementUserApiException  {
 
         SubscriptionState currentState = getState();
@@ -214,6 +207,7 @@ public class Subscription extends PrivateFields  implements ISubscription {
         }
 
         DateTime now = clock.getUTCNow();
+        requestedDate = (requestedDate != null) ? Clock.truncateMs(requestedDate) : null;
         if (requestedDate != null && requestedDate.isAfter(now)) {
             throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_DATE, requestedDate.toString());
         }
@@ -256,8 +250,8 @@ public class Subscription extends PrivateFields  implements ISubscription {
     public void changePlan(String productName, BillingPeriod term,
             String priceList, DateTime requestedDate) throws EntitlementUserApiException {
 
+        requestedDate = (requestedDate != null) ? Clock.truncateMs(requestedDate) : null;
         String currentPriceList = getCurrentPriceList();
-        String realPriceList = (priceList == null) ? currentPriceList : priceList;
 
         SubscriptionState currentState = getState();
         if (currentState != SubscriptionState.ACTIVE) {
@@ -269,30 +263,47 @@ public class Subscription extends PrivateFields  implements ISubscription {
         }
 
         DateTime now = clock.getUTCNow();
-        IPlan newPlan = catalog.getPlan(productName, term, realPriceList);
+        PlanChangeResult planChangeResult = null;
+        try {
+
+            IProduct destProduct = catalog.getProductFromName(productName);
+            // STEPH really catalog exception
+            if (destProduct == null) {
+                throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_BAD_CATALOG,
+                        productName, term.toString(), "");
+            }
+
+            IPlan currentPlan = getCurrentPlan();
+            PlanPhaseSpecifier fromPlanPhase = new PlanPhaseSpecifier(currentPlan.getProduct().getName(),
+                    currentPlan.getProduct().getCategory(),
+                    currentPlan.getBillingPeriod(),
+                    currentPriceList, getCurrentPhase().getPhaseType());
+            PlanSpecifier toPlanPhase = new PlanSpecifier(productName,
+                    destProduct.getCategory(),
+                    term,
+                    priceList);
+
+            planChangeResult = catalog.planChange(fromPlanPhase, toPlanPhase);
+        } catch (CatalogApiException e) {
+            throw new EntitlementUserApiException(e);
+        }
+
+        ActionPolicy policy = planChangeResult.getPolicy();
+        IPriceList newPriceList = planChangeResult.getNewPriceList();
+
+        IPlan newPlan = catalog.getPlan(productName, term, newPriceList.getName());
         if (newPlan == null) {
             throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_BAD_CATALOG,
-                    productName, term.toString(), realPriceList);
+                    productName, term.toString(), newPriceList.getName());
         }
 
-        IPlan currentPlan = getCurrentPlan();
-        PlanPhaseSpecifier fromPlanPhase = new PlanPhaseSpecifier(currentPlan.getProduct().getName(),
-        		currentPlan.getProduct().getCategory(),
-                currentPlan.getBillingPeriod(),
-        		currentPriceList, getCurrentPhase().getPhaseType());
-        PlanSpecifier toPlanPhase = new PlanSpecifier(newPlan.getProduct().getName(),
-                newPlan.getProduct().getCategory(),
-        		newPlan.getBillingPeriod(),
-        		realPriceList);
-
-        ActionPolicy policy = catalog.getPlanChangePolicy(fromPlanPhase, toPlanPhase);
         DateTime effectiveDate = getPlanChangeEffectiveDate(policy, now);
 
-        TimedPhase currentTimedPhase = planAligner.getCurrentTimedPhaseOnChange(this, newPlan, realPriceList, effectiveDate);
+        TimedPhase currentTimedPhase = planAligner.getCurrentTimedPhaseOnChange(this, newPlan, newPriceList.getName(), effectiveDate);
         IEvent changeEvent = new ApiEventChange(id, bundleStartDate, now, newPlan.getName(), currentTimedPhase.getPhase().getName(),
-                realPriceList, now, effectiveDate, activeVersion);
+                newPriceList.getName(), now, effectiveDate, activeVersion);
 
-        TimedPhase nextTimedPhase = planAligner.getNextTimedPhaseOnChange(this, newPlan, realPriceList, effectiveDate);
+        TimedPhase nextTimedPhase = planAligner.getNextTimedPhaseOnChange(this, newPlan, newPriceList.getName(), effectiveDate);
         IPhaseEvent nextPhaseEvent = PhaseEvent.getNextPhaseEvent(nextTimedPhase, this, now);
         List<IEvent> changeEvents = new ArrayList<IEvent>();
         // Only add the PHASE if it does not coincide with the CHANGE, if not this is 'just' a CHANGE.
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessor.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessor.java
index 541de37..593afc9 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessor.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessor.java
@@ -16,14 +16,15 @@
 
 package com.ning.billing.entitlement.engine.core;
 
-import java.util.List;
-
 import com.google.inject.Inject;
 import com.ning.billing.config.IEntitlementConfig;
 import com.ning.billing.entitlement.engine.dao.IEntitlementDao;
 import com.ning.billing.entitlement.events.IEvent;
 import com.ning.billing.util.clock.IClock;
 
+import java.util.Collection;
+import java.util.List;
+
 public class ApiEventProcessor extends ApiEventProcessorBase {
 
     @Inject
@@ -34,13 +35,17 @@ public class ApiEventProcessor extends ApiEventProcessorBase {
 
     @Override
     protected boolean doProcessEvents(int sequenceId) {
-        long prev = nbProcessedEvents;
         List<IEvent> claimedEvents = dao.getEventsReady(apiProcessorId, sequenceId);
         if (claimedEvents.size() == 0) {
             return false;
         }
         log.debug(String.format("ApiEventProcessor got %d events", claimedEvents.size()));
+        return doProcessEventsFromList(sequenceId, claimedEvents);
+    }
+
 
+    protected boolean doProcessEventsFromList(int sequenceId, Collection<IEvent> claimedEvents) {
+        long prev = nbProcessedEvents;
         for (IEvent cur : claimedEvents) {
             log.debug(String.format("ApiEventProcessor seq = %d got event %s", sequenceId, cur.getId()));
             listener.processEventReady(cur);
@@ -52,6 +57,6 @@ public class ApiEventProcessor extends ApiEventProcessorBase {
         log.debug(String.format("ApiEventProcessor cleared events %d", nbProcessedEvents - prev));
         //log.debug(String.format("ApiEventProcessor seq = %d cleared events %s", sequenceId, claimedEvents.get(0).getId()));
         return true;
-    }
 
+    }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorBase.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorBase.java
index f3faf8e..704c7a5 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorBase.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorBase.java
@@ -16,23 +16,22 @@
 
 package com.ning.billing.entitlement.engine.core;
 
-import java.lang.Thread.UncaughtExceptionHandler;
-import java.util.UUID;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.skife.config.Config;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
 import com.google.inject.Inject;
 import com.ning.billing.config.IEntitlementConfig;
 import com.ning.billing.entitlement.engine.dao.IEntitlementDao;
 import com.ning.billing.entitlement.events.IEvent;
 import com.ning.billing.util.clock.IClock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.*;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public abstract class ApiEventProcessorBase implements IApiEventProcessor {
 
@@ -71,7 +70,7 @@ public abstract class ApiEventProcessorBase implements IApiEventProcessor {
 
 
     @Override
-    public void startNotifications(IEventListener listener) {
+    public void startNotifications(final IEventListener listener) {
 
         this.listener = listener;
         this.isProcessingEvents = true;
@@ -80,6 +79,7 @@ public abstract class ApiEventProcessorBase implements IApiEventProcessor {
 
         if (config.isEventProcessingOff()) {
             log.warn("KILLBILL ENTITLEMENT EVENT PROCESSING IS OFF !!!");
+            listener.completedNotificationStart();
             return;
         }
         final ApiEventProcessorBase apiEventProcessor = this;
@@ -88,6 +88,7 @@ public abstract class ApiEventProcessorBase implements IApiEventProcessor {
 
             if (executor != null) {
                 log.warn("There is already an executor thread running, return");
+                listener.completedNotificationStart();
                 return;
             }
 
@@ -115,6 +116,10 @@ public abstract class ApiEventProcessorBase implements IApiEventProcessor {
 
                 log.info(String.format("ApiEventProcessor thread %s  [%d] started", API_EVENT_THREAD_NAME,
                         Thread.currentThread().getId()));
+
+                // Thread is now started, notify the listener
+                listener.completedNotificationStart();
+
                 try {
                     while (true) {
                         synchronized (apiEventProcessor) {
@@ -158,6 +163,11 @@ public abstract class ApiEventProcessorBase implements IApiEventProcessor {
 
     @Override
     public void stopNotifications() {
+
+        if (config.isEventProcessingOff()) {
+            return;
+        }
+
         synchronized(this) {
             isProcessingEvents = false;
             try {
@@ -172,20 +182,72 @@ public abstract class ApiEventProcessorBase implements IApiEventProcessor {
     }
 
 
+    //
     // Used for system test purpose only when event processing has been disabled.
+    // This is not necessarily pretty
+    //
     @Override
-    public void processAllReadyEvents() {
+    public void processAllReadyEvents(final UUID [] subscriptionsIds, final Boolean recursive, final Boolean oneEventOnly) {
+        processAllReadyEventsRecursively(subscriptionsIds, recursive, oneEventOnly);
+    }
+
+    private boolean processAllReadyEventsRecursively(final UUID [] subscriptionsIds,
+            final Boolean recursive,
+            final Boolean oneEventOnly) {
 
+        int curSequenceId = sequenceId.getAndIncrement();
 
-        boolean keepProcessing = false;
-        /*
+        //Get all current ready events
+        List<IEvent> claimedEvents = new LinkedList<IEvent>();
         do {
-         */
-        keepProcessing = doProcessEvents(sequenceId.incrementAndGet());
-        /*
-         } while (keepProcessing);
-         */
+            List<IEvent> tmpEvents = dao.getEventsReady(apiProcessorId, curSequenceId);
+            if (tmpEvents.size() == 0) {
+                break;
+            }
+            claimedEvents.addAll(tmpEvents);
+        } while(true);
+        if (claimedEvents.size() == 0) {
+            return false;
+        }
+
+        // Filter for specific subscriptions if needed
+        Collection<IEvent> claimedEventsFiltered = null;
+        if (subscriptionsIds.length == 0) {
+            claimedEventsFiltered = claimedEvents;
+        } else {
+
+            claimedEventsFiltered = Collections2.filter(claimedEvents, new Predicate<IEvent>() {
+                @Override
+                public boolean apply(IEvent input) {
+                    for (UUID cur : subscriptionsIds) {
+                        if (cur.equals(input.getSubscriptionId())) {
+                            return true;
+                        }
+                    }
+                    return false;
+                }
+            });
+        }
+        if (claimedEventsFiltered.size() == 0) {
+            return false;
+        }
+
+        // If only one event is requested extract it
+        if (oneEventOnly) {
+            List<IEvent> oneEventList = new ArrayList<IEvent>(1);
+            oneEventList.add(claimedEventsFiltered.iterator().next());
+            claimedEventsFiltered = oneEventList;
+        }
+
+        // Call processing method
+        doProcessEventsFromList(curSequenceId, claimedEventsFiltered);
+        // Keep going is recursive
+        if (recursive && !oneEventOnly) {
+            processAllReadyEventsRecursively(subscriptionsIds, recursive, oneEventOnly);
+        }
+        return true;
     }
 
     protected abstract boolean doProcessEvents(int sequenceId);
+    protected abstract boolean doProcessEventsFromList(int sequenceId, Collection<IEvent> events);
 }
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 622fa12..4aa966a 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
@@ -16,13 +16,8 @@
 
 package com.ning.billing.entitlement.engine.core;
 
-import org.joda.time.DateTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import com.google.inject.Inject;
 import com.ning.billing.config.IEntitlementConfig;
-
 import com.ning.billing.entitlement.alignment.IPlanAligner;
 import com.ning.billing.entitlement.alignment.IPlanAligner.TimedPhase;
 import com.ning.billing.entitlement.api.IEntitlementService;
@@ -38,18 +33,24 @@ import com.ning.billing.entitlement.events.IEvent;
 import com.ning.billing.entitlement.events.IEvent.EventType;
 import com.ning.billing.entitlement.events.phase.IPhaseEvent;
 import com.ning.billing.entitlement.events.phase.PhaseEvent;
-import com.ning.billing.entitlement.events.user.IApiEvent;
-import com.ning.billing.lifecycle.IService;
+import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.lifecycle.LyfecycleHandlerType;
 import com.ning.billing.lifecycle.LyfecycleHandlerType.LyfecycleLevel;
 import com.ning.billing.util.clock.IClock;
 import com.ning.billing.util.eventbus.IEventBus;
 import com.ning.billing.util.eventbus.IEventBus.EventBusException;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class Engine implements IEventListener, IEntitlementService {
 
     private static final String ENTITLEMENT_SERVICE_NAME = "entitlement-service";
 
+    private final long MAX_NOTIFICATION_THREAD_WAIT_MS = 10000; // 10 secs
+    private final long NOTIFICATION_THREAD_WAIT_INCREMENT_MS = 1000; // 1 sec
+    private final long NANO_TO_MS = (1000 * 1000);
+
     private final static Logger log = LoggerFactory.getLogger(Engine.class);
 
     private final IClock clock;
@@ -61,6 +62,8 @@ public class Engine implements IEventListener, IEntitlementService {
     private final IEntitlementTestApi testApi;
     private final IEventBus eventBus;
 
+    private boolean startedNotificationThread;
+
     @Inject
     public Engine(IClock clock, IEntitlementDao dao, IApiEventProcessor apiEventProcessor,
             IPlanAligner planAligner, IEntitlementConfig config, EntitlementUserApi userApi,
@@ -74,6 +77,8 @@ public class Engine implements IEventListener, IEntitlementService {
         this.testApi = testApi;
         this.billingApi = billingApi;
         this.eventBus = eventBus;
+
+        this.startedNotificationThread = false;
     }
 
     @Override
@@ -89,11 +94,13 @@ public class Engine implements IEventListener, IEntitlementService {
     @LyfecycleHandlerType(LyfecycleLevel.START_SERVICE)
     public void start() {
         apiEventProcessor.startNotifications(this);
+        waitForNotificationStartCompletion();
     }
 
     @LyfecycleHandlerType(LyfecycleLevel.STOP_SERVICE)
     public void stop() {
         apiEventProcessor.stopNotifications();
+        startedNotificationThread = false;
     }
 
     @Override
@@ -127,7 +134,42 @@ public class Engine implements IEventListener, IEntitlementService {
         } catch (EventBusException e) {
             log.warn("Failed to post entitlement event " + event, e);
         }
+    }
+
+    //
+    // We want to ensure the notification thread is indeed started when we return from start()
+    //
+    @Override
+    public void completedNotificationStart() {
+        synchronized (this) {
+            startedNotificationThread = true;
+            this.notifyAll();
+        }
+    }
 
+    private void waitForNotificationStartCompletion() {
+
+        long ini = System.nanoTime();
+        synchronized(this) {
+            do {
+                if (startedNotificationThread) {
+                    break;
+                }
+                try {
+                    this.wait(NOTIFICATION_THREAD_WAIT_INCREMENT_MS);
+                } catch (InterruptedException e ) {
+                    Thread.currentThread().interrupt();
+                    throw new EntitlementError(e);
+                }
+            } while (!startedNotificationThread &&
+                    (System.nanoTime() - ini) / NANO_TO_MS < MAX_NOTIFICATION_THREAD_WAIT_MS);
+
+            if (!startedNotificationThread) {
+                log.error("Could not start notification thread in {} msec !!!", MAX_NOTIFICATION_THREAD_WAIT_MS);
+                throw new EntitlementError("Failed to start service!!");
+            }
+            log.info("Notification thread has been started in {} ms", (System.nanoTime() - ini) / NANO_TO_MS);
+        }
     }
 
     private void insertNextPhaseEvent(Subscription subscription) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IApiEventProcessor.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IApiEventProcessor.java
index f51ae7e..dc04996 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IApiEventProcessor.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IApiEventProcessor.java
@@ -16,9 +16,11 @@
 
 package com.ning.billing.entitlement.engine.core;
 
+import java.util.UUID;
+
 
 public interface IApiEventProcessor extends IEventNotifier {
 
-    public void processAllReadyEvents();
+    public void processAllReadyEvents(UUID [] subscriptionsIds, Boolean recursive, Boolean oneEventOnly);
 
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IEventListener.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IEventListener.java
index f973279..651f8a9 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IEventListener.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/IEventListener.java
@@ -21,5 +21,8 @@ import com.ning.billing.entitlement.events.IEvent;
 
 
 public interface IEventListener {
+
     public void processEventReady(IEvent event);
+
+    public void completedNotificationStart();
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
index 197e4bd..ea14a16 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
@@ -16,21 +16,6 @@
 
 package com.ning.billing.entitlement.engine.dao;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.UUID;
-
-import org.skife.jdbi.v2.DBI;
-import org.skife.jdbi.v2.Transaction;
-import org.skife.jdbi.v2.TransactionStatus;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.collect.Lists;
 import com.google.inject.Inject;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.config.IEntitlementConfig;
@@ -45,6 +30,13 @@ import com.ning.billing.entitlement.events.user.IApiEvent;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.util.Hostname;
 import com.ning.billing.util.clock.IClock;
+import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
 
 public class EntitlementDao implements IEntitlementDao {
 
@@ -107,6 +99,15 @@ public class EntitlementDao implements IEntitlementDao {
     }
 
     @Override
+    public List<ISubscription> getSubscriptionsForKey(String bundleKey) {
+        ISubscriptionBundle bundle =  bundlesDao.getBundleFromKey(bundleKey);
+        if (bundle == null) {
+            return Collections.emptyList();
+        }
+        return subscriptionsDao.getSubscriptionsFromBundleId(bundle.getId().toString());
+    }
+
+    @Override
     public void updateSubscription(Subscription subscription) {
         Date ctd = (subscription.getChargedThroughDate() != null)  ? subscription.getChargedThroughDate().toDate() : null;
         Date ptd = (subscription.getPaidThroughDate() != null)  ? subscription.getPaidThroughDate().toDate() : null;
@@ -178,7 +179,7 @@ public class EntitlementDao implements IEntitlementDao {
     }
 
     @Override
-    public void clearEventsReady(final UUID ownerId, final List<IEvent> cleared) {
+    public void clearEventsReady(final UUID ownerId, final Collection<IEvent> cleared) {
 
         log.debug(String.format("EntitlementDao clearEventsReady START cleared size = %d", cleared.size()));
 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.java
index 737bf52..63e65d8 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.java
@@ -16,13 +16,8 @@
 
 package com.ning.billing.entitlement.engine.dao;
 
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Timestamp;
-import java.util.Date;
-import java.util.List;
-import java.util.UUID;
-
+import com.ning.billing.entitlement.api.user.ISubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.skife.jdbi.v2.SQLStatement;
@@ -38,8 +33,12 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
-import com.ning.billing.entitlement.api.user.ISubscriptionBundle;
-import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
 
 @ExternalizedSqlViaStringTemplate3()
 public interface IBundleSqlDao extends Transactional<IEventSqlDao>, CloseMe, Transmogrifier {
@@ -53,8 +52,13 @@ public interface IBundleSqlDao extends Transactional<IEventSqlDao>, CloseMe, Tra
 
     @SqlQuery
     @Mapper(ISubscriptionBundleSqlMapper.class)
+    public ISubscriptionBundle getBundleFromKey(@Bind("name") String name);
+
+    @SqlQuery
+    @Mapper(ISubscriptionBundleSqlMapper.class)
     public List<ISubscriptionBundle> getBundleFromAccount(@Bind("account_id") String accountId);
 
+
     public static class SubscriptionBundleBinder implements Binder<Bind, SubscriptionBundle> {
 
         private Date getDate(DateTime dateTime) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IEntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IEntitlementDao.java
index 1f35fa0..88c3e53 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IEntitlementDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IEntitlementDao.java
@@ -16,15 +16,16 @@
 
 package com.ning.billing.entitlement.engine.dao;
 
-import java.util.List;
-import java.util.UUID;
-
 import com.ning.billing.entitlement.api.user.ISubscription;
 import com.ning.billing.entitlement.api.user.ISubscriptionBundle;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.events.IEvent;
 
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
 public interface IEntitlementDao {
 
 
@@ -43,6 +44,8 @@ public interface IEntitlementDao {
 
     public List<ISubscription> getSubscriptions(UUID bundleId);
 
+    public List<ISubscription> getSubscriptionsForKey(String bundleKey);
+
     // Update
     public void updateSubscription(Subscription subscription);
 
@@ -55,7 +58,7 @@ public interface IEntitlementDao {
 
     public List<IEvent> getEventsReady(UUID ownerId, int sequenceId);
 
-    public void clearEventsReady(UUID ownerId, List<IEvent> cleared);
+    public void clearEventsReady(UUID ownerId, Collection<IEvent> cleared);
 
     // Subscription creation, cancellation, changePlan apis
     public ISubscription createSubscription(Subscription subscription, List<IEvent> initialEvents);
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.sql.stg
index 22a85f8..045a241 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.sql.stg
@@ -27,6 +27,18 @@ getBundleFromId(id) ::= <<
     ;
 >>
 
+getBundleFromKey(name) ::= <<
+    select
+      id
+      , start_dt
+      , name
+      , account_id
+    from bundles
+    where
+      name = :name
+    ;
+>>
+
 
 getBundleFromAccount(account_id) ::= <<
     select
@@ -39,3 +51,4 @@ getBundleFromAccount(account_id) ::= <<
       account_id = :account_id
     ;
 >>
+
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiBase.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiBase.java
index 21a5064..3485ba8 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiBase.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiBase.java
@@ -50,7 +50,6 @@ import org.testng.annotations.BeforeMethod;
 import java.io.IOException;
 import java.lang.reflect.Method;
 import java.net.URL;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
@@ -309,11 +308,6 @@ public abstract class TestUserApiBase {
             }
 
             @Override
-            public String getIdAsString() {
-                return null;  //To change body of implemented methods use File | Settings | File Templates.
-            }
-
-            @Override
             public boolean isNew() {
                 return false;  //To change body of implemented methods use File | Settings | File Templates.
             }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorMemoryMock.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorMemoryMock.java
index 79aef55..59a0a22 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorMemoryMock.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorMemoryMock.java
@@ -16,14 +16,16 @@
 
 package com.ning.billing.entitlement.engine.core;
 
-import java.util.List;
-
 import com.google.inject.Inject;
 import com.ning.billing.config.IEntitlementConfig;
 import com.ning.billing.entitlement.engine.dao.IEntitlementDao;
 import com.ning.billing.entitlement.events.IEvent;
+import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.util.clock.IClock;
 
+import java.util.Collection;
+import java.util.List;
+
 public class ApiEventProcessorMemoryMock extends ApiEventProcessorBase {
 
 
@@ -51,4 +53,11 @@ public class ApiEventProcessorMemoryMock extends ApiEventProcessorBase {
         log.info(String.format("doProcessEvents : clearEvents"));
         return true;
     }
+
+
+    @Override
+    protected boolean doProcessEventsFromList(int sequenceId,
+            Collection<IEvent> events) {
+        throw new EntitlementError("Method not implemented");
+    }
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/EntitlementDaoMemoryMock.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/EntitlementDaoMemoryMock.java
index 1e043eb..7110812 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/EntitlementDaoMemoryMock.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/EntitlementDaoMemoryMock.java
@@ -16,18 +16,6 @@
 
 package com.ning.billing.entitlement.engine.dao;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.TreeSet;
-import java.util.UUID;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import com.google.inject.Inject;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.catalog.api.TimeUnit;
@@ -42,6 +30,10 @@ import com.ning.billing.entitlement.events.IEventLyfecycle.IEventLyfecycleState;
 import com.ning.billing.entitlement.events.user.ApiEventType;
 import com.ning.billing.entitlement.events.user.IApiEvent;
 import com.ning.billing.util.clock.IClock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
 
 public class EntitlementDaoMemoryMock implements IEntitlementDao, IEntitlementDaoMock {
 
@@ -53,6 +45,8 @@ public class EntitlementDaoMemoryMock implements IEntitlementDao, IEntitlementDa
     private final IClock clock;
     private final IEntitlementConfig config;
 
+
+
     @Inject
     public EntitlementDaoMemoryMock(IClock clock, IEntitlementConfig config) {
         super();
@@ -108,6 +102,18 @@ public class EntitlementDaoMemoryMock implements IEntitlementDao, IEntitlementDa
     }
 
     @Override
+    public List<ISubscription> getSubscriptionsForKey(String bundleKey) {
+
+        for (ISubscriptionBundle cur : bundles) {
+            if (cur.getKey().equals(bundleKey)) {
+                return getSubscriptions(cur.getId());
+            }
+        }
+        return Collections.emptyList();
+    }
+
+
+    @Override
     public ISubscription createSubscription(Subscription subscription, List<IEvent> initalEvents) {
 
         synchronized(events) {
@@ -199,7 +205,7 @@ public class EntitlementDaoMemoryMock implements IEntitlementDao, IEntitlementDa
     }
 
     @Override
-    public void clearEventsReady(UUID ownerId, List<IEvent> cleared) {
+    public void clearEventsReady(UUID ownerId, Collection<IEvent> cleared) {
         synchronized(events) {
             for (IEvent cur : cleared) {
                 if (cur.getOwner().equals(ownerId)) {
@@ -328,4 +334,5 @@ public class EntitlementDaoMemoryMock implements IEntitlementDao, IEntitlementDa
             }
         }
     }
+
 }

invoice/pom.xml 2(+1 -1)

diff --git a/invoice/pom.xml b/invoice/pom.xml
index 62b0c51..bedc67e 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.0.14-SNAPSHOT</version>
+        <version>0.0.15-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-invoice</artifactId>

payment/pom.xml 2(+1 -1)

diff --git a/payment/pom.xml b/payment/pom.xml
index e69d221..1a57af5 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.0.14-SNAPSHOT</version>
+        <version>0.0.15-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-payment</artifactId>

pom.xml 45(+29 -16)

diff --git a/pom.xml b/pom.xml
index 8038652..beac545 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,7 @@
     <groupId>com.ning.billing</groupId>
     <artifactId>killbill</artifactId>
     <packaging>pom</packaging>
-    <version>0.0.14-SNAPSHOT</version>
+    <version>0.0.15-SNAPSHOT</version>
     <name>killbill</name>
     <description>Library for managing recurring subscriptions and the associated billing</description>
     <url>http://github.com/ning/killbill</url>
@@ -56,6 +56,11 @@
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-account</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
                 <artifactId>killbill-entitlement</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -327,21 +332,6 @@
                     </execution>
                 </executions>
             </plugin>
-            <!-- Add plugins bound to verify phase before gpg plugin -->
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-gpg-plugin</artifactId>
-                <version>1.4</version>
-                <executions>
-                    <execution>
-                        <id>sign-artifacts</id>
-                        <phase>verify</phase>
-                        <goals>
-                            <goal>sign</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-release-plugin</artifactId>
@@ -389,6 +379,29 @@
             </plugin>
         </plugins>
     </build>
+    <profiles>
+        <profile>
+            <id>sonatype-oss-release</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-gpg-plugin</artifactId>
+                        <version>1.4</version>
+                        <executions>
+                            <execution>
+                                <id>sign-artifacts</id>
+                                <phase>verify</phase>
+                                <goals>
+                                    <goal>sign</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
     <reporting>
         <plugins>
             <plugin>

util/pom.xml 2(+1 -1)

diff --git a/util/pom.xml b/util/pom.xml
index 3f3a40e..8c47782 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.0.14-SNAPSHOT</version>
+        <version>0.0.15-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-util</artifactId>
diff --git a/util/src/main/java/com/ning/billing/util/clock/Clock.java b/util/src/main/java/com/ning/billing/util/clock/Clock.java
index 43819ba..d165dab 100644
--- a/util/src/main/java/com/ning/billing/util/clock/Clock.java
+++ b/util/src/main/java/com/ning/billing/util/clock/Clock.java
@@ -16,20 +16,19 @@
 
 package com.ning.billing.util.clock;
 
-import java.util.ArrayList;
-import java.util.List;
-
+import com.ning.billing.catalog.api.IDuration;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 
-import com.ning.billing.catalog.api.IDuration;
+import java.util.ArrayList;
+import java.util.List;
 
 public class Clock implements IClock {
 
     @Override
     public DateTime getNow(DateTimeZone tz) {
        DateTime result = new DateTime(tz);
-       return result.minus(result.getMillisOfSecond());
+       return truncateMs(result);
     }
 
     @Override
@@ -38,6 +37,10 @@ public class Clock implements IClock {
     }
 
 
+    public static DateTime truncateMs(DateTime input) {
+        return input.minus(input.getMillisOfSecond());
+    }
+
     public static DateTime addDuration(DateTime input, List<IDuration> durations) {
 
         DateTime result = input;
diff --git a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
index d8aad63..e8a77f6 100644
--- a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
+++ b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
@@ -25,6 +25,7 @@ import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.tweak.HandleCallback;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.testng.Assert;
 
 import java.io.File;
 import java.io.IOException;
@@ -47,13 +48,22 @@ public class MysqlTestingHelper
     private MysqldResource mysqldResource;
     private int port = 0;
 
-    public void startMysql() throws IOException
+    public MysqlTestingHelper()
     {
         // New socket on any free port
-        final ServerSocket socket = new ServerSocket(0);
-        port = socket.getLocalPort();
-        socket.close();
+        final ServerSocket socket;
+        try {
+            socket = new ServerSocket(0);
+            port = socket.getLocalPort();
+            socket.close();
+        }
+        catch (IOException e) {
+            Assert.fail();
+        }
+    }
 
+    public void startMysql() throws IOException
+    {
         dbDir = File.createTempFile("mysql", "");
         dbDir.delete();
         dbDir.mkdir();
@@ -76,7 +86,7 @@ public class MysqlTestingHelper
 
     public void cleanupTable(final String table)
     {
-        if (mysqldResource == null || mysqldResource.isRunning()) {
+        if (mysqldResource == null || !mysqldResource.isRunning()) {
             log.error("Asked to cleanup table " + table + " but MySQL is not running!");
             return;
         }
@@ -88,7 +98,7 @@ public class MysqlTestingHelper
             @Override
             public Void withHandle(final Handle handle) throws Exception
             {
-                handle.createQuery("delete * from " + table);
+                handle.execute("truncate " + table);
                 return null;
             }
         });
@@ -103,7 +113,7 @@ public class MysqlTestingHelper
         }
     }
 
-    public IDBI getDBI()
+    public DBI getDBI()
     {
         final String dbiString = "jdbc:mysql://localhost:" + port + "/" + DB_NAME + "?createDatabaseIfNotExist=true";
         return new DBI(dbiString, USERNAME, PASSWORD);