killbill-aplcache

Changes

jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestJsonViews.java 340(+0 -340)

junction/pom.xml 7(+0 -7)

pom.xml 5(+1 -4)

server/pom.xml 14(+9 -5)

Details

diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
index d59701a..8681d59 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
@@ -25,8 +25,8 @@ import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionEventTransition.SubscriptionTransitionType;
 
 public interface BillingEvent extends Comparable<BillingEvent> {
 
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java b/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
index bebffb0..50d45a1 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
@@ -19,6 +19,4 @@ package com.ning.billing.entitlement.api;
 import com.ning.billing.lifecycle.KillbillService;
 
 public interface EntitlementService extends KillbillService {
-
-
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/SubscriptionTransitionType.java b/api/src/main/java/com/ning/billing/entitlement/api/SubscriptionTransitionType.java
new file mode 100644
index 0000000..8e75675
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/SubscriptionTransitionType.java
@@ -0,0 +1,27 @@
+/* 
+ * 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.entitlement.api;
+
+public enum SubscriptionTransitionType {
+    MIGRATE_ENTITLEMENT,
+    CREATE,
+    MIGRATE_BILLING,
+    CHANGE,
+    RE_CREATE,
+    CANCEL,
+    UNCANCEL,
+    PHASE
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/timeline/BundleTimeline.java b/api/src/main/java/com/ning/billing/entitlement/api/timeline/BundleTimeline.java
new file mode 100644
index 0000000..6b12f9d
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/timeline/BundleTimeline.java
@@ -0,0 +1,30 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface BundleTimeline {
+
+    String getViewId();
+    
+    UUID getBundleId();
+    
+    String getExternalKey();
+
+    List<SubscriptionTimeline> getSubscriptions();
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementRepairException.java b/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementRepairException.java
new file mode 100644
index 0000000..56cdd99
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementRepairException.java
@@ -0,0 +1,41 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+
+public class EntitlementRepairException extends BillingExceptionBase {
+
+    private static final long serialVersionUID = 19067233L;
+
+    public EntitlementRepairException(EntitlementUserApiException e) {
+        super(e, e.getCode(), e.getMessage());
+    }
+    
+    public EntitlementRepairException(CatalogApiException e) {
+        super(e, e.getCode(), e.getMessage());
+    }
+    public EntitlementRepairException(Throwable e, ErrorCode code, Object...args) {
+        super(e, code, args);
+    }
+
+    public EntitlementRepairException(ErrorCode code, Object...args) {
+        super(code, args);
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementTimelineApi.java b/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementTimelineApi.java
new file mode 100644
index 0000000..b76f25a
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementTimelineApi.java
@@ -0,0 +1,27 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+import java.util.UUID;
+
+import com.ning.billing.util.callcontext.CallContext;
+
+public interface EntitlementTimelineApi {
+    
+    public BundleTimeline getBundleRepair(final UUID bundleId) throws EntitlementRepairException;
+    
+    public BundleTimeline repairBundle(final BundleTimeline input, final boolean dryRun, final CallContext context) throws EntitlementRepairException;
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementEvent.java b/api/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementEvent.java
new file mode 100644
index 0000000..0ab6720
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementEvent.java
@@ -0,0 +1,31 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public interface RepairEntitlementEvent extends BusEvent {
+
+    public UUID getAccountId();
+    
+    public UUID getBundleId();
+
+    public DateTime getEffectiveDate();
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionTimeline.java b/api/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionTimeline.java
new file mode 100644
index 0000000..434cb39
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionTimeline.java
@@ -0,0 +1,50 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+
+public interface SubscriptionTimeline {
+
+    public UUID getId();
+    
+    public List<DeletedEvent> getDeletedEvents();
+
+    public List<NewEvent> getNewEvents();    
+   
+    public List<ExistingEvent> getExistingEvents();    
+    
+    public interface DeletedEvent {
+        public UUID getEventId();
+    }
+    
+    public interface NewEvent {
+        public PlanPhaseSpecifier getPlanPhaseSpecifier();
+        public DateTime getRequestedDate();
+        public SubscriptionTransitionType getSubscriptionTransitionType();
+        
+    }
+    
+    public interface ExistingEvent extends DeletedEvent, NewEvent {
+        public DateTime getEffectiveDate();
+    }
+}
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 9c35ff0..11cf455 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
@@ -38,5 +38,4 @@ public class EntitlementUserApiException extends BillingExceptionBase {
     public EntitlementUserApiException(ErrorCode code, Object...args) {
         super(code, args);
     }
-
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionEventTransition.java b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionEventTransition.java
index bd93598..eae5701 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionEventTransition.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionEventTransition.java
@@ -22,23 +22,16 @@ import org.joda.time.DateTime;
 
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
+
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+
 import com.ning.billing.catalog.api.PriceList;
+
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
 import com.ning.billing.util.bus.BusEvent;
 
 public interface SubscriptionEventTransition extends BusEvent {
 
-    public enum SubscriptionTransitionType {
-        MIGRATE_ENTITLEMENT,
-        CREATE,
-        MIGRATE_BILLING,
-        CHANGE,
-        RE_CREATE,
-        CANCEL,
-        UNCANCEL,
-        PHASE
-    }
-
     UUID getId();
 
     SubscriptionTransitionType getTransitionType();
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index 9680237..dd22ea6 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -18,6 +18,8 @@ package com.ning.billing;
 
 public enum ErrorCode {
 
+
+    
     /*
      * Range 0 : COMMON EXCEPTIONS
      */
@@ -58,6 +60,23 @@ public enum ErrorCode {
     ENT_INVALID_SUBSCRIPTION_ID(1082, "Unknown subscription %s"),
     ENT_GET_INVALID_BUNDLE_KEY(1083, "Could not find a bundle matching key %s"),
 
+    /* Repair */
+    ENT_REPAIR_INVALID_DELETE_SET(1091, "Event %s is not deleted for subscription %s but prior events were"),
+    ENT_REPAIR_NON_EXISTENT_DELETE_EVENT(1092, "Event %s does not exist for subscription %s"),    
+    ENT_REPAIR_MISSING_AO_DELETE_EVENT(1093, "Event %s should be in deleted set for subscription %s because BP events got deleted earlier"),
+    ENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING(1094, "New event %s for subscription %s is before last remaining event for BP"),
+    ENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING(1095, "New event %s for subscription %s is before last remaining event"),
+    ENT_REPAIR_UNKNOWN_TYPE(1096, "Unknown new event type %s for subscription %s"),
+    ENT_REPAIR_UNKNOWN_BUNDLE(1097, "Unknown bundle %s"), 
+    ENT_REPAIR_UNKNOWN_SUBSCRIPTION(1098, "Unknown subscription %s"),     
+    ENT_REPAIR_NO_ACTIVE_SUBSCRIPTIONS(1099, "No active subscriptions on bundle %s"),         
+    ENT_REPAIR_VIEW_CHANGED(1100, "View for bundle %s has changed from %s to %s"),             
+    ENT_REPAIR_SUB_RECREATE_NOT_EMPTY(1101, "Subscription %s with recreation for bundle %s should specify all existing events to be deleted"),    
+    ENT_REPAIR_SUB_EMPTY(1102, "Subscription %s with recreation for bundle %s should specify all existing events to be deleted"),    
+    ENT_REPAIR_BP_RECREATE_MISSING_AO(1103, "BP recreation for bundle %s implies repair all subscriptions"),    
+    ENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE(1104, "BP recreation for bundle %s implies that all AO should be start also with a CREATE"),        
+    ENT_REPAIR_AO_CREATE_BEFORE_BP_START(1105, "Can't recreate AO %s for bundle %s before BP starts"),    
+    
     
     ENT_BUNDLE_IS_OVERDUE_BLOCKED(1090, "Changes to this bundle are blocked by overdue enforcement (%s :  %s)"),
     ENT_ACCOUNT_IS_OVERDUE_BLOCKED(1091, "Changes to this account are blocked by overdue enforcement (%s)"),
@@ -180,6 +199,7 @@ public enum ErrorCode {
     
     ;
 
+    
     private int code;
     private String format;
 
diff --git a/api/src/main/java/com/ning/billing/junction/api/BillingApi.java b/api/src/main/java/com/ning/billing/junction/api/BillingApi.java
index a1b3657..4128811 100644
--- a/api/src/main/java/com/ning/billing/junction/api/BillingApi.java
+++ b/api/src/main/java/com/ning/billing/junction/api/BillingApi.java
@@ -19,8 +19,11 @@ package com.ning.billing.junction.api;
 import java.util.SortedSet;
 import java.util.UUID;
 
+import org.joda.time.DateTime;
+
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.billing.ChargeThruApi;
+import com.ning.billing.util.callcontext.CallContext;
 
 public interface BillingApi extends ChargeThruApi {
     /**
@@ -30,6 +33,4 @@ public interface BillingApi extends ChargeThruApi {
      *
      */
     public SortedSet<BillingEvent> getBillingEventsForAccountAndUpdateAccountBCD(UUID accountId);
-
-
 }
diff --git a/api/src/main/java/com/ning/billing/util/bus/BusEvent.java b/api/src/main/java/com/ning/billing/util/bus/BusEvent.java
index d44e1b5..d969440 100644
--- a/api/src/main/java/com/ning/billing/util/bus/BusEvent.java
+++ b/api/src/main/java/com/ning/billing/util/bus/BusEvent.java
@@ -24,6 +24,7 @@ public interface BusEvent {
 		ACCOUNT_CREATE,
 		ACCOUNT_CHANGE,
 		SUBSCRIPTION_TRANSITION,
+		BUNDLE_REPAIR,
 		INVOICE_EMPTY,
 		INVOICE_CREATION,
 		PAYMENT_INFO,
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBusHandler.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBusHandler.java
index ffa25e6..e7bcd9a 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBusHandler.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBusHandler.java
@@ -27,6 +27,7 @@ import org.testng.Assert;
 
 import com.google.common.base.Joiner;
 import com.google.common.eventbus.Subscribe;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementEvent;
 import com.ning.billing.entitlement.api.user.SubscriptionEventTransition;
 import com.ning.billing.invoice.api.InvoiceCreationEvent;
 import com.ning.billing.payment.api.PaymentErrorEvent;
@@ -38,11 +39,14 @@ public class TestBusHandler {
 
     private final List<NextEvent> nextExpectedEvent;
 
+    private final TestFailure testFailure;
+    
     private volatile boolean completed;
 
-    public TestBusHandler() {
+    public TestBusHandler(TestFailure testFailure) {
         nextExpectedEvent = new Stack<NextEvent>();
         this.completed = false;
+        this.testFailure = testFailure;
     }
 
     public enum NextEvent {
@@ -55,7 +59,15 @@ public class TestBusHandler {
         RESUME,
         PHASE,
         INVOICE,
-        PAYMENT
+        PAYMENT,
+        REPAIR_BUNDLE
+    }
+    
+    @Subscribe
+    public void handleEntitlementEvents(RepairEntitlementEvent event) {
+        log.info(String.format("TestBusHandler Got RepairEntitlementEvent event %s", event.toString()));
+        assertEqualsNicely(NextEvent.REPAIR_BUNDLE);
+        notifyIfStackEmpty();
     }
 
     @Subscribe
@@ -184,7 +196,9 @@ public class TestBusHandler {
             if (!foundIt) {
                 Joiner joiner = Joiner.on(" ");
                 log.error("TestBusHandler Received event " + received + "; expected " + joiner.join(nextExpectedEvent));
-                Assert.fail();
+                if (testFailure != null) {
+                    testFailure.failed("TestBusHandler Received event " + received + "; expected " + joiner.join(nextExpectedEvent));
+                }
             }
         }
     }
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestFailure.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestFailure.java
new file mode 100644
index 0000000..fd33a6b
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestFailure.java
@@ -0,0 +1,23 @@
+/* 
+ * 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.beatrix.integration;
+
+public interface TestFailure {
+
+    public void failed(String msg);
+
+    public void reset();
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
index 5766862..c6d37bb 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
@@ -19,231 +19,35 @@ package com.ning.billing.beatrix.integration;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
 
-import java.io.IOException;
 import java.math.BigDecimal;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang.RandomStringUtils;
 import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
 import org.joda.time.Interval;
-import org.skife.jdbi.v2.Handle;
-import org.skife.jdbi.v2.IDBI;
-import org.skife.jdbi.v2.TransactionCallback;
-import org.skife.jdbi.v2.TransactionStatus;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.AfterSuite;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.BeforeSuite;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
-import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountData;
-import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.beatrix.integration.TestBusHandler.NextEvent;
-import com.ning.billing.beatrix.lifecycle.Lifecycle;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
-import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.api.InvoiceUserApi;
-import com.ning.billing.invoice.model.InvoicingConfiguration;
-import com.ning.billing.junction.plumbing.api.BlockingSubscription;
-import com.ning.billing.util.bus.BusService;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.clock.ClockMock;
 
 @Test(groups = "slow")
 @Guice(modules = {MockModule.class})
-public class TestIntegration {
-    private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
-    private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
-
-    private static final BigDecimal ONE = new BigDecimal("1.0000").setScale(NUMBER_OF_DECIMALS);
-    private static final BigDecimal TWENTY_NINE = new BigDecimal("29.0000").setScale(NUMBER_OF_DECIMALS);
-    private static final BigDecimal THIRTY = new BigDecimal("30.0000").setScale(NUMBER_OF_DECIMALS);
-    private static final BigDecimal THIRTY_ONE = new BigDecimal("31.0000").setScale(NUMBER_OF_DECIMALS);
-
-    private static final Logger log = LoggerFactory.getLogger(TestIntegration.class);
-    private static long AT_LEAST_ONE_MONTH_MS =  31L * 24L * 3600L * 1000L;
-
-    private static final long DELAY = 5000;
-
-    @Inject IDBI dbi;
-
-    @Inject
-    private ClockMock clock;
-    private CallContext context;
-
-    @Inject
-    private Lifecycle lifecycle;
-
-    @Inject
-    private BusService busService;
-
-    @Inject
-    private MysqlTestingHelper helper;
-
-    @Inject
-    private EntitlementUserApi entitlementUserApi;
-
-    @Inject
-    private InvoiceUserApi invoiceUserApi;
-
-    @Inject
-    private AccountUserApi accountUserApi;
-
-    private TestBusHandler busHandler;
-
-    private void setupMySQL() throws IOException
-    {
-        final String accountDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
-        final String entitlementDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
-        final String invoiceDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
-        final String paymentDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
-        final String utilDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
-        final String blockingDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/junction/ddl.sql"));
-
-        helper.startMysql();
-
-        helper.initDb(accountDdl);
-        helper.initDb(entitlementDdl);
-        helper.initDb(invoiceDdl);
-        helper.initDb(paymentDdl);
-        helper.initDb(utilDdl);
-        helper.initDb(blockingDdl);
-    }
-
-    @BeforeSuite(groups = "slow")
-    public void setup() throws Exception{
-
-        setupMySQL();
-        cleanupData();
-        context = new DefaultCallContextFactory(clock).createCallContext("Integration Test", CallOrigin.TEST, UserType.TEST);
-
-        /**
-         * Initialize lifecyle for subset of services
-         */
-        busHandler = new TestBusHandler();
-        lifecycle.fireStartupSequencePriorEventRegistration();
-        busService.getBus().register(busHandler);
-        lifecycle.fireStartupSequencePostEventRegistration();
-
-
-
-    }
-
-    @AfterSuite(groups = "slow")
-    public void tearDown() throws Exception {
-        lifecycle.fireShutdownSequencePriorEventUnRegistration();
-        busService.getBus().unregister(busHandler);
-        lifecycle.fireShutdownSequencePostEventUnRegistration();
-        helper.stopMysql();
-    }
-
-
-    @BeforeMethod(groups = "slow")
-    public void setupTest() {
-
-        log.warn("\n");
-        log.warn("RESET TEST FRAMEWORK\n\n");
-        cleanupData();
-        busHandler.reset();
-        clock.resetDeltaFromReality();
-
-    }
-
-    @AfterMethod(groups = "slow")
-    public void cleanupTest() {
-        log.warn("DONE WITH TEST\n");
-    }
-
-    private void cleanupData() {
-        dbi.inTransaction(new TransactionCallback<Void>() {
-            @Override
-            public Void inTransaction(Handle h, TransactionStatus status)
-                    throws Exception {
-                h.execute("truncate table accounts");
-                h.execute("truncate table entitlement_events");
-                h.execute("truncate table subscriptions");
-                h.execute("truncate table bundles");
-                h.execute("truncate table notifications");
-                h.execute("truncate table claimed_notifications");
-                h.execute("truncate table invoices");
-                h.execute("truncate table fixed_invoice_items");
-                h.execute("truncate table recurring_invoice_items");
-                h.execute("truncate table tag_definitions");
-                h.execute("truncate table tags");
-                h.execute("truncate table custom_fields");
-                h.execute("truncate table invoice_payments");
-                h.execute("truncate table payment_attempts");
-                h.execute("truncate table payments");
-                return null;
-            }
-        });
-    }
-
-    private void verifyTestResult(UUID accountId, UUID subscriptionId,
-                                  DateTime startDate, DateTime endDate,
-                                  BigDecimal amount, DateTime chargeThroughDate,
-                                  int totalInvoiceItemCount) throws EntitlementUserApiException {
-        BlockingSubscription bSubscription = (BlockingSubscription) entitlementUserApi.getSubscriptionFromId(subscriptionId);
-        SubscriptionData subscription = (SubscriptionData) bSubscription.getDelegateSubscription();
-
-        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId);
-        List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
-        for (Invoice invoice : invoices) {
-            invoiceItems.addAll(invoice.getInvoiceItems());
-        }
-        assertEquals(invoiceItems.size(), totalInvoiceItemCount);
-
-        boolean wasFound = false;
-
-        for (InvoiceItem item : invoiceItems) {
-            if (item.getStartDate().compareTo(startDate) == 0) {
-                if (item.getEndDate().compareTo(endDate) == 0) {
-                    if (item.getAmount().compareTo(amount) == 0) {
-                        wasFound = true;
-                        break;
-                    }
-                }
-            }
-        }
-
-        if (!wasFound) {
-            fail();
-        }
-
-        DateTime ctd = subscription.getChargedThroughDate();
-        assertNotNull(ctd);
-        log.info("Checking CTD: " + ctd.toString() + "; clock is " + clock.getUTCNow().toString());
-        assertTrue(clock.getUTCNow().isBefore(ctd));
-        assertTrue(ctd.compareTo(chargeThroughDate) == 0);
-    }
-
+public class TestIntegration extends TestIntegrationBase {
+ 
     @Test(groups = "slow", enabled = true)
     public void testBasePlanCompleteWithBillingDayInPast() throws Exception {
         DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
@@ -292,6 +96,60 @@ public class TestIntegration {
         }
     }
 
+   @Test(groups = "slow", enabled = true) 
+   public void testRepairChangeBPWithAddonIncluded() throws Exception {
+        
+        DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+        
+        Account account = accountUserApi.createAccount(getAccountData(25), null, null, context);
+        assertNotNull(account);
+
+        SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever", context);
+
+        String productName = "Shotgun";
+        BillingPeriod term = BillingPeriod.MONTHLY;
+        String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        SubscriptionData baseSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context));
+        assertNotNull(baseSubscription);
+        assertTrue(busHandler.isCompleted(DELAY));
+   
+        // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        SubscriptionData aoSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null, context));
+        assertTrue(busHandler.isCompleted(DELAY));
+        
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        SubscriptionData aoSubscription2 = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier("Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null, context)); 
+        assertTrue(busHandler.isCompleted(DELAY));
+        
+
+        // MOVE CLOCK A LITTLE BIT MORE -- EITHER STAY IN TRIAL OR GET OUT   
+        int duration = 35;
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(duration));
+        busHandler.pushExpectedEvent(NextEvent.PHASE);
+        busHandler.pushExpectedEvent(NextEvent.PHASE);
+        busHandler.pushExpectedEvent(NextEvent.PHASE);            
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(busHandler.isCompleted(DELAY));
+   }
+   
+   
     @Test(groups = "slow", enabled = true)
     public void testWithRecreatePlan() throws Exception {
 
@@ -363,16 +221,13 @@ public class TestIntegration {
 
 
     }
-    
-    private SubscriptionData subscriptionDataFromSubscription(Subscription sub) {
-        return (SubscriptionData)((BlockingSubscription)sub).getDelegateSubscription();
-    }
-    
+     
     private void testBasePlanComplete(DateTime initialCreationDate, int billingDay,
                                       boolean proRationExpected) throws Exception {
 
         log.info("Beginning test with BCD of " + billingDay);
-        Account account = accountUserApi.createAccount(getAccountData(billingDay), null, null, context);
+        AccountData accountData = getAccountData(billingDay);
+        Account account = accountUserApi.createAccount(accountData, null, null, context);
         UUID accountId = account.getId();
         assertNotNull(account);
 
@@ -626,88 +481,4 @@ public class TestIntegration {
         assertNotNull(invoices);
         assertEquals(invoices.size(),14);
     }
-
-    protected AccountData getAccountData(final int billingDay) {
-
-        final String someRandomKey = RandomStringUtils.randomAlphanumeric(10);
-        return new AccountData() {
-            @Override
-            public String getName() {
-                return "firstName lastName";
-            }
-            @Override
-            public int getFirstNameLength() {
-                return "firstName".length();
-            }
-            @Override
-            public String getEmail() {
-                return  someRandomKey + "@laposte.fr";
-            }
-            @Override
-            public String getPhone() {
-                return "4152876341";
-            }
-            @Override
-            public String getExternalKey() {
-                return someRandomKey;
-            }
-            @Override
-            public int getBillCycleDay() {
-                return billingDay;
-            }
-            @Override
-            public Currency getCurrency() {
-                return Currency.USD;
-            }
-            @Override
-            public String getPaymentProviderName() {
-                return MockModule.PLUGIN_NAME;
-            }
-
-            @Override
-            public DateTimeZone getTimeZone() {
-                return null;
-            }
-
-            @Override
-            public String getLocale() {
-                return null;
-            }
-
-            @Override
-            public String getAddress1() {
-                return null;
-            }
-
-            @Override
-            public String getAddress2() {
-                return null;
-            }
-
-            @Override
-            public String getCompanyName() {
-                return null;
-            }
-
-            @Override
-            public String getCity() {
-                return null;
-            }
-
-            @Override
-            public String getStateOrProvince() {
-                return null;
-            }
-
-            @Override
-            public String getPostalCode() {
-                return null;
-            }
-
-            @Override
-            public String getCountry() {
-                return null;
-            }
-        };
-    }
 }
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
new file mode 100644
index 0000000..3955cac
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
@@ -0,0 +1,356 @@
+/* 
+ * 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.beatrix.integration;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.RandomStringUtils;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.TransactionCallback;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.AccountService;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.beatrix.lifecycle.Lifecycle;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.EntitlementService;
+import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceService;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.invoice.model.InvoicingConfiguration;
+import com.ning.billing.junction.plumbing.api.BlockingSubscription;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.ClockMock;
+
+public class TestIntegrationBase implements TestFailure {
+
+    protected static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
+    protected static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
+
+    protected static final BigDecimal ONE = new BigDecimal("1.0000").setScale(NUMBER_OF_DECIMALS);
+    protected static final BigDecimal TWENTY_NINE = new BigDecimal("29.0000").setScale(NUMBER_OF_DECIMALS);
+    protected static final BigDecimal THIRTY = new BigDecimal("30.0000").setScale(NUMBER_OF_DECIMALS);
+    protected static final BigDecimal THIRTY_ONE = new BigDecimal("31.0000").setScale(NUMBER_OF_DECIMALS);
+
+    protected static final Logger log = LoggerFactory.getLogger(TestIntegration.class);
+    protected static long AT_LEAST_ONE_MONTH_MS =  31L * 24L * 3600L * 1000L;
+
+    protected static final long DELAY = 5000;
+
+    @Inject
+    protected IDBI dbi;
+
+    @Inject
+    protected ClockMock clock;
+    
+    protected CallContext context;
+
+    @Inject
+    protected Lifecycle lifecycle;
+
+    @Inject
+    protected BusService busService;
+
+    @Inject
+    protected EntitlementService entitlementService;
+
+    @Inject
+    protected InvoiceService invoiceService;
+
+    @Inject
+    protected AccountService accountService;
+
+    @Inject
+    protected MysqlTestingHelper helper;
+    @Inject
+    protected EntitlementUserApi entitlementUserApi;
+
+    @Inject
+    protected EntitlementTimelineApi repairApi;
+    
+    @Inject
+    protected InvoiceUserApi invoiceUserApi;
+
+    @Inject
+    protected AccountUserApi accountUserApi;
+
+    protected TestBusHandler busHandler;
+
+    
+    private boolean currentTestStatusSuccess;
+    private String currentTestFailedMsg;
+    
+    @Override
+    public void failed(String msg) {
+        currentTestStatusSuccess = false;
+        currentTestFailedMsg = msg;
+    }
+
+    @Override
+    public void reset() {
+        currentTestStatusSuccess = true;
+        currentTestFailedMsg = null;
+    }
+
+    protected void assertFailureFromBusHandler() {
+        if (!currentTestStatusSuccess) {
+            log.error(currentTestFailedMsg);
+            fail();
+        }
+    }
+
+    protected void setupMySQL() throws IOException
+    {
+        final String accountDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
+        final String entitlementDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+        final String invoiceDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+        final String paymentDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+        final String utilDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+
+        helper.startMysql();
+
+        helper.initDb(accountDdl);
+        helper.initDb(entitlementDdl);
+        helper.initDb(invoiceDdl);
+        helper.initDb(paymentDdl);
+        helper.initDb(utilDdl);
+    }
+
+    @BeforeClass(groups = "slow")
+    public void setup() throws Exception{
+
+        setupMySQL();
+        
+        cleanupData();
+        
+        context = new DefaultCallContextFactory(clock).createCallContext("Integration Test", CallOrigin.TEST, UserType.TEST);
+
+        /**
+         * Initialize lifecyle for subset of services
+         */
+        busHandler = new TestBusHandler(this);
+        lifecycle.fireStartupSequencePriorEventRegistration();
+        busService.getBus().register(busHandler);
+        lifecycle.fireStartupSequencePostEventRegistration();
+    }
+
+    @AfterClass(groups = "slow")
+    public void tearDown() throws Exception {
+        lifecycle.fireShutdownSequencePriorEventUnRegistration();
+        busService.getBus().unregister(busHandler);
+        lifecycle.fireShutdownSequencePostEventUnRegistration();
+        helper.stopMysql();
+    }
+
+
+    @BeforeMethod(groups = "slow")
+    public void setupTest() {
+
+        log.warn("\n");
+        log.warn("RESET TEST FRAMEWORK\n\n");
+        cleanupData();
+        busHandler.reset();
+        clock.resetDeltaFromReality();
+        reset();
+    }
+
+    @AfterMethod(groups = "slow")
+    public void cleanupTest() {
+        log.warn("DONE WITH TEST\n");
+    }
+    
+    protected void cleanupData() {
+        dbi.inTransaction(new TransactionCallback<Void>() {
+            @Override
+            public Void inTransaction(Handle h, TransactionStatus status)
+                    throws Exception {
+                h.execute("truncate table accounts");
+                h.execute("truncate table entitlement_events");
+                h.execute("truncate table subscriptions");
+                h.execute("truncate table bundles");
+                h.execute("truncate table notifications");
+                h.execute("truncate table claimed_notifications");
+                h.execute("truncate table invoices");
+                h.execute("truncate table fixed_invoice_items");
+                h.execute("truncate table recurring_invoice_items");
+                h.execute("truncate table tag_definitions");
+                h.execute("truncate table tags");
+                h.execute("truncate table custom_fields");
+                h.execute("truncate table invoice_payments");
+                h.execute("truncate table payment_attempts");
+                h.execute("truncate table payments");
+                return null;
+            }
+        });
+    }
+
+    protected void verifyTestResult(UUID accountId, UUID subscriptionId,
+                                  DateTime startDate, DateTime endDate,
+                                  BigDecimal amount, DateTime chargeThroughDate,
+                                  int totalInvoiceItemCount) throws EntitlementUserApiException {
+        SubscriptionData subscription = subscriptionDataFromSubscription(entitlementUserApi.getSubscriptionFromId(subscriptionId));
+
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId);
+        List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
+        for (Invoice invoice : invoices) {
+            invoiceItems.addAll(invoice.getInvoiceItems());
+        }
+        assertEquals(invoiceItems.size(), totalInvoiceItemCount);
+
+        boolean wasFound = false;
+
+        for (InvoiceItem item : invoiceItems) {
+            if (item.getStartDate().compareTo(startDate) == 0) {
+                if (item.getEndDate().compareTo(endDate) == 0) {
+                    if (item.getAmount().compareTo(amount) == 0) {
+                        wasFound = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (!wasFound) {
+            fail();
+        }
+
+        DateTime ctd = subscription.getChargedThroughDate();
+        assertNotNull(ctd);
+        log.info("Checking CTD: " + ctd.toString() + "; clock is " + clock.getUTCNow().toString());
+        assertTrue(clock.getUTCNow().isBefore(ctd));
+        assertTrue(ctd.compareTo(chargeThroughDate) == 0);
+    }
+       
+    protected SubscriptionData subscriptionDataFromSubscription(Subscription sub) {
+        return (SubscriptionData)((BlockingSubscription)sub).getDelegateSubscription();
+    }
+    
+    protected AccountData getAccountData(final int billingDay) {
+
+        final String someRandomKey = RandomStringUtils.randomAlphanumeric(10);
+        return new AccountData() {
+            @Override
+            public String getName() {
+                return "firstName lastName";
+            }
+            @Override
+            public int getFirstNameLength() {
+                return "firstName".length();
+            }
+            @Override
+            public String getEmail() {
+                return  someRandomKey + "@laposte.fr";
+            }
+            @Override
+            public String getPhone() {
+                return "4152876341";
+            }
+            @Override
+            public String getExternalKey() {
+                return someRandomKey;
+            }
+            @Override
+            public int getBillCycleDay() {
+                return billingDay;
+            }
+            @Override
+            public Currency getCurrency() {
+                return Currency.USD;
+            }
+            @Override
+            public String getPaymentProviderName() {
+                return MockModule.PLUGIN_NAME;
+            }
+
+            @Override
+            public DateTimeZone getTimeZone() {
+                return null;
+            }
+
+            @Override
+            public String getLocale() {
+                return null;
+            }
+
+            @Override
+            public String getAddress1() {
+                return null;
+            }
+
+            @Override
+            public String getAddress2() {
+                return null;
+            }
+
+            @Override
+            public String getCompanyName() {
+                return null;
+            }
+
+            @Override
+            public String getCity() {
+                return null;
+            }
+
+            @Override
+            public String getStateOrProvince() {
+                return null;
+            }
+
+            @Override
+            public String getPostalCode() {
+                return null;
+            }
+
+            @Override
+            public String getCountry() {
+                return null;
+            }
+        };
+    }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestRepairIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestRepairIntegration.java
new file mode 100644
index 0000000..6490640
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestRepairIntegration.java
@@ -0,0 +1,332 @@
+/* 
+ * 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.beatrix.integration;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.testng.Assert;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.beatrix.integration.TestBusHandler.NextEvent;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionEvents;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+
+@Test(groups = "slow")
+@Guice(modules = {MockModule.class})
+public class TestRepairIntegration extends TestIntegrationBase {
+
+    
+    @Test(groups={"slow"}, enabled=true)
+    public void testRepairChangeBPWithAddonIncludedIntrial() throws Exception {
+        testRepairChangeBPWithAddonIncluded(true);
+    }
+    
+    @Test(groups={"slow"}, enabled=true)
+    public void testRepairChangeBPWithAddonIncludedOutOfTrial() throws Exception {
+        testRepairChangeBPWithAddonIncluded(false);
+    }
+    
+    private void testRepairChangeBPWithAddonIncluded(boolean inTrial) throws Exception {
+        
+        DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+        
+        Account account = accountUserApi.createAccount(getAccountData(25), null, null, context);
+        assertNotNull(account);
+
+        SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever", context);
+
+        String productName = "Shotgun";
+        BillingPeriod term = BillingPeriod.MONTHLY;
+        String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        SubscriptionData baseSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context));
+        assertNotNull(baseSubscription);
+        assertTrue(busHandler.isCompleted(DELAY));
+   
+        // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        SubscriptionData aoSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null, context));
+        assertTrue(busHandler.isCompleted(DELAY));
+        
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        SubscriptionData aoSubscription2 = subscriptionDataFromSubscription( entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier("Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null, context)); 
+        assertTrue(busHandler.isCompleted(DELAY));
+        
+
+        // MOVE CLOCK A LITTLE BIT MORE -- EITHER STAY IN TRIAL OR GET OUT   
+        int duration = inTrial ? 3 : 35;
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(duration));
+        if (!inTrial) {
+            busHandler.pushExpectedEvent(NextEvent.PHASE);
+            busHandler.pushExpectedEvent(NextEvent.PHASE);
+            busHandler.pushExpectedEvent(NextEvent.PHASE);            
+            busHandler.pushExpectedEvent(NextEvent.INVOICE);
+            busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        }
+        clock.addDeltaFromReality(it.toDurationMillis());
+        if (!inTrial) {
+            assertTrue(busHandler.isCompleted(DELAY));
+        }
+        boolean ifRepair = false;
+        if (ifRepair) {
+            BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+            sortEventsOnBundle(bundleRepair);
+
+            // Quick check
+            SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+            assertEquals(bpRepair.getExistingEvents().size(), 2);
+
+            SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+            assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+            SubscriptionTimeline aoRepair2 = getSubscriptionRepair(aoSubscription2.getId(), bundleRepair);
+            assertEquals(aoRepair2.getExistingEvents().size(), 2);
+
+            DateTime bpChangeDate = clock.getUTCNow().minusDays(1);
+
+            List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+            des.add(createDeletedEvent(bpRepair.getExistingEvents().get(1).getEventId()));        
+
+            PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+            NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, bpChangeDate, spec);
+
+            bpRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+            bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(bpRepair));
+
+            // TIME TO  REPAIR
+            busHandler.pushExpectedEvent(NextEvent.INVOICE);
+            busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+            busHandler.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+            repairApi.repairBundle(bundleRepair, false, context);
+            assertTrue(busHandler.isCompleted(DELAY));
+
+            SubscriptionData newAoSubscription = subscriptionDataFromSubscription(  entitlementUserApi.getSubscriptionFromId(aoSubscription.getId()));
+            assertEquals(newAoSubscription.getState(), SubscriptionState.CANCELLED);
+            assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+            assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+            SubscriptionData newAoSubscription2 = subscriptionDataFromSubscription(  entitlementUserApi.getSubscriptionFromId(aoSubscription2.getId()));
+            assertEquals(newAoSubscription2.getState(), SubscriptionState.ACTIVE);
+            assertEquals(newAoSubscription2.getAllTransitions().size(), 2);
+            assertEquals(newAoSubscription2.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+
+            SubscriptionData newBaseSubscription = subscriptionDataFromSubscription(  entitlementUserApi.getSubscriptionFromId(baseSubscription.getId()));
+            assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+            assertEquals(newBaseSubscription.getAllTransitions().size(), 3);
+            assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+            assertFailureFromBusHandler();
+        }
+     }
+    
+    protected SubscriptionTimeline createSubscriptionReapir(final UUID id, final List<DeletedEvent> deletedEvents, final List<NewEvent> newEvents) {
+        return new SubscriptionTimeline() {
+            @Override
+            public UUID getId() {
+                return id;
+            }
+            @Override
+            public List<NewEvent> getNewEvents() {
+                return newEvents;
+            }
+            @Override
+            public List<ExistingEvent> getExistingEvents() {
+                return null;
+            }
+            @Override
+            public List<DeletedEvent> getDeletedEvents() {
+                return deletedEvents;
+            }
+        };
+    }
+
+    
+    protected BundleTimeline createBundleRepair(final UUID bundleId, final String viewId, final List<SubscriptionTimeline> subscriptionRepair) {
+        return new BundleTimeline() {
+            @Override
+            public String getViewId() {
+                return viewId;
+            }
+            @Override
+            public List<SubscriptionTimeline> getSubscriptions() {
+                return subscriptionRepair;
+            }
+            @Override
+            public UUID getBundleId() {
+                return bundleId;
+            }
+            @Override
+            public String getExternalKey() {
+                return null;
+            }
+        };
+    }
+
+    protected ExistingEvent createExistingEventForAssertion(final SubscriptionTransitionType type, 
+            final String productName, final PhaseType phaseType, final ProductCategory category, final String priceListName, final BillingPeriod billingPeriod,
+            final DateTime effectiveDateTime) {
+
+        final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
+        ExistingEvent ev = new ExistingEvent() {
+            @Override
+            public SubscriptionTransitionType getSubscriptionTransitionType() {
+                return type;
+            }
+             @Override
+            public DateTime getRequestedDate() {
+                 return null;
+            }
+            @Override
+            public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+                return spec;
+            }
+            @Override
+            public UUID getEventId() {
+                return null;
+            }
+            @Override
+            public DateTime getEffectiveDate() {
+                return effectiveDateTime;
+            }
+        };
+        return ev;
+    }
+    
+    protected SubscriptionTimeline getSubscriptionRepair(final UUID id, final BundleTimeline bundleRepair) {
+        for (SubscriptionTimeline cur : bundleRepair.getSubscriptions()) {
+            if (cur.getId().equals(id)) {
+                return cur;
+            }
+        }
+        Assert.fail("Failed to find SubscriptionReapir " + id);
+        return null;
+    }
+    protected void validateExistingEventForAssertion(final ExistingEvent expected, final ExistingEvent input) {
+        
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName()));
+        assertEquals(input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName());
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType()));
+        assertEquals(input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType());
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory()));
+        assertEquals(input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory());                    
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName()));
+        assertEquals(input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName());                    
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod()));
+        assertEquals(input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod());
+        log.info(String.format("Got %s -> Expected %s", input.getEffectiveDate(), expected.getEffectiveDate()));
+        assertEquals(input.getEffectiveDate(), expected.getEffectiveDate());        
+    }
+    
+    protected DeletedEvent createDeletedEvent(final UUID eventId) {
+        return new DeletedEvent() {
+            @Override
+            public UUID getEventId() {
+                return eventId;
+            }
+        };
+    }
+
+    protected NewEvent createNewEvent(final SubscriptionTransitionType type, final DateTime requestedDate, final PlanPhaseSpecifier spec) {
+
+        return new NewEvent() {
+            @Override
+            public SubscriptionTransitionType getSubscriptionTransitionType() {
+                return type;
+            }
+            @Override
+            public DateTime getRequestedDate() {
+                return requestedDate;
+            }
+            @Override
+            public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+                return spec;
+            }
+        };
+    }
+
+    protected void sortEventsOnBundle(final BundleTimeline bundle) {
+        if (bundle.getSubscriptions() == null) {
+            return;
+        }
+        for (SubscriptionTimeline cur : bundle.getSubscriptions()) {
+            if (cur.getExistingEvents() != null) {
+                sortExistingEvent(cur.getExistingEvents());
+            }
+            if (cur.getNewEvents() != null) {
+                sortNewEvent(cur.getNewEvents());
+            }
+        }
+    }
+
+    protected void sortExistingEvent(final List<ExistingEvent> events) {
+        Collections.sort(events, new Comparator<ExistingEvent>() {
+            @Override
+            public int compare(ExistingEvent arg0, ExistingEvent arg1) {
+                return arg0.getEffectiveDate().compareTo(arg1.getEffectiveDate());
+            }
+        });
+    }
+    protected void sortNewEvent(final List<NewEvent> events) {
+        Collections.sort(events, new Comparator<NewEvent>() {
+            @Override
+            public int compare(NewEvent arg0, NewEvent arg1) {
+                return arg0.getRequestedDate().compareTo(arg1.getRequestedDate());
+            }
+        });
+    }
+    
+
+}
diff --git a/beatrix/src/test/resources/catalogSample.xml b/beatrix/src/test/resources/catalogSample.xml
index 5b7aeaf..8c13707 100644
--- a/beatrix/src/test/resources/catalogSample.xml
+++ b/beatrix/src/test/resources/catalogSample.xml
@@ -1,43 +1,23 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!--
-  ~ 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.
-  -->
+<!-- ~ 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. -->
 
-<!-- 
-Use cases covered so far:
-	Tiered Product (Pistol/Shotgun/Assault-Rifle)
-	Multiple changeEvent plan policies
-	Multiple PlanAlignment (see below, trial add-on alignments and rescue discount package)
-	Product transition rules
-	Add on (Scopes, Holster)
-	Multi-pack addon (Extra-Ammo)
-	Addon Trial aligned to base plan (holster-monthly-regular)
-	Addon Trial aligned to creation (holster-monthly-special)
-	Rescue discount package (assault-rifle-annual-rescue)
-	Plan phase with a recurring and a one off (refurbish-maintenance)
-	Plan with more than 2 phase (gunclub discount plans)
-		
-Use Cases to do:
-	Tiered Add On
-	Riskfree period
-	
-
-
- -->
+<!-- Use cases covered so far: Tiered Product (Pistol/Shotgun/Assault-Rifle) 
+	Multiple changeEvent plan policies Multiple PlanAlignment (see below, trial 
+	add-on alignments and rescue discount package) Product transition rules Add 
+	on (Scopes, Hoster) Multi-pack addon (Extra-Ammo) Addon Trial aligned to 
+	base plan (holster-monthly-regular) Addon Trial aligned to creation (holster-monthly-special) 
+	Rescue discount package (assault-rifle-annual-rescue) Plan phase with a reccurring 
+	and a one off (refurbish-maintenance) Phan with more than 2 phase (gunclub 
+	discount plans) Use Cases to do: Tiered Add On Riskfree period -->
 <catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:noNamespaceSchemaLocation="CatalogSchema.xsd">
+	xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
 
 	<effectiveDate>2011-01-01T00:00:00+00:00</effectiveDate>
 	<catalogName>Firearms</catalogName>
@@ -47,24 +27,24 @@ Use Cases to do:
 		<currency>EUR</currency>
 		<currency>GBP</currency>
 	</currencies>
-	
+
 	<products>
+		<product name="Blowdart">
+			<category>BASE</category>
+		</product>
 		<product name="Pistol">
 			<category>BASE</category>
+		</product>
+		<product name="Shotgun">
+			<category>BASE</category>
 			<available>
 				<addonProduct>Telescopic-Scope</addonProduct>
 				<addonProduct>Laser-Scope</addonProduct>
 			</available>
 		</product>
-        <product name="Blowdart">
-            <category>BASE</category>
-        </product>
-		<product name="Shotgun">
-			<category>BASE</category>
-		</product>
 		<product name="Assault-Rifle">
 			<category>BASE</category>
-			<included> 
+			<included>
 				<addonProduct>Telescopic-Scope</addonProduct>
 			</included>
 			<available>
@@ -87,60 +67,42 @@ Use Cases to do:
 			<category>ADD_ON</category>
 		</product>
 	</products>
-	 
+
 	<rules>
 		<changePolicy>
-			<changePolicyCase> 
+			<changePolicyCase>
 				<phaseType>TRIAL</phaseType>
 				<policy>IMMEDIATE</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
-				<toProduct>Pistol</toProduct>
-				<policy>END_OF_TERM</policy>
+			<changePolicyCase>
+				<toProduct>Assault-Rifle</toProduct>
+				<policy>IMMEDIATE</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
-				<toPriceList>rescue</toPriceList>
-				<policy>END_OF_TERM</policy>
-			</changePolicyCase>		
-			<changePolicyCase> 
+			<changePolicyCase>
 				<fromProduct>Pistol</fromProduct>
 				<toProduct>Shotgun</toProduct>
 				<policy>IMMEDIATE</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
-				<fromProduct>Assault-Rifle</fromProduct>
-				<toProduct>Shotgun</toProduct>
-				<policy>END_OF_TERM</policy>
-			</changePolicyCase>
-			<changePolicyCase> 
-				<fromBillingPeriod>MONTHLY</fromBillingPeriod>
-				<toProduct>Assault-Rifle</toProduct>
-				<toBillingPeriod>MONTHLY</toBillingPeriod>
+			<changePolicyCase>
+				<toPriceList>rescue</toPriceList>
 				<policy>END_OF_TERM</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
-				<toProduct>Assault-Rifle</toProduct>
-				<policy>IMMEDIATE</policy>
-			</changePolicyCase>
-			<changePolicyCase> 
+			<changePolicyCase>
 				<fromBillingPeriod>MONTHLY</fromBillingPeriod>
 				<toBillingPeriod>ANNUAL</toBillingPeriod>
 				<policy>IMMEDIATE</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
+			<changePolicyCase>
 				<fromBillingPeriod>ANNUAL</fromBillingPeriod>
 				<toBillingPeriod>MONTHLY</toBillingPeriod>
 				<policy>END_OF_TERM</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
+			<changePolicyCase>
 				<policy>END_OF_TERM</policy>
 			</changePolicyCase>
 		</changePolicy>
 		<changeAlignment>
 			<changeAlignmentCase>
-				<alignment>START_OF_SUBSCRIPTION</alignment>
-			</changeAlignmentCase>
-			<changeAlignmentCase>
 				<toPriceList>rescue</toPriceList>
 				<alignment>CHANGE_OF_PLAN</alignment>
 			</changeAlignmentCase>
@@ -149,18 +111,25 @@ Use Cases to do:
 				<toPriceList>rescue</toPriceList>
 				<alignment>CHANGE_OF_PRICELIST</alignment>
 			</changeAlignmentCase>
+			<changeAlignmentCase>
+				<alignment>START_OF_SUBSCRIPTION</alignment>
+			</changeAlignmentCase>
 		</changeAlignment>
 		<cancelPolicy>
 			<cancelPolicyCase>
-				<policy>END_OF_TERM</policy>
-			</cancelPolicyCase>
-			<cancelPolicyCase>
 				<phaseType>TRIAL</phaseType>
 				<policy>IMMEDIATE</policy>
 			</cancelPolicyCase>
+			<cancelPolicyCase>
+				<policy>END_OF_TERM</policy>
+			</cancelPolicyCase>
 		</cancelPolicy>
 		<createAlignment>
 			<createAlignmentCase>
+				<product>Laser-Scope</product>
+				<alignment>START_OF_SUBSCRIPTION</alignment>
+			</createAlignmentCase>
+			<createAlignmentCase>
 				<alignment>START_OF_BUNDLE</alignment>
 			</createAlignmentCase>
 		</createAlignment>
@@ -186,21 +155,7 @@ Use Cases to do:
 	</rules>
 
 	<plans>
-		<plan name="pistol-monthly-no-trial">
-			<product>Pistol</product>
-			<finalPhase type="EVERGREEN">
-				<duration>
-					<unit>UNLIMITED</unit>
-				</duration>
-				<billingPeriod>MONTHLY</billingPeriod>
-				<recurringPrice>
-					<price><currency>GBP</currency><value>29.95</value></price>
-					<price><currency>EUR</currency><value>29.95</value></price> 
-					<price><currency>USD</currency><value>29.95</value></price>								
-				</recurringPrice>
-			</finalPhase>
-		</plan>
-        <plan name="blowdart-monthly">
+		<plan name="blowdart-monthly">
 			<product>Blowdart</product>
 			<initialPhases>
 				<phase type="TRIAL">
@@ -219,9 +174,18 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>MONTHLY</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>9.95</value></price>
-						<price><currency>EUR</currency><value>9.95</value></price>
-						<price><currency>GBP</currency><value>9.95</value></price>
+						<price>
+							<currency>USD</currency>
+							<value>9.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>9.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>9.95</value>
+						</price>
 					</recurringPrice>
 				</phase>
 			</initialPhases>
@@ -231,25 +195,34 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>29.95</value></price>
-					<price><currency>EUR</currency><value>29.95</value></price>
-					<price><currency>GBP</currency><value>29.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>29.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>29.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>29.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
+
 		<plan name="pistol-monthly">
 			<product>Pistol</product>
 			<initialPhases>
-                <phase type="TRIAL">
-                    <duration>
-                        <unit>DAYS</unit>
-                        <number>30</number>
-                    </duration>
-                    <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
-                    <fixedPrice>
-                    </fixedPrice>
-                    <!-- no price implies $0 -->
-                </phase>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice> <!-- empty price implies $0 -->
+					</fixedPrice>
+				</phase>
 			</initialPhases>
 			<finalPhase type="EVERGREEN">
 				<duration>
@@ -257,9 +230,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>GBP</currency><value>29.95</value></price>
-					<price><currency>EUR</currency><value>29.95</value></price> 
-					<price><currency>USD</currency><value>29.95</value></price>								
+					<price>
+						<currency>GBP</currency>
+						<value>29.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>29.95</value>
+					</price>
+					<price>
+						<currency>USD</currency>
+						<value>29.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -271,10 +253,9 @@ Use Cases to do:
 						<unit>DAYS</unit>
 						<number>30</number>
 					</duration>
-                    <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
-                    <fixedPrice>
-                    </fixedPrice>
-				    <!-- no price implies $0 -->
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice></fixedPrice>
+					<!-- no price implies $0 -->
 				</phase>
 			</initialPhases>
 			<finalPhase type="EVERGREEN">
@@ -284,25 +265,33 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>249.95</value></price>								
-					<price><currency>EUR</currency><value>149.95</value></price>
-					<price><currency>GBP</currency><value>169.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>249.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>149.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>169.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
 		<plan name="assault-rifle-monthly">
 			<product>Assault-Rifle</product>
 			<initialPhases>
-                                <phase type="TRIAL">
-                                        <duration>
-                                                <unit>DAYS</unit>
-                                                <number>30</number>
-                                        </duration>
-                                        <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
-                                        <fixedPrice>
-                                        </fixedPrice>
-                                    <!-- no price implies $0 -->
-                                </phase>
+				<phase type="TRIAL">
+					<duration>
+						<unit>DAYS</unit>
+						<number>30</number>
+					</duration>
+					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+					<fixedPrice>
+					</fixedPrice>
+				</phase>
 			</initialPhases>
 			<finalPhase type="EVERGREEN">
 				<duration>
@@ -310,9 +299,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>599.95</value></price>								
-					<price><currency>EUR</currency><value>349.95</value></price>
-					<price><currency>GBP</currency><value>399.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>599.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>349.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>399.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -335,9 +333,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -360,9 +367,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>2399.95</value></price>								
-					<price><currency>EUR</currency><value>1499.95</value></price>
-					<price><currency>GBP</currency><value>1699.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>2399.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>1499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>1699.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -385,9 +401,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>5999.95</value></price>								
-					<price><currency>EUR</currency><value>3499.95</value></price>
-					<price><currency>GBP</currency><value>3999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>5999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>3499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>3999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -410,9 +435,18 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>MONTHLY</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>9.95</value></price>								
-						<price><currency>EUR</currency><value>9.95</value></price>
-						<price><currency>GBP</currency><value>9.95</value></price>
+						<price>
+							<currency>USD</currency>
+							<value>9.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>9.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>9.95</value>
+						</price>
 					</recurringPrice>
 				</phase>
 			</initialPhases>
@@ -422,9 +456,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -447,9 +490,18 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>MONTHLY</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>19.95</value></price>								
-						<price><currency>EUR</currency><value>49.95</value></price>
-						<price><currency>GBP</currency><value>69.95</value></price>
+						<price>
+							<currency>USD</currency>
+							<value>19.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>49.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>69.95</value>
+						</price>
 					</recurringPrice>
 				</phase>
 			</initialPhases>
@@ -459,9 +511,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>2399.95</value></price>								
-					<price><currency>EUR</currency><value>1499.95</value></price>
-					<price><currency>GBP</currency><value>1699.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>2399.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>1499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>1699.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -484,10 +545,19 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>MONTHLY</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>99.95</value></price>								
-						<price><currency>EUR</currency><value>99.95</value></price>
-						<price><currency>GBP</currency><value>99.95</value></price>
-						</recurringPrice>
+						<price>
+							<currency>USD</currency>
+							<value>99.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>99.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>99.95</value>
+						</price>
+					</recurringPrice>
 				</phase>
 			</initialPhases>
 			<finalPhase type="EVERGREEN">
@@ -496,37 +566,110 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>5999.95</value></price>								
-					<price><currency>EUR</currency><value>3499.95</value></price>
-					<price><currency>GBP</currency><value>3999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>5999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>3499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>3999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
 		<plan name="laser-scope-monthly">
-		<product>Laser-Scope</product>
+			<product>Laser-Scope</product>
+			<initialPhases>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>1</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price>
+							<currency>USD</currency>
+							<value>999.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>499.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>999.95</value>
+						</price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
 			<finalPhase type="EVERGREEN">
 				<duration>
 					<unit>UNLIMITED</unit>
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>1999.95</value></price>								
-					<price><currency>EUR</currency><value>1499.95</value></price>
-					<price><currency>GBP</currency><value>1999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>1999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>1499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>1999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
 		<plan name="telescopic-scope-monthly">
 			<product>Telescopic-Scope</product>
+			<initialPhases>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>1</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price>
+							<currency>USD</currency>
+							<value>399.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>299.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>399.95</value>
+						</price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
 			<finalPhase type="EVERGREEN">
 				<duration>
 					<unit>UNLIMITED</unit>
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>999.95</value></price>								
-					<price><currency>EUR</currency><value>499.95</value></price>
-					<price><currency>GBP</currency><value>999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -538,9 +681,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>999.95</value></price>								
-					<price><currency>EUR</currency><value>499.95</value></price>
-					<price><currency>GBP</currency><value>999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 			<plansAllowedInBundle>-1</plansAllowedInBundle> <!-- arbitrary number of these (multipack) -->
@@ -564,9 +716,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -589,9 +750,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -605,9 +775,18 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>ANNUAL</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>5999.95</value></price>								
-						<price><currency>EUR</currency><value>3499.95</value></price>
-						<price><currency>GBP</currency><value>3999.95</value></price>
+						<price>
+							<currency>USD</currency>
+							<value>5999.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>3499.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>3999.95</value>
+						</price>
 					</recurringPrice>
 				</phase>
 			</initialPhases>
@@ -617,9 +796,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>5999.95</value></price>								
-					<price><currency>EUR</currency><value>3499.95</value></price>
-					<price><currency>GBP</currency><value>3999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>5999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>3499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>3999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -632,23 +820,40 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 				<fixedPrice>
-					<price><currency>USD</currency><value>599.95</value></price>								
-					<price><currency>EUR</currency><value>599.95</value></price>
-					<price><currency>GBP</currency><value>599.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>599.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>599.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>599.95</value>
+					</price>
 				</fixedPrice>
 			</finalPhase>
 		</plan>
 	</plans>
-
 	<priceLists>
-		<defaultPriceList name="DEFAULT"> 
+		<defaultPriceList name="DEFAULT">
 			<plans>
-                <plan>blowdart-monthly</plan>
+			    <plan>blowdart-monthly</plan>
 				<plan>pistol-monthly</plan>
 				<plan>shotgun-monthly</plan>
 				<plan>assault-rifle-monthly</plan>
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 5517eb8..5e0ded9 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
@@ -129,6 +129,7 @@ public class PlanAligner  {
             // If we never had any Plan change, borrow the logics for createPlan alignment
             case MIGRATE_ENTITLEMENT:
             case CREATE:
+            case RE_CREATE:                
                 List<TimedPhase> timedPhases = getTimedPhaseOnCreate(subscription.getStartDate(),
                         subscription.getBundleStartDate(),
                         lastPlanTransition.getNextPlan(),
@@ -211,11 +212,6 @@ public class PlanAligner  {
 
         Catalog catalog = catalogService.getFullCatalog();
         ProductCategory currentCategory = currentPlan.getProduct().getCategory();
-        // STEPH tiered ADDON not implemented yet
-        if (currentCategory != ProductCategory.BASE) {
-            throw new EntitlementError(String.format("Only implemented changePlan for BasePlan"));
-        }
-
         PlanPhaseSpecifier fromPlanPhaseSpecifier = new PlanPhaseSpecifier(currentPlan.getProduct().getName(),
                 currentCategory,
                 currentPlan.getBillingPeriod(),
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultChargeThruApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultChargeThruApi.java
index 3de7b6c..7bde4a2 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultChargeThruApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultChargeThruApi.java
@@ -26,9 +26,9 @@ import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
 import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.engine.dao.SubscriptionSqlDao;
 import com.ning.billing.util.ChangeType;
@@ -38,10 +38,8 @@ import com.ning.billing.util.callcontext.CallContextFactory;
 
 public class DefaultChargeThruApi implements ChargeThruApi {
 	private static final Logger log = LoggerFactory.getLogger(DefaultChargeThruApi.class);
-    private static final String API_USER_NAME = "Entitlement Billing Api";
-    private final CallContextFactory factory;
+
     private final EntitlementDao entitlementDao;
-    private final AccountUserApi accountApi;
     private final SubscriptionFactory subscriptionFactory;
   
     private static final String SUBSCRIPTION_TABLE_NAME = "subscriptions";
@@ -49,10 +47,8 @@ public class DefaultChargeThruApi implements ChargeThruApi {
     @Inject
     public DefaultChargeThruApi(final CallContextFactory factory, final SubscriptionFactory subscriptionFactory, final EntitlementDao dao, final AccountUserApi accountApi) {
         super();
-        this.factory = factory;
         this.subscriptionFactory = subscriptionFactory;
         this.entitlementDao = dao;
-        this.accountApi = accountApi;
     }
     
     @Override
@@ -67,8 +63,7 @@ public class DefaultChargeThruApi implements ChargeThruApi {
         SubscriptionBuilder builder = new SubscriptionBuilder(subscription)
             .setChargedThroughDate(ctd)
             .setPaidThroughDate(subscription.getPaidThroughDate());
-
-        entitlementDao.updateSubscription(new SubscriptionData(builder), context);
+        entitlementDao.updateChargedThroughDate(new SubscriptionData(builder), context);
     }
 
     @Override
@@ -80,17 +75,13 @@ public class DefaultChargeThruApi implements ChargeThruApi {
         if (subscription == null) {
             log.warn("Subscription not found when setting CTD.");
         } else {
-            Date paidThroughDate = (subscription.getPaidThroughDate() == null) ? null : subscription.getPaidThroughDate().toDate();
-
             DateTime chargedThroughDate = subscription.getChargedThroughDate();
             if (chargedThroughDate == null || chargedThroughDate.isBefore(ctd)) {
-                subscriptionSqlDao.updateSubscription(subscriptionId.toString(), subscription.getActiveVersion(),
-                                                      ctd.toDate(), paidThroughDate, context);
+                subscriptionSqlDao.updateChargedThroughDate(subscriptionId.toString(),
+                        ctd.toDate(), context);
                 AuditSqlDao auditSqlDao = transactionalDao.become(AuditSqlDao.class);
                 auditSqlDao.insertAuditFromTransaction(SUBSCRIPTION_TABLE_NAME, subscriptionId.toString(), ChangeType.UPDATE, context);
             }
         }
     }
-
-
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/AccountMigrationData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/AccountMigrationData.java
index 24c4e07..07a0034 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/AccountMigrationData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/AccountMigrationData.java
@@ -20,7 +20,7 @@ import java.util.List;
 
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.user.ApiEventMigrateBilling;
 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
index 8390504..b09fc17 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
@@ -31,12 +31,12 @@ import com.google.inject.Inject;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.alignment.MigrationPlanAligner;
 import com.ning.billing.entitlement.alignment.TimedMigration;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData.SubscriptionMigrationData;
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
@@ -86,7 +86,7 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
 
         for (final EntitlementBundleMigration curBundle : toBeMigrated.getBundles()) {
 
-            SubscriptionBundleData bundleData = new SubscriptionBundleData(curBundle.getBundleKey(), accountId);
+            SubscriptionBundleData bundleData = new SubscriptionBundleData(curBundle.getBundleKey(), accountId, clock.getUTCNow());
             List<SubscriptionMigrationData> bundleSubscriptionData = new LinkedList<AccountMigrationData.SubscriptionMigrationData>();
 
 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionApiService.java
new file mode 100644
index 0000000..046b982
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionApiService.java
@@ -0,0 +1,51 @@
+/* 
+ * 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.entitlement.api;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.util.callcontext.CallContext;
+
+public interface SubscriptionApiService {
+
+    public SubscriptionData createPlan(SubscriptionBuilder builder, Plan plan, PhaseType initialPhase,
+            String realPriceList, DateTime requestedDate, DateTime effectiveDate, DateTime processedDate,
+            CallContext context)
+        throws EntitlementUserApiException;
+
+    public boolean recreatePlan(SubscriptionData subscription, PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
+        throws EntitlementUserApiException;
+
+
+    public boolean cancel(SubscriptionData subscription, DateTime requestedDate, boolean eot, CallContext context)
+        throws EntitlementUserApiException;
+
+    public boolean uncancel(SubscriptionData subscription, CallContext context)
+        throws EntitlementUserApiException;
+
+    public boolean changePlan(SubscriptionData subscription, String productName, BillingPeriod term,
+            String priceList, DateTime requestedDate, CallContext context)
+        throws EntitlementUserApiException;
+
+    public void commitCustomFields(SubscriptionData subscription, CallContext context);
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionFactory.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionFactory.java
new file mode 100644
index 0000000..2b12487
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionFactory.java
@@ -0,0 +1,27 @@
+/* 
+ * 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.entitlement.api;
+
+import java.util.List;
+
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+
+public interface SubscriptionFactory {
+    
+    public SubscriptionData createSubscription(SubscriptionBuilder builder, List<EntitlementEvent> events);
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultDeletedEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultDeletedEvent.java
new file mode 100644
index 0000000..14b1e9e
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultDeletedEvent.java
@@ -0,0 +1,42 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+
+public class DefaultDeletedEvent implements DeletedEvent {
+
+    private final UUID id;
+    private final DateTime effectiveDate;
+    
+    public DefaultDeletedEvent(UUID id, DateTime effectiveDate) {
+        this.id = id;
+        this.effectiveDate = effectiveDate;
+    }
+    
+    @Override
+    public UUID getEventId() {
+        return id;
+    }
+    
+    public DateTime getEffectiveDate() {
+        return effectiveDate;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultEntitlementTimelineApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultEntitlementTimelineApi.java
new file mode 100644
index 0000000..d65a606
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultEntitlementTimelineApi.java
@@ -0,0 +1,476 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.CatalogUserApi;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
+import com.ning.billing.entitlement.api.timeline.EntitlementRepairException;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.SubscriptionEventTransition;
+import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
+import com.ning.billing.entitlement.exceptions.EntitlementError;
+import com.ning.billing.entitlement.glue.EntitlementModule;
+import com.ning.billing.util.callcontext.CallContext;
+
+public class DefaultEntitlementTimelineApi implements EntitlementTimelineApi {
+
+    private final EntitlementDao dao;
+    private final SubscriptionFactory factory;
+    private final RepairEntitlementLifecycleDao repairDao;
+    private final CatalogService catalogService;
+
+
+    private enum RepairType  {
+        BASE_REPAIR,
+        ADD_ON_REPAIR,
+        STANDALONE_REPAIR
+    }
+
+
+    @Inject
+    public DefaultEntitlementTimelineApi(@Named(EntitlementModule.REPAIR_NAMED) final SubscriptionFactory factory, final CatalogService catalogService,
+            @Named(EntitlementModule.REPAIR_NAMED) final RepairEntitlementLifecycleDao repairDao, final EntitlementDao dao) {
+        this.catalogService = catalogService;
+        this.dao = dao;
+        this.repairDao = repairDao;
+        this.factory = factory;
+    }
+
+
+    @Override
+    public BundleTimeline getBundleRepair(final UUID bundleId) 
+    throws EntitlementRepairException {
+
+        try {
+            SubscriptionBundle bundle = dao.getSubscriptionBundleFromId(bundleId);
+            if (bundle == null) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_BUNDLE, bundleId);
+            }
+            final List<Subscription> subscriptions = dao.getSubscriptions(factory, bundleId);
+            if (subscriptions.size() == 0) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_NO_ACTIVE_SUBSCRIPTIONS, bundleId);
+            }
+            final String viewId = getViewId(((SubscriptionBundleData) bundle).getLastSysUpdateTime(), subscriptions);
+            final List<SubscriptionTimeline> repairs = createGetSubscriptionRepairList(subscriptions, Collections.<SubscriptionTimeline>emptyList()); 
+            return createGetBundleRepair(bundleId, bundle.getKey(), viewId, repairs);
+        } catch (CatalogApiException e) {
+            throw new EntitlementRepairException(e);
+        }
+    }
+
+
+
+    @Override
+    public BundleTimeline repairBundle(final BundleTimeline input, final boolean dryRun, final CallContext context)
+    throws EntitlementRepairException {
+
+        try {
+
+            SubscriptionBundle bundle = dao.getSubscriptionBundleFromId(input.getBundleId());
+            if (bundle == null) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_BUNDLE, input.getBundleId());
+            }
+            
+
+            // Subscriptions are ordered with BASE subscription first-- if exists
+            final List<Subscription> subscriptions = dao.getSubscriptions(factory, input.getBundleId());
+            if (subscriptions.size() == 0) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_NO_ACTIVE_SUBSCRIPTIONS, input.getBundleId());
+            }
+
+            final String viewId = getViewId(((SubscriptionBundleData) bundle).getLastSysUpdateTime(), subscriptions);
+            if (!viewId.equals(input.getViewId())) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_VIEW_CHANGED,input.getBundleId(), input.getViewId(), viewId);
+            }
+
+            DateTime firstDeletedBPEventTime = null;
+            DateTime lastRemainingBPEventTime = null;
+
+            boolean isBasePlanRecreate = false;
+            DateTime newBundleStartDate = null;
+
+            SubscriptionDataRepair baseSubscriptionRepair = null;
+            List<SubscriptionDataRepair> addOnSubscriptionInRepair = new LinkedList<SubscriptionDataRepair>();
+            List<SubscriptionDataRepair> inRepair =  new LinkedList<SubscriptionDataRepair>();
+            for (Subscription cur : subscriptions) {
+
+                //
+                SubscriptionTimeline curRepair = findAndCreateSubscriptionRepair(cur.getId(), input.getSubscriptions());
+                if (curRepair != null) {
+                    SubscriptionDataRepair curInputRepair = ((SubscriptionDataRepair) cur);
+                    final List<EntitlementEvent> remaining = getRemainingEventsAndValidateDeletedEvents(curInputRepair, firstDeletedBPEventTime, curRepair.getDeletedEvents());
+
+                    final boolean isPlanRecreate = (curRepair.getNewEvents().size() > 0 
+                            && (curRepair.getNewEvents().get(0).getSubscriptionTransitionType() == SubscriptionTransitionType.CREATE 
+                                    || curRepair.getNewEvents().get(0).getSubscriptionTransitionType() == SubscriptionTransitionType.RE_CREATE));
+
+                    final DateTime newSubscriptionStartDate = isPlanRecreate ? curRepair.getNewEvents().get(0).getRequestedDate() : null;
+
+                    if (isPlanRecreate && remaining.size() != 0) {
+                        throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_SUB_RECREATE_NOT_EMPTY, cur.getId(), cur.getBundleId());
+                    }
+
+                    if (!isPlanRecreate && remaining.size() == 0) {
+                        throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_SUB_EMPTY, cur.getId(), cur.getBundleId());
+                    }
+
+                    if (cur.getCategory() == ProductCategory.BASE) {
+
+                        int bpTransitionSize =((SubscriptionData) cur).getAllTransitions().size();
+                        lastRemainingBPEventTime = (remaining.size() > 0) ? curInputRepair.getAllTransitions().get(remaining.size() - 1).getEffectiveTransitionTime() : null;
+                        firstDeletedBPEventTime =  (remaining.size() < bpTransitionSize) ? curInputRepair.getAllTransitions().get(remaining.size()).getEffectiveTransitionTime() : null;
+
+                        isBasePlanRecreate = isPlanRecreate;
+                        newBundleStartDate = newSubscriptionStartDate;
+                    }
+
+                    if (curRepair.getNewEvents().size() > 0) {
+                        DateTime lastRemainingEventTime = (remaining.size() == 0) ? null : curInputRepair.getAllTransitions().get(remaining.size() - 1).getEffectiveTransitionTime();
+                        validateFirstNewEvent(curInputRepair, curRepair.getNewEvents().get(0), lastRemainingBPEventTime, lastRemainingEventTime);
+                    }
+
+
+                    SubscriptionDataRepair curOutputRepair = createSubscriptionDataRepair(curInputRepair, newBundleStartDate, newSubscriptionStartDate, remaining);
+                    repairDao.initializeRepair(curInputRepair.getId(), remaining);
+                    inRepair.add(curOutputRepair);
+                    if (curOutputRepair.getCategory() == ProductCategory.ADD_ON) {
+                        // Check if ADD_ON RE_CREATE is before BP start
+                        if (isPlanRecreate && subscriptions.get(0).getStartDate().isAfter(curRepair.getNewEvents().get(0).getRequestedDate())) {
+                            throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_AO_CREATE_BEFORE_BP_START, cur.getId(), cur.getBundleId());                            
+                        }
+                        addOnSubscriptionInRepair.add(curOutputRepair);
+                    } else if (curOutputRepair.getCategory() == ProductCategory.BASE) {
+                        baseSubscriptionRepair = curOutputRepair;
+                    }
+                }
+            }
+
+            final RepairType repairType = getRepairType(subscriptions.get(0), (baseSubscriptionRepair != null));
+            switch(repairType) {
+            case BASE_REPAIR:
+                // We need to add any existing addon that are not in the input repair list
+                for (Subscription cur : subscriptions) {
+                    if (cur.getCategory() == ProductCategory.ADD_ON && !inRepair.contains(cur)) {
+                        SubscriptionDataRepair curOutputRepair = createSubscriptionDataRepair((SubscriptionDataRepair) cur, newBundleStartDate, null, ((SubscriptionDataRepair) cur).getEvents());
+                        repairDao.initializeRepair(curOutputRepair.getId(), ((SubscriptionDataRepair) cur).getEvents());
+                        inRepair.add(curOutputRepair);
+                        addOnSubscriptionInRepair.add(curOutputRepair);
+                    }
+                }
+
+                break;
+            case ADD_ON_REPAIR:
+                // We need to set the baseSubscription as it is useful to calculate addon validity
+                SubscriptionDataRepair baseSubscription =  (SubscriptionDataRepair) subscriptions.get(0);
+                baseSubscriptionRepair = createSubscriptionDataRepair(baseSubscription, baseSubscription.getBundleStartDate(), baseSubscription.getStartDate(), baseSubscription.getEvents());
+                break;
+            case STANDALONE_REPAIR:
+            default:
+                break;
+
+            }
+
+            validateBasePlanRecreate(isBasePlanRecreate, subscriptions, input.getSubscriptions());
+            validateInputSubscriptionsKnown(subscriptions, input.getSubscriptions());
+
+
+            Collection<NewEvent> newEvents = createOrderedNewEventInput(input.getSubscriptions());
+            Iterator<NewEvent> it = newEvents.iterator();
+            while (it.hasNext()) {
+                DefaultNewEvent cur = (DefaultNewEvent) it.next();
+                SubscriptionDataRepair curDataRepair = findSubscriptionDataRepair(cur.getSubscriptionId(), inRepair);
+                if (curDataRepair == null) {
+                    throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_SUBSCRIPTION, cur.getSubscriptionId());
+                }
+                curDataRepair.addNewRepairEvent(cur, baseSubscriptionRepair, addOnSubscriptionInRepair, context);
+            }
+            
+            if (dryRun) {
+                
+                baseSubscriptionRepair.addFutureAddonCancellation(addOnSubscriptionInRepair, context);
+
+                final List<SubscriptionTimeline> repairs = createGetSubscriptionRepairList(subscriptions, convertDataRepair(inRepair)); 
+                return createGetBundleRepair(input.getBundleId(), bundle.getKey(), input.getViewId(), repairs);
+            } else {
+                dao.repair(bundle.getAccountId(), input.getBundleId(), inRepair, context);
+                return getBundleRepair(input.getBundleId());
+            }
+        } catch (CatalogApiException e) {
+            throw new EntitlementRepairException(e);
+        } finally {
+            repairDao.cleanup();
+        }
+    }
+
+ 
+    
+    
+    private RepairType getRepairType(final Subscription firstSubscription, final boolean gotBaseSubscription) {
+        if (firstSubscription.getCategory() == ProductCategory.BASE) {
+            return gotBaseSubscription ? RepairType.BASE_REPAIR : RepairType.ADD_ON_REPAIR;
+        } else {
+            return RepairType.STANDALONE_REPAIR;
+        }
+    }
+
+    private void validateBasePlanRecreate(boolean isBasePlanRecreate, List<Subscription> subscriptions, List<SubscriptionTimeline> input) 
+    throws EntitlementRepairException  {
+
+        if (!isBasePlanRecreate) {
+            return;
+        }
+        if (subscriptions.size() != input.size()) {
+            throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_BP_RECREATE_MISSING_AO, subscriptions.get(0).getBundleId());
+        }
+        for (SubscriptionTimeline cur : input) {
+            if (cur.getNewEvents().size() != 0 
+                    && (cur.getNewEvents().get(0).getSubscriptionTransitionType() != SubscriptionTransitionType.CREATE
+                            && cur.getNewEvents().get(0).getSubscriptionTransitionType() != SubscriptionTransitionType.RE_CREATE)) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE, subscriptions.get(0).getBundleId());
+            }
+        }
+    }
+
+
+    private void validateInputSubscriptionsKnown(List<Subscription> subscriptions, List<SubscriptionTimeline> input)
+    throws EntitlementRepairException {
+
+        for (SubscriptionTimeline cur : input) {
+            boolean found = false;
+            for (Subscription s : subscriptions) {
+                if (s.getId().equals(cur.getId())) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_SUBSCRIPTION, cur.getId());
+            }
+        }
+    }
+
+    private void validateFirstNewEvent(final SubscriptionData data, final NewEvent firstNewEvent, final DateTime lastBPRemainingTime, final DateTime lastRemainingTime) 
+    throws EntitlementRepairException {
+        if (lastBPRemainingTime != null &&
+                firstNewEvent.getRequestedDate().isBefore(lastBPRemainingTime)) {
+            throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING, firstNewEvent.getSubscriptionTransitionType(), data.getId());
+        }
+        if (lastRemainingTime != null &&
+                firstNewEvent.getRequestedDate().isBefore(lastRemainingTime)) {
+            throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING, firstNewEvent.getSubscriptionTransitionType(), data.getId());
+        }
+
+    }
+
+    private Collection<NewEvent> createOrderedNewEventInput(List<SubscriptionTimeline> subscriptionsReapir) {
+        TreeSet<NewEvent> newEventSet = new TreeSet<SubscriptionTimeline.NewEvent>(new Comparator<NewEvent>() {
+            @Override
+            public int compare(NewEvent o1, NewEvent o2) {
+                return o1.getRequestedDate().compareTo(o2.getRequestedDate());
+            }
+        });
+        for (SubscriptionTimeline cur : subscriptionsReapir) {
+            for (NewEvent e : cur.getNewEvents()) {
+                newEventSet.add(new DefaultNewEvent(cur.getId(), e.getPlanPhaseSpecifier(), e.getRequestedDate(), e.getSubscriptionTransitionType()));    
+            }
+        }
+        return newEventSet;
+    }
+
+
+    private List<EntitlementEvent> getRemainingEventsAndValidateDeletedEvents(final SubscriptionDataRepair data, final DateTime firstBPDeletedTime,
+            final List<SubscriptionTimeline.DeletedEvent> deletedEvents) 
+            throws EntitlementRepairException  {
+
+        if (deletedEvents == null || deletedEvents.size() == 0) {
+            return data.getEvents();
+        }
+
+        int nbDeleted = 0;
+        LinkedList<EntitlementEvent> result = new LinkedList<EntitlementEvent>();
+        for (EntitlementEvent cur : data.getEvents()) {
+
+            boolean foundDeletedEvent = false;
+            for (SubscriptionTimeline.DeletedEvent d : deletedEvents) {
+                if (cur.getId().equals(d.getEventId())) {
+                    foundDeletedEvent = true;
+                    nbDeleted++;
+                    break;
+                }
+            }
+            if (!foundDeletedEvent && nbDeleted > 0) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_INVALID_DELETE_SET, cur.getId(), data.getId());
+            }
+            if (firstBPDeletedTime != null && 
+                    ! cur.getEffectiveDate().isBefore(firstBPDeletedTime) &&
+                    ! foundDeletedEvent) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_MISSING_AO_DELETE_EVENT, cur.getId(), data.getId());                
+            }
+
+            if (nbDeleted == 0) {
+                result.add(cur);
+            }
+        }
+        if (nbDeleted != deletedEvents.size()) {
+            for (SubscriptionTimeline.DeletedEvent d : deletedEvents) {
+                boolean found = false;
+                for (SubscriptionTransitionData cur : data.getAllTransitions()) {
+                    if (cur.getId().equals(d.getEventId())) {
+                        found = true;
+                        continue;
+                    }
+                }
+                if (!found) {
+                    throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_NON_EXISTENT_DELETE_EVENT, d.getEventId(), data.getId());
+                }
+            }
+
+        }
+        return result;
+    }
+
+
+    private String getViewId(DateTime lastUpdateBundleDate, List<Subscription> subscriptions) {
+        StringBuilder tmp = new StringBuilder();
+        long lastOrderedId = -1;
+        for (Subscription cur : subscriptions) {
+            lastOrderedId = lastOrderedId < ((SubscriptionData) cur).getLastEventOrderedId() ? ((SubscriptionData) cur).getLastEventOrderedId() : lastOrderedId;
+        }
+        tmp.append(lastOrderedId);
+        tmp.append("-");
+        tmp.append(lastUpdateBundleDate.toDate().getTime());
+        return tmp.toString();
+    }
+
+    private BundleTimeline createGetBundleRepair(final UUID bundleId, final String externalKey, final String viewId, final List<SubscriptionTimeline> repairList) {
+        return new BundleTimeline() {
+            @Override
+            public String getViewId() {
+                return viewId;
+            }
+            @Override
+            public List<SubscriptionTimeline> getSubscriptions() {
+                return repairList;
+            }
+            @Override
+            public UUID getBundleId() {
+                return bundleId;
+            }
+            @Override
+            public String getExternalKey() {
+                return null;
+            }
+        };
+
+    }
+
+    private List<SubscriptionTimeline> createGetSubscriptionRepairList(final List<Subscription> subscriptions, final List<SubscriptionTimeline> inRepair) throws CatalogApiException {
+
+        final List<SubscriptionTimeline> result = new LinkedList<SubscriptionTimeline>();
+        Set<UUID> repairIds = new TreeSet<UUID>();
+        for (final SubscriptionTimeline cur : inRepair) {
+            repairIds.add(cur.getId());
+            result.add(cur);
+        }
+        for (final Subscription cur : subscriptions) {
+            if ( !repairIds.contains(cur.getId())) { 
+                result.add(new DefaultSubscriptionTimeline((SubscriptionDataRepair) cur, catalogService.getFullCatalog()));
+            }
+        }
+        return result;
+    }
+
+
+    private List<SubscriptionTimeline> convertDataRepair(List<SubscriptionDataRepair> input) throws CatalogApiException  {
+        List<SubscriptionTimeline> result = new LinkedList<SubscriptionTimeline>();
+        for (SubscriptionDataRepair cur : input) {
+            result.add(new DefaultSubscriptionTimeline(cur, catalogService.getFullCatalog()));
+        }
+        return result;
+    }
+
+    private SubscriptionDataRepair findSubscriptionDataRepair(final UUID targetId, final List<SubscriptionDataRepair> input) {
+        for (SubscriptionDataRepair cur : input) {
+            if (cur.getId().equals(targetId)) {
+                return cur;
+            }
+        }
+        return null;
+    }
+
+
+    private SubscriptionDataRepair createSubscriptionDataRepair(final SubscriptionData curData, final DateTime newBundleStartDate, final DateTime newSubscriptionStartDate, final List<EntitlementEvent> initialEvents) {
+        SubscriptionBuilder builder = new SubscriptionBuilder(curData);
+        builder.setActiveVersion(curData.getActiveVersion() + 1);
+        if (newBundleStartDate != null) {
+            builder.setBundleStartDate(newBundleStartDate);
+        }
+        if (newSubscriptionStartDate != null) {
+            builder.setStartDate(newSubscriptionStartDate);
+        }
+        if (initialEvents.size() > 0) {
+            for (EntitlementEvent cur : initialEvents) {
+                cur.setActiveVersion(builder.getActiveVersion());
+            }
+        }
+        SubscriptionDataRepair result = (SubscriptionDataRepair) factory.createSubscription(builder, initialEvents);
+        return result;
+    }
+
+
+    private SubscriptionTimeline findAndCreateSubscriptionRepair(final UUID target, final List<SubscriptionTimeline> input) {
+        for (SubscriptionTimeline cur : input) {
+            if (target.equals(cur.getId())) {
+                return new DefaultSubscriptionTimeline(cur);
+            }
+        }
+        return null;
+    }
+}
+
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultNewEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultNewEvent.java
new file mode 100644
index 0000000..87ece9f
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultNewEvent.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.entitlement.api.timeline;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+
+public class DefaultNewEvent implements NewEvent {
+
+    private final UUID subscriptionId;
+    private final PlanPhaseSpecifier spec;
+    private final DateTime requestedDate;
+    private final SubscriptionTransitionType transitionType;
+    
+    public DefaultNewEvent(final UUID subscriptionId, final PlanPhaseSpecifier spec, final DateTime requestedDate, final SubscriptionTransitionType transitionType) {
+        this.subscriptionId = subscriptionId;
+        this.spec = spec;
+        this.requestedDate = requestedDate;
+        this.transitionType = transitionType;
+    }
+    
+    @Override
+    public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+        return spec;
+    }
+
+    @Override
+    public DateTime getRequestedDate() {
+        return requestedDate;
+    }
+
+    @Override
+    public SubscriptionTransitionType getSubscriptionTransitionType() {
+        return transitionType;
+    }
+    
+    public UUID getSubscriptionId() {
+        return subscriptionId;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultRepairEntitlementEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultRepairEntitlementEvent.java
new file mode 100644
index 0000000..4b53842
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultRepairEntitlementEvent.java
@@ -0,0 +1,63 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementEvent;
+
+public class DefaultRepairEntitlementEvent implements RepairEntitlementEvent {
+
+    private final UUID userToken;
+    private final UUID bundleId;
+    private final UUID accountId;
+    private final DateTime efectiveDate;
+    
+    
+    public DefaultRepairEntitlementEvent(final UUID userToken, final UUID acountId, final UUID bundleId, final DateTime efectiveDate) {
+        this.userToken = userToken;
+        this.bundleId = bundleId;
+        this.accountId = acountId;
+        this.efectiveDate = efectiveDate;
+    }
+    
+    @Override
+    public BusEventType getBusEventType() {
+        return BusEventType.BUNDLE_REPAIR;
+    }
+
+    @Override
+    public UUID getUserToken() {
+        return userToken;
+    }
+
+    @Override
+    public UUID getBundleId() {
+        return bundleId;
+    }
+
+    @Override
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    @Override
+    public DateTime getEffectiveDate() {
+        return efectiveDate;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultSubscriptionTimeline.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultSubscriptionTimeline.java
new file mode 100644
index 0000000..315550d
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultSubscriptionTimeline.java
@@ -0,0 +1,289 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.events.phase.PhaseEvent;
+import com.ning.billing.entitlement.events.user.ApiEvent;
+import com.ning.billing.entitlement.events.user.ApiEventType;
+
+public class DefaultSubscriptionTimeline implements SubscriptionTimeline  {
+
+    private final UUID id;
+    private final List<ExistingEvent> existingEvents;
+    private final List<NewEvent> newEvents;
+    private final List<DeletedEvent> deletedEvents;    
+    
+    public DefaultSubscriptionTimeline(final UUID id) {
+        this.id = id;
+        this.existingEvents = Collections.<SubscriptionTimeline.ExistingEvent>emptyList();
+        this.deletedEvents =  Collections.<SubscriptionTimeline.DeletedEvent>emptyList();
+        this.newEvents = Collections.<SubscriptionTimeline.NewEvent>emptyList();
+    }
+    
+    public DefaultSubscriptionTimeline(SubscriptionTimeline input) {
+        this.id = input.getId();
+        this.existingEvents = (input.getExistingEvents() != null) ? new ArrayList<SubscriptionTimeline.ExistingEvent>(input.getExistingEvents()) : 
+            Collections.<SubscriptionTimeline.ExistingEvent>emptyList();
+        sortExistingEvent(this.existingEvents);
+        this.deletedEvents = (input.getDeletedEvents() != null) ? new ArrayList<SubscriptionTimeline.DeletedEvent>(input.getDeletedEvents()) : 
+            Collections.<SubscriptionTimeline.DeletedEvent>emptyList();
+        this.newEvents = (input.getNewEvents() != null) ? new ArrayList<SubscriptionTimeline.NewEvent>(input.getNewEvents()) : 
+            Collections.<SubscriptionTimeline.NewEvent>emptyList();
+        sortNewEvent(this.newEvents);
+    }
+    
+     // CTOR for returning events only
+    public DefaultSubscriptionTimeline(SubscriptionDataRepair input, Catalog catalog) throws CatalogApiException {
+        this.id = input.getId();
+        this.existingEvents = toExistingEvents(catalog, input.getActiveVersion(), input.getCategory(), input.getEvents());
+        this.deletedEvents = null;
+        this.newEvents = null;
+    }
+    
+   private List<ExistingEvent> toExistingEvents(final Catalog catalog, final long activeVersion, final ProductCategory category, final List<EntitlementEvent> events) 
+       throws CatalogApiException {
+        
+        List<ExistingEvent> result = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        
+        String prevProductName = null; 
+        BillingPeriod prevBillingPeriod = null;
+        String prevPriceListName = null;
+        PhaseType prevPhaseType = null;
+        
+        DateTime startDate = null;
+        
+        for (final EntitlementEvent cur : events) {
+            
+            // First active event is used to figure out which catalog version to use.
+            //startDate = (startDate == null && cur.getActiveVersion() == activeVersion) ?  cur.getEffectiveDate() : startDate;
+            
+            // STEPH that needs tp be reviewed if we support mutli version events
+            if (cur.getActiveVersion() != activeVersion) {
+                continue;
+            }
+            startDate = (startDate == null) ?  cur.getEffectiveDate() : startDate;
+            
+            
+            String productName = null; 
+            BillingPeriod billingPeriod = null;
+            String priceListName = null;
+            PhaseType phaseType = null;
+
+            ApiEventType apiType = null;
+            switch (cur.getType()) {
+            case PHASE:
+                PhaseEvent phaseEV = (PhaseEvent) cur;
+                phaseType = catalog.findPhase(phaseEV.getPhase(), cur.getEffectiveDate(), startDate).getPhaseType();
+                productName = prevProductName;
+                billingPeriod = catalog.findPhase(phaseEV.getPhase(), cur.getEffectiveDate(), startDate).getBillingPeriod();
+                priceListName = prevPriceListName;
+                break;
+
+            case API_USER:
+                ApiEvent userEV = (ApiEvent) cur;
+                apiType = userEV.getEventType();
+                Plan plan =  (userEV.getEventPlan() != null) ? catalog.findPlan(userEV.getEventPlan(), cur.getRequestedDate(), startDate) : null;
+                phaseType = (userEV.getEventPlanPhase() != null) ? catalog.findPhase(userEV.getEventPlanPhase(), cur.getEffectiveDate(), startDate).getPhaseType() : prevPhaseType;
+                productName = (plan != null) ? plan.getProduct().getName() : prevProductName;
+                billingPeriod = (userEV.getEventPlanPhase() != null) ? catalog.findPhase(userEV.getEventPlanPhase(), cur.getEffectiveDate(), startDate).getBillingPeriod() : prevBillingPeriod;
+                priceListName = (userEV.getPriceList() != null) ? userEV.getPriceList() : prevPriceListName;
+                break;
+            }
+
+            final SubscriptionTransitionType transitionType = SubscriptionTransitionData.toSubscriptionTransitionType(cur.getType(), apiType);
+            
+            final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
+            result.add(new ExistingEvent() {
+                @Override
+                public SubscriptionTransitionType getSubscriptionTransitionType() {
+                    return transitionType;
+                }
+                @Override
+                public DateTime getRequestedDate() {
+                    return cur.getRequestedDate();
+                }
+                @Override
+                public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+                    return spec;
+                }
+                @Override
+                public UUID getEventId() {
+                    return cur.getId();
+                }
+                @Override
+                public DateTime getEffectiveDate() {
+                    return cur.getEffectiveDate();
+                }
+            });
+            
+            prevProductName = productName; 
+            prevBillingPeriod = billingPeriod;
+            prevPriceListName = priceListName;
+            prevPhaseType = phaseType;
+
+        }
+        sortExistingEvent(result);
+        return result;
+    }
+   
+   
+   /*
+   
+   private List<ExistingEvent> toExistingEvents(final Catalog catalog, final long processingVersion, final ProductCategory category, final List<EntitlementEvent> events, List<ExistingEvent> result)
+       throws CatalogApiException {
+       
+       
+       String prevProductName = null; 
+       BillingPeriod prevBillingPeriod = null;
+       String prevPriceListName = null;
+       PhaseType prevPhaseType = null;
+       
+       DateTime startDate = null;
+       
+       for (final EntitlementEvent cur : events) {
+           
+           if (processingVersion != cur.getActiveVersion()) {
+               continue;
+           }
+           
+           // First active event is used to figure out which catalog version to use.
+           startDate = (startDate == null && cur.getActiveVersion() == processingVersion) ?  cur.getEffectiveDate() : startDate;
+           
+           String productName = null; 
+           BillingPeriod billingPeriod = null;
+           String priceListName = null;
+           PhaseType phaseType = null;
+
+           ApiEventType apiType = null;
+           switch (cur.getType()) {
+           case PHASE:
+               PhaseEvent phaseEV = (PhaseEvent) cur;
+               phaseType = catalog.findPhase(phaseEV.getPhase(), cur.getEffectiveDate(), startDate).getPhaseType();
+               productName = prevProductName;
+               billingPeriod = prevBillingPeriod;
+               priceListName = prevPriceListName;
+               break;
+
+           case API_USER:
+               ApiEvent userEV = (ApiEvent) cur;
+               apiType = userEV.getEventType();
+               Plan plan =  (userEV.getEventPlan() != null) ? catalog.findPlan(userEV.getEventPlan(), cur.getRequestedDate(), startDate) : null;
+               phaseType = (userEV.getEventPlanPhase() != null) ? catalog.findPhase(userEV.getEventPlanPhase(), cur.getEffectiveDate(), startDate).getPhaseType() : prevPhaseType;
+               productName = (plan != null) ? plan.getProduct().getName() : prevProductName;
+               billingPeriod = (plan != null) ? plan.getBillingPeriod() : prevBillingPeriod;
+               priceListName = (userEV.getPriceList() != null) ? userEV.getPriceList() : prevPriceListName;
+               break;
+           }
+
+           final SubscriptionTransitionType transitionType = SubscriptionTransitionData.toSubscriptionTransitionType(cur.getType(), apiType);
+           
+           final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
+           result.add(new ExistingEvent() {
+               @Override
+               public SubscriptionTransitionType getSubscriptionTransitionType() {
+                   return transitionType;
+               }
+               @Override
+               public DateTime getRequestedDate() {
+                   return cur.getRequestedDate();
+               }
+               @Override
+               public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+                   return spec;
+               }
+               @Override
+               public UUID getEventId() {
+                   return cur.getId();
+               }
+               @Override
+               public DateTime getEffectiveDate() {
+                   return cur.getEffectiveDate();
+               }
+           });
+           prevProductName = productName; 
+           prevBillingPeriod = billingPeriod;
+           prevPriceListName = priceListName;
+           prevPhaseType = phaseType;
+       }
+   }
+   */
+   
+   
+    
+    
+    
+    
+    @Override
+    public UUID getId() {
+        return id;
+    }
+    
+    @Override
+    public List<DeletedEvent> getDeletedEvents() {
+        return deletedEvents;
+    }
+
+    @Override
+    public List<NewEvent> getNewEvents() {
+        return newEvents;
+    }
+    
+    @Override
+    public List<ExistingEvent> getExistingEvents() {
+        return existingEvents;
+    }
+    
+    private void sortExistingEvent(final List<ExistingEvent> events) {
+        if (events != null) {
+            Collections.sort(events, new Comparator<ExistingEvent>() {
+                @Override
+                public int compare(ExistingEvent arg0, ExistingEvent arg1) {
+                    return arg0.getEffectiveDate().compareTo(arg1.getEffectiveDate());
+                }
+            });
+        }
+    }
+    private void sortNewEvent(final List<NewEvent> events) {
+        if (events != null) {
+            Collections.sort(events, new Comparator<NewEvent>() {
+                @Override
+                public int compare(NewEvent arg0, NewEvent arg1) {
+                    return arg0.getRequestedDate().compareTo(arg1.getRequestedDate());
+                }
+            });
+        }
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementLifecycleDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementLifecycleDao.java
new file mode 100644
index 0000000..dff77b3
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementLifecycleDao.java
@@ -0,0 +1,28 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.entitlement.events.EntitlementEvent;
+
+public interface RepairEntitlementLifecycleDao {
+
+    public void initializeRepair(final UUID subscriptionId, final List<EntitlementEvent> initialEvents);
+    
+    public void cleanup();
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionApiService.java
new file mode 100644
index 0000000..659b3be
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionApiService.java
@@ -0,0 +1,36 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.entitlement.alignment.PlanAligner;
+import com.ning.billing.entitlement.api.SubscriptionApiService;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionApiService;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.glue.EntitlementModule;
+import com.ning.billing.util.clock.Clock;
+
+public class RepairSubscriptionApiService extends DefaultSubscriptionApiService implements SubscriptionApiService {
+
+    @Inject
+    public RepairSubscriptionApiService(Clock clock, @Named(EntitlementModule.REPAIR_NAMED) EntitlementDao dao,
+            CatalogService catalogService, PlanAligner planAligner) {
+        super(clock, dao, catalogService, planAligner);
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionFactory.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionFactory.java
new file mode 100644
index 0000000..e63c3a4
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionFactory.java
@@ -0,0 +1,56 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+import java.util.List;
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.entitlement.api.SubscriptionApiService;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionApiService;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.engine.addon.AddonUtils;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.glue.EntitlementModule;
+import com.ning.billing.util.clock.Clock;
+
+public class RepairSubscriptionFactory extends DefaultSubscriptionFactory implements SubscriptionFactory {
+
+    private final AddonUtils addonUtils;
+    private final EntitlementDao repairDao;
+    
+    @Inject
+    public RepairSubscriptionFactory(@Named(EntitlementModule.REPAIR_NAMED) SubscriptionApiService apiService,
+            @Named(EntitlementModule.REPAIR_NAMED) EntitlementDao dao,
+            Clock clock, CatalogService catalogService, AddonUtils addonUtils) {
+        super(apiService, clock, catalogService);
+        this.addonUtils = addonUtils;
+        this.repairDao = dao;
+    }
+     
+    @Override
+    public SubscriptionData createSubscription(SubscriptionBuilder builder,
+            List<EntitlementEvent> events) {
+        SubscriptionData subscription = new SubscriptionDataRepair(builder, events, apiService, repairDao, clock, addonUtils, catalogService);
+        subscription.rebuildTransitions(events, catalogService.getFullCatalog());
+        return subscription;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionDataRepair.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionDataRepair.java
new file mode 100644
index 0000000..1dfe9c3
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionDataRepair.java
@@ -0,0 +1,213 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.joda.time.DateTime;
+import org.omg.CORBA.Request;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionApiService;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.EntitlementRepairException;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionEventTransition;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.engine.addon.AddonUtils;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
+import com.ning.billing.entitlement.events.user.ApiEventBuilder;
+import com.ning.billing.entitlement.events.user.ApiEventCancel;
+
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.clock.Clock;
+
+public class SubscriptionDataRepair extends SubscriptionData {
+
+    private final AddonUtils addonUtils;
+    private final Clock clock;
+    private final EntitlementDao repairDao;
+    private final CatalogService catalogService;
+    
+    private final List<EntitlementEvent> initialEvents;
+
+    // Low level events are ONLY used for Repair APIs
+    protected List<EntitlementEvent> events;
+
+
+    public SubscriptionDataRepair(SubscriptionBuilder builder, List<EntitlementEvent> initialEvents, SubscriptionApiService apiService,
+            EntitlementDao dao, Clock clock, AddonUtils addonUtils, CatalogService catalogService) {
+        super(builder, apiService, clock);
+        this.repairDao = dao;
+        this.addonUtils = addonUtils;
+        this.clock = clock;
+        this.catalogService = catalogService;
+        this.initialEvents = initialEvents;
+    }
+
+    
+    DateTime getLastUserEventEffectiveDate() {
+        DateTime res = null;
+        for (EntitlementEvent cur : events) {
+            if (cur.getActiveVersion() != getActiveVersion()) {
+                break;
+            }
+            if (cur.getType() == EventType.PHASE) {
+                continue;
+            }
+            res = cur.getEffectiveDate();
+        }
+        return res;
+    }
+
+    public void addNewRepairEvent(final DefaultNewEvent input, final SubscriptionDataRepair baseSubscription, final List<SubscriptionDataRepair> addonSubscriptions, final CallContext context)
+    throws EntitlementRepairException {
+
+        try {
+            final PlanPhaseSpecifier spec = input.getPlanPhaseSpecifier();
+            switch(input.getSubscriptionTransitionType()) {
+            case CREATE:
+            case RE_CREATE:
+                recreate(spec, input.getRequestedDate(), context);
+                checkAddonRights(baseSubscription);
+                break;
+            case CHANGE:
+                changePlan(spec.getProductName(), spec.getBillingPeriod(), spec.getPriceListName(), input.getRequestedDate(), context);
+                checkAddonRights(baseSubscription);
+                trickleDownBPEffectForAddon(addonSubscriptions, getLastUserEventEffectiveDate(), context);
+                break;
+            case CANCEL:
+                cancel(input.getRequestedDate(), false, context);
+                trickleDownBPEffectForAddon(addonSubscriptions, getLastUserEventEffectiveDate(), context);
+                break;
+            case PHASE:
+                break;
+            default:
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_TYPE, input.getSubscriptionTransitionType(), id);
+            }
+        } catch (EntitlementUserApiException e) {
+            throw new EntitlementRepairException(e);
+        } catch (CatalogApiException e) {
+            throw new EntitlementRepairException(e);
+        }
+    }
+
+
+    public void addFutureAddonCancellation(List<SubscriptionDataRepair> addOnSubscriptionInRepair, final CallContext context) {
+
+        if (category != ProductCategory.BASE) {
+            return;
+        }
+
+        SubscriptionEventTransition pendingTransition = getPendingTransition();
+        if (pendingTransition == null) {
+            return;
+        }
+        Product baseProduct = (pendingTransition.getTransitionType() == SubscriptionTransitionType.CANCEL) ? null : 
+            pendingTransition.getNextPlan().getProduct();
+
+        addAddonCancellationIfRequired(addOnSubscriptionInRepair, baseProduct, pendingTransition.getEffectiveTransitionTime(), context);
+    }
+    
+    private void trickleDownBPEffectForAddon(final List<SubscriptionDataRepair> addOnSubscriptionInRepair, final DateTime effectiveDate, final CallContext context)
+     throws EntitlementUserApiException {
+
+        if (category != ProductCategory.BASE) {
+            return;
+        }
+
+        Product baseProduct = (getState() == SubscriptionState.CANCELLED ) ?
+                null : getCurrentPlan().getProduct();
+        addAddonCancellationIfRequired(addOnSubscriptionInRepair, baseProduct, effectiveDate, context);
+    }
+    
+    
+    
+    private void addAddonCancellationIfRequired(final List<SubscriptionDataRepair> addOnSubscriptionInRepair, Product baseProduct, final DateTime effectiveDate, final CallContext context) {
+
+        DateTime now = clock.getUTCNow();
+        Iterator<SubscriptionDataRepair> it = addOnSubscriptionInRepair.iterator();
+        while (it.hasNext()) {
+            SubscriptionDataRepair cur = it.next();
+            if (cur.getState() == SubscriptionState.CANCELLED ||
+                    cur.getCategory() != ProductCategory.ADD_ON) {
+                continue;
+            }
+            Plan addonCurrentPlan = cur.getCurrentPlan();
+            if (baseProduct == null ||
+                    addonUtils.isAddonIncluded(baseProduct, addonCurrentPlan) ||
+                    ! addonUtils.isAddonAvailable(baseProduct, addonCurrentPlan)) {
+
+                EntitlementEvent cancelEvent = new ApiEventCancel(new ApiEventBuilder()
+                .setSubscriptionId(cur.getId())
+                .setActiveVersion(cur.getActiveVersion())
+                .setProcessedDate(now)
+                .setEffectiveDate(effectiveDate)
+                .setRequestedDate(now)
+                .setUserToken(context.getUserToken())
+                .setFromDisk(true));
+                repairDao.cancelSubscription(cur.getId(), cancelEvent, context, 0);
+                cur.rebuildTransitions(repairDao.getEventsForSubscription(cur.getId()), catalogService.getFullCatalog());
+            }
+        }
+    }
+
+    private void checkAddonRights(final SubscriptionDataRepair baseSubscription) 
+        throws EntitlementUserApiException, CatalogApiException  {
+        if (category == ProductCategory.ADD_ON) {
+            addonUtils.checkAddonCreationRights(baseSubscription, getCurrentPlan());
+        }
+    }
+    
+    public void rebuildTransitions(final List<EntitlementEvent> inputEvents, final Catalog catalog) {
+        this.events = inputEvents;
+        super.rebuildTransitions(inputEvents, catalog);
+    }
+
+    public List<EntitlementEvent> getEvents() {
+        return events;
+    }
+
+    public List<EntitlementEvent> getInitialEvents() {
+        return initialEvents;
+    }
+
+    
+    public Collection<EntitlementEvent> getNewEvents() {
+        Collection<EntitlementEvent> newEvents  = Collections2.filter(events, new Predicate<EntitlementEvent>() {
+            @Override
+            public boolean apply(EntitlementEvent input) {
+                return ! initialEvents.contains(input);
+            }
+        });
+        return newEvents;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
index 59040be..115c60b 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
@@ -31,8 +31,9 @@ import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.Product;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.engine.addon.AddonUtils;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
@@ -44,13 +45,13 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
     private final Clock clock;
     private final EntitlementDao dao;
     private final CatalogService catalogService;
-    private final SubscriptionApiService apiService;
+    private final DefaultSubscriptionApiService apiService;
     private final AddonUtils addonUtils;
     private final SubscriptionFactory subscriptionFactory;
 
     @Inject
     public DefaultEntitlementUserApi(Clock clock, EntitlementDao dao, CatalogService catalogService,
-            SubscriptionApiService apiService, final SubscriptionFactory subscriptionFactory, AddonUtils addonUtils) {
+            DefaultSubscriptionApiService apiService, final SubscriptionFactory subscriptionFactory, AddonUtils addonUtils) {
         super();
         this.clock = clock;
         this.apiService = apiService;
@@ -98,7 +99,7 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
 
     public SubscriptionBundle createBundleForAccount(UUID accountId, String bundleName, CallContext context)
     throws EntitlementUserApiException {
-        SubscriptionBundleData bundle = new SubscriptionBundleData(bundleName, accountId);
+        SubscriptionBundleData bundle = new SubscriptionBundleData(bundleName, accountId, clock.getUTCNow());
         return dao.createSubscriptionBundle(bundle, context);
     }
 
@@ -147,7 +148,10 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
                 if (baseSubscription == null) {
                     throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_NO_BP, bundleId);
                 }
-                checkAddonCreationRights(baseSubscription, plan);
+                if (effectiveDate.isBefore(baseSubscription.getStartDate())) {
+                    throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_DATE, requestedDate.toString());
+                }
+                addonUtils.checkAddonCreationRights(baseSubscription, plan);
                 bundleStartDate = baseSubscription.getStartDate();
                 break;
             case STANDALONE:
@@ -177,25 +181,6 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
     }
 
 
-    private void checkAddonCreationRights(SubscriptionData baseSubscription, Plan targetAddOnPlan)
-            throws EntitlementUserApiException, CatalogApiException {
-
-        if (baseSubscription.getState() != SubscriptionState.ACTIVE) {
-            throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_BP_NON_ACTIVE, targetAddOnPlan.getName());
-        }
-
-        Product baseProduct = baseSubscription.getCurrentPlan().getProduct();
-        if (addonUtils.isAddonIncluded(baseProduct, targetAddOnPlan)) {
-            throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_ALREADY_INCLUDED,
-                    targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
-        }
-
-        if (!addonUtils.isAddonAvailable(baseProduct, targetAddOnPlan)) {
-            throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_NOT_AVAILABLE,
-                    targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
-        }
-    }
-
     @Override
     public DateTime getNextBillingDate(UUID accountId) {
         List<SubscriptionBundle> bundles = getBundlesForAccount(accountId);
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java
index 5c7b848..4da1fe0 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java
@@ -29,18 +29,24 @@ public class SubscriptionBundleData implements SubscriptionBundle {
     private final String key;
     private final UUID accountId;
     private final DateTime startDate;
+    private final DateTime lastSysTimeUpdate; 
     private final OverdueState<SubscriptionBundle> overdueState;
+    
+    public SubscriptionBundleData(String name, UUID accountId, DateTime startDate) {
+        this(UUID.randomUUID(), name, accountId, startDate, startDate);
+    }
 
-    public SubscriptionBundleData(String name, UUID accountId) {
-        this(UUID.randomUUID(), name, accountId, null, null);
+    public SubscriptionBundleData(UUID id, String key, UUID accountId, DateTime startDate, DateTime lastSysUpdate) {
+        this(id, key, accountId, startDate, lastSysUpdate, null);
     }
 
-    public SubscriptionBundleData(UUID id, String key, UUID accountId, DateTime startDate, OverdueState<SubscriptionBundle> overdueState) {
+    public SubscriptionBundleData(UUID id, String key, UUID accountId, DateTime startDate, DateTime lastSysUpdate, OverdueState<SubscriptionBundle> overdueState) {
         super();
         this.id = id;
         this.key = key;
         this.accountId = accountId;
         this.startDate = startDate;
+        this.lastSysTimeUpdate = lastSysUpdate;
         this.overdueState = overdueState;
     }
 
@@ -59,13 +65,16 @@ public class SubscriptionBundleData implements SubscriptionBundle {
         return accountId;
     }
 
-
     // STEPH do we need it ? and should we return that and when is that populated/updated?
     @Override
     public DateTime getStartDate() {
         return startDate;
     }
-
+    
+    public DateTime getLastSysUpdateTime() {
+        return lastSysTimeUpdate;
+    }
+    
     @Override
     public OverdueState<SubscriptionBundle> getOverdueState() {
         return overdueState;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
index 4ccbf95..3f801a9 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
@@ -37,8 +37,9 @@ import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceList;
 import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.entitlement.api.user.SubscriptionEventTransition.SubscriptionTransitionType;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.SubscriptionApiService;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.api.user.SubscriptionTransitionDataIterator.Kind;
 import com.ning.billing.entitlement.api.user.SubscriptionTransitionDataIterator.Order;
 import com.ning.billing.entitlement.api.user.SubscriptionTransitionDataIterator.TimeLimit;
@@ -54,41 +55,45 @@ import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.entity.ExtendedEntityBase;
 
-public class SubscriptionData extends ExtendedEntityBase implements Subscription {
+public class SubscriptionData extends ExtendedEntityBase implements
+        Subscription {
 
     private final static Logger log = LoggerFactory.getLogger(SubscriptionData.class);
 
-    private final Clock clock;
-    private final SubscriptionApiService apiService;
+
+    protected final Clock clock;
+    protected final SubscriptionApiService apiService;
     //
     // Final subscription fields
     //
-    private final UUID bundleId;
-    private final DateTime startDate;
-    private final DateTime bundleStartDate;
-    private final ProductCategory category;
+    protected final UUID bundleId;
+    protected final DateTime startDate;
+    protected final DateTime bundleStartDate;
+    protected final ProductCategory category;
 
     //
-    // Those can be modified through non User APIs, and a new Subscription object would be created
+    // Those can be modified through non User APIs, and a new Subscription
+    // object would be created
     //
-    private final long activeVersion;
-    private final DateTime chargedThroughDate;
-    private final DateTime paidThroughDate;
+    protected final long activeVersion;
+    protected final DateTime chargedThroughDate;
+    protected final DateTime paidThroughDate;
 
+    
     //
     // User APIs (create, change, cancel,...) will recompute those each time,
     // so the user holding that subscription object get the correct state when
     // the call completes
     //
-    private LinkedList<SubscriptionTransitionData> transitions;
+    protected LinkedList<SubscriptionTransitionData> transitions;
 
     // Transient object never returned at the API
     public SubscriptionData(SubscriptionBuilder builder) {
         this(builder, null, null);
     }
 
-    public SubscriptionData(SubscriptionBuilder builder, @Nullable SubscriptionApiService apiService,
-                            @Nullable Clock clock) {
+    public SubscriptionData(SubscriptionBuilder builder,
+            @Nullable SubscriptionApiService apiService, @Nullable Clock clock) {
         super(builder.getId(), null, null);
         this.apiService = apiService;
         this.clock = clock;
@@ -107,7 +112,8 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
     }
 
     @Override
-    public void saveFieldValue(String fieldName, @Nullable String fieldValue, CallContext context) {
+    public void saveFieldValue(String fieldName, @Nullable String fieldValue,
+            CallContext context) {
         super.setFieldValue(fieldName, fieldValue);
         apiService.commitCustomFields(this, context);
     }
@@ -136,25 +142,28 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
 
     @Override
     public SubscriptionState getState() {
-        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextState();
+        return (getPreviousTransition() == null) ? null
+                : getPreviousTransition().getNextState();
     }
 
     @Override
     public PlanPhase getCurrentPhase() {
-        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPhase();
+        return (getPreviousTransition() == null) ? null
+                : getPreviousTransition().getNextPhase();
     }
 
-
     @Override
     public Plan getCurrentPlan() {
-        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPlan();
+        return (getPreviousTransition() == null) ? null
+                : getPreviousTransition().getNextPlan();
     }
 
     @Override
     public PriceList getCurrentPriceList() {
-        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPriceList();
-    }
+        return (getPreviousTransition() == null) ? null :
+            getPreviousTransition().getNextPriceList();
 
+    }
 
     @Override
     public DateTime getEndDate() {
@@ -165,42 +174,32 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
         return null;
     }
 
-
     @Override
-    public boolean cancel(DateTime requestedDate, boolean eot, CallContext context) throws EntitlementUserApiException  {
+    public boolean cancel(DateTime requestedDate, boolean eot,
+            CallContext context) throws EntitlementUserApiException {
         return apiService.cancel(this, requestedDate, eot, context);
     }
 
     @Override
-    public boolean uncancel(CallContext context) throws EntitlementUserApiException {
+    public boolean uncancel(CallContext context)
+            throws EntitlementUserApiException {
         return apiService.uncancel(this, context);
     }
 
     @Override
     public boolean changePlan(String productName, BillingPeriod term,
-            String priceList, DateTime requestedDate, CallContext context) throws EntitlementUserApiException {
-        return apiService.changePlan(this, productName, term, priceList, requestedDate, context);
+            String priceList, DateTime requestedDate, CallContext context)
+            throws EntitlementUserApiException {
+        return apiService.changePlan(this, productName, term, priceList,
+                requestedDate, context);
     }
 
     @Override
-    public boolean recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
-            throws EntitlementUserApiException {
+    public boolean recreate(PlanPhaseSpecifier spec, DateTime requestedDate,
+            CallContext context) throws EntitlementUserApiException {
         return apiService.recreatePlan(this, spec, requestedDate, context);
     }
 
-    public List<SubscriptionEventTransition> getBillingTransitions() {
-
-        if (transitions == null) {
-            return Collections.emptyList();
-        }
-        List<SubscriptionEventTransition> result = new ArrayList<SubscriptionEventTransition>();
-        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
-                Order.ASC_FROM_PAST, Kind.BILLING, Visibility.ALL, TimeLimit.ALL);
-        while (it.hasNext()) {
-            result.add(it.next());
-        }
-        return result;
-    }
 
     @Override
     public SubscriptionEventTransition getPendingTransition() {
@@ -208,8 +207,9 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
         if (transitions == null) {
             return null;
         }
-        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
-                Order.ASC_FROM_PAST, Kind.ENTITLEMENT, Visibility.ALL, TimeLimit.FUTURE_ONLY);
+        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+                clock, transitions, Order.ASC_FROM_PAST, Kind.ENTITLEMENT,
+                Visibility.ALL, TimeLimit.FUTURE_ONLY);
         return it.hasNext() ? it.next() : null;
     }
 
@@ -218,27 +218,12 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
         if (transitions == null) {
             return null;
         }
-        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
-                Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT, Visibility.FROM_DISK_ONLY, TimeLimit.PAST_OR_PRESENT_ONLY);
+        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+                clock, transitions, Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT,
+                Visibility.FROM_DISK_ONLY, TimeLimit.PAST_OR_PRESENT_ONLY);
         return it.hasNext() ? it.next() : null;
     }
-
-    public SubscriptionEventTransition getTransitionFromEvent(final EntitlementEvent event, final int seqId) {
-        if (transitions == null || event == null) {
-            return null;
-        }
-        for (SubscriptionEventTransition cur : transitions) {
-            if (cur.getId().equals(event.getId())) {
-                return new SubscriptionTransitionData((SubscriptionTransitionData) cur, seqId);
-            }
-        }
-        return null;
-    }
-
-    public long getActiveVersion() {
-        return activeVersion;
-    }
-
+    
     @Override
     public ProductCategory getCategory() {
         return category;
@@ -258,32 +243,107 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
         return paidThroughDate;
     }
 
-    public SubscriptionTransitionData getInitialTransitionForCurrentPlan() {
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((id == null) ? 0 : id.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        SubscriptionData other = (SubscriptionData) obj;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        return true;
+    }
+
+    public List<SubscriptionEventTransition> getBillingTransitions() {
+
         if (transitions == null) {
-            throw new EntitlementError(String.format("No transitions for subscription %s", getId()));
+            return Collections.emptyList();
+        }
+        List<SubscriptionEventTransition> result = new ArrayList<SubscriptionEventTransition>();
+        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+                clock, transitions, Order.ASC_FROM_PAST, Kind.BILLING,
+                Visibility.ALL, TimeLimit.ALL);
+        while (it.hasNext()) {
+            result.add(it.next());
+        }
+        return result;
+    }
+
+
+    public SubscriptionEventTransition getTransitionFromEvent(final EntitlementEvent event, final int seqId) {
+        if (transitions == null || event == null) {
+            return null;
         }
+        for (SubscriptionEventTransition cur : transitions) {
+            if (cur.getId().equals(event.getId())) {
+                return new SubscriptionTransitionData(
+                        (SubscriptionTransitionData) cur, seqId);
+            }
+        }
+        return null;
+    }
+
+    public long getLastEventOrderedId() {
+        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+                clock, transitions, Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT,
+                Visibility.FROM_DISK_ONLY, TimeLimit.ALL);
+        return it.hasNext() ? it.next().getTotalOrdering() :  -1L;
+    }
+    
+    public long getActiveVersion() {
+        return activeVersion;
+    }
+
 
+    public List<SubscriptionTransitionData> getAllTransitions() {
+        return transitions;
+    }
+
+    public SubscriptionTransitionData getInitialTransitionForCurrentPlan() {
+        if (transitions == null) {
+            throw new EntitlementError(String.format(
+                    "No transitions for subscription %s", getId()));
+        }
 
-        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
-                Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT, Visibility.ALL, TimeLimit.PAST_OR_PRESENT_ONLY);
+        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+                clock, transitions, Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT,
+                Visibility.ALL, TimeLimit.PAST_OR_PRESENT_ONLY);
         while (it.hasNext()) {
             SubscriptionTransitionData cur = it.next();
-            if (cur.getTransitionType() == SubscriptionTransitionType.CREATE ||
-                    cur.getTransitionType() == SubscriptionTransitionType.RE_CREATE ||
-                    cur.getTransitionType() == SubscriptionTransitionType.CHANGE ||
-                    cur.getTransitionType() == SubscriptionTransitionType.MIGRATE_ENTITLEMENT) {
+            if (cur.getTransitionType() == SubscriptionTransitionType.CREATE
+                    || cur.getTransitionType() == SubscriptionTransitionType.RE_CREATE
+                    || cur.getTransitionType() == SubscriptionTransitionType.CHANGE
+                    || cur.getTransitionType() == SubscriptionTransitionType.MIGRATE_ENTITLEMENT) {
                 return cur;
             }
         }
-        throw new EntitlementError(String.format("Failed to find InitialTransitionForCurrentPlan id = %s", getId().toString()));
+        throw new EntitlementError(String.format(
+                "Failed to find InitialTransitionForCurrentPlan id = %s",
+                getId().toString()));
     }
 
     public boolean isSubscriptionFutureCancelled() {
         if (transitions == null) {
             return false;
         }
-        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
-                Order.ASC_FROM_PAST, Kind.ENTITLEMENT, Visibility.ALL, TimeLimit.FUTURE_ONLY);
+        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+                clock, transitions, Order.ASC_FROM_PAST, Kind.ENTITLEMENT,
+                Visibility.ALL, TimeLimit.FUTURE_ONLY);
         while (it.hasNext()) {
             SubscriptionTransitionData cur = it.next();
             if (cur.getTransitionType() == SubscriptionTransitionType.CANCEL) {
@@ -293,46 +353,53 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
         return false;
     }
 
-    public DateTime getPlanChangeEffectiveDate(ActionPolicy policy, DateTime requestedDate) {
+    public DateTime getPlanChangeEffectiveDate(ActionPolicy policy,
+            DateTime requestedDate) {
 
         if (policy == ActionPolicy.IMMEDIATE) {
             return requestedDate;
         }
         if (policy != ActionPolicy.END_OF_TERM) {
-            throw new EntitlementError(String.format("Unexpected policy type %s", policy.toString()));
+            throw new EntitlementError(String.format(
+                    "Unexpected policy type %s", policy.toString()));
         }
 
         if (chargedThroughDate == null) {
             return requestedDate;
         } else {
-            return chargedThroughDate.isBefore(requestedDate) ? requestedDate : chargedThroughDate;
+            return chargedThroughDate.isBefore(requestedDate) ? requestedDate
+                    : chargedThroughDate;
         }
     }
 
     public DateTime getCurrentPhaseStart() {
 
         if (transitions == null) {
-            throw new EntitlementError(String.format("No transitions for subscription %s", getId()));
+            throw new EntitlementError(String.format(
+                    "No transitions for subscription %s", getId()));
         }
-        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
-                Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT, Visibility.ALL, TimeLimit.PAST_OR_PRESENT_ONLY);
+        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+                clock, transitions, Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT,
+                Visibility.ALL, TimeLimit.PAST_OR_PRESENT_ONLY);
         while (it.hasNext()) {
             SubscriptionTransitionData cur = it.next();
 
-            if (cur.getTransitionType() == SubscriptionTransitionType.PHASE ||
-                    cur.getTransitionType() == SubscriptionTransitionType.CREATE ||
-                    cur.getTransitionType() == SubscriptionTransitionType.RE_CREATE ||
-                    cur.getTransitionType() == SubscriptionTransitionType.CHANGE ||
-                    cur.getTransitionType() == SubscriptionTransitionType.MIGRATE_ENTITLEMENT) {
+            if (cur.getTransitionType() == SubscriptionTransitionType.PHASE
+                    || cur.getTransitionType() == SubscriptionTransitionType.CREATE
+                    || cur.getTransitionType() == SubscriptionTransitionType.RE_CREATE
+                    || cur.getTransitionType() == SubscriptionTransitionType.CHANGE
+                    || cur.getTransitionType() == SubscriptionTransitionType.MIGRATE_ENTITLEMENT) {
                 return cur.getEffectiveTransitionTime();
             }
         }
-        throw new EntitlementError(String.format("Failed to find CurrentPhaseStart id = %s", getId().toString()));
+        throw new EntitlementError(String.format(
+                "Failed to find CurrentPhaseStart id = %s", getId().toString()));
     }
 
-    public void rebuildTransitions(final List<EntitlementEvent> events, final Catalog catalog) {
+    public void rebuildTransitions(final List<EntitlementEvent> inputEvents,
+            final Catalog catalog) {
 
-        if (events == null) {
+        if (inputEvents == null) {
             return;
         }
 
@@ -341,7 +408,7 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
         String nextPhaseName = null;
         String nextPriceListName = null; 
         UUID nextUserToken = null;
-        
+
         SubscriptionState previousState = null;
         PriceList previousPriceList = null;
 
@@ -349,7 +416,7 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
         Plan previousPlan = null;
         PlanPhase previousPhase = null;
 
-        for (final EntitlementEvent cur : events) {
+        for (final EntitlementEvent cur : inputEvents) {
 
             if (!cur.isActive() || cur.getActiveVersion() < activeVersion) {
                 continue;
@@ -371,8 +438,8 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
                 apiEventType = userEV.getEventType();
                 isFromDisk = userEV.isFromDisk();
                 nextUserToken = userEV.getUserToken();
-                
-                switch(apiEventType) {
+
+                switch (apiEventType) {
                 case MIGRATE_BILLING:
                 case MIGRATE_ENTITLEMENT:
                 case CREATE:
@@ -399,16 +466,16 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
                 case UNCANCEL:
                     break;
                 default:
-                    throw new EntitlementError(String.format("Unexpected UserEvent type = %s",
-                            userEV.getEventType().toString()));
+                    throw new EntitlementError(String.format(
+                            "Unexpected UserEvent type = %s", userEV
+                                    .getEventType().toString()));
                 }
                 break;
             default:
-                throw new EntitlementError(String.format("Unexpected Event type = %s",
-                        cur.getType()));
+                throw new EntitlementError(String.format(
+                        "Unexpected Event type = %s", cur.getType()));
             }
 
-
             Plan nextPlan = null;
             PlanPhase nextPhase = null;
             PriceList nextPriceList = null;
@@ -418,27 +485,17 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
                 nextPhase = (nextPhaseName != null) ? catalog.findPhase(nextPhaseName, cur.getRequestedDate(), getStartDate()) : null;
                 nextPriceList = (nextPriceListName != null) ? catalog.findPriceList(nextPriceListName, cur.getRequestedDate()) : null;
             } catch (CatalogApiException e) {
-                log.error(String.format("Failed to build transition for subscription %s", id), e);
+                log.error(String.format(
+                        "Failed to build transition for subscription %s", id),
+                        e);
             }
-            SubscriptionTransitionData transition =
-                new SubscriptionTransitionData(cur.getId(),
-                        id,
-                        bundleId,
-                        cur.getType(),
-                        apiEventType,
-                        cur.getRequestedDate(),
-                        cur.getEffectiveDate(),
-                        previousState,
-                        previousPlan,
-                        previousPhase,
-                        previousPriceList,
-                        nextState,
-                        nextPlan,
-                        nextPhase,
-                        nextPriceList,
-                        cur.getTotalOrdering(),
-                        nextUserToken,
-                        isFromDisk);
+            SubscriptionTransitionData transition = new SubscriptionTransitionData(
+                    cur.getId(), id, bundleId, cur.getType(), apiEventType,
+                    cur.getRequestedDate(), cur.getEffectiveDate(),
+                    previousState, previousPlan, previousPhase,
+                    previousPriceList, nextState, nextPlan, nextPhase,
+                    nextPriceList, cur.getTotalOrdering(), nextUserToken,
+                    isFromDisk);
 
             transitions.add(transition);
 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
index 3e52a2e..461fe3c 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
@@ -22,6 +22,7 @@ import org.joda.time.DateTime;
 
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.catalog.api.PriceList;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
 import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
@@ -177,6 +178,10 @@ public class SubscriptionTransitionData implements SubscriptionEventTransition {
 
     @Override
     public SubscriptionTransitionType getTransitionType() {
+        return toSubscriptionTransitionType(eventType, apiEventType);
+    }
+    
+    public static SubscriptionTransitionType toSubscriptionTransitionType(EventType eventType, ApiEventType apiEventType) {
         switch(eventType) {
         case API_USER:
             return apiEventType.getSubscriptionTransitionType();
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java
index 3e5e961..355628d 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java
@@ -19,7 +19,7 @@ package com.ning.billing.entitlement.api.user;
 import java.util.Iterator;
 import java.util.LinkedList;
 
-import com.ning.billing.entitlement.api.user.SubscriptionEventTransition.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.util.clock.Clock;
 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
index e7c5139..48a3c99 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
@@ -28,6 +28,7 @@ import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.entitlement.api.user.SubscriptionEventTransition;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
@@ -43,6 +44,24 @@ public class AddonUtils {
         this.catalogService = catalogService;
     }
 
+    public void checkAddonCreationRights(SubscriptionData baseSubscription, Plan targetAddOnPlan)
+    throws EntitlementUserApiException, CatalogApiException {
+
+        if (baseSubscription.getState() != SubscriptionState.ACTIVE) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_BP_NON_ACTIVE, targetAddOnPlan.getName());
+        }
+
+        Product baseProduct = baseSubscription.getCurrentPlan().getProduct();
+        if (isAddonIncluded(baseProduct, targetAddOnPlan)) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_ALREADY_INCLUDED,
+                    targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
+        }
+
+        if (!isAddonAvailable(baseProduct, targetAddOnPlan)) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_NOT_AVAILABLE,
+                    targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
+        }
+    }
 
     public boolean isAddonAvailable(final String basePlanName, final DateTime requestedDate, final Plan targetAddOnPlan) {
         try {
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 00d16df..c8f82a2 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
@@ -29,6 +29,10 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
+
+import com.google.inject.name.Named;
+
+
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.ProductCategory;
@@ -37,16 +41,10 @@ import com.ning.billing.config.NotificationConfig;
 import com.ning.billing.entitlement.alignment.PlanAligner;
 import com.ning.billing.entitlement.alignment.TimedPhase;
 import com.ning.billing.entitlement.api.EntitlementService;
-import com.ning.billing.entitlement.api.billing.ChargeThruApi;
-import com.ning.billing.entitlement.api.billing.DefaultChargeThruApi;
-import com.ning.billing.entitlement.api.migration.DefaultEntitlementMigrationApi;
-import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
-import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
-import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
 import com.ning.billing.entitlement.engine.addon.AddonUtils;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.events.EntitlementEvent;
@@ -57,7 +55,6 @@ import com.ning.billing.entitlement.events.user.ApiEvent;
 import com.ning.billing.entitlement.events.user.ApiEventBuilder;
 import com.ning.billing.entitlement.events.user.ApiEventCancel;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
-import com.ning.billing.junction.api.BillingApi;
 import com.ning.billing.lifecycle.LifecycleHandlerType;
 import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
 import com.ning.billing.util.bus.Bus;
@@ -94,7 +91,8 @@ public class Engine implements EventListener, EntitlementService {
 
     @Inject
     public Engine(Clock clock, EntitlementDao dao, PlanAligner planAligner,
-            EntitlementConfig config, AddonUtils addonUtils, Bus eventBus,
+            EntitlementConfig config,
+            AddonUtils addonUtils, Bus eventBus,
             NotificationQueueService notificationQueueService,
             SubscriptionFactory subscriptionFactory,
             CallContextFactory factory) {
@@ -135,9 +133,6 @@ public class Engine implements EventListener, EntitlementService {
                     final CallContext context = factory.createCallContext("SubscriptionEventQueue", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
                     processEventReady(event, key.getSeqId(), context);
                 }
-                
-    
-
             },
             new NotificationConfig() {
                 @Override
@@ -173,6 +168,7 @@ public class Engine implements EventListener, EntitlementService {
             subscriptionEventQueue.stopQueue();
          }
     }
+
     @Override
     public void processEventReady(final EntitlementEvent event, final int seqId, final CallContext context) {
         if (!event.isActive()) {
@@ -183,6 +179,11 @@ public class Engine implements EventListener, EntitlementService {
             log.warn("Failed to retrieve subscription for id %s", event.getSubscriptionId());
             return;
         }
+        if (subscription.getActiveVersion() > event.getActiveVersion()) {
+            // Skip repaired events
+            return;
+        }
+        
         //
         // Do any internal processing on that event before we send the event to the bus
         //
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
index f6f5497..5af5294 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
@@ -18,6 +18,7 @@ package com.ning.billing.entitlement.engine.dao;
 
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.Date;
 import java.util.List;
 import java.util.UUID;
 
@@ -42,6 +43,7 @@ import com.ning.billing.util.callcontext.CallContextBinder;
 import com.ning.billing.util.dao.BinderBase;
 import com.ning.billing.util.dao.MapperBase;
 
+
 @ExternalizedSqlViaStringTemplate3()
 public interface BundleSqlDao extends Transactional<BundleSqlDao>, CloseMe, Transmogrifier {
 
@@ -49,13 +51,16 @@ public interface BundleSqlDao extends Transactional<BundleSqlDao>, CloseMe, Tran
     public void insertBundle(@Bind(binder = SubscriptionBundleBinder.class) SubscriptionBundleData bundle,
                              @CallContextBinder final CallContext context);
 
+    @SqlUpdate
+    public void updateBundleLastSysTime(@Bind("id") String id, @Bind("last_sys_update_dt") Date lastSysUpdate);
+    
     @SqlQuery
     @Mapper(ISubscriptionBundleSqlMapper.class)
     public SubscriptionBundle getBundleFromId(@Bind("id") String id);
 
     @SqlQuery
     @Mapper(ISubscriptionBundleSqlMapper.class)
-    public SubscriptionBundle getBundleFromKey(@Bind("name") String name);
+    public SubscriptionBundle getBundleFromKey(@Bind("external_key") String externalKey);
 
     @SqlQuery
     @Mapper(ISubscriptionBundleSqlMapper.class)
@@ -66,8 +71,9 @@ public interface BundleSqlDao extends Transactional<BundleSqlDao>, CloseMe, Tran
         public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, SubscriptionBundleData bundle) {
             stmt.bind("id", bundle.getId().toString());
             stmt.bind("start_dt", getDate(bundle.getStartDate()));
-            stmt.bind("name", bundle.getKey());
+            stmt.bind("external_key", bundle.getKey());
             stmt.bind("account_id", bundle.getAccountId().toString());
+            stmt.bind("last_sys_update_dt", getDate(bundle.getLastSysUpdateTime()));            
         }
     }
 
@@ -76,14 +82,13 @@ public interface BundleSqlDao extends Transactional<BundleSqlDao>, CloseMe, Tran
         @Override
         public SubscriptionBundle map(int arg, ResultSet r,
                 StatementContext ctx) throws SQLException {
-
             UUID id = UUID.fromString(r.getString("id"));
-            String name = r.getString("name");
+            String key = r.getString("external_key");
             UUID accountId = UUID.fromString(r.getString("account_id"));
             DateTime startDate = getDate(r, "start_dt");
-            SubscriptionBundleData bundle = new SubscriptionBundleData(id, name, accountId, startDate, null);
+            DateTime lastSysUpdateDate = getDate(r, "last_sys_update_dt");
+            SubscriptionBundleData bundle = new SubscriptionBundleData(id, key, accountId, startDate, lastSysUpdateDate);
             return bundle;
         }
-
     }
 }
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 4415c1d..fd897f6 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
@@ -17,16 +17,18 @@
 package com.ning.billing.entitlement.engine.dao;
 
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import com.ning.billing.util.callcontext.CallContext;
 
+import com.ning.billing.entitlement.api.SubscriptionFactory;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.timeline.SubscriptionDataRepair;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 
 public interface EntitlementDao {
@@ -52,13 +54,15 @@ public interface EntitlementDao {
     public List<Subscription> getSubscriptionsForKey(final SubscriptionFactory factory, final String bundleKey);
 
     // Update
-    public void updateSubscription(final SubscriptionData subscription, final CallContext context);
+    public void updateChargedThroughDate(final SubscriptionData subscription, final CallContext context);
 
     // Event apis
     public void createNextPhaseEvent(final UUID subscriptionId, final EntitlementEvent nextPhase, final CallContext context);
 
     public EntitlementEvent getEventById(final UUID eventId);
 
+    public Map<UUID, List<EntitlementEvent>> getEventsForBundle(final UUID bundleId);
+    
     public List<EntitlementEvent> getEventsForSubscription(final UUID subscriptionId);
 
     public List<EntitlementEvent> getPendingEventsForSubscription(final UUID subscriptionId);
@@ -76,6 +80,9 @@ public interface EntitlementDao {
 
     public void migrate(final UUID accountId, final AccountMigrationData data, final CallContext context);
 
+    // Repair
+    public void repair(final UUID accountId, final UUID bundleId, final List<SubscriptionDataRepair> inRepair, final CallContext context);
+    
     // Custom Fields
     public void saveCustomFields(final SubscriptionData subscription, final CallContext context);
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
index 89d2b1e..efa2e44 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
@@ -21,10 +21,19 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
-import javax.annotation.Nullable;
+
+import com.ning.billing.util.ChangeType;
+import com.ning.billing.util.audit.dao.AuditSqlDao;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.dao.CustomFieldDao;
 
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.IDBI;
@@ -41,15 +50,18 @@ import com.ning.billing.ErrorCode;
 import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData.SubscriptionMigrationData;
+import com.ning.billing.entitlement.api.timeline.DefaultRepairEntitlementEvent;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionDataRepair;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.engine.addon.AddonUtils;
 import com.ning.billing.entitlement.engine.core.Engine;
 import com.ning.billing.entitlement.engine.core.EntitlementNotificationKey;
@@ -61,12 +73,8 @@ 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.exceptions.EntitlementError;
-import com.ning.billing.util.ChangeType;
-import com.ning.billing.util.audit.dao.AuditSqlDao;
-import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.customfield.dao.CustomFieldDao;
 import com.ning.billing.util.customfield.dao.CustomFieldSqlDao;
 import com.ning.billing.util.notificationq.NotificationKey;
 import com.ning.billing.util.notificationq.NotificationQueue;
@@ -87,20 +95,15 @@ public class EntitlementSqlDao implements EntitlementDao {
     private final NotificationQueueService notificationQueueService;
     private final AddonUtils addonUtils;
     private final CustomFieldDao customFieldDao;
-    private final CatalogService catalogService;
-
-    
-    //
-    // We are not injecting SubscriptionFactory since that creates circular dependencies--
-    // Guice would still work, but this is playing with fire.
-    //
-    // Instead that factory passed through API top to bottom for the call where is it needed-- where we returned fully rehydrated Subscriptions
-    //
+    private final Bus eventBus;
+
+
     @Inject
     public EntitlementSqlDao(final IDBI dbi, final Clock clock,
                              final AddonUtils addonUtils, final NotificationQueueService notificationQueueService,
                              final CustomFieldDao customFieldDao,
-                             final CatalogService catalogService) {
+                             final Bus eventBus) {
+
         this.clock = clock;
         this.subscriptionsDao = dbi.onDemand(SubscriptionSqlDao.class);
         this.eventsDao = dbi.onDemand(EventSqlDao.class);
@@ -108,7 +111,7 @@ public class EntitlementSqlDao implements EntitlementDao {
         this.notificationQueueService = notificationQueueService;
         this.addonUtils = addonUtils;
         this.customFieldDao = customFieldDao;
-        this.catalogService = catalogService;
+        this.eventBus = eventBus;
     }
 
     @Override
@@ -191,17 +194,18 @@ public class EntitlementSqlDao implements EntitlementDao {
     }
 
     @Override
-    public void updateSubscription(final SubscriptionData subscription, final CallContext context) {
+    public void updateChargedThroughDate(final SubscriptionData subscription, final CallContext context) {
 
         final Date ctd = (subscription.getChargedThroughDate() != null)  ? subscription.getChargedThroughDate().toDate() : null;
-        final Date ptd = (subscription.getPaidThroughDate() != null)  ? subscription.getPaidThroughDate().toDate() : null;
 
         subscriptionsDao.inTransaction(new Transaction<Void, SubscriptionSqlDao>() {
             @Override
             public Void inTransaction(SubscriptionSqlDao transactionalDao,
                     TransactionStatus status) throws Exception {
-                transactionalDao.updateSubscription(subscription.getId().toString(), subscription.getActiveVersion(), ctd, ptd, context);
+                transactionalDao.updateChargedThroughDate(subscription.getId().toString(), ctd, context);
 
+                BundleSqlDao tmpDao = transactionalDao.become(BundleSqlDao.class);
+                tmpDao.updateBundleLastSysTime(subscription.getBundleId().toString(), clock.getUTCNow().toDate());
                 AuditSqlDao auditSqlDao = transactionalDao.become(AuditSqlDao.class);
                 String subscriptionId = subscription.getId().toString();
                 auditSqlDao.insertAuditFromTransaction(SUBSCRIPTIONS_TABLE_NAME, subscriptionId, ChangeType.UPDATE, context);
@@ -240,6 +244,29 @@ public class EntitlementSqlDao implements EntitlementDao {
     }
 
     @Override
+    public Map<UUID, List<EntitlementEvent>> getEventsForBundle(final UUID bundleId) {
+
+        Map<UUID, List<EntitlementEvent>> result = subscriptionsDao.inTransaction(new Transaction<Map<UUID, List<EntitlementEvent>>, SubscriptionSqlDao>() {
+            @Override
+            public Map<UUID, List<EntitlementEvent>> inTransaction(SubscriptionSqlDao transactional,
+                    TransactionStatus status) throws Exception {
+                List<Subscription> subscriptions = transactional.getSubscriptionsFromBundleId(bundleId.toString());
+                if (subscriptions.size() == 0) {
+                    return Collections.emptyMap();
+                }
+                EventSqlDao eventsDaoFromSameTransaction = transactional.become(EventSqlDao.class);
+                Map<UUID, List<EntitlementEvent>> result = new HashMap<UUID, List<EntitlementEvent>>();
+                for (Subscription cur : subscriptions) {
+                    List<EntitlementEvent> events = eventsDaoFromSameTransaction.getEventsForSubscription(cur.getId().toString());
+                    result.put(cur.getId(), events);
+                }
+                return result;
+            }
+        });
+        return result;
+    }
+
+    @Override
     public List<EntitlementEvent> getPendingEventsForSubscription(UUID subscriptionId) {
         Date now = clock.getUTCNow().toDate();
         return eventsDao.getFutureActiveEventForSubscription(subscriptionId.toString(), now);
@@ -406,7 +433,7 @@ public class EntitlementSqlDao implements EntitlementDao {
     }
 
     private void cancelFutureEventFromTransaction(final UUID subscriptionId, final EventSqlDao dao,
-            final EventType type, @Nullable final ApiEventType apiType,
+            final EventType type, final ApiEventType apiType,
             final CallContext context) {
 
         UUID futureEventId = null;
@@ -577,6 +604,39 @@ public class EntitlementSqlDao implements EntitlementDao {
         });
     }
 
+    public void repair(final UUID accountId, final UUID bundleId, final List<SubscriptionDataRepair> inRepair, final CallContext context) {
+        subscriptionsDao.inTransaction(new Transaction<Void, SubscriptionSqlDao>() {
+
+            @Override
+            public Void inTransaction(SubscriptionSqlDao transactional,
+                    TransactionStatus status) throws Exception {
+
+                EventSqlDao transEventDao = transactional.become(EventSqlDao.class);
+                for (final SubscriptionDataRepair cur : inRepair) {
+                    transactional.updateForRepair(cur.getId().toString(), cur.getActiveVersion(), cur.getStartDate().toDate(), cur.getBundleStartDate().toDate(), context);
+                    for (EntitlementEvent event : cur.getInitialEvents()) {
+                        transEventDao.updateVersion(event.getId().toString(), event.getActiveVersion(), context);
+                    }
+                    for (EntitlementEvent event : cur.getNewEvents()) {
+                        transEventDao.insertEvent(event, context);
+                        if (event.getEffectiveDate().isAfter(clock.getUTCNow())) {
+                            recordFutureNotificationFromTransaction(transactional,
+                                    event.getEffectiveDate(),
+                                    new EntitlementNotificationKey(event.getId()));
+                        }
+                    }
+                }
+                try {
+                    RepairEntitlementEvent busEvent = new DefaultRepairEntitlementEvent(context.getUserToken(), accountId, bundleId, clock.getUTCNow());
+                    eventBus.postFromTransaction(busEvent, transactional);
+                } catch (EventBusException e) {
+                    log.warn("Failed to post repair entitlement event for bundle " + bundleId);
+                }
+                return null;
+            }
+        });
+    }
+
 
     private Subscription getBaseSubscription(final SubscriptionFactory factory, final UUID bundleId, boolean rebuildSubscription) {
         List<Subscription> subscriptions = subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString());
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java
index 89baed8..a927be3 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java
@@ -66,6 +66,11 @@ public interface EventSqlDao extends Transactional<EventSqlDao>, CloseMe, Transm
     public void reactiveEvent(@Bind("event_id")String eventId,
                               @CallContextBinder final CallContext context);
 
+    @SqlUpdate
+    public void updateVersion(@Bind("event_id")String eventId,
+                              @Bind("current_version") Long currentVersion,
+                              @CallContextBinder final CallContext context);
+    
     @SqlQuery
     @Mapper(EventSqlMapper.class)
     public List<EntitlementEvent> getFutureActiveEventForSubscription(@Bind("subscription_id") String subscriptionId, @Bind("now") Date now);
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/RepairEntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/RepairEntitlementDao.java
new file mode 100644
index 0000000..cdfc221
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/RepairEntitlementDao.java
@@ -0,0 +1,249 @@
+/* 
+ * 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.entitlement.engine.dao;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import com.ning.billing.entitlement.api.SubscriptionFactory;
+import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementLifecycleDao;
+import com.ning.billing.entitlement.api.timeline.SubscriptionDataRepair;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.exceptions.EntitlementError;
+import com.ning.billing.util.callcontext.CallContext;
+
+public class RepairEntitlementDao implements EntitlementDao, RepairEntitlementLifecycleDao {
+
+    private final ThreadLocal<Map<UUID, SubscriptionRepairEvent>> preThreadsInRepairSubscriptions = new ThreadLocal<Map<UUID, SubscriptionRepairEvent>>();
+    
+    private final static class SubscriptionRepairEvent {
+        
+        private final Set<EntitlementEvent> events;
+        
+        public SubscriptionRepairEvent(List<EntitlementEvent> initialEvents) {
+            events = new TreeSet<EntitlementEvent>(new Comparator<EntitlementEvent>() {
+                @Override
+                public int compare(EntitlementEvent o1, EntitlementEvent o2) {
+                    return o1.compareTo(o2);
+                }
+            });
+            if (initialEvents != null) {
+                events.addAll(initialEvents);
+            }
+        }
+        
+        public Set<EntitlementEvent> getEvents() {
+            return events;
+        }
+        
+        public void addEvents(List<EntitlementEvent> newEvents) {
+            events.addAll(newEvents);
+        }
+    }
+    
+    private Map<UUID, SubscriptionRepairEvent> getRepairMap() {
+        if (preThreadsInRepairSubscriptions.get() == null) {
+            preThreadsInRepairSubscriptions.set(new HashMap<UUID, SubscriptionRepairEvent>());
+        }
+        return preThreadsInRepairSubscriptions.get();
+    }
+    
+    private SubscriptionRepairEvent getRepairSubscriptionEvents(UUID subscriptionId) {
+        Map<UUID, SubscriptionRepairEvent> map = getRepairMap();
+        return map.get(subscriptionId);
+    }
+    
+    @Override
+    public List<EntitlementEvent> getEventsForSubscription(UUID subscriptionId) {
+        SubscriptionRepairEvent target =  getRepairSubscriptionEvents(subscriptionId);
+        return new LinkedList<EntitlementEvent>(target.getEvents());
+    }
+
+    @Override
+    public void createSubscription(SubscriptionData subscription,
+            List<EntitlementEvent> createEvents, CallContext context) {
+        addEvents(subscription.getId(), createEvents);
+    }
+
+    @Override
+    public void recreateSubscription(UUID subscriptionId,
+            List<EntitlementEvent> recreateEvents, CallContext context) {
+        addEvents(subscriptionId, recreateEvents);
+    }
+
+    @Override
+    public void cancelSubscription(UUID subscriptionId,
+            EntitlementEvent cancelEvent, CallContext context, int cancelSeq) {
+        long activeVersion = cancelEvent.getActiveVersion();
+        addEvents(subscriptionId, Collections.singletonList(cancelEvent));
+        SubscriptionRepairEvent target =  getRepairSubscriptionEvents(subscriptionId);
+        boolean foundCancelEvent = false;
+        for (EntitlementEvent cur : target.getEvents()) {
+            if (cur.getId().equals(cancelEvent.getId())) {
+                foundCancelEvent = true;
+            } else if (foundCancelEvent) { 
+                cur.setActiveVersion(activeVersion - 1);
+            }
+        }
+    }
+
+    
+    @Override
+    public void changePlan(UUID subscriptionId,
+            List<EntitlementEvent> changeEvents, CallContext context) {
+        addEvents(subscriptionId, changeEvents);        
+    }
+
+    @Override
+    public void initializeRepair(UUID subscriptionId, List<EntitlementEvent> initialEvents) {
+        Map<UUID, SubscriptionRepairEvent> map = getRepairMap();
+        if (map.get(subscriptionId) == null) {
+            SubscriptionRepairEvent value = new SubscriptionRepairEvent(initialEvents);
+            map.put(subscriptionId, value);
+        } else {
+            throw new EntitlementError(String.format("Unexpected SubscriptionRepairEvent %s for thread %s", subscriptionId, Thread.currentThread().getName()));
+        }
+    }
+
+    @Override
+    public void cleanup() {
+        Map<UUID, SubscriptionRepairEvent> map = getRepairMap();
+        map.clear();
+    }
+
+    
+    private void addEvents(UUID subscriptionId, List<EntitlementEvent> events) {
+        SubscriptionRepairEvent target =  getRepairSubscriptionEvents(subscriptionId);
+        target.addEvents(events);        
+    }
+
+    
+    @Override
+    public void uncancelSubscription(UUID subscriptionId,
+            List<EntitlementEvent> uncancelEvents, CallContext context) {
+        throw new EntitlementError("Not implemented");        
+    }
+    
+    @Override
+    public List<SubscriptionBundle> getSubscriptionBundleForAccount(UUID accountId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public SubscriptionBundle createSubscriptionBundle(
+            SubscriptionBundleData bundle, CallContext context) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public Subscription getSubscriptionFromId(SubscriptionFactory factory,
+            UUID subscriptionId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public UUID getAccountIdFromSubscriptionId(UUID subscriptionId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public Subscription getBaseSubscription(SubscriptionFactory factory,
+            UUID bundleId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public List<Subscription> getSubscriptions(SubscriptionFactory factory,
+            UUID bundleId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public List<Subscription> getSubscriptionsForKey(
+            SubscriptionFactory factory, String bundleKey) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public void updateChargedThroughDate(SubscriptionData subscription,
+            CallContext context) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public void createNextPhaseEvent(UUID subscriptionId,
+            EntitlementEvent nextPhase, CallContext context) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public EntitlementEvent getEventById(UUID eventId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public Map<UUID, List<EntitlementEvent>> getEventsForBundle(UUID bundleId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+
+    @Override
+    public List<EntitlementEvent> getPendingEventsForSubscription(
+            UUID subscriptionId) {
+        throw new EntitlementError("Not implemented");
+    }
+
+
+    @Override
+    public void migrate(UUID accountId, AccountMigrationData data,
+            CallContext context) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public void saveCustomFields(SubscriptionData subscription,
+            CallContext context) {
+        throw new EntitlementError("Not implemented");
+    }
+
+    @Override
+    public void repair(UUID accountId, UUID bundleId, List<SubscriptionDataRepair> inRepair,
+            CallContext context) {
+        throw new EntitlementError("Not implemented");
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java
index 04f2965..92c37db 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java
@@ -19,7 +19,7 @@ package com.ning.billing.entitlement.engine.dao;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallContextBinder;
 import com.ning.billing.util.dao.BinderBase;
@@ -59,10 +59,18 @@ public interface SubscriptionSqlDao extends Transactional<SubscriptionSqlDao>, C
     public List<Subscription> getSubscriptionsFromBundleId(@Bind("bundle_id") String bundleId);
 
     @SqlUpdate
-    public void updateSubscription(@Bind("id") String id, @Bind("active_version") long activeVersion,
-                                   @Bind("ctd_dt") Date ctd, @Bind("ptd_dt") Date ptd,
+    public void updateChargedThroughDate(@Bind("id") String id, @Bind("ctd_dt") Date ctd,
                                    @CallContextBinder final CallContext context);
-   
+
+    @SqlUpdate void updateActiveVersion(@Bind("id") String id, @Bind("active_version") long activeVersion,
+            @CallContextBinder final CallContext context);
+    
+    @SqlUpdate
+    public void updateForRepair(@Bind("id") String id, @Bind("active_version") long activeVersion,
+            @Bind("start_dt") Date startDate,
+            @Bind("bundle_start_dt") Date bundleStartDate,
+            @CallContextBinder final CallContext context);
+
     public static class SubscriptionBinder extends BinderBase implements Binder<Bind, SubscriptionData> {
         @Override
         public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, SubscriptionData sub) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java
index 27d5b61..d1eae92 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java
@@ -16,7 +16,7 @@
 
 package com.ning.billing.entitlement.events.user;
 
-import com.ning.billing.entitlement.api.user.SubscriptionEventTransition.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 
 
 public enum ApiEventType {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java b/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
index cb8730a..5f79674 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
@@ -19,25 +19,37 @@ package com.ning.billing.entitlement.glue;
 import org.skife.config.ConfigurationObjectFactory;
 
 import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
 import com.ning.billing.config.EntitlementConfig;
 import com.ning.billing.entitlement.alignment.MigrationPlanAligner;
 import com.ning.billing.entitlement.alignment.PlanAligner;
 import com.ning.billing.entitlement.api.EntitlementService;
+import com.ning.billing.entitlement.api.SubscriptionApiService;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
 import com.ning.billing.entitlement.api.billing.ChargeThruApi;
 import com.ning.billing.entitlement.api.billing.DefaultChargeThruApi;
 import com.ning.billing.entitlement.api.migration.DefaultEntitlementMigrationApi;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
+import com.ning.billing.entitlement.api.timeline.DefaultEntitlementTimelineApi;
+import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementLifecycleDao;
+import com.ning.billing.entitlement.api.timeline.RepairSubscriptionApiService;
+import com.ning.billing.entitlement.api.timeline.RepairSubscriptionFactory;
 import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionApiService;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
-import com.ning.billing.entitlement.api.user.SubscriptionApiService;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
 import com.ning.billing.entitlement.engine.addon.AddonUtils;
 import com.ning.billing.entitlement.engine.core.Engine;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.engine.dao.EntitlementSqlDao;
+import com.ning.billing.entitlement.engine.dao.RepairEntitlementDao;
 import com.ning.billing.util.glue.RealImplementation;
 
 public class EntitlementModule extends AbstractModule {
+    
+    public static final String REPAIR_NAMED = "repair";
+    
     protected void installConfig() {
         final EntitlementConfig config = new ConfigurationObjectFactory(System.getProperties()).build(EntitlementConfig.class);
         bind(EntitlementConfig.class).toInstance(config);
@@ -45,17 +57,27 @@ public class EntitlementModule extends AbstractModule {
 
     protected void installEntitlementDao() {
         bind(EntitlementDao.class).to(EntitlementSqlDao.class).asEagerSingleton();
+        bind(EntitlementDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairEntitlementDao.class);
+        bind(RepairEntitlementLifecycleDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairEntitlementDao.class);
+        bind(RepairEntitlementDao.class).asEagerSingleton();
     }
     
     protected void installEntitlementCore() {
-    	bind(SubscriptionFactory.class).asEagerSingleton();
-        bind(SubscriptionApiService.class).asEagerSingleton();
+        
+        bind(SubscriptionFactory.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairSubscriptionFactory.class).asEagerSingleton();
+        bind(SubscriptionFactory.class).to(DefaultSubscriptionFactory.class).asEagerSingleton();
+        
+        bind(SubscriptionApiService.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairSubscriptionApiService.class).asEagerSingleton();
+        bind(SubscriptionApiService.class).to(DefaultSubscriptionApiService.class).asEagerSingleton();
+        
         bind(EntitlementService.class).to(Engine.class).asEagerSingleton();
         bind(Engine.class).asEagerSingleton();
         bind(PlanAligner.class).asEagerSingleton();
         bind(AddonUtils.class).asEagerSingleton();
         bind(MigrationPlanAligner.class).asEagerSingleton();
         bind(EntitlementUserApi.class).annotatedWith(RealImplementation.class).to(DefaultEntitlementUserApi.class).asEagerSingleton();
+        
+        bind(EntitlementTimelineApi.class).to(DefaultEntitlementTimelineApi.class).asEagerSingleton();
         bind(EntitlementMigrationApi.class).to(DefaultEntitlementMigrationApi.class).asEagerSingleton();
         bind(ChargeThruApi.class).to(DefaultChargeThruApi.class).asEagerSingleton();
     }
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql b/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
index d2bb5b7..2064310 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
@@ -44,7 +44,8 @@ DROP TABLE IF EXISTS bundles;
 CREATE TABLE bundles (
     id char(36) NOT NULL,
     start_dt datetime, /*NOT NULL*/
-    name varchar(64) NOT NULL,
+    external_key varchar(64) NOT NULL,
     account_id char(36) NOT NULL,
+    last_sys_update_dt datetime,
     PRIMARY KEY(id)
 ) ENGINE=innodb;
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg
index 4f0e4db..184604d 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg
@@ -4,37 +4,49 @@ insertBundle() ::= <<
     insert into bundles (
       id
       , start_dt
-      , name
+      , external_key
       , account_id
+      , last_sys_update_dt
     ) values (
       :id
       , :start_dt
-      , :name
+      , :external_key
       , :account_id
+      , :last_sys_update_dt
     );
 >>
 
+updateBundleLastSysTime(id, last_sys_update_dt)  ::= <<
+    update bundles
+    set
+        last_sys_update_dt = :last_sys_update_dt
+    where id = :id
+    ;
+>>
+
 getBundleFromId(id) ::= <<
     select
       id
       , start_dt
-      , name
+      , external_key
       , account_id
+      , last_sys_update_dt
     from bundles
     where
       id = :id
     ;
 >>
 
-getBundleFromKey(name) ::= <<
+getBundleFromKey(external_key) ::= <<
     select
       id
       , start_dt
-      , name
+      , external_key
       , account_id
+      , last_sys_update_dt
     from bundles
     where
-      name = :name
+      external_key = :external_key
     ;
 >>
 
@@ -43,8 +55,9 @@ getBundleFromAccount(account_id) ::= <<
     select
       id
       , start_dt
-      , name
+      , external_key
       , account_id
+      , last_sys_update_dt
     from bundles
     where
       account_id = :account_id
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
index cd5afbf..817afcc 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
@@ -50,6 +50,15 @@ insertEvent() ::= <<
     );   
 >>
 
+updateVersion() ::= <<
+    update entitlement_events
+    set
+      current_version = :current_version
+    where
+      event_id = :event_id
+    ;
+>>
+
 unactiveEvent() ::= <<
     update entitlement_events
     set
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg
index 780c06a..804f6ae 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg
@@ -60,12 +60,32 @@ getSubscriptionsFromBundleId(bundle_id) ::= <<
     ;
 >>
 
-updateSubscription(id, active_version, ctd_dt, ptd_dt) ::= <<
+updateChargedThroughDate() ::= <<
+    update subscriptions
+    set
+      ctd_dt = :ctd_dt
+      , updated_by = :userName
+      , updated_date = :updatedDate
+    where id = :id
+    ;
+>>
+
+updateActiveVersion() ::= <<
+    update subscriptions
+    set
+      active_version = :active_version
+      , updated_by = :userName
+      , updated_date = :updatedDate
+    where id = :id
+    ;
+>>
+
+updateForRepair() ::= <<
     update subscriptions
     set
       active_version = :active_version
-      , ctd_dt = :ctd_dt
-      , ptd_dt = :ptd_dt
+      , start_dt = :start_dt
+      , bundle_start_dt = :bundle_start_dt
       , updated_by = :userName
       , updated_date = :updatedDate
     where id = :id
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
index 694613e..e83cb32 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
@@ -18,6 +18,7 @@ package com.ning.billing.entitlement.api;
 
 import com.google.common.base.Joiner;
 import com.google.common.eventbus.Subscribe;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementEvent;
 import com.ning.billing.entitlement.api.user.SubscriptionEventTransition;
 import com.ning.billing.util.bus.Bus;
 import org.slf4j.Logger;
@@ -32,9 +33,13 @@ public class ApiTestListener {
 
     private static final Logger log = LoggerFactory.getLogger(ApiTestListener.class);
 
-    private final List<NextEvent> nextExpectedEvent;
+    private final List<NextEvent> nextApiExpectedEvent;
 
-    private volatile boolean completed;
+
+    private boolean isApiCompleted;
+
+    private boolean expectRepairCompletion;
+    private boolean isRepairCompleted;    
 
     public enum NextEvent {
         MIGRATE_ENTITLEMENT,
@@ -47,8 +52,21 @@ public class ApiTestListener {
     }
 
     public ApiTestListener(Bus eventBus) {
-        this.nextExpectedEvent = new Stack<NextEvent>();
-        this.completed = false;
+        this.nextApiExpectedEvent = new Stack<NextEvent>();
+        reset();
+    }
+    
+    @Subscribe
+    public void handleRepairEvent(RepairEntitlementEvent event) {
+        log.debug("-> Got event RepairEntitlementEvent for bundle " + event.getBundleId());        
+        if (!expectRepairCompletion) {
+            log.error("Did not expect repair any event!!!");
+        } else {
+            synchronized(this) {
+                isRepairCompleted = true;
+                notify();
+            }
+        }
     }
 
     @Subscribe
@@ -83,39 +101,63 @@ public class ApiTestListener {
 
     }
 
-    public void pushExpectedEvent(NextEvent next) {
+    public void pushNextApiExpectedEvent(NextEvent next) {
         synchronized (this) {
-            nextExpectedEvent.add(next);
-            completed = false;
+            nextApiExpectedEvent.add(next);
+            isApiCompleted = false;
         }
     }
+    
+    public void expectRepairCompletion() {
+        expectRepairCompletion = true;
+        isRepairCompleted = false;
+    }
 
-    public boolean isCompleted(long timeout) {
+    public boolean isRepairCompleted(long timeout) {
+        synchronized (this) {
+            if (isRepairCompleted) {
+                return isRepairCompleted;
+            }
+            try {
+                wait(timeout);
+            } catch (Exception ignore) {
+            }
+        }
+        if (!isRepairCompleted) {
+            log.debug("ApiTestListener did not complete in " + timeout + " ms");
+        }
+        return isRepairCompleted;
+    }
+    
+    public boolean isApiCompleted(long timeout) {
         synchronized (this) {
-            if (completed) {
-                return completed;
+            if (isApiCompleted) {
+                return isApiCompleted;
             }
             try {
                 wait(timeout);
             } catch (Exception ignore) {
             }
         }
-        if (!completed) {
+        if (!isApiCompleted) {
             log.debug("ApiTestListener did not complete in " + timeout + " ms");
         }
-        return completed;
+        return isApiCompleted;
     }
 
     public void reset() {
-        nextExpectedEvent.clear();
+        nextApiExpectedEvent.clear();
+        this.isApiCompleted = false;
+        this.expectRepairCompletion = false;
+        this.isRepairCompleted = false;
     }
 
     private void notifyIfStackEmpty() {
         log.debug("notifyIfStackEmpty ENTER");
         synchronized (this) {
-            if (nextExpectedEvent.isEmpty()) {
+            if (nextApiExpectedEvent.isEmpty()) {
                 log.debug("notifyIfStackEmpty EMPTY");
-                completed = true;
+                isApiCompleted = true;
                 notify();
             }
         }
@@ -125,7 +167,7 @@ public class ApiTestListener {
     private void assertEqualsNicely(NextEvent expected) {
 
         boolean foundIt = false;
-        Iterator<NextEvent> it = nextExpectedEvent.iterator();
+        Iterator<NextEvent> it = nextApiExpectedEvent.iterator();
         while (it.hasNext()) {
             NextEvent ev = it.next();
             if (ev == expected) {
@@ -137,7 +179,7 @@ public class ApiTestListener {
 
         if (!foundIt) {
             Joiner joiner = Joiner.on(" ");
-            Assert.fail("Expected event " + expected + " got " + joiner.join(nextExpectedEvent));
+            Assert.fail("Expected event " + expected + " got " + joiner.join(nextApiExpectedEvent));
         }
     }
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
index 3e1b421..7380ec9 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
@@ -46,7 +46,6 @@ import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import org.testng.annotations.Test;
 
-@Test(groups = {"slow"})
 public abstract class TestMigration extends TestApiBase {
     public void testSingleBasePlan() {
 
@@ -56,9 +55,9 @@ public abstract class TestMigration extends TestApiBase {
             EntitlementAccountMigration toBeMigrated = createAccountWithRegularBasePlan(startDate);
             DateTime afterMigration = clock.getUTCNow();
 
-            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            testListener.pushNextApiExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
             migrationApi.migrate(toBeMigrated, context);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
             List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(toBeMigrated.getAccountKey());
             assertEquals(bundles.size(), 1);
@@ -87,10 +86,10 @@ public abstract class TestMigration extends TestApiBase {
             EntitlementAccountMigration toBeMigrated = createAccountWithRegularBasePlanAndAddons(initalBPStart, initalAddonStart);
             DateTime afterMigration = clock.getUTCNow();
 
-            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
-            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            testListener.pushNextApiExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            testListener.pushNextApiExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
             migrationApi.migrate(toBeMigrated, context);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
             List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(toBeMigrated.getAccountKey());
             assertEquals(bundles.size(), 1);
@@ -111,7 +110,9 @@ public abstract class TestMigration extends TestApiBase {
 
             Subscription aoSubscription = (subscriptions.get(0).getCurrentPlan().getProduct().getCategory() == ProductCategory.ADD_ON) ?
                     subscriptions.get(0) : subscriptions.get(1);
-            assertEquals(aoSubscription.getStartDate(), initalAddonStart);
+            // initalAddonStart.plusMonths(1).minusMonths(1) may be different from initalAddonStart, depending on exact date
+            // e.g : March 31 + 1 month => April 30 and April 30 - 1 month = March 30 which is != March 31 !!!! 
+            assertEquals(aoSubscription.getStartDate(), initalAddonStart.plusMonths(1).minusMonths(1));
             assertEquals(aoSubscription.getEndDate(), null);
             assertEquals(aoSubscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
             assertEquals(aoSubscription.getCurrentPhase().getPhaseType(), PhaseType.DISCOUNT);
@@ -133,9 +134,9 @@ public abstract class TestMigration extends TestApiBase {
             EntitlementAccountMigration toBeMigrated = createAccountWithRegularBasePlanFutreCancelled(startDate);
             DateTime afterMigration = clock.getUTCNow();
 
-            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            testListener.pushNextApiExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
             migrationApi.migrate(toBeMigrated, context);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
             List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(toBeMigrated.getAccountKey());
             assertEquals(bundles.size(), 1);
@@ -152,11 +153,11 @@ public abstract class TestMigration extends TestApiBase {
             assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-annual");
             assertEquals(subscription.getChargedThroughDate(), startDate.plusYears(1));
 
-            testListener.pushExpectedEvent(NextEvent.MIGRATE_BILLING);
-            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            testListener.pushNextApiExpectedEvent(NextEvent.MIGRATE_BILLING);
+            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
             Duration oneYear = getDurationYear(1);
             clock.setDeltaFromReality(oneYear, 0);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
             assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
             assertNotNull(subscription.getEndDate());
@@ -177,9 +178,9 @@ public abstract class TestMigration extends TestApiBase {
             final DateTime trialDate = clock.getUTCNow().minusDays(10);
             EntitlementAccountMigration toBeMigrated = createAccountFuturePendingPhase(trialDate);
 
-            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            testListener.pushNextApiExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
             migrationApi.migrate(toBeMigrated, context);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
             List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(toBeMigrated.getAccountKey());
             assertEquals(bundles.size(), 1);
@@ -197,11 +198,11 @@ public abstract class TestMigration extends TestApiBase {
             assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-monthly");
             assertEquals(subscription.getChargedThroughDate(), trialDate.plusDays(30));
 
-            testListener.pushExpectedEvent(NextEvent.MIGRATE_BILLING);
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.MIGRATE_BILLING);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             Duration thirtyDays = getDurationDay(30);
             clock.setDeltaFromReality(thirtyDays, 0);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
             assertEquals(subscription.getStartDate(), trialDate);
             assertEquals(subscription.getEndDate(), null);
@@ -223,9 +224,9 @@ public abstract class TestMigration extends TestApiBase {
             EntitlementAccountMigration toBeMigrated = createAccountFuturePendingChange();
             DateTime afterMigration = clock.getUTCNow();
 
-            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            testListener.pushNextApiExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
             migrationApi.migrate(toBeMigrated, context);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
             List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(toBeMigrated.getAccountKey());
             assertEquals(bundles.size(), 1);
@@ -241,10 +242,10 @@ public abstract class TestMigration extends TestApiBase {
             assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
             assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-monthly");
 
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             Duration oneMonth = getDurationMonth(1);
             clock.setDeltaFromReality(oneMonth, 0);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
             assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
             assertEquals(subscription.getEndDate(), null);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationSql.java
index a17cbcd..34b2266 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationSql.java
@@ -23,34 +23,32 @@ import com.google.inject.Injector;
 import com.google.inject.Stage;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
 
-@Test(groups = "slow")
 public class TestMigrationSql extends TestMigration {
-//TODO MDW These tests pass but not when you run them all together - some clean up needed
     @Override
     protected Injector getInjector() {
         return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
     }
 
     @Override
-    @Test(enabled=false, groups="slow")
+    @Test(enabled=true, groups="slow")
     public void testSingleBasePlan() {
         super.testSingleBasePlan();
     }
 
     @Override
-    @Test(enabled=false, groups="slow")
+    @Test(enabled=true, groups="slow")
     public void testPlanWithAddOn() {
         super.testPlanWithAddOn();
     }
 
     @Override
-    @Test(enabled=false, groups="slow")
+    @Test(enabled=true, groups="slow")
     public void testSingleBasePlanFutureCancelled() {
         super.testSingleBasePlanFutureCancelled();
     }
 
     @Override
-    @Test(enabled=false, groups="slow")
+    @Test(enabled=true, groups="slow")
     public void testSingleBasePlanWithPendingPhase() {
         super.testSingleBasePlanWithPendingPhase();
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
index 155e1ed..4525d16 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
@@ -56,6 +56,7 @@ import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.billing.ChargeThruApi;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
+import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
@@ -88,6 +89,7 @@ public abstract class TestApiBase {
     protected ChargeThruApi billingApi;
 
     protected EntitlementMigrationApi migrationApi;
+    protected EntitlementTimelineApi repairApi;
 
     protected CatalogService catalogService;
     protected EntitlementConfig config;
@@ -139,6 +141,7 @@ public abstract class TestApiBase {
         entitlementApi = entApi;
         billingApi = g.getInstance(ChargeThruApi.class);
         migrationApi = g.getInstance(EntitlementMigrationApi.class);
+        repairApi = g.getInstance(EntitlementTimelineApi.class);
         catalogService = g.getInstance(CatalogService.class);
         busService = g.getInstance(BusService.class);
         config = g.getInstance(EntitlementConfig.class);
@@ -146,11 +149,10 @@ public abstract class TestApiBase {
         clock = (ClockMock) g.getInstance(Clock.class);
         helper = (isSqlTest(dao)) ? g.getInstance(MysqlTestingHelper.class) : null;
 
-
         ((DefaultCatalogService) catalogService).loadCatalog();
         ((DefaultBusService) busService).startBus();
         ((Engine) entitlementService).initialize();
-        init();
+        init(g);
     }
 
     private static boolean isSqlTest(EntitlementDao theDao) {
@@ -167,16 +169,13 @@ public abstract class TestApiBase {
         }
     }
 
-    private void init() throws Exception {
+    private void init(Injector g) throws Exception {
 
         setupMySQL();
-
         accountData = getAccountData();
         assertNotNull(accountData);
-
         catalog = catalogService.getFullCatalog();
         assertNotNull(catalog);
-
         testListener = new ApiTestListener(busService.getBus());
     }
 
@@ -207,18 +206,24 @@ public abstract class TestApiBase {
         log.warn("DONE WITH TEST\n");
     }
 
-    protected SubscriptionData createSubscription(final String productName, final BillingPeriod term, final String planSet) throws EntitlementUserApiException {
-        return createSubscriptionWithBundle(bundle.getId(), productName, term, planSet);
+    protected SubscriptionData createSubscription(final String productName, final BillingPeriod term, final String planSet, final DateTime requestedDate)
+        throws EntitlementUserApiException {
+        return createSubscriptionWithBundle(bundle.getId(), productName, term, planSet, requestedDate);
+    }
+    protected SubscriptionData createSubscription(final String productName, final BillingPeriod term, final String planSet)
+    throws EntitlementUserApiException {
+        return createSubscriptionWithBundle(bundle.getId(), productName, term, planSet, null);
     }
 
-    protected SubscriptionData createSubscriptionWithBundle(final UUID bundleId, final String productName, final BillingPeriod term, final String planSet) throws EntitlementUserApiException {
-        testListener.pushExpectedEvent(NextEvent.CREATE);
+    protected SubscriptionData createSubscriptionWithBundle(final UUID bundleId, final String productName, final BillingPeriod term, final String planSet, final DateTime requestedDate)
+        throws EntitlementUserApiException {
+        testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
 
         SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundleId,
                 new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSet, null),
-                clock.getUTCNow(), context);
+                requestedDate == null ? clock.getUTCNow() : requestedDate, context);
         assertNotNull(subscription);
-        assertTrue(testListener.isCompleted(5000));
+        assertTrue(testListener.isApiCompleted(5000));
         return subscription;
     }
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestApiBaseRepair.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestApiBaseRepair.java
new file mode 100644
index 0000000..4fc11ab
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestApiBaseRepair.java
@@ -0,0 +1,222 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.TestApiBase;
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.entitlement.api.timeline.EntitlementRepairException;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+
+
+public abstract class TestApiBaseRepair extends TestApiBase {
+
+    protected final static Logger log = LoggerFactory.getLogger(TestApiBaseRepair.class);
+    
+    public interface TestWithExceptionCallback {
+        public void doTest() throws EntitlementRepairException, EntitlementUserApiException;
+    }
+    
+    public static class TestWithException {
+        public void withException(TestWithExceptionCallback callback, ErrorCode code) throws Exception {
+            try {
+                callback.doTest();
+                Assert.fail("Failed to catch exception " + code);
+            } catch (EntitlementRepairException e) {
+                assertEquals(e.getCode(), code.getCode());
+            }
+        }
+    }
+
+    
+    protected SubscriptionTimeline createSubscriptionReapir(final UUID id, final List<DeletedEvent> deletedEvents, final List<NewEvent> newEvents) {
+        return new SubscriptionTimeline() {
+            @Override
+            public UUID getId() {
+                return id;
+            }
+            @Override
+            public List<NewEvent> getNewEvents() {
+                return newEvents;
+            }
+            @Override
+            public List<ExistingEvent> getExistingEvents() {
+                return null;
+            }
+            @Override
+            public List<DeletedEvent> getDeletedEvents() {
+                return deletedEvents;
+            }
+        };
+    }
+
+    protected BundleTimeline createBundleRepair(final UUID bundleId, final String viewId, final List<SubscriptionTimeline> subscriptionRepair) {
+        return new BundleTimeline() {
+            @Override
+            public String getViewId() {
+                return viewId;
+            }
+            @Override
+            public List<SubscriptionTimeline> getSubscriptions() {
+                return subscriptionRepair;
+            }
+            @Override
+            public UUID getBundleId() {
+                return bundleId;
+            }
+            @Override
+            public String getExternalKey() {
+                return null;
+            }
+        };
+    }
+
+    protected ExistingEvent createExistingEventForAssertion(final SubscriptionTransitionType type, 
+            final String productName, final PhaseType phaseType, final ProductCategory category, final String priceListName, final BillingPeriod billingPeriod,
+            final DateTime effectiveDateTime) {
+
+        final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
+        ExistingEvent ev = new ExistingEvent() {
+            @Override
+            public SubscriptionTransitionType getSubscriptionTransitionType() {
+                return type;
+            }
+             @Override
+            public DateTime getRequestedDate() {
+                 return null;
+            }
+            @Override
+            public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+                return spec;
+            }
+            @Override
+            public UUID getEventId() {
+                return null;
+            }
+            @Override
+            public DateTime getEffectiveDate() {
+                return effectiveDateTime;
+            }
+        };
+        return ev;
+    }
+    
+    protected SubscriptionTimeline getSubscriptionRepair(final UUID id, final BundleTimeline bundleRepair) {
+        for (SubscriptionTimeline cur : bundleRepair.getSubscriptions()) {
+            if (cur.getId().equals(id)) {
+                return cur;
+            }
+        }
+        Assert.fail("Failed to find SubscriptionReapir " + id);
+        return null;
+    }
+    protected void validateExistingEventForAssertion(final ExistingEvent expected, final ExistingEvent input) {
+        
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName()));
+        assertEquals(input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName());
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType()));
+        assertEquals(input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType());
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory()));
+        assertEquals(input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory());                    
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName()));
+        assertEquals(input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName());                    
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod()));
+        assertEquals(input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod());
+        log.info(String.format("Got %s -> Expected %s", input.getEffectiveDate(), expected.getEffectiveDate()));
+        assertEquals(input.getEffectiveDate(), expected.getEffectiveDate());        
+    }
+    
+    protected DeletedEvent createDeletedEvent(final UUID eventId) {
+        return new DeletedEvent() {
+            @Override
+            public UUID getEventId() {
+                return eventId;
+            }
+        };
+    }
+
+    protected NewEvent createNewEvent(final SubscriptionTransitionType type, final DateTime requestedDate, final PlanPhaseSpecifier spec) {
+
+        return new NewEvent() {
+            @Override
+            public SubscriptionTransitionType getSubscriptionTransitionType() {
+                return type;
+            }
+            @Override
+            public DateTime getRequestedDate() {
+                return requestedDate;
+            }
+            @Override
+            public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+                return spec;
+            }
+        };
+    }
+
+    protected void sortEventsOnBundle(final BundleTimeline bundle) {
+        if (bundle.getSubscriptions() == null) {
+            return;
+        }
+        for (SubscriptionTimeline cur : bundle.getSubscriptions()) {
+            if (cur.getExistingEvents() != null) {
+                sortExistingEvent(cur.getExistingEvents());
+            }
+            if (cur.getNewEvents() != null) {
+                sortNewEvent(cur.getNewEvents());
+            }
+        }
+    }
+
+    protected void sortExistingEvent(final List<ExistingEvent> events) {
+        Collections.sort(events, new Comparator<ExistingEvent>() {
+            @Override
+            public int compare(ExistingEvent arg0, ExistingEvent arg1) {
+                return arg0.getEffectiveDate().compareTo(arg1.getEffectiveDate());
+            }
+        });
+    }
+    protected void sortNewEvent(final List<NewEvent> events) {
+        Collections.sort(events, new Comparator<NewEvent>() {
+            @Override
+            public int compare(NewEvent arg0, NewEvent arg1) {
+                return arg0.getRequestedDate().compareTo(arg1.getRequestedDate());
+            }
+        });
+    }
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairBP.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairBP.java
new file mode 100644
index 0000000..e988805
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairBP.java
@@ -0,0 +1,711 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.entitlement.api.timeline.EntitlementRepairException;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionEvents;
+import com.ning.billing.entitlement.glue.MockEngineModuleSql;
+
+public class TestRepairBP extends TestApiBaseRepair {
+
+    @Override
+    public Injector getInjector() {
+        return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
+    }
+
+    @Test(groups={"slow"})
+    public void testFetchBundleRepair() throws Exception  {
+
+        String baseProduct = "Shotgun";
+        BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+        String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        // CREATE BP
+        Subscription baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+        String aoProduct = "Telescopic-Scope";
+        BillingPeriod aoTerm = BillingPeriod.MONTHLY;
+        String aoPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        List<SubscriptionTimeline> subscriptionRepair = bundleRepair.getSubscriptions();
+        assertEquals(subscriptionRepair.size(), 2);
+
+        for (SubscriptionTimeline cur : subscriptionRepair) {
+            assertNull(cur.getDeletedEvents());
+            assertNull(cur.getNewEvents());                
+
+            List<ExistingEvent> events = cur.getExistingEvents();
+            assertEquals(events.size(), 2);
+            sortExistingEvent(events);
+
+            assertEquals(events.get(0).getSubscriptionTransitionType(), SubscriptionTransitionType.CREATE);
+            assertEquals(events.get(1).getSubscriptionTransitionType(), SubscriptionTransitionType.PHASE);                    
+            final boolean isBP = cur.getId().equals(baseSubscription.getId());
+            if (isBP) {
+                assertEquals(cur.getId(), baseSubscription.getId());
+
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getProductName(), baseProduct);
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getPhaseType(), PhaseType.TRIAL);
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getProductCategory(),ProductCategory.BASE);                    
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getPriceListName(), basePriceList);                    
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getBillingPeriod(), BillingPeriod.NO_BILLING_PERIOD);
+
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getProductName(), baseProduct);
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getPhaseType(), PhaseType.EVERGREEN);
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getProductCategory(),ProductCategory.BASE);                    
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getPriceListName(), basePriceList);                    
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getBillingPeriod(), baseTerm);
+            } else {
+                assertEquals(cur.getId(), aoSubscription.getId());
+
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getProductName(), aoProduct);
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getPhaseType(), PhaseType.DISCOUNT);                    
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getProductCategory(),ProductCategory.ADD_ON); 
+                assertEquals(events.get(0).getPlanPhaseSpecifier().getPriceListName(), aoPriceList); 
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getBillingPeriod(), aoTerm);                    
+
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getProductName(), aoProduct);
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getPhaseType(), PhaseType.EVERGREEN);                    
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getProductCategory(),ProductCategory.ADD_ON); 
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getPriceListName(), aoPriceList);  
+                assertEquals(events.get(1).getPlanPhaseSpecifier().getBillingPeriod(), aoTerm);                    
+            }
+        }
+    }
+    
+    @Test(groups={"slow"})
+    public void testBPRepairWithCancellationOnstart() throws Exception {
+
+        String baseProduct = "Shotgun";
+        DateTime startDate = clock.getUTCNow();
+        
+        // CREATE BP
+        Subscription baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+
+        // Stays in trial-- for instance
+        Duration durationShift = getDurationDay(10);
+        clock.setDeltaFromReality(durationShift, 0);
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+
+        List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+        des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, baseSubscription.getStartDate(), null);
+        
+        
+        SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+        
+        // FIRST ISSUE DRY RUN
+        BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        sortEventsOnBundle(dryRunBundleRepair);
+        List<SubscriptionTimeline> subscriptionRepair = dryRunBundleRepair.getSubscriptions();
+        assertEquals(subscriptionRepair.size(), 1);
+        SubscriptionTimeline cur = subscriptionRepair.get(0);
+        int index = 0;
+        List<ExistingEvent> events = subscriptionRepair.get(0).getExistingEvents();
+        assertEquals(events.size(), 2);
+        List<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, baseProduct, PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate()));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, baseProduct, PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD,baseSubscription.getStartDate()));
+
+        for (ExistingEvent e : expected) {
+           validateExistingEventForAssertion(e, events.get(index++));           
+        }
+        
+        SubscriptionData dryRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        
+        assertEquals(dryRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+        assertEquals(dryRunBaseSubscription.getBundleId(), bundle.getId());
+        assertEquals(dryRunBaseSubscription.getStartDate(), baseSubscription.getStartDate());
+
+        Plan currentPlan = dryRunBaseSubscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), baseProduct);
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        PlanPhase currentPhase = dryRunBaseSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
+        
+       // SECOND RE-ISSUE CALL-- NON DRY RUN
+        dryRun = false;
+        testListener.expectRepairCompletion();
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        assertTrue(testListener.isRepairCompleted(5000));
+        
+        subscriptionRepair = realRunBundleRepair.getSubscriptions();
+        assertEquals(subscriptionRepair.size(), 1);
+        cur = subscriptionRepair.get(0);
+        assertEquals(cur.getId(), baseSubscription.getId());
+        index = 0;
+        for (ExistingEvent e : expected) {
+           validateExistingEventForAssertion(e, events.get(index++));           
+        }
+        SubscriptionData realRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(realRunBaseSubscription.getAllTransitions().size(), 2);
+        
+        
+        assertEquals(realRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+        assertEquals(realRunBaseSubscription.getBundleId(), bundle.getId());
+        assertEquals(realRunBaseSubscription.getStartDate(), startDate);
+
+        assertEquals(realRunBaseSubscription.getState(), SubscriptionState.CANCELLED);
+    }
+    
+    @Test(groups={"slow"})
+    public void testBPRepairReplaceCreateBeforeTrial() throws Exception {
+        String baseProduct = "Shotgun";
+        String newBaseProduct = "Assault-Rifle";
+        
+        DateTime startDate = clock.getUTCNow();
+        int clockShift = -1;
+        DateTime restartDate =  startDate.plusDays(clockShift).minusDays(1);
+        LinkedList<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, newBaseProduct, PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, restartDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, newBaseProduct, PhaseType.EVERGREEN,
+                    ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, restartDate.plusDays(30)));
+
+        testBPRepairCreate(true, startDate, clockShift, baseProduct, newBaseProduct, expected);
+    }
+
+    @Test(groups={"slow"}, enabled=true)
+    public void testBPRepairReplaceCreateInTrial() throws Exception {
+        String baseProduct = "Shotgun";
+        String newBaseProduct = "Assault-Rifle";
+        
+        DateTime startDate = clock.getUTCNow();
+        int clockShift = 10;
+        DateTime restartDate =  startDate.plusDays(clockShift).minusDays(1);
+        LinkedList<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, newBaseProduct, PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, restartDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, newBaseProduct, PhaseType.EVERGREEN,
+                    ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, restartDate.plusDays(30)));
+
+        UUID baseSubscriptionId = testBPRepairCreate(true, startDate, clockShift, baseProduct, newBaseProduct, expected);
+        
+        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+        clock.addDeltaFromReality(getDurationDay(32));
+        assertTrue(testListener.isApiCompleted(5000));
+        
+        // CHECK WHAT"S GOING ON AFTER WE MOVE CLOCK-- FUTURE MOTIFICATION SHOULD KICK IN
+        SubscriptionData subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscriptionId);
+        
+        assertEquals(subscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+        assertEquals(subscription.getBundleId(), bundle.getId());
+        assertEquals(subscription.getStartDate(), restartDate);
+        assertEquals(subscription.getBundleStartDate(), restartDate);        
+
+        Plan currentPlan = subscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), newBaseProduct);
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        PlanPhase currentPhase = subscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+    }
+
+    
+    @Test(groups={"slow"})
+    public void testBPRepairReplaceCreateAfterTrial() throws Exception {
+        String baseProduct = "Shotgun";
+        String newBaseProduct = "Assault-Rifle";
+        
+        DateTime startDate = clock.getUTCNow();
+        int clockShift = 40;
+        DateTime restartDate =  startDate.plusDays(clockShift).minusDays(1);
+        LinkedList<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, newBaseProduct, PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, restartDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, newBaseProduct, PhaseType.EVERGREEN,
+                    ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, restartDate.plusDays(30)));
+
+        testBPRepairCreate(false, startDate, clockShift, baseProduct, newBaseProduct, expected);
+        
+    }
+    
+    
+    private UUID testBPRepairCreate(boolean inTrial, DateTime startDate, int clockShift, 
+            String baseProduct, String newBaseProduct, List<ExistingEvent> expectedEvents) throws Exception {
+
+        // CREATE BP
+        Subscription baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+
+        // MOVE CLOCK
+        if (clockShift > 0) {
+            if (!inTrial) {
+                testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+            }               
+            Duration durationShift = getDurationDay(clockShift);
+            clock.setDeltaFromReality(durationShift, 0);
+            if (!inTrial) {
+                assertTrue(testListener.isApiCompleted(5000));
+            }
+        }
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        DateTime newCreateTime = baseSubscription.getStartDate().plusDays(clockShift - 1);
+
+        PlanPhaseSpecifier spec = new PlanPhaseSpecifier(newBaseProduct, ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, newCreateTime, spec);
+        List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+        des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));
+        des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+
+        SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+        
+        // FIRST ISSUE DRY RUN
+        BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        List<SubscriptionTimeline> subscriptionRepair = dryRunBundleRepair.getSubscriptions();
+        assertEquals(subscriptionRepair.size(), 1);
+        SubscriptionTimeline cur = subscriptionRepair.get(0);
+        assertEquals(cur.getId(), baseSubscription.getId());
+
+        List<ExistingEvent> events = cur.getExistingEvents();
+        assertEquals(expectedEvents.size(), events.size());
+        int index = 0;
+        for (ExistingEvent e : expectedEvents) {
+           validateExistingEventForAssertion(e, events.get(index++));           
+        }
+        SubscriptionData dryRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        
+        assertEquals(dryRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+        assertEquals(dryRunBaseSubscription.getBundleId(), bundle.getId());
+        assertEquals(dryRunBaseSubscription.getStartDate(), baseSubscription.getStartDate());
+
+        Plan currentPlan = dryRunBaseSubscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), baseProduct);
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        PlanPhase currentPhase = dryRunBaseSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        if (inTrial) {
+            assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
+        } else {
+            assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+        }
+        
+       // SECOND RE-ISSUE CALL-- NON DRY RUN
+        dryRun = false;
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+
+        subscriptionRepair = realRunBundleRepair.getSubscriptions();
+        assertEquals(subscriptionRepair.size(), 1);
+        cur = subscriptionRepair.get(0);
+        assertEquals(cur.getId(), baseSubscription.getId());
+
+        events = cur.getExistingEvents();
+        for (ExistingEvent e : events) {
+            log.info(String.format("%s, %s, %s, %s", e.getSubscriptionTransitionType(), e.getEffectiveDate(), e.getPlanPhaseSpecifier().getProductName(),  e.getPlanPhaseSpecifier().getPhaseType()));
+        }
+        assertEquals(events.size(), expectedEvents.size());
+        index = 0;
+        for (ExistingEvent e : expectedEvents) {
+           validateExistingEventForAssertion(e, events.get(index++));           
+        }
+        SubscriptionData realRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(realRunBaseSubscription.getAllTransitions().size(), 2);
+        
+        
+        assertEquals(realRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+        assertEquals(realRunBaseSubscription.getBundleId(), bundle.getId());
+        assertEquals(realRunBaseSubscription.getStartDate(), newCreateTime);
+
+        currentPlan = realRunBaseSubscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), newBaseProduct);
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        currentPhase = realRunBaseSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
+        
+        return baseSubscription.getId();
+    }
+
+    @Test(groups={"slow"})
+    public void testBPRepairAddChangeInTrial() throws Exception {
+        
+        String baseProduct = "Shotgun";
+        String newBaseProduct = "Assault-Rifle";
+        
+        DateTime startDate = clock.getUTCNow();
+        int clockShift = 10;
+        DateTime changeDate =  startDate.plusDays(clockShift).minusDays(1);
+        LinkedList<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, baseProduct, PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, startDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CHANGE, newBaseProduct, PhaseType.TRIAL,
+                    ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, changeDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, newBaseProduct, PhaseType.EVERGREEN,
+                    ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, startDate.plusDays(30)));
+
+        UUID baseSubscriptionId = testBPRepairAddChange(true, startDate, clockShift, baseProduct, newBaseProduct, expected, 3);
+        
+        // CHECK WHAT"S GOING ON AFTER WE MOVE CLOCK-- FUTURE MOTIFICATION SHOULD KICK IN
+        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+        clock.addDeltaFromReality(getDurationDay(32));
+        assertTrue(testListener.isApiCompleted(5000));
+        SubscriptionData subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscriptionId);
+        
+        assertEquals(subscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+        assertEquals(subscription.getBundleId(), bundle.getId());
+        assertEquals(subscription.getStartDate(), startDate);
+        assertEquals(subscription.getBundleStartDate(), startDate);        
+
+        Plan currentPlan = subscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), newBaseProduct);
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        PlanPhase currentPhase = subscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+    }
+
+    @Test(groups={"slow"})
+    public void testBPRepairAddChangeAfterTrial() throws Exception {
+        
+        String baseProduct = "Shotgun";
+        String newBaseProduct = "Assault-Rifle";
+        
+        DateTime startDate = clock.getUTCNow();
+        int clockShift = 40;
+        DateTime changeDate =  startDate.plusDays(clockShift).minusDays(1);
+        
+        LinkedList<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, baseProduct, PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, startDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, baseProduct, PhaseType.EVERGREEN,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, startDate.plusDays(30)));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CHANGE, newBaseProduct, PhaseType.EVERGREEN,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, changeDate));
+        testBPRepairAddChange(false, startDate, clockShift, baseProduct, newBaseProduct, expected, 3);
+    }
+    
+
+    private UUID testBPRepairAddChange(boolean inTrial, DateTime startDate, int clockShift, 
+            String baseProduct, String newBaseProduct, List<ExistingEvent> expectedEvents, int expectedTransitions) throws Exception {
+
+        // CREATE BP
+        Subscription baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+
+        // MOVE CLOCK
+        if (!inTrial) {
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+        }               
+
+        Duration durationShift = getDurationDay(clockShift);
+        clock.setDeltaFromReality(durationShift, 0);
+        if (!inTrial) {
+            assertTrue(testListener.isApiCompleted(5000));
+        }
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        DateTime changeTime = baseSubscription.getStartDate().plusDays(clockShift - 1);
+
+        PlanPhaseSpecifier spec = new PlanPhaseSpecifier(newBaseProduct, ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, changeTime, spec);
+        List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+        if (inTrial) {
+            des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+        }
+        SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+        
+        // FIRST ISSUE DRY RUN
+        BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        List<SubscriptionTimeline> subscriptionRepair = dryRunBundleRepair.getSubscriptions();
+        assertEquals(subscriptionRepair.size(), 1);
+        SubscriptionTimeline cur = subscriptionRepair.get(0);
+        assertEquals(cur.getId(), baseSubscription.getId());
+
+        List<ExistingEvent> events = cur.getExistingEvents();
+       assertEquals(expectedEvents.size(), events.size());
+       int index = 0;
+       for (ExistingEvent e : expectedEvents) {
+           validateExistingEventForAssertion(e, events.get(index++));           
+       }
+        SubscriptionData dryRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        
+        assertEquals(dryRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+        assertEquals(dryRunBaseSubscription.getBundleId(), bundle.getId());
+        assertEquals(dryRunBaseSubscription.getStartDate(), baseSubscription.getStartDate());
+
+        Plan currentPlan = dryRunBaseSubscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), baseProduct);
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        PlanPhase currentPhase = dryRunBaseSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        if (inTrial) {
+            assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
+        } else {
+            assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+        }
+        
+        
+       // SECOND RE-ISSUE CALL-- NON DRY RUN
+        dryRun = false;
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+
+        subscriptionRepair = realRunBundleRepair.getSubscriptions();
+        assertEquals(subscriptionRepair.size(), 1);
+        cur = subscriptionRepair.get(0);
+        assertEquals(cur.getId(), baseSubscription.getId());
+
+        events = cur.getExistingEvents();
+        assertEquals(expectedEvents.size(), events.size());
+        index = 0;
+        for (ExistingEvent e : expectedEvents) {
+           validateExistingEventForAssertion(e, events.get(index++));           
+        }
+        SubscriptionData realRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(realRunBaseSubscription.getAllTransitions().size(), expectedTransitions);
+        
+        
+        assertEquals(realRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+        assertEquals(realRunBaseSubscription.getBundleId(), bundle.getId());
+        assertEquals(realRunBaseSubscription.getStartDate(), baseSubscription.getStartDate());
+
+        currentPlan = realRunBaseSubscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), newBaseProduct);
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        currentPhase = realRunBaseSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        if (inTrial) {
+            assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
+        } else {
+            assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+        }
+        return baseSubscription.getId();
+    }
+    
+    @Test(groups={"slow"})
+    public void testRepairWithFurureCancelEvent() throws Exception {
+      
+        DateTime startDate = clock.getUTCNow();
+        
+        // CREATE BP
+        Subscription baseSubscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+
+        // MOVE CLOCK -- OUT OF TRIAL
+        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);                
+        clock.setDeltaFromReality(getDurationDay(35), 0);
+        assertTrue(testListener.isApiCompleted(5000));
+        
+        // SET CTD to BASE SUBSCRIPTION SP CANCEL OCCURS EOT
+        DateTime newChargedThroughDate = baseSubscription.getStartDate().plusDays(30).plusMonths(1);
+        billingApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, context);
+        baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+
+        
+        DateTime requestedChange = clock.getUTCNow();
+        baseSubscription.changePlan("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, requestedChange, context);
+        
+        
+        // CHECK CHANGE DID NOT OCCUR YET
+        Plan currentPlan = baseSubscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), "Shotgun");
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+        
+        
+        DateTime repairTime = clock.getUTCNow().minusDays(1);
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, repairTime, spec);
+        List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+        des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(2).getEventId()));
+
+        SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+        
+        // SKIP DRY RUN AND DO REPAIR...
+        BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+        
+        boolean dryRun = false;
+        repairApi.repairBundle(bRepair, dryRun, context);
+     
+        baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        
+        assertEquals(((SubscriptionData) baseSubscription).getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+        assertEquals(baseSubscription.getBundleId(), bundle.getId());
+        assertEquals(baseSubscription.getStartDate(), baseSubscription.getStartDate());
+
+        currentPlan = baseSubscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), "Assault-Rifle");
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        PlanPhase currentPhase = baseSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+    }
+    
+    
+    // Needs real SQL backend to be tested properly
+    @Test(groups={"slow"})
+    public void testENT_REPAIR_VIEW_CHANGED_newEvent() throws Exception {
+       
+        TestWithException test = new TestWithException();
+        DateTime startDate = clock.getUTCNow();
+        
+        testListener.reset();
+        clock.resetDeltaFromReality();
+
+        final Subscription baseSubscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+        
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+                List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));                
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));                                
+                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+                testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+                DateTime changeTime = clock.getUTCNow();
+                baseSubscription.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, changeTime, context);
+                assertTrue(testListener.isApiCompleted(5000));
+                                
+                repairApi.repairBundle(bRepair, true, context);
+            }
+        }, ErrorCode.ENT_REPAIR_VIEW_CHANGED);
+    }
+
+    @Test(groups={"slow"}, enabled=false)
+    public void testENT_REPAIR_VIEW_CHANGED_ctd() throws Exception {
+       
+        TestWithException test = new TestWithException();
+        DateTime startDate = clock.getUTCNow();
+        
+        testListener.reset();
+        clock.resetDeltaFromReality();
+
+        final Subscription baseSubscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+        
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+                List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));                
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));                                
+                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+                DateTime newChargedThroughDate = baseSubscription.getStartDate().plusDays(30).plusMonths(1);
+                billingApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, context);
+                entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+
+                repairApi.repairBundle(bRepair, true, context);
+            }
+        }, ErrorCode.ENT_REPAIR_VIEW_CHANGED);
+    }
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithAO.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithAO.java
new file mode 100644
index 0000000..57a78e6
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithAO.java
@@ -0,0 +1,747 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import org.joda.time.DateTime;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionEvents;
+import com.ning.billing.entitlement.glue.MockEngineModuleSql;
+
+public class TestRepairWithAO extends TestApiBaseRepair {
+
+    @Override
+    public Injector getInjector() {
+        return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
+    }    
+
+    @Test(groups={"slow"})
+    public void testRepairChangeBPWithAddonIncluded() throws Exception {
+        
+        String baseProduct = "Shotgun";
+        BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+        String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        // CREATE BP
+        SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+        // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+        Duration someTimeLater = getDurationDay(3);
+        clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
+
+        SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+        
+        SubscriptionData aoSubscription2 = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+        // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+        clock.addDeltaFromReality(someTimeLater);
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        // Quick check
+        SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 2);
+        
+        SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+        SubscriptionTimeline aoRepair2 = getSubscriptionRepair(aoSubscription2.getId(), bundleRepair);
+        assertEquals(aoRepair2.getExistingEvents().size(), 2);
+
+        DateTime bpChangeDate = clock.getUTCNow().minusDays(1);
+        
+        List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+        des.add(createDeletedEvent(bpRepair.getExistingEvents().get(1).getEventId()));        
+        
+        PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, bpChangeDate, spec);
+        
+        bpRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+        
+        bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(bpRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+                
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+        aoRepair2 = getSubscriptionRepair(aoSubscription2.getId(), dryRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+        bpRepair = getSubscriptionRepair(baseSubscription.getId(), dryRunBundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 3);        
+        
+        // Check expected for AO
+        List<ExistingEvent> expectedAO = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, bpChangeDate));
+        int index = 0;
+        for (ExistingEvent e : expectedAO) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+
+        List<ExistingEvent> expectedAO2 = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expectedAO2.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Laser-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription2.getStartDate()));
+        expectedAO2.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Laser-Scope", PhaseType.EVERGREEN,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription2.getStartDate().plusMonths(1)));
+        index = 0;
+        for (ExistingEvent e : expectedAO2) {
+           validateExistingEventForAssertion(e, aoRepair2.getExistingEvents().get(index++));           
+        }
+        
+        // Check expected for BP        
+        List<ExistingEvent> expectedBP = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Shotgun", PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate()));
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CHANGE, "Assault-Rifle", PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, bpChangeDate));
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Assault-Rifle", PhaseType.EVERGREEN,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusDays(30)));
+        index = 0;
+        for (ExistingEvent e : expectedBP) {
+           validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));           
+        }
+
+        
+        SubscriptionData newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+        SubscriptionData newAoSubscription2 = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription2.getId());
+        assertEquals(newAoSubscription2.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription2.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription2.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+        
+        SubscriptionData newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 2);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+        
+        dryRun = false;
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+        
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+        bpRepair = getSubscriptionRepair(baseSubscription.getId(), realRunBundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 3);        
+        
+        index = 0;
+        for (ExistingEvent e : expectedAO) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        
+        index = 0;
+        for (ExistingEvent e : expectedAO2) {
+           validateExistingEventForAssertion(e, aoRepair2.getExistingEvents().get(index++));           
+        }
+
+        index = 0;
+        for (ExistingEvent e : expectedBP) {
+           validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));           
+        }
+
+        newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.CANCELLED);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+            
+        newAoSubscription2 = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription2.getId());
+        assertEquals(newAoSubscription2.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription2.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription2.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+        
+        newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 3);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+    }
+
+    @Test(groups={"slow"})
+    public void testRepairChangeBPWithAddonNonAvailable() throws Exception {
+        
+        String baseProduct = "Shotgun";
+        BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+        String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        // CREATE BP
+        SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+        // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+        Duration someTimeLater = getDurationDay(3);
+        clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
+
+        SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+        
+        // MOVE CLOCK A LITTLE BIT MORE -- AFTER TRIAL
+        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+        someTimeLater = getDurationDay(32);
+        clock.addDeltaFromReality(someTimeLater);
+        assertTrue(testListener.isApiCompleted(7000));        
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        // Quick check
+        SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 2);
+        
+        SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+        DateTime bpChangeDate = clock.getUTCNow().minusDays(1);
+        
+        PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, bpChangeDate, spec);
+        
+        bpRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.<SubscriptionTimeline.DeletedEvent>emptyList(), Collections.singletonList(ne));
+        
+        bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(bpRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+                
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 3);
+
+        bpRepair = getSubscriptionRepair(baseSubscription.getId(), dryRunBundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 3);        
+        
+        // Check expected for AO
+        List<ExistingEvent> expectedAO = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.EVERGREEN,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusMonths(1)));
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Telescopic-Scope", PhaseType.EVERGREEN,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, bpChangeDate));
+        int index = 0;
+        for (ExistingEvent e : expectedAO) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+
+        // Check expected for BP        
+        List<ExistingEvent> expectedBP = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Shotgun", PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate()));
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Shotgun", PhaseType.EVERGREEN,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusDays(30)));
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CHANGE, "Pistol", PhaseType.EVERGREEN,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, bpChangeDate));
+        index = 0;
+        for (ExistingEvent e : expectedBP) {
+           validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));           
+        }
+        
+        SubscriptionData newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+        SubscriptionData newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 2);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+        
+        dryRun = false;
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 3);
+
+        bpRepair = getSubscriptionRepair(baseSubscription.getId(), realRunBundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 3);        
+        
+        index = 0;
+        for (ExistingEvent e : expectedAO) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        
+        index = 0;
+        for (ExistingEvent e : expectedBP) {
+           validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));           
+        }
+
+        newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.CANCELLED);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 3);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+            
+        newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 3);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+    }
+
+    @Test(groups={"slow"})
+    public void testRepairCancelBP_EOT_WithAddons() throws Exception {
+        
+        String baseProduct = "Shotgun";
+        BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+        String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        // CREATE BP
+        SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+        // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+        Duration someTimeLater = getDurationDay(3);
+        clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
+
+        SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+        
+        // MOVE CLOCK A LITTLE BIT MORE -- AFTER TRIAL
+        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+        someTimeLater = getDurationDay(40);
+        clock.addDeltaFromReality(someTimeLater);
+        assertTrue(testListener.isApiCompleted(7000));
+        
+        // SET CTD to BASE SUBSCRIPTION SP CANCEL OCCURS EOT
+        DateTime newChargedThroughDate = baseSubscription.getStartDate().plusDays(30).plusMonths(1);
+        billingApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, context);
+        baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        // Quick check
+        SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 2);
+        
+        SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+        DateTime bpCancelDate = clock.getUTCNow().minusDays(1);
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, bpCancelDate, null);
+        bpRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.<SubscriptionTimeline.DeletedEvent>emptyList(), Collections.singletonList(ne));
+        bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(bpRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+                
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 3);
+
+        bpRepair = getSubscriptionRepair(baseSubscription.getId(), dryRunBundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 3);        
+        
+        // Check expected for AO
+        List<ExistingEvent> expectedAO = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Telescopic-Scope", PhaseType.EVERGREEN,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusMonths(1)));
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Telescopic-Scope", PhaseType.EVERGREEN,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, newChargedThroughDate));
+
+        int index = 0;
+        for (ExistingEvent e : expectedAO) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+
+        // Check expected for BP        
+        List<ExistingEvent> expectedBP = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Shotgun", PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate()));
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Shotgun", PhaseType.EVERGREEN,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusDays(30)));
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Shotgun", PhaseType.EVERGREEN,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, newChargedThroughDate));
+        index = 0;
+        for (ExistingEvent e : expectedBP) {
+           validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));           
+        }
+        
+        SubscriptionData newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+        SubscriptionData newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 2);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+        
+        dryRun = false;
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 3);
+
+        bpRepair = getSubscriptionRepair(baseSubscription.getId(), realRunBundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 3);        
+        
+        index = 0;
+        for (ExistingEvent e : expectedAO) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        
+        index = 0;
+        for (ExistingEvent e : expectedBP) {
+           validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));           
+        }
+
+        newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 3);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+            
+        newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 3);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+        
+        // MOVE CLOCK AFTER CANCEL DATE
+        testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
+        testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
+        someTimeLater = getDurationDay(32);
+        clock.addDeltaFromReality(someTimeLater);
+        assertTrue(testListener.isApiCompleted(7000));
+
+        newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.CANCELLED);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 3);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+            
+        newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.CANCELLED);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 3);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+    }
+
+    
+    
+    @Test(groups={"slow"})
+    public void testRepairCancelAO() throws Exception {
+        String baseProduct = "Shotgun";
+        BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+        String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        // CREATE BP
+        SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+        // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+        Duration someTimeLater = getDurationDay(3);
+        clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
+
+        SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+        // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+        clock.addDeltaFromReality(someTimeLater);
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        // Quick check
+        SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 2);
+        
+        SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+        
+
+        List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+        des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));        
+        DateTime aoCancelDate = aoSubscription.getStartDate().plusDays(1);
+        
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, aoCancelDate, null);
+        
+        SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
+        
+        BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+        
+        bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 2);        
+        
+        List<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoCancelDate));
+        int index = 0;
+        for (ExistingEvent e : expected) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        SubscriptionData newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+                
+        SubscriptionData newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 2);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+        
+        dryRun = false;
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+        index = 0;
+        for (ExistingEvent e : expected) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        
+        newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.CANCELLED);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);        
+                
+        newBaseSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+        assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newBaseSubscription.getAllTransitions().size(), 2);
+        assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+    }
+    
+    
+    @Test(groups={"slow"})
+    public void testRepairRecreateAO() throws Exception {
+        String baseProduct = "Shotgun";
+        BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+        String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        // CREATE BP
+        SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+        // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+        Duration someTimeLater = getDurationDay(3);
+        clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
+
+        SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+        // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+        clock.addDeltaFromReality(someTimeLater);
+
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        // Quick check
+        SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 2);
+        
+        SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+        
+
+        List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+        des.add(createDeletedEvent(aoRepair.getExistingEvents().get(0).getEventId()));        
+        des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));
+
+        DateTime aoRecreateDate = aoSubscription.getStartDate().plusDays(1);
+        PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.DISCOUNT);
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, aoRecreateDate, spec);
+        
+        SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
+        
+        BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+        
+        
+        List<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoRecreateDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Telescopic-Scope", PhaseType.EVERGREEN,
+                    ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusMonths(1) /* Bundle align */));
+        int index = 0;
+        for (ExistingEvent e : expected) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        SubscriptionData newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription.getStartDate(), aoSubscription.getStartDate());
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);        
+        
+        // NOW COMMIT
+        dryRun = false;
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+        index = 0;
+        for (ExistingEvent e : expected) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        
+        newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        assertEquals(newAoSubscription.getStartDate(), aoRecreateDate);
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);        
+
+    }
+    
+    // Fasten your seatbelt here:
+    //
+    // We are doing repair for multi-phase tiered-addon with different alignment:
+    // Telescopic-Scope -> Laser-Scope
+    // Tiered ADON logic
+    // . Both multi phase
+    // . Telescopic-Scope (bundle align) and Laser-Scope is Subscription align
+    //
+    @Test(groups={"slow"})
+    public void testRepairChangeAOOK() throws Exception {
+        
+        String baseProduct = "Shotgun";
+        BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+        String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        // CREATE BP
+        SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+        // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+        Duration someTimeLater = getDurationDay(3);
+        clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
+
+        SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+        // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+        clock.addDeltaFromReality(someTimeLater);
+        
+        BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+        
+        // Quick check
+        SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+        assertEquals(bpRepair.getExistingEvents().size(), 2);
+        
+        SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+        List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+        des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));        
+        DateTime aoChangeDate = aoSubscription.getStartDate().plusDays(1);
+        PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, aoChangeDate, spec);
+        
+        SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
+        
+        BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
+        
+        boolean dryRun = true;
+        BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 3);
+        
+        
+        List<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CHANGE, "Laser-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoChangeDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Laser-Scope", PhaseType.EVERGREEN,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY,
+                aoSubscription.getStartDate().plusMonths(1) /* Subscription alignment */));
+                
+        int index = 0;
+        for (ExistingEvent e : expected) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        SubscriptionData newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+        
+        // AND NOW COMMIT
+        dryRun = false;
+        BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        
+        aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+        assertEquals(aoRepair.getExistingEvents().size(), 3);
+        index = 0;
+        for (ExistingEvent e : expected) {
+           validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
+        }
+        
+        newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+        assertEquals(newAoSubscription.getAllTransitions().size(), 3);
+        
+        
+        assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+        assertEquals(newAoSubscription.getBundleId(), bundle.getId());
+        assertEquals(newAoSubscription.getStartDate(), aoSubscription.getStartDate());
+
+        Plan currentPlan = newAoSubscription.getCurrentPlan();
+        assertNotNull(currentPlan);
+        assertEquals(currentPlan.getProduct().getName(), "Laser-Scope");
+        assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.ADD_ON);
+        assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        PlanPhase currentPhase = newAoSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
+        
+        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+        someTimeLater = getDurationDay(60);
+        clock.addDeltaFromReality(someTimeLater);
+        assertTrue(testListener.isApiCompleted(5000));
+        
+        newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+        currentPhase = newAoSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+    }
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java
new file mode 100644
index 0000000..285b17d
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java
@@ -0,0 +1,431 @@
+/* 
+ * 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.entitlement.api.timeline;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.entitlement.api.timeline.EntitlementRepairException;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
+
+public class TestRepairWithError extends TestApiBaseRepair {
+
+    private static final String baseProduct = "Shotgun";
+    private TestWithException test;
+    private Subscription baseSubscription;
+    private DateTime startDate;
+    @Override
+    public Injector getInjector() {
+        return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleMemory());
+    }
+
+
+    @BeforeMethod(groups={"fast"})
+    public void beforeMethod() throws Exception {
+        test = new TestWithException();
+        startDate = clock.getUTCNow();
+        baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+        testListener.reset();
+        clock.resetDeltaFromReality();
+    }
+  
+    @Test(groups={"fast"})
+    public void testENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING() throws Exception {
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException {
+
+                // MOVE AFTER TRIAL
+                testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+                Duration durationShift = getDurationDay(40);
+                clock.setDeltaFromReality(durationShift, 0);
+                assertTrue(testListener.isApiCompleted(5000));
+                
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+                
+                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.<DeletedEvent>emptyList(), Collections.singletonList(ne));
+                
+                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+                repairApi.repairBundle(bRepair, true, context);
+            }
+        }, ErrorCode.ENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING);
+    }
+    
+    @Test(groups={"fast"})
+    public void testENT_REPAIR_INVALID_DELETE_SET() throws Exception {
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+                Duration durationShift = getDurationDay(3);
+                clock.setDeltaFromReality(durationShift, 0);
+                
+                testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+                DateTime changeTime = clock.getUTCNow();
+                baseSubscription.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, changeTime, context);
+                assertTrue(testListener.isApiCompleted(5000));
+                
+                // MOVE AFTER TRIAL
+                testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+                durationShift = getDurationDay(40);
+                clock.addDeltaFromReality(durationShift);
+                assertTrue(testListener.isApiCompleted(5000));
+                
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+                DeletedEvent de = createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId());                
+
+                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.singletonList(de), Collections.singletonList(ne));
+                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+                repairApi.repairBundle(bRepair, true, context);
+            }
+        }, ErrorCode.ENT_REPAIR_INVALID_DELETE_SET);
+    }
+
+    @Test(groups={"fast"})
+    public void testENT_REPAIR_NON_EXISTENT_DELETE_EVENT() throws Exception {
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException {
+                
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+                DeletedEvent de = createDeletedEvent(UUID.randomUUID());
+                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.singletonList(de), Collections.singletonList(ne));
+                
+                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+                repairApi.repairBundle(bRepair, true, context);
+            }
+        }, ErrorCode.ENT_REPAIR_NON_EXISTENT_DELETE_EVENT);
+    }
+    
+    @Test(groups={"fast"})
+    public void testENT_REPAIR_SUB_RECREATE_NOT_EMPTY() throws Exception {
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException {
+                
+                // MOVE AFTER TRIAL
+                   testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+                   Duration durationShift = getDurationDay(40);
+                   clock.setDeltaFromReality(durationShift, 0);
+                   assertTrue(testListener.isApiCompleted(5000));
+                   
+                   BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                   sortEventsOnBundle(bundleRepair);
+                   PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+                   NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, baseSubscription.getStartDate().plusDays(10), spec);
+                   List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+                   des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));                
+                   SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+                   
+                   BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+                   repairApi.repairBundle(bRepair, true, context);
+                
+            }
+        }, ErrorCode.ENT_REPAIR_SUB_RECREATE_NOT_EMPTY);
+    }
+
+    @Test(groups={"fast"})
+    public void testENT_REPAIR_SUB_EMPTY() throws Exception {
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException {
+                
+             // MOVE AFTER TRIAL
+                testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+                Duration durationShift = getDurationDay(40);
+                clock.setDeltaFromReality(durationShift, 0);
+                assertTrue(testListener.isApiCompleted(5000));
+                
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+                List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));                
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));                                
+                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+                
+                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+                repairApi.repairBundle(bRepair, true, context);
+            }
+        }, ErrorCode.ENT_REPAIR_SUB_EMPTY);
+    }
+    
+    @Test(groups={"fast"})
+    public void testENT_REPAIR_AO_CREATE_BEFORE_BP_START() throws Exception {
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+               
+
+                // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+                Duration someTimeLater = getDurationDay(3);
+                clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
+
+                SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+                // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+                clock.addDeltaFromReality(someTimeLater);
+
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                
+                // Quick check
+                SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+                assertEquals(bpRepair.getExistingEvents().size(), 2);
+                
+                SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+                assertEquals(aoRepair.getExistingEvents().size(), 2);
+                
+
+                List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+                des.add(createDeletedEvent(aoRepair.getExistingEvents().get(0).getEventId()));        
+                des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));
+
+                DateTime aoRecreateDate = aoSubscription.getStartDate().minusDays(5);
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.DISCOUNT);
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, aoRecreateDate, spec);
+                
+                SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
+                
+                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
+                
+                boolean dryRun = true;
+                repairApi.repairBundle(bRepair, dryRun, context);
+            }
+        }, ErrorCode.ENT_REPAIR_AO_CREATE_BEFORE_BP_START);
+    }
+    
+    @Test(groups={"fast"})
+    public void testENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING() throws Exception {
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+                
+
+                // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+                Duration someTimeLater = getDurationDay(3);
+                clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
+
+                SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+                // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+                clock.addDeltaFromReality(someTimeLater);
+
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                
+                // Quick check
+                SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+                assertEquals(bpRepair.getExistingEvents().size(), 2);
+                
+                SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+                assertEquals(aoRepair.getExistingEvents().size(), 2);
+                
+
+                List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+                //des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));        
+                DateTime aoCancelDate = aoSubscription.getStartDate().plusDays(10);
+                
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, aoCancelDate, null);
+                
+                SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
+                
+                bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
+                
+                boolean dryRun = true;
+                repairApi.repairBundle(bundleRepair, dryRun, context);
+            }
+        }, ErrorCode.ENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING);
+    }
+
+
+    @Test(groups={"fast"})
+    public void testENT_REPAIR_BP_RECREATE_MISSING_AO() throws Exception {
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+              //testListener.pushExpectedEvent(NextEvent.PHASE);
+
+                clock.setDeltaFromReality(getDurationDay(5), 0);
+                //assertTrue(testListener.isCompleted(5000));
+
+                SubscriptionData aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+                
+                BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                
+                DateTime newCreateTime = baseSubscription.getStartDate().plusDays(3);
+
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, newCreateTime, spec);
+                List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+
+                SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+                
+                // FIRST ISSUE DRY RUN
+                BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+                
+                boolean dryRun = true;
+                repairApi.repairBundle(bRepair, dryRun, context);
+            }
+        }, ErrorCode.ENT_REPAIR_BP_RECREATE_MISSING_AO);
+    }
+    
+    //
+    // CAN'T seem to trigger such case easily, other errors trigger before...
+    //
+    @Test(groups={"fast"}, enabled=false)
+    public void testENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE() throws Exception {
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+                /*
+              //testListener.pushExpectedEvent(NextEvent.PHASE);
+
+                clock.setDeltaFromReality(getDurationDay(5), 0);
+                //assertTrue(testListener.isCompleted(5000));
+
+                SubscriptionData aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+                
+                BundleRepair bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                
+                DateTime newCreateTime = baseSubscription.getStartDate().plusDays(3);
+
+                PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, newCreateTime, spec);
+                List<DeletedEvent> des = new LinkedList<SubscriptionRepair.DeletedEvent>();
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));
+                des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+
+                SubscriptionRepair bpRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+                
+                ne = createNewEvent(SubscriptionTransitionType.CANCEL, clock.getUTCNow().minusDays(1),  null);
+                SubscriptionRepair aoRepair = createSubscriptionReapir(aoSubscription.getId(), Collections.<SubscriptionRepair.DeletedEvent>emptyList(), Collections.singletonList(ne));
+                
+                
+                List<SubscriptionRepair> allRepairs = new LinkedList<SubscriptionRepair>();
+                allRepairs.add(bpRepair);
+                allRepairs.add(aoRepair);
+                bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), allRepairs);
+                // FIRST ISSUE DRY RUN
+                BundleRepair bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), allRepairs);
+                
+                boolean dryRun = true;
+                repairApi.repairBundle(bRepair, dryRun, context);
+                */
+            }
+        }, ErrorCode.ENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE);
+    }
+    
+    @Test(groups={"fast"}, enabled=false)
+    public void testENT_REPAIR_MISSING_AO_DELETE_EVENT() throws Exception {
+        test.withException(new TestWithExceptionCallback() {
+            @Override
+            public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+                
+                /*
+                // MOVE CLOCK -- JUST BEFORE END OF TRIAL
+                clock.setDeltaFromReality(getDurationDay(29), 0);
+                
+                SubscriptionData aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+                
+                // MOVE CLOCK -- RIGHT OUT OF TRIAL
+                testListener.pushExpectedEvent(NextEvent.PHASE);                
+                clock.addDeltaFromReality(getDurationDay(5));
+                assertTrue(testListener.isCompleted(5000));
+
+                DateTime requestedChange = clock.getUTCNow();
+                baseSubscription.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, requestedChange, context);
+
+                DateTime reapairTime = clock.getUTCNow().minusDays(1);
+
+                BundleRepair bundleRepair = repairApi.getBundleRepair(bundle.getId());
+                sortEventsOnBundle(bundleRepair);
+                
+                SubscriptionRepair bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+                SubscriptionRepair aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+
+                List<DeletedEvent> bpdes = new LinkedList<SubscriptionRepair.DeletedEvent>();
+                bpdes.add(createDeletedEvent(bpRepair.getExistingEvents().get(2).getEventId()));    
+                bpRepair = createSubscriptionReapir(baseSubscription.getId(), bpdes, Collections.<NewEvent>emptyList());
+                
+                NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, reapairTime, null);
+                aoRepair = createSubscriptionReapir(aoSubscription.getId(), Collections.<SubscriptionRepair.DeletedEvent>emptyList(), Collections.singletonList(ne));
+                
+                List<SubscriptionRepair> allRepairs = new LinkedList<SubscriptionRepair>();
+                allRepairs.add(bpRepair);
+                allRepairs.add(aoRepair);
+                bundleRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), allRepairs);
+                
+                boolean dryRun = false;
+                repairApi.repairBundle(bundleRepair, dryRun, context);
+                */
+                }
+        }, ErrorCode.ENT_REPAIR_MISSING_AO_DELETE_EVENT);
+    }
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
index 1f8979e..a3afc31 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
@@ -74,8 +74,8 @@ public class TestUserApiAddOn extends TestApiBase {
             aoSubscription.cancel(now, false, context);
 
             testListener.reset();
-            testListener.pushExpectedEvent(NextEvent.CANCEL);
-            assertTrue(testListener.isCompleted(5000));
+            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
+            assertTrue(testListener.isApiCompleted(5000));
 
             assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
 
@@ -103,13 +103,13 @@ public class TestUserApiAddOn extends TestApiBase {
             SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
 
             testListener.reset();
-            testListener.pushExpectedEvent(NextEvent.PHASE);
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
 
             // MOVE CLOCK AFTER TRIAL + AO DISCOUNT
             Duration twoMonths = getDurationMonth(2);
             clock.setDeltaFromReality(twoMonths, DAY_IN_MS);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
             // SET CTD TO CANCEL IN FUTURE
             DateTime now = clock.getUTCNow();
@@ -129,10 +129,10 @@ public class TestUserApiAddOn extends TestApiBase {
 
             // MOVE AFTER CANCELLATION
             testListener.reset();
-            testListener.pushExpectedEvent(NextEvent.CANCEL);
-            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
+            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
             clock.addDeltaFromReality(ctd);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
             // REFETCH AO SUBSCRIPTION AND CHECK THIS IS CANCELLED
             aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
@@ -162,13 +162,13 @@ public class TestUserApiAddOn extends TestApiBase {
             SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
 
             testListener.reset();
-            testListener.pushExpectedEvent(NextEvent.PHASE);
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
 
             // MOVE CLOCK AFTER TRIAL + AO DISCOUNT
             Duration twoMonths = getDurationMonth(2);
             clock.setDeltaFromReality(twoMonths, DAY_IN_MS);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
             // SET CTD TO CHANGE IN FUTURE
             DateTime now = clock.getUTCNow();
@@ -183,10 +183,10 @@ public class TestUserApiAddOn extends TestApiBase {
             String newBasePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
 
             testListener.reset();
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
-            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
             baseSubscription.changePlan(newBaseProduct, newBaseTerm, newBasePriceList, now, context);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
             // REFETCH AO SUBSCRIPTION AND CHECK THIS CANCELLED
             aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
@@ -216,13 +216,13 @@ public class TestUserApiAddOn extends TestApiBase {
             SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
 
             testListener.reset();
-            testListener.pushExpectedEvent(NextEvent.PHASE);
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
 
             // MOVE CLOCK AFTER TRIAL + AO DISCOUNT
             Duration twoMonths = getDurationMonth(2);
             clock.setDeltaFromReality(twoMonths, DAY_IN_MS);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
             // SET CTD TO CANCEL IN FUTURE
             DateTime now = clock.getUTCNow();
@@ -246,10 +246,10 @@ public class TestUserApiAddOn extends TestApiBase {
 
             // MOVE AFTER CHANGE
             testListener.reset();
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
-            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
             clock.addDeltaFromReality(ctd);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
 
             // REFETCH AO SUBSCRIPTION AND CHECK THIS CANCELLED
@@ -353,8 +353,8 @@ public class TestUserApiAddOn extends TestApiBase {
 
            // ADD TWO PHASE EVENTS (BP + AO)
            testListener.reset();
-           testListener.pushExpectedEvent(NextEvent.PHASE);
-           testListener.pushExpectedEvent(NextEvent.PHASE);
+           testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+           testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
 
            // MOVE THROUGH TIME TO GO INTO EVERGREEN
            
@@ -381,7 +381,8 @@ public class TestUserApiAddOn extends TestApiBase {
            };
            
            clock.addDeltaFromReality(someTimeLater);
-           assertTrue(testListener.isCompleted(5000));
+           clock.addDeltaFromReality(getDurationDay(1));
+           assertTrue(testListener.isApiCompleted(5000));
 
 
            // CHECK EVERYTHING AGAIN
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
index 5a7d33e..09046f1 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
@@ -59,12 +59,12 @@ public abstract class TestUserApiCancel extends TestApiBase {
             clock.setDeltaFromReality(moveALittleInTime, 0);
 
             DateTime future = clock.getUTCNow();
-            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
 
             // CANCEL in trial period to get IMM policy
             subscription.cancel(clock.getUTCNow(), false, context);
             currentPhase = subscription.getCurrentPhase();
-            testListener.isCompleted(1000);
+            testListener.isApiCompleted(1000);
 
             assertNull(currentPhase);
             checkNextPhaseChange(subscription, 0, null);
@@ -94,9 +94,9 @@ public abstract class TestUserApiCancel extends TestApiBase {
             checkNextPhaseChange(subscription, 1, expectedPhaseTrialChange);
 
             // MOVE TO NEXT PHASE
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
             trialPhase = subscription.getCurrentPhase();
             assertEquals(trialPhase.getPhaseType(), PhaseType.EVERGREEN);
 
@@ -106,16 +106,16 @@ public abstract class TestUserApiCancel extends TestApiBase {
             billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, context);
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
-            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
 
             // CANCEL
             subscription.cancel(clock.getUTCNow(), false, context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isApiCompleted(2000));
 
             // MOVE TO EOT + RECHECK
             clock.addDeltaFromReality(ctd);
             DateTime future = clock.getUTCNow();
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
 
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertNull(currentPhase);
@@ -147,17 +147,17 @@ public abstract class TestUserApiCancel extends TestApiBase {
             checkNextPhaseChange(subscription, 1, expectedPhaseTrialChange);
 
             // MOVE TO NEXT PHASE
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
             trialPhase = subscription.getCurrentPhase();
             assertEquals(trialPhase.getPhaseType(), PhaseType.EVERGREEN);
 
-            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
 
             // CANCEL
             subscription.cancel(clock.getUTCNow(), false, context);
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
 
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertNull(currentPhase);
@@ -191,9 +191,9 @@ public abstract class TestUserApiCancel extends TestApiBase {
             checkNextPhaseChange(subscription, 1, expectedPhaseTrialChange);
 
             // MOVE TO NEXT PHASE
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
 
@@ -203,18 +203,18 @@ public abstract class TestUserApiCancel extends TestApiBase {
             billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, context);
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
-            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
 
             // CANCEL
             subscription.cancel(clock.getUTCNow(), false, context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isApiCompleted(2000));
 
             subscription.uncancel(context);
 
             // MOVE TO EOT + RECHECK
             clock.addDeltaFromReality(ctd);
             DateTime future = clock.getUTCNow();
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isApiCompleted(2000));
 
             Plan currentPlan = subscription.getCurrentPlan();
             assertEquals(currentPlan.getProduct().getName(), prod);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
index 98e495d..f43e843 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
@@ -78,17 +78,17 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
             // MOVE TO NEXT PHASE
             PlanPhase currentPhase = subscription.getCurrentPhase();
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
             DateTime futureNow = clock.getUTCNow();
             DateTime nextExpectedPhaseChange = DefaultClock.addDuration(subscription.getStartDate(), currentPhase.getDuration());
             assertTrue(futureNow.isAfter(nextExpectedPhaseChange));
-            assertTrue(testListener.isCompleted(3000));
+            assertTrue(testListener.isApiCompleted(3000));
 
             // CHANGE PLAN
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan(toProd, toTerm, toPlanSet, clock.getUTCNow(), context);
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
 
             // CHECK CHANGE PLAN
             currentPhase = subscription.getCurrentPhase();
@@ -118,9 +118,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
 
             // MOVE TO NEXT PHASE
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
 
@@ -131,10 +131,10 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, context);
 
             // RE READ SUBSCRIPTION + CHANGE PLAN
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
             subscription.changePlan(toProd, toTerm, toPlanSet, clock.getUTCNow(), context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isApiCompleted(2000));
             testListener.reset();
 
             // CHECK CHANGE PLAN
@@ -151,9 +151,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
 
             // MOVE TO EOT
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             clock.addDeltaFromReality(ctd);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
             currentPhase = subscription.getCurrentPhase();
@@ -179,7 +179,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
             SubscriptionData subscription = createSubscription(fromProd, fromTerm, fromPlanSet);
 
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
 
             Duration moveALittleInTime = getDurationDay(3);
             clock.setDeltaFromReality(moveALittleInTime, 0);
@@ -188,14 +188,14 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             subscription.changePlan(toProd, toTerm, toPlanSet, clock.getUTCNow(), context);
             checkChangePlan(subscription, toProd, ProductCategory.BASE, toTerm, PhaseType.TRIAL);
 
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
 
             PlanPhase currentPhase = subscription.getCurrentPhase();
             DateTime nextExpectedPhaseChange = DefaultClock.addDuration(subscription.getStartDate(), currentPhase.getDuration());
             checkNextPhaseChange(subscription, 1, nextExpectedPhaseChange);
 
             // NEXT PHASE
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             clock.addDeltaFromReality(currentPhase.getDuration());
             DateTime futureNow = clock.getUTCNow();
 
@@ -208,7 +208,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             */
 
             assertTrue(futureNow.isAfter(nextExpectedPhaseChange));
-            assertTrue(testListener.isCompleted(3000));
+            assertTrue(testListener.isApiCompleted(3000));
 
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
@@ -235,11 +235,11 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
 
             // MOVE TO NEXT PHASE
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             currentTime = clock.getUTCNow();
             clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
             currentTime = clock.getUTCNow();
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
 
             // SET CTD
             Duration ctd = getDurationMonth(1);
@@ -255,18 +255,18 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             // CHANGE PLAN
             currentTime = clock.getUTCNow();
 
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan(toProd, toTerm, toPlanSet, clock.getUTCNow(), context);
 
             checkChangePlan(subscription, fromProd, ProductCategory.BASE, fromTerm, PhaseType.EVERGREEN);
 
             // CHECK CHANGE DID NOT KICK IN YET
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isApiCompleted(2000));
 
             // MOVE TO AFTER CTD
             clock.addDeltaFromReality(ctd);
             currentTime = clock.getUTCNow();
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
 
             // CHECK CORRECT PRODUCT, PHASE, PLAN SET
             String currentProduct =  subscription.getCurrentPlan().getProduct().getName();
@@ -276,13 +276,13 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
 
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
 
             // MOVE TIME ABOUT ONE MONTH BEFORE NEXT EXPECTED PHASE CHANGE
             clock.addDeltaFromReality(getDurationMonth(11));
 
             currentTime = clock.getUTCNow();
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isApiCompleted(2000));
 
             DateTime nextExpectedPhaseChange = DefaultClock.addDuration(newChargedThroughDate, currentPhase.getDuration());
             checkNextPhaseChange(subscription, 1, nextExpectedPhaseChange);
@@ -290,7 +290,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             // MOVE TIME RIGHT AFTER NEXT EXPECTED PHASE CHANGE
             clock.addDeltaFromReality(getDurationMonth(1));
             currentTime = clock.getUTCNow();
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
 
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
@@ -305,9 +305,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
 
             // MOVE TO NEXT PHASE
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
 
             // SET CTD
             List<Duration> durationList = new ArrayList<Duration>();
@@ -320,14 +320,14 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
             // CHANGE EOT
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Pistol", BillingPeriod.MONTHLY, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isApiCompleted(2000));
 
             // CHANGE
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isApiCompleted(2000));
 
             Plan currentPlan = subscription.getCurrentPlan();
             assertNotNull(currentPlan);
@@ -352,9 +352,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             PlanPhase trialPhase = subscription.getCurrentPhase();
             assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
 
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
 
             // SET CTD
             List<Duration> durationList = new ArrayList<Duration>();
@@ -366,15 +366,15 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
             // CHANGE EOT
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Shotgun", BillingPeriod.MONTHLY, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isApiCompleted(2000));
             testListener.reset();
 
             // CHANGE EOT
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isApiCompleted(2000));
             testListener.reset();
 
             // CHECK NO CHANGE OCCURED YET
@@ -389,9 +389,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
 
             // ACTIVATE CHNAGE BY MOVING AFTER CTD
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             clock.addDeltaFromReality(ctd);
-            assertTrue(testListener.isCompleted(3000));
+            assertTrue(testListener.isApiCompleted(3000));
 
             currentPlan = subscription.getCurrentPlan();
             assertNotNull(currentPlan);
@@ -406,9 +406,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
 
             // MOVE TO NEXT PHASE
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             clock.addDeltaFromReality(currentPhase.getDuration());
-            assertTrue(testListener.isCompleted(3000));
+            assertTrue(testListener.isApiCompleted(3000));
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
             currentPlan = subscription.getCurrentPlan();
@@ -440,9 +440,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
             // CHANGE IMMEDIATE TO A 3 PHASES PLAN
             testListener.reset();
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertTrue(testListener.isCompleted(3000));
+            assertTrue(testListener.isApiCompleted(3000));
             testListener.reset();
 
             // CHECK EVERYTHING LOOKS CORRECT
@@ -456,9 +456,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
 
             // MOVE AFTER TRIAL PERIOD -> DISCOUNT
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             clock.addDeltaFromReality(trialPhase.getDuration());
-            assertTrue(testListener.isCompleted(3000));
+            assertTrue(testListener.isApiCompleted(3000));
 
             trialPhase = subscription.getCurrentPhase();
             assertEquals(trialPhase.getPhaseType(), PhaseType.DISCOUNT);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
index 01ced07..ef4533d 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
@@ -56,8 +56,8 @@ public abstract class TestUserApiCreate extends TestApiBase {
             String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
 
 
-            testListener.pushExpectedEvent(NextEvent.PHASE);
-            testListener.pushExpectedEvent(NextEvent.CREATE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
 
 
             SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
@@ -69,7 +69,7 @@ public abstract class TestUserApiCreate extends TestApiBase {
             assertEquals(subscription.getBundleId(), bundle.getId());
             assertEquals(subscription.getStartDate(), requestedDate);
 
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
         } catch (EntitlementUserApiException e) {
         	log.error("Unexpected exception",e);
@@ -89,7 +89,7 @@ public abstract class TestUserApiCreate extends TestApiBase {
             BillingPeriod term = BillingPeriod.MONTHLY;
             String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
 
-            testListener.pushExpectedEvent(NextEvent.CREATE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
 
             SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
                     getProductSpecifier(productName, planSetName, term, PhaseType.EVERGREEN), clock.getUTCNow(), context);
@@ -127,7 +127,7 @@ public abstract class TestUserApiCreate extends TestApiBase {
             BillingPeriod term = BillingPeriod.MONTHLY;
             String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
 
-            testListener.pushExpectedEvent(NextEvent.CREATE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
 
             SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
                     getProductSpecifier(productName, planSetName, term, null),
@@ -149,7 +149,7 @@ public abstract class TestUserApiCreate extends TestApiBase {
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
             List<EntitlementEvent> events = dao.getPendingEventsForSubscription(subscription.getId());
             assertNotNull(events);
@@ -160,14 +160,14 @@ public abstract class TestUserApiCreate extends TestApiBase {
             DateTime nextExpectedPhaseChange = DefaultClock.addDuration(subscription.getStartDate(), currentPhase.getDuration());
             assertEquals(nextPhaseChange, nextExpectedPhaseChange);
 
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
 
             clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
 
             DateTime futureNow = clock.getUTCNow();
             assertTrue(futureNow.isAfter(nextPhaseChange));
 
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
@@ -184,7 +184,7 @@ public abstract class TestUserApiCreate extends TestApiBase {
             BillingPeriod term = BillingPeriod.ANNUAL;
             String planSetName = "gunclubDiscount";
 
-            testListener.pushExpectedEvent(NextEvent.CREATE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
 
             // CREATE SUBSCRIPTION
             SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
@@ -194,20 +194,20 @@ public abstract class TestUserApiCreate extends TestApiBase {
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
 
             // MOVE TO DISCOUNT PHASE
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
             currentPhase = subscription.getCurrentPhase();
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
 
             // MOVE TO EVERGREEN PHASE + RE-READ SUBSCRIPTION
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             clock.addDeltaFromReality(currentPhase.getDuration());
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
 
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
             currentPhase = subscription.getCurrentPhase();
@@ -229,7 +229,7 @@ public abstract class TestUserApiCreate extends TestApiBase {
             BillingPeriod term = BillingPeriod.ANNUAL;
             String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
 
-            testListener.pushExpectedEvent(NextEvent.CREATE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
 
             SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
                     getProductSpecifier(productName, planSetName, term, null), clock.getUTCNow(), context);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
index a206aaa..fdd6e66 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
@@ -80,16 +80,16 @@ public class TestUserApiDemos extends TestApiBase {
             displayState(subscription.getId(), "STEP 1. CREATED SUBSCRIPTION");
 
             /* STEP 2. CHANGE PLAN WHILE IN TRIAL */
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertTrue(testListener.isCompleted(3000));
+            assertTrue(testListener.isApiCompleted(3000));
 
             displayState(subscription.getId(), "STEP 2. CHANGED PLAN WHILE IN TRIAL");
 
             /* STEP 3. MOVE TO DISCOUNT PHASE */
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(3000));
+            assertTrue(testListener.isApiCompleted(3000));
 
             displayState(subscription.getId(), "STEP 3. MOVE TO DISCOUNT PHASE");
 
@@ -103,25 +103,25 @@ public class TestUserApiDemos extends TestApiBase {
             billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, context);
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Shotgun", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isApiCompleted(2000));
             testListener.reset();
 
             displayState(subscription.getId(), "STEP 4. SET CTD AND CHANGE PLAN EOT (Shotgun)");
 
             /* STEP 5. CHANGE AGAIN */
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isApiCompleted(2000));
             testListener.reset();
 
             displayState(subscription.getId(), "STEP 5. CHANGE AGAIN EOT (Pistol)");
 
             /* STEP 6. MOVE TO EOT AND CHECK CHANGE OCCURED */
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             clock.addDeltaFromReality(ctd);
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
 
             Plan currentPlan = subscription.getCurrentPlan();
             assertNotNull(currentPlan);
@@ -136,9 +136,9 @@ public class TestUserApiDemos extends TestApiBase {
             displayState(subscription.getId(), "STEP 6. MOVE TO EOT");
 
             /* STEP 7.  MOVE TO NEXT PHASE */
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             clock.addDeltaFromReality(currentPhase.getDuration());
-            assertTrue(testListener.isCompleted(5000));
+            assertTrue(testListener.isApiCompleted(5000));
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
             currentPlan = subscription.getCurrentPlan();
@@ -154,7 +154,7 @@ public class TestUserApiDemos extends TestApiBase {
             displayState(subscription.getId(), "STEP 7.  MOVE TO NEXT PHASE");
 
             /* STEP 8. CANCEL IMM (NO CTD) */
-            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
             subscription.cancel(clock.getUTCNow(), false, context);
 
             displayState(subscription.getId(), "STEP 8.  CANCELLATION");
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
index 440b780..c3f6061 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
@@ -108,7 +108,7 @@ public class TestUserApiError extends TestApiBase {
         try {
             UUID accountId = UUID.randomUUID();
             SubscriptionBundle aoBundle = entitlementApi.createBundleForAccount(accountId, "myAOBundle", context);
-            createSubscriptionWithBundle(aoBundle.getId(), "Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+            createSubscriptionWithBundle(aoBundle.getId(), "Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
             tCreateSubscriptionInternal(aoBundle.getId(), "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.ENT_CREATE_AO_NOT_AVAILABLE);
         } catch (Exception e) {
             e.printStackTrace();
@@ -121,7 +121,7 @@ public class TestUserApiError extends TestApiBase {
         try {
             UUID accountId = UUID.randomUUID();
             SubscriptionBundle aoBundle = entitlementApi.createBundleForAccount(accountId, "myAOBundle", context);
-            createSubscriptionWithBundle(aoBundle.getId(), "Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+            createSubscriptionWithBundle(aoBundle.getId(), "Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
             tCreateSubscriptionInternal(aoBundle.getId(), "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.ENT_CREATE_AO_ALREADY_INCLUDED);
         } catch (Exception e) {
             e.printStackTrace();
@@ -153,7 +153,7 @@ public class TestUserApiError extends TestApiBase {
         try {
             Subscription subscription = createSubscription("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME);
 
-            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
             subscription.cancel(clock.getUTCNow(), false, context);
             try {
                 subscription.changePlan("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, clock.getUTCNow(), context);
@@ -180,9 +180,9 @@ public class TestUserApiError extends TestApiBase {
 
             // MOVE TO NEXT PHASE
             PlanPhase currentPhase = subscription.getCurrentPhase();
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(3000));
+            assertTrue(testListener.isApiCompleted(3000));
 
 
             // SET CTD TO CANCEL IN FUTURE
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiRecreate.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiRecreate.java
index 91aaf02..b7506dd 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiRecreate.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiRecreate.java
@@ -65,8 +65,8 @@ public abstract class TestUserApiRecreate extends TestApiBase {
         BillingPeriod term = BillingPeriod.MONTHLY;
         String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
 
-        testListener.pushExpectedEvent(NextEvent.PHASE);
-        testListener.pushExpectedEvent(NextEvent.CREATE);
+        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+        testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
         SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
                 getProductSpecifier(productName, planSetName, term, null), requestedDate, context);
         assertNotNull(subscription);
@@ -75,7 +75,7 @@ public abstract class TestUserApiRecreate extends TestApiBase {
         assertEquals(subscription.getStartDate(), requestedDate);
         assertEquals(productName, subscription.getCurrentPlan().getProduct().getName());
 
-        assertTrue(testListener.isCompleted(5000));
+        assertTrue(testListener.isApiCompleted(5000));
 
         // CREATE (AGAIN) WITH NEW PRODUCT
         productName = "Pistol";
@@ -95,11 +95,11 @@ public abstract class TestUserApiRecreate extends TestApiBase {
         }
 
         // NOW CANCEL ADN THIS SHOULD WORK
-        testListener.pushExpectedEvent(NextEvent.CANCEL);
+        testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
         subscription.cancel(null, false, context);
 
-        testListener.pushExpectedEvent(NextEvent.PHASE);
-        testListener.pushExpectedEvent(NextEvent.RE_CREATE);
+        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+        testListener.pushNextApiExpectedEvent(NextEvent.RE_CREATE);
 
         // Avoid ordering issue for events at exact same date; this is actually a real good test, we
         // we test it at Beatrix level. At this level that would work for sql tests but not for in memory.
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
index 08f1ac1..92ac1b0 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
@@ -54,14 +54,14 @@ public class TestUserApiScenarios extends TestApiBase {
             PlanPhase trialPhase = subscription.getCurrentPhase();
             assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
 
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            testListener.isCompleted(3000);
+            testListener.isApiCompleted(3000);
 
             // MOVE TO NEXT PHASE
-            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
             clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isCompleted(2000));
+            assertTrue(testListener.isApiCompleted(2000));
 
             // SET CTD
             Duration ctd = getDurationMonth(1);
@@ -71,21 +71,21 @@ public class TestUserApiScenarios extends TestApiBase {
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
             // CANCEL EOT
-            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
             subscription.cancel(clock.getUTCNow(), false, context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isApiCompleted(2000));
             testListener.reset();
 
             // UNCANCEL
             subscription.uncancel(context);
 
             // CHANGE EOT
-            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Pistol", BillingPeriod.MONTHLY, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isCompleted(2000));
+            assertFalse(testListener.isApiCompleted(2000));
 
             clock.addDeltaFromReality(ctd);
-            assertTrue(testListener.isCompleted(3000));
+            assertTrue(testListener.isApiCompleted(3000));
 
 
         } catch (EntitlementUserApiException e) {
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java
index afc5591..90966d8 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java
@@ -76,8 +76,8 @@ public class TestUserCustomFieldsSql extends TestApiBase {
             BillingPeriod term = BillingPeriod.MONTHLY;
             String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
 
-            testListener.pushExpectedEvent(NextEvent.PHASE);
-            testListener.pushExpectedEvent(NextEvent.CREATE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
             SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
                     getProductSpecifier(productName, planSetName, term, null), requestedDate, context);
             assertNotNull(subscription);
@@ -132,8 +132,8 @@ public class TestUserCustomFieldsSql extends TestApiBase {
             BillingPeriod term = BillingPeriod.MONTHLY;
             String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
 
-            testListener.pushExpectedEvent(NextEvent.PHASE);
-            testListener.pushExpectedEvent(NextEvent.CREATE);
+            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+            testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
             SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
                     getProductSpecifier(productName, planSetName, term, null), requestedDate, context);
             assertNotNull(subscription);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
index 5bb0fb5..9b75099 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
@@ -21,6 +21,7 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.TreeSet;
 import java.util.UUID;
 
@@ -35,15 +36,16 @@ import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.catalog.api.TimeUnit;
 import com.ning.billing.config.EntitlementConfig;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData.SubscriptionMigrationData;
+import com.ning.billing.entitlement.api.timeline.SubscriptionDataRepair;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.engine.core.Engine;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
@@ -263,7 +265,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     }
 
     @Override
-    public void updateSubscription(final SubscriptionData subscription, final CallContext context) {
+    public void updateChargedThroughDate(final SubscriptionData subscription, final CallContext context) {
 
         boolean found = false;
         Iterator<Subscription> it = subscriptions.iterator();
@@ -446,4 +448,15 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     public void saveCustomFields(SubscriptionData subscription, CallContext context) {
         throw new NotImplementedException();
     }
+
+    @Override
+    public Map<UUID, List<EntitlementEvent>> getEventsForBundle(UUID bundleId) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public void repair(UUID accountId, UUID bundleId, List<SubscriptionDataRepair> inRepair,
+            CallContext context) {
+    }
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
index 8deaef6..3851eb2 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
@@ -16,6 +16,9 @@
 
 package com.ning.billing.entitlement.engine.dao;
 
+
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.customfield.dao.CustomFieldDao;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
@@ -24,10 +27,9 @@ import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 
 import com.google.inject.Inject;
-import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.entitlement.engine.addon.AddonUtils;
 import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.customfield.dao.CustomFieldDao;
+
 import com.ning.billing.util.notificationq.NotificationQueueService;
 
 public class MockEntitlementDaoSql extends EntitlementSqlDao implements MockEntitlementDao {
@@ -36,9 +38,8 @@ public class MockEntitlementDaoSql extends EntitlementSqlDao implements MockEnti
 
     @Inject
     public MockEntitlementDaoSql(IDBI dbi, Clock clock, AddonUtils addonUtils, NotificationQueueService notificationQueueService,
-                                 CustomFieldDao customFieldDao, 
-                                 final CatalogService catalogService) {
-        super(dbi, clock, addonUtils, notificationQueueService, customFieldDao, catalogService);
+                                 CustomFieldDao customFieldDao, final Bus eventBus) {
+        super(dbi, clock, addonUtils, notificationQueueService, customFieldDao, eventBus);
         this.resetDao = dbi.onDemand(ResetSqlDao.class);
     }
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleMemory.java
index 8b78c2d..e46e4bc 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleMemory.java
@@ -17,8 +17,11 @@
 package com.ning.billing.entitlement.glue;
 
 
+import com.google.inject.name.Names;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementLifecycleDao;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.engine.dao.MockEntitlementDaoMemory;
+import com.ning.billing.entitlement.engine.dao.RepairEntitlementDao;
 import com.ning.billing.util.notificationq.MockNotificationQueueService;
 import com.ning.billing.util.notificationq.NotificationQueueService;
 
@@ -27,6 +30,9 @@ public class MockEngineModuleMemory extends MockEngineModule {
     @Override
     protected void installEntitlementDao() {
         bind(EntitlementDao.class).to(MockEntitlementDaoMemory.class).asEagerSingleton();
+        bind(EntitlementDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairEntitlementDao.class);
+        bind(RepairEntitlementLifecycleDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairEntitlementDao.class);
+        bind(RepairEntitlementDao.class).asEagerSingleton();
     }
 
     private void installNotificationQueue() {
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java
index a1744ca..9195c91 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java
@@ -16,14 +16,22 @@
 
 package com.ning.billing.entitlement.glue;
 
+
+import com.google.inject.name.Names;
+
 import org.skife.config.ConfigurationObjectFactory;
 import org.skife.jdbi.v2.IDBI;
 
+
 import com.ning.billing.dbi.DBIProvider;
 import com.ning.billing.dbi.DbiConfig;
 import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementLifecycleDao;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.engine.dao.MockEntitlementDaoSql;
+
+import com.ning.billing.entitlement.engine.dao.RepairEntitlementDao;
+
 import com.ning.billing.util.glue.FieldStoreModule;
 import com.ning.billing.util.glue.NotificationQueueModule;
 
@@ -33,7 +41,11 @@ public class MockEngineModuleSql extends MockEngineModule {
     @Override
     protected void installEntitlementDao() {
         bind(EntitlementDao.class).to(MockEntitlementDaoSql.class).asEagerSingleton();
+        bind(EntitlementDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairEntitlementDao.class);
+        bind(RepairEntitlementLifecycleDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairEntitlementDao.class);
+        bind(RepairEntitlementDao.class).asEagerSingleton();
     }
+    
 
     protected void installDBI() {
         final MysqlTestingHelper helper = new MysqlTestingHelper();
diff --git a/entitlement/src/test/resources/testInput.xml b/entitlement/src/test/resources/testInput.xml
index 8a97d57..bdc73db 100644
--- a/entitlement/src/test/resources/testInput.xml
+++ b/entitlement/src/test/resources/testInput.xml
@@ -1,41 +1,21 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!--
-  ~ 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.
-  -->
+<!-- ~ 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. -->
 
-<!-- 
-Use cases covered so far:
-	Tiered Product (Pistol/Shotgun/Assault-Rifle)
-	Multiple changeEvent plan policies
-	Multiple PlanAlignment (see below, trial add-on alignments and rescue discount package)
-	Product transition rules
-	Add on (Scopes, Hoster)
-	Multi-pack addon (Extra-Ammo)
-	Addon Trial aligned to base plan (holster-monthly-regular)
-	Addon Trial aligned to creation (holster-monthly-special)
-	Rescue discount package (assault-rifle-annual-rescue)
-	Plan phase with a reccurring and a one off (refurbish-maintenance)
-	Phan with more than 2 phase (gunclub discount plans)
-		
-Use Cases to do:
-	Tiered Add On
-	Riskfree period
-	
-
-
- -->
+<!-- Use cases covered so far: Tiered Product (Pistol/Shotgun/Assault-Rifle) 
+	Multiple changeEvent plan policies Multiple PlanAlignment (see below, trial 
+	add-on alignments and rescue discount package) Product transition rules Add 
+	on (Scopes, Hoster) Multi-pack addon (Extra-Ammo) Addon Trial aligned to 
+	base plan (holster-monthly-regular) Addon Trial aligned to creation (holster-monthly-special) 
+	Rescue discount package (assault-rifle-annual-rescue) Plan phase with a reccurring 
+	and a one off (refurbish-maintenance) Phan with more than 2 phase (gunclub 
+	discount plans) Use Cases to do: Tiered Add On Riskfree period -->
 <catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 	xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
 
@@ -47,21 +27,21 @@ Use Cases to do:
 		<currency>EUR</currency>
 		<currency>GBP</currency>
 	</currencies>
-	
+
 	<products>
 		<product name="Pistol">
 			<category>BASE</category>
 		</product>
 		<product name="Shotgun">
 			<category>BASE</category>
-            <available>
-                <addonProduct>Telescopic-Scope</addonProduct>
-                <addonProduct>Laser-Scope</addonProduct>
-            </available>
+			<available>
+				<addonProduct>Telescopic-Scope</addonProduct>
+				<addonProduct>Laser-Scope</addonProduct>
+			</available>
 		</product>
 		<product name="Assault-Rifle">
 			<category>BASE</category>
-			<included> 
+			<included>
 				<addonProduct>Telescopic-Scope</addonProduct>
 			</included>
 			<available>
@@ -84,37 +64,37 @@ Use Cases to do:
 			<category>ADD_ON</category>
 		</product>
 	</products>
-	 
+
 	<rules>
 		<changePolicy>
-			<changePolicyCase> 
+			<changePolicyCase>
 				<phaseType>TRIAL</phaseType>
 				<policy>IMMEDIATE</policy>
 			</changePolicyCase>
-            <changePolicyCase> 
-                <toProduct>Assault-Rifle</toProduct>
-                <policy>IMMEDIATE</policy>
-            </changePolicyCase>
-            <changePolicyCase> 
-                <fromProduct>Pistol</fromProduct>            
-                <toProduct>Shotgun</toProduct>
-                <policy>IMMEDIATE</policy>
-            </changePolicyCase>
-			<changePolicyCase> 
+			<changePolicyCase>
+				<toProduct>Assault-Rifle</toProduct>
+				<policy>IMMEDIATE</policy>
+			</changePolicyCase>
+			<changePolicyCase>
+				<fromProduct>Pistol</fromProduct>
+				<toProduct>Shotgun</toProduct>
+				<policy>IMMEDIATE</policy>
+			</changePolicyCase>
+			<changePolicyCase>
 				<toPriceList>rescue</toPriceList>
 				<policy>END_OF_TERM</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
+			<changePolicyCase>
 				<fromBillingPeriod>MONTHLY</fromBillingPeriod>
 				<toBillingPeriod>ANNUAL</toBillingPeriod>
 				<policy>IMMEDIATE</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
+			<changePolicyCase>
 				<fromBillingPeriod>ANNUAL</fromBillingPeriod>
 				<toBillingPeriod>MONTHLY</toBillingPeriod>
 				<policy>END_OF_TERM</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
+			<changePolicyCase>
 				<policy>END_OF_TERM</policy>
 			</changePolicyCase>
 		</changePolicy>
@@ -128,27 +108,27 @@ Use Cases to do:
 				<toPriceList>rescue</toPriceList>
 				<alignment>CHANGE_OF_PRICELIST</alignment>
 			</changeAlignmentCase>
-            <changeAlignmentCase>
-                <alignment>START_OF_SUBSCRIPTION</alignment>
-            </changeAlignmentCase>
+			<changeAlignmentCase>
+				<alignment>START_OF_SUBSCRIPTION</alignment>
+			</changeAlignmentCase>
 		</changeAlignment>
 		<cancelPolicy>
 			<cancelPolicyCase>
 				<phaseType>TRIAL</phaseType>
 				<policy>IMMEDIATE</policy>
 			</cancelPolicyCase>
-            <cancelPolicyCase>
-                <policy>END_OF_TERM</policy>
-            </cancelPolicyCase>
+			<cancelPolicyCase>
+				<policy>END_OF_TERM</policy>
+			</cancelPolicyCase>
 		</cancelPolicy>
 		<createAlignment>
-		    <createAlignmentCase>
-		        <product>Laser-Scope</product>
-                <alignment>START_OF_SUBSCRIPTION</alignment>
-            </createAlignmentCase>
-            <createAlignmentCase>
-                <alignment>START_OF_BUNDLE</alignment>
-            </createAlignmentCase>
+			<createAlignmentCase>
+				<product>Laser-Scope</product>
+				<alignment>START_OF_SUBSCRIPTION</alignment>
+			</createAlignmentCase>
+			<createAlignmentCase>
+				<alignment>START_OF_BUNDLE</alignment>
+			</createAlignmentCase>
 		</createAlignment>
 		<billingAlignment>
 			<billingAlignmentCase>
@@ -191,9 +171,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>GBP</currency><value>29.95</value></price>
-					<price><currency>EUR</currency><value>29.95</value></price> 
-					<price><currency>USD</currency><value>29.95</value></price>								
+					<price>
+						<currency>GBP</currency>
+						<value>29.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>29.95</value>
+					</price>
+					<price>
+						<currency>USD</currency>
+						<value>29.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -207,7 +196,7 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
 					<fixedPrice></fixedPrice>
-				    <!-- no price implies $0 -->
+					<!-- no price implies $0 -->
 				</phase>
 			</initialPhases>
 			<finalPhase type="EVERGREEN">
@@ -217,9 +206,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>249.95</value></price>								
-					<price><currency>EUR</currency><value>149.95</value></price>
-					<price><currency>GBP</currency><value>169.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>249.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>149.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>169.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -242,9 +240,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>599.95</value></price>								
-					<price><currency>EUR</currency><value>349.95</value></price>
-					<price><currency>GBP</currency><value>399.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>599.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>349.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>399.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -267,9 +274,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -292,9 +308,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>2399.95</value></price>								
-					<price><currency>EUR</currency><value>1499.95</value></price>
-					<price><currency>GBP</currency><value>1699.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>2399.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>1499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>1699.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -317,9 +342,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>5999.95</value></price>								
-					<price><currency>EUR</currency><value>3499.95</value></price>
-					<price><currency>GBP</currency><value>3999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>5999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>3499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>3999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -342,9 +376,18 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>MONTHLY</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>9.95</value></price>								
-						<price><currency>EUR</currency><value>9.95</value></price>
-						<price><currency>GBP</currency><value>9.95</value></price>
+						<price>
+							<currency>USD</currency>
+							<value>9.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>9.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>9.95</value>
+						</price>
 					</recurringPrice>
 				</phase>
 			</initialPhases>
@@ -354,9 +397,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -379,9 +431,18 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>MONTHLY</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>19.95</value></price>								
-						<price><currency>EUR</currency><value>49.95</value></price>
-						<price><currency>GBP</currency><value>69.95</value></price>
+						<price>
+							<currency>USD</currency>
+							<value>19.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>49.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>69.95</value>
+						</price>
 					</recurringPrice>
 				</phase>
 			</initialPhases>
@@ -391,9 +452,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>2399.95</value></price>								
-					<price><currency>EUR</currency><value>1499.95</value></price>
-					<price><currency>GBP</currency><value>1699.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>2399.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>1499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>1699.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -416,10 +486,19 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>MONTHLY</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>99.95</value></price>								
-						<price><currency>EUR</currency><value>99.95</value></price>
-						<price><currency>GBP</currency><value>99.95</value></price>
-						</recurringPrice>
+						<price>
+							<currency>USD</currency>
+							<value>99.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>99.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>99.95</value>
+						</price>
+					</recurringPrice>
 				</phase>
 			</initialPhases>
 			<finalPhase type="EVERGREEN">
@@ -428,65 +507,110 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>5999.95</value></price>								
-					<price><currency>EUR</currency><value>3499.95</value></price>
-					<price><currency>GBP</currency><value>3999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>5999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>3499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>3999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
 		<plan name="laser-scope-monthly">
-		<product>Laser-Scope</product>
-           <initialPhases>
-              <phase type="DISCOUNT">
-                 <duration>
-                    <unit>MONTHS</unit>
-                    <number>1</number>
-                  </duration>
-                 <billingPeriod>MONTHLY</billingPeriod>
-                    <recurringPrice>
-                      <price><currency>USD</currency><value>999.95</value></price>                             
-                      <price><currency>EUR</currency><value>499.95</value></price>
-                      <price><currency>GBP</currency><value>999.95</value></price>
-                      </recurringPrice>
-                </phase>
-            </initialPhases>
+			<product>Laser-Scope</product>
+			<initialPhases>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>1</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price>
+							<currency>USD</currency>
+							<value>999.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>499.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>999.95</value>
+						</price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
 			<finalPhase type="EVERGREEN">
 				<duration>
 					<unit>UNLIMITED</unit>
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>1999.95</value></price>								
-					<price><currency>EUR</currency><value>1499.95</value></price>
-					<price><currency>GBP</currency><value>1999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>1999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>1499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>1999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
 		<plan name="telescopic-scope-monthly">
 			<product>Telescopic-Scope</product>
 			<initialPhases>
-              <phase type="DISCOUNT">
-                 <duration>
-                    <unit>MONTHS</unit>
-                    <number>1</number>
-                  </duration>
-                 <billingPeriod>MONTHLY</billingPeriod>
-                    <recurringPrice>
-                      <price><currency>USD</currency><value>399.95</value></price>                             
-                      <price><currency>EUR</currency><value>299.95</value></price>
-                      <price><currency>GBP</currency><value>399.95</value></price>
-                      </recurringPrice>
-                </phase>
-            </initialPhases>
+				<phase type="DISCOUNT">
+					<duration>
+						<unit>MONTHS</unit>
+						<number>1</number>
+					</duration>
+					<billingPeriod>MONTHLY</billingPeriod>
+					<recurringPrice>
+						<price>
+							<currency>USD</currency>
+							<value>399.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>299.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>399.95</value>
+						</price>
+					</recurringPrice>
+				</phase>
+			</initialPhases>
 			<finalPhase type="EVERGREEN">
 				<duration>
 					<unit>UNLIMITED</unit>
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>999.95</value></price>								
-					<price><currency>EUR</currency><value>499.95</value></price>
-					<price><currency>GBP</currency><value>999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -498,9 +622,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>999.95</value></price>								
-					<price><currency>EUR</currency><value>499.95</value></price>
-					<price><currency>GBP</currency><value>999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 			<plansAllowedInBundle>-1</plansAllowedInBundle> <!-- arbitrary number of these (multipack) -->
@@ -524,9 +657,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -549,9 +691,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -565,9 +716,18 @@ Use Cases to do:
 					</duration>
 					<billingPeriod>ANNUAL</billingPeriod>
 					<recurringPrice>
-						<price><currency>USD</currency><value>5999.95</value></price>								
-						<price><currency>EUR</currency><value>3499.95</value></price>
-						<price><currency>GBP</currency><value>3999.95</value></price>
+						<price>
+							<currency>USD</currency>
+							<value>5999.95</value>
+						</price>
+						<price>
+							<currency>EUR</currency>
+							<value>3499.95</value>
+						</price>
+						<price>
+							<currency>GBP</currency>
+							<value>3999.95</value>
+						</price>
 					</recurringPrice>
 				</phase>
 			</initialPhases>
@@ -577,9 +737,18 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>ANNUAL</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>5999.95</value></price>								
-					<price><currency>EUR</currency><value>3499.95</value></price>
-					<price><currency>GBP</currency><value>3999.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>5999.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>3499.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>3999.95</value>
+					</price>
 				</recurringPrice>
 			</finalPhase>
 		</plan>
@@ -592,20 +761,38 @@ Use Cases to do:
 				</duration>
 				<billingPeriod>MONTHLY</billingPeriod>
 				<recurringPrice>
-					<price><currency>USD</currency><value>199.95</value></price>								
-					<price><currency>EUR</currency><value>199.95</value></price>
-					<price><currency>GBP</currency><value>199.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>199.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>199.95</value>
+					</price>
 				</recurringPrice>
 				<fixedPrice>
-					<price><currency>USD</currency><value>599.95</value></price>								
-					<price><currency>EUR</currency><value>599.95</value></price>
-					<price><currency>GBP</currency><value>599.95</value></price>
+					<price>
+						<currency>USD</currency>
+						<value>599.95</value>
+					</price>
+					<price>
+						<currency>EUR</currency>
+						<value>599.95</value>
+					</price>
+					<price>
+						<currency>GBP</currency>
+						<value>599.95</value>
+					</price>
 				</fixedPrice>
 			</finalPhase>
 		</plan>
 	</plans>
 	<priceLists>
-		<defaultPriceList name="DEFAULT"> 
+		<defaultPriceList name="DEFAULT">
 			<plans>
 				<plan>pistol-monthly</plan>
 				<plan>shotgun-monthly</plan>
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
index b02c3e8..9e4eb08 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
@@ -28,6 +28,8 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementEvent;
 import com.ning.billing.entitlement.api.user.SubscriptionEventTransition;
 import com.ning.billing.invoice.api.InvoiceApiException;
 
@@ -43,12 +45,25 @@ public class InvoiceListener {
     }
 
     @Subscribe
+    public void handleRepairEntitlementEvent(final RepairEntitlementEvent repairEvent) {
+        try {
+            CallContext context = factory.createCallContext("RepairBundle", CallOrigin.INTERNAL, UserType.SYSTEM, repairEvent.getUserToken());
+            dispatcher.processAccount(repairEvent.getAccountId(), repairEvent.getEffectiveDate(), false, context);
+        } catch (InvoiceApiException e) {
+            log.error(e.getMessage());
+        }
+    }
+    
+    @Subscribe
     public void handleSubscriptionTransition(final SubscriptionEventTransition transition) {
         try {
-            if (transition.getRemainingEventsForUserOperation() > 0) {
-                // Skip invoice generation as there is more coming...
+            //  Skip future uncancel event
+            //  Skip events which are marked as not being the last one
+            if (transition.getTransitionType() == SubscriptionTransitionType.UNCANCEL 
+                    || transition.getRemainingEventsForUserOperation() > 0) {
                 return;
             }
+
             CallContext context = factory.createCallContext("Transition", CallOrigin.INTERNAL, UserType.SYSTEM, transition.getUserToken());
         	dispatcher.processSubscription(transition, context);
         } catch (InvoiceApiException e) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java
index 8ddb29d..4aad036 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java
@@ -31,7 +31,8 @@ import com.ning.billing.util.notificationq.NotificationQueueService;
 import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotificationQueue;
 
 public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
-    private final static Logger log = LoggerFactory.getLogger(DefaultNextBillingDateNotifier.class);
+    
+    private final static Logger log = LoggerFactory.getLogger(DefaultNextBillingDatePoster.class);
 
 	private final NotificationQueueService notificationQueueService;
 
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
index 047c5d6..8efba0d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
@@ -43,10 +43,10 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.billing.BillingModeType;
 import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionEventTransition.SubscriptionTransitionType;
 import com.ning.billing.invoice.InvoiceDispatcher;
 import com.ning.billing.invoice.TestInvoiceDispatcher;
 import com.ning.billing.invoice.api.Invoice;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
index 7506f20..85d1c16 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
@@ -41,10 +41,10 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.billing.BillingModeType;
 import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionEventTransition.SubscriptionTransitionType;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceItem;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
index d1ff615..3f61a4e 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
@@ -24,6 +24,25 @@ import java.sql.SQLException;
 import java.util.UUID;
 import java.util.concurrent.Callable;
 
+
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.account.api.MockAccountUserApi;
+import com.ning.billing.entitlement.api.SubscriptionApiService;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
+import com.ning.billing.invoice.InvoiceDispatcher;
+import com.ning.billing.invoice.dao.DefaultInvoiceDao;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
+import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.customfield.dao.AuditedCustomFieldDao;
+import com.ning.billing.util.customfield.dao.CustomFieldDao;
+import com.ning.billing.util.globallocker.GlobalLocker;
+import com.ning.billing.util.globallocker.MySqlGlobalLocker;
+import com.ning.billing.util.tag.dao.AuditedTagDao;
+import com.ning.billing.util.tag.dao.TagDao;
+
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.skife.config.ConfigurationObjectFactory;
@@ -39,25 +58,29 @@ import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
-import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.account.api.MockAccountUserApi;
+import com.google.inject.name.Names;
 import com.ning.billing.catalog.DefaultCatalogService;
 import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.config.CatalogConfig;
 import com.ning.billing.config.InvoiceConfig;
 import com.ning.billing.dbi.MysqlTestingHelper;
+
 import com.ning.billing.entitlement.api.billing.ChargeThruApi;
+
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementLifecycleDao;
+import com.ning.billing.entitlement.api.timeline.RepairSubscriptionApiService;
+import com.ning.billing.entitlement.api.timeline.RepairSubscriptionFactory;
 import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionApiService;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.engine.dao.EntitlementSqlDao;
-import com.ning.billing.invoice.InvoiceDispatcher;
+import com.ning.billing.entitlement.engine.dao.RepairEntitlementDao;
+import com.ning.billing.entitlement.glue.EntitlementModule;
+
 import com.ning.billing.invoice.InvoiceListener;
-import com.ning.billing.invoice.dao.DefaultInvoiceDao;
-import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
-import com.ning.billing.invoice.model.InvoiceGenerator;
 import com.ning.billing.junction.api.BillingApi;
 import com.ning.billing.junction.plumbing.billing.DefaultBillingApi;
 import com.ning.billing.lifecycle.KillbillService.ServiceException;
@@ -66,20 +89,14 @@ import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 import com.ning.billing.mock.glue.MockJunctionModule;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.bus.InMemoryBus;
-import com.ning.billing.util.callcontext.CallContextFactory;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.customfield.dao.AuditedCustomFieldDao;
-import com.ning.billing.util.customfield.dao.CustomFieldDao;
-import com.ning.billing.util.globallocker.GlobalLocker;
-import com.ning.billing.util.globallocker.MySqlGlobalLocker;
 import com.ning.billing.util.notificationq.DefaultNotificationQueueService;
 import com.ning.billing.util.notificationq.DummySqlTest;
 import com.ning.billing.util.notificationq.NotificationQueueService;
 import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
-import com.ning.billing.util.tag.dao.AuditedTagDao;
-import com.ning.billing.util.tag.dao.TagDao;
+
 
 public class TestNextBillingDateNotifier {
 	private Clock clock;
@@ -90,6 +107,7 @@ public class TestNextBillingDateNotifier {
 	private InvoiceListenerMock listener;
 	private NotificationQueueService notificationQueueService;
 
+	
 	private static final class InvoiceListenerMock extends InvoiceListener {
 		int eventCount = 0;
 		UUID latestSubscriptionId = null;
@@ -137,12 +155,19 @@ public class TestNextBillingDateNotifier {
                 bind(IDBI.class).toInstance(dbi);
                 bind(TagDao.class).to(AuditedTagDao.class).asEagerSingleton();
                 bind(EntitlementDao.class).to(EntitlementSqlDao.class).asEagerSingleton();
+                bind(EntitlementDao.class).annotatedWith(Names.named(EntitlementModule.REPAIR_NAMED)).to(RepairEntitlementDao.class);
+                bind(RepairEntitlementLifecycleDao.class).annotatedWith(Names.named(EntitlementModule.REPAIR_NAMED)).to(RepairEntitlementDao.class);
+                bind(RepairEntitlementDao.class).asEagerSingleton();
                 bind(CustomFieldDao.class).to(AuditedCustomFieldDao.class).asEagerSingleton();
                 bind(GlobalLocker.class).to(MySqlGlobalLocker.class).asEagerSingleton();
                 bind(InvoiceGenerator.class).to(DefaultInvoiceGenerator.class).asEagerSingleton();
-                bind(InvoiceDao.class).to(DefaultInvoiceDao.class);
+                bind(InvoiceDao.class).to(DefaultInvoiceDao.class).asEagerSingleton();
                 bind(NextBillingDatePoster.class).to(DefaultNextBillingDatePoster.class).asEagerSingleton();
-                bind(ChargeThruApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(ChargeThruApi.class));
+                bind(SubscriptionApiService.class).annotatedWith(Names.named(EntitlementModule.REPAIR_NAMED)).to(RepairSubscriptionApiService.class).asEagerSingleton();
+                bind(SubscriptionApiService.class).to(DefaultSubscriptionApiService.class).asEagerSingleton();
+                bind(SubscriptionFactory.class).annotatedWith(Names.named(EntitlementModule.REPAIR_NAMED)).to(RepairSubscriptionFactory.class).asEagerSingleton();
+                bind(SubscriptionFactory.class).to(DefaultSubscriptionFactory.class).asEagerSingleton();
+            
                 install(new MockJunctionModule());
             }
         });
diff --git a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
index 7b53850..68a024e 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
@@ -43,10 +43,10 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.billing.BillingModeType;
 import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionEventTransition.SubscriptionTransitionType;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.dao.InvoiceDao;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
index 3e9c99f..1eaa8af 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
@@ -41,12 +41,12 @@ import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionEventTransition.SubscriptionTransitionType;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.model.BillingEventSet;
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
index 13ad140..4401b06 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
@@ -18,38 +18,61 @@ package com.ning.billing.jaxrs.json;
 
 import org.codehaus.jackson.annotate.JsonCreator;
 import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
 import org.joda.time.DateTimeZone;
 
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountData;
 import com.ning.billing.catalog.api.Currency;
 
-public class AccountJson {
+public class AccountJson extends AccountJsonSimple {
 
-    // Missing city, locale, postalCode from https://home.ninginc.com:8443/display/REVINFRA/Killbill+1.0+APIs
+    // STEPH Missing city, locale, postalCode from https://home.ninginc.com:8443/display/REVINFRA/Killbill+1.0+APIs
 
-    private final String acountId;
+    @JsonView(BundleTimelineViews.Base.class)
     private final String name;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final Integer length;
-    private final String externalKey;
+        
+    @JsonView(BundleTimelineViews.Base.class)
     private final String email;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final Integer billCycleDay;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final String currency;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final String paymentProvider;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final String timeZone;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final String address1;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final String address2;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final String company;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final String state;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final String country;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final String phone;
 
 
     public AccountJson(Account account) {
-        this.acountId = account.getId().toString();
+        super(account.getId().toString(), account.getExternalKey());
         this.name = account.getName();
         this.length = account.getFirstNameLength();
-        this.externalKey = account.getExternalKey();
         this.email = account.getEmail();
         this.billCycleDay = account.getBillCycleDay();
         this.currency = account.getCurrency().toString();
@@ -140,10 +163,9 @@ public class AccountJson {
     // Seems like Jackson (JacksonJsonProvider.readFrom(Class<Object>, Type, Annotation[], MediaType, MultivaluedMap<String,String>, InputStream) line: 443)
     // needs us to define a default CTOR to instanciate the class first.
     public AccountJson() {
-        this.acountId = null;
+        super();
         this.name = null;
         this.length = null;
-        this.externalKey = null;
         this.email = null;
         this.billCycleDay = null;
         this.currency = null;
@@ -173,11 +195,9 @@ public class AccountJson {
             @JsonProperty("state") String state,
             @JsonProperty("country") String country,
             @JsonProperty("phone") String phone) {
-        super();
-        this.acountId = acountId;
+        super(acountId, externalKey);
         this.name = name;
         this.length = length;
-        this.externalKey = externalKey;
         this.email = email;
         this.billCycleDay = billCycleDay;
         this.currency = currency;
@@ -191,10 +211,6 @@ public class AccountJson {
         this.phone = phone;
     }
 
-    public String getAcountId() {
-        return acountId;
-    }
-
     public String getName() {
         return name;
     }
@@ -203,10 +219,6 @@ public class AccountJson {
         return length;
     }
 
-    public String getExternalKey() {
-        return externalKey;
-    }
-
     public String getEmail() {
         return email;
     }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonSimple.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonSimple.java
new file mode 100644
index 0000000..548635e
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonSimple.java
@@ -0,0 +1,49 @@
+/* 
+ * 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.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+public class AccountJsonSimple {
+
+    @JsonView(BundleTimelineViews.Base.class)
+    protected final String acountId;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    protected final String externalKey;
+    
+    public AccountJsonSimple() {
+        this.acountId = null;
+        this.externalKey = null;
+    }
+
+    @JsonCreator
+    public AccountJsonSimple(@JsonProperty("account_id") String acountId,
+            @JsonProperty("external_key") String externalKey) {
+        this.acountId = acountId;
+        this.externalKey = externalKey;
+    }
+
+    public String getAcountId() {
+        return acountId;
+    }
+
+    public String getExternalKey() {
+        return externalKey;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
new file mode 100644
index 0000000..1241b7d
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
@@ -0,0 +1,94 @@
+/* 
+ * 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.jaxrs.json;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.PaymentInfoEvent;
+
+public class AccountTimelineJson {
+
+    @JsonView(BundleTimelineViews.ReadTimeline.class)
+    private final List<PaymentJson> payments;
+
+    @JsonView(BundleTimelineViews.ReadTimeline.class)
+    private final List<InvoiceJson> invoices;
+    
+    @JsonView(BundleTimelineViews.ReadTimeline.class)
+    private final AccountJsonSimple account;
+    
+    @JsonView(BundleTimelineViews.Timeline.class)
+    private final List<BundleJsonWithSubscriptions> bundles;
+    
+    @JsonCreator
+    public AccountTimelineJson(@JsonProperty("account") AccountJsonSimple account,
+            @JsonProperty("bundles") List<BundleJsonWithSubscriptions> bundles,
+            @JsonProperty("invoices") List<InvoiceJson> invoices,            
+            @JsonProperty("payments") List<PaymentJson> payments) {
+        this.account = account;
+        this.bundles = bundles;
+        this.invoices = invoices;
+        this.payments = payments;
+    }
+    
+    public AccountTimelineJson(Account account, List<Invoice> invoices, List<PaymentInfoEvent> payments, List<BundleTimeline> bundles) {
+        this.account = new AccountJsonSimple(account.getId().toString(), account.getExternalKey());
+        this.bundles = new LinkedList<BundleJsonWithSubscriptions>();
+        for (BundleTimeline cur : bundles) {
+            this.bundles.add(new BundleJsonWithSubscriptions(account.getId(), cur));            
+        }
+        this.invoices = new LinkedList<InvoiceJson>();
+        for (Invoice cur : invoices) {
+            this.invoices.add(new InvoiceJson(cur.getTotalAmount(), cur.getId().toString(), cur.getInvoiceDate(), Integer.toString(cur.getInvoiceNumber()), cur.getBalance()));
+        }
+        this.payments = new LinkedList<PaymentJson>();
+        for (PaymentInfoEvent cur : payments) {
+            // STEPH how to link that payment with the invoice ??
+            this.payments.add(new PaymentJson(cur.getAmount(), null , cur.getPaymentNumber(), null, cur.getEffectiveDate(), cur.getStatus()));
+        }
+    }
+    
+    public AccountTimelineJson() {
+        this.account = null;
+        this.bundles = null;
+        this.invoices = null;
+        this.payments = null;
+    }
+
+    public List<PaymentJson> getPayments() {
+        return payments;
+    }
+
+    public List<InvoiceJson> getInvoices() {
+        return invoices;
+    }
+
+    public AccountJsonSimple getAccount() {
+        return account;
+    }
+
+    public List<BundleJsonWithSubscriptions> getBundles() {
+        return bundles;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonSimple.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonSimple.java
new file mode 100644
index 0000000..3053f0d
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonSimple.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.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+public class BundleJsonSimple {
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    protected final String bundleId;
+
+    @JsonView(BundleTimelineViews.Base.class)
+    protected final String externalKey;
+
+    @JsonCreator
+    public BundleJsonSimple(String bundleId, String externalKey) {
+        super();
+        this.bundleId = bundleId;
+        this.externalKey = externalKey;
+    }
+    
+    public BundleJsonSimple() {
+        this.bundleId = null;
+        this.externalKey = null;
+    }
+
+    public String getBundleId() {
+        return bundleId;
+    }
+
+    public String getExternalKey() {
+        return externalKey;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonWithSubscriptions.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonWithSubscriptions.java
new file mode 100644
index 0000000..dd2c788
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonWithSubscriptions.java
@@ -0,0 +1,65 @@
+/* 
+ * 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.jaxrs.json;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+
+public class BundleJsonWithSubscriptions extends BundleJsonSimple {
+
+    @JsonView(BundleTimelineViews.Timeline.class)
+    private final List<SubscriptionJsonWithEvents> subscriptions;
+
+    @JsonCreator
+    public BundleJsonWithSubscriptions(@JsonProperty("bundle_id") String bundleId,
+            @JsonProperty("external_key") String externalKey,
+            @JsonProperty("subscriptions") List<SubscriptionJsonWithEvents> subscriptions) {
+        super(bundleId, externalKey);
+        this.subscriptions = subscriptions;
+    }
+
+    public List<SubscriptionJsonWithEvents> getSubscriptions() {
+        return subscriptions;
+    }
+
+    public BundleJsonWithSubscriptions(final UUID accountId, final BundleTimeline bundle) {
+        super(bundle.getBundleId().toString(), bundle.getExternalKey());
+        this.subscriptions = new LinkedList<SubscriptionJsonWithEvents>();
+        for (SubscriptionTimeline cur : bundle.getSubscriptions()) {
+            this.subscriptions.add(new SubscriptionJsonWithEvents(bundle.getBundleId(), cur)); 
+        }
+    }
+    
+    public BundleJsonWithSubscriptions(SubscriptionBundle bundle) {
+        super(bundle.getId().toString(), bundle.getKey());        
+        this.subscriptions = null;
+    }
+    
+    public BundleJsonWithSubscriptions() {
+        super(null, null);        
+        this.subscriptions = null;
+    }
+
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineJson.java
index 61761a7..64a416c 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineJson.java
@@ -28,7 +28,7 @@ public class BundleTimelineJson {
     private final String viewId;
 
     @JsonView(BundleTimelineViews.Timeline.class)
-    private final BundleJson bundle;
+    private final BundleJsonWithSubscriptions bundle;
 
     @JsonView(BundleTimelineViews.ReadTimeline.class)
     private final List<PaymentJson> payments;
@@ -41,7 +41,7 @@ public class BundleTimelineJson {
 
     @JsonCreator
     public BundleTimelineJson(@JsonProperty("view_id") String viewId,
-            @JsonProperty("bundle") BundleJson bundle,
+            @JsonProperty("bundle") BundleJsonWithSubscriptions bundle,
             @JsonProperty("payments") List<PaymentJson> payments,
             @JsonProperty("invoices") List<InvoiceJson> invoices,
             @JsonProperty("reason_for_change") String reason) {
@@ -56,7 +56,7 @@ public class BundleTimelineJson {
         return viewId;
     }
 
-    public BundleJson getBundle() {
+    public BundleJsonWithSubscriptions getBundle() {
         return bundle;
     }
 
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineViews.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineViews.java
index 020764e..0397bec 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineViews.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineViews.java
@@ -18,8 +18,8 @@ package com.ning.billing.jaxrs.json;
 
 
 public class BundleTimelineViews {
-    static class Base {};
-    static class Timeline extends Base {};
-    static class ReadTimeline extends Timeline {};
-    static class WriteTimeline extends Timeline {};
+    public static class Base {};
+    public static class Timeline extends Base {};
+    public static class ReadTimeline extends Timeline {};
+    public static class WriteTimeline extends Timeline {};
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJson.java
index b13bdee..30264d7 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJson.java
@@ -20,26 +20,36 @@ import java.math.BigDecimal;
 
 import org.codehaus.jackson.annotate.JsonCreator;
 import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
 import org.joda.time.DateTime;
 
 public class InvoiceJson {
 
+    @JsonView(BundleTimelineViews.Base.class)
     private final BigDecimal amount;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final String invoiceId;
-    private final DateTime requestedDate;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final DateTime invoiceDate;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final String invoiceNumber;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final BigDecimal balance;
 
     @JsonCreator
     public InvoiceJson(@JsonProperty("amount") BigDecimal amount,
             @JsonProperty("invoice_id") String invoiceId,
-            @JsonProperty("requested_dt") DateTime requestedDate,
+            @JsonProperty("invoice_date") DateTime invoiceDate,
             @JsonProperty("invoice_number") String invoiceNumber,
             @JsonProperty("balance") BigDecimal balance) {
         super();
         this.amount = amount;
         this.invoiceId = invoiceId;
-        this.requestedDate = requestedDate;
+        this.invoiceDate = invoiceDate;
         this.invoiceNumber = invoiceNumber;
         this.balance = balance;
     }
@@ -52,8 +62,8 @@ public class InvoiceJson {
         return invoiceId;
     }
 
-    public DateTime getRequestedDate() {
-        return requestedDate;
+    public DateTime getInvoiceDate() {
+        return invoiceDate;
     }
 
     public String getInvoiceNumber() {
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJson.java
index 1f932d4..3a9fd4d 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJson.java
@@ -20,17 +20,29 @@ import java.math.BigDecimal;
 
 import org.codehaus.jackson.annotate.JsonCreator;
 import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
 import org.joda.time.DateTime;
 
 import com.ning.billing.util.clock.DefaultClock;
 
 public class PaymentJson {
 
+    @JsonView(BundleTimelineViews.Base.class)
     private final BigDecimal paidAmount;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final String invoiceId;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final String paymentId;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final DateTime requestedDate;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final DateTime effectiveDate;
+    
+    @JsonView(BundleTimelineViews.Base.class)
     private final String status;
 
     @JsonCreator
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonNoEvents.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonNoEvents.java
new file mode 100644
index 0000000..2ec1c44
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonNoEvents.java
@@ -0,0 +1,210 @@
+/*
+ * 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.jaxrs.json;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.util.clock.DefaultClock;
+
+public class SubscriptionJsonNoEvents extends SubscriptionJsonSimple {
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final DateTime startDate;
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String bundleId;
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String productName;
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String productCategory;
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String billingPeriod;
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final String priceList;
+
+    @JsonView(BundleTimelineViews.Base.class)
+    private final DateTime chargedThroughDate;
+    
+
+
+    @JsonCreator
+    public SubscriptionJsonNoEvents(@JsonProperty("subscription_id") String subscriptionId,
+            @JsonProperty("bundle_id") String bundleId,
+            @JsonProperty("start_date") DateTime startDate,
+            @JsonProperty("product_name") String productName,
+            @JsonProperty("product_category") String productCategory,
+            @JsonProperty("billing_period") String billingPeriod,
+            @JsonProperty("price_list") String priceList,
+            @JsonProperty("charged_through_date") DateTime chargedThroughDate) {
+        super(subscriptionId);
+        this.bundleId = bundleId;
+        this.startDate = startDate;
+        this.productName = productName;
+        this.productCategory = productCategory;
+        this.billingPeriod = billingPeriod;
+        this.priceList = priceList;
+        this.chargedThroughDate = chargedThroughDate;
+    }
+    
+    public SubscriptionJsonNoEvents() {
+        super(null);
+        this.bundleId = null;
+        this.startDate = null;
+        this.productName = null;
+        this.productCategory = null;
+        this.billingPeriod = null;
+        this.priceList = null;
+        this.chargedThroughDate = null;        
+    }
+    
+    public SubscriptionJsonNoEvents(final Subscription data) {
+        super(data.getId().toString());
+        this.bundleId = data.getBundleId().toString();
+        this.startDate = data.getStartDate();
+        this.productName = data.getCurrentPlan().getProduct().getName();
+        this.productCategory = data.getCurrentPlan().getProduct().getCategory().toString();
+        this.billingPeriod = data.getCurrentPlan().getBillingPeriod().toString();
+        this.priceList = data.getCurrentPriceList().getName();
+        this.chargedThroughDate = data.getChargedThroughDate();
+    }
+   
+
+    public String getSubscriptionId() {
+        return subscriptionId;
+    }
+
+    public String getBundleId() {
+        return bundleId;
+    }
+    
+    public DateTime getStartDate() {
+        return startDate;
+    }
+
+    public String getProductName() {
+        return productName;
+    }
+
+    public String getProductCategory() {
+        return productCategory;
+    }
+
+    public String getBillingPeriod() {
+        return billingPeriod;
+    }
+
+    public String getPriceList() {
+        return priceList;
+    }
+    
+    public DateTime getChargedThroughDate() {
+        return chargedThroughDate;
+    }
+
+
+    @Override
+    public String toString() {
+        return "SubscriptionJson [subscriptionId=" + subscriptionId
+                + ", bundleId=" + bundleId + ", productName=" + productName
+                + ", productCategory=" + productCategory + ", billingPeriod="
+                + billingPeriod + ", priceList=" + priceList + "]";
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((billingPeriod == null) ? 0 : billingPeriod.hashCode());
+        result = prime * result
+                + ((bundleId == null) ? 0 : bundleId.hashCode());
+        result = prime * result
+                + ((priceList == null) ? 0 : priceList.hashCode());
+        result = prime * result
+                + ((productCategory == null) ? 0 : productCategory.hashCode());
+        result = prime * result
+                + ((productName == null) ? 0 : productName.hashCode());
+        result = prime * result
+                + ((subscriptionId == null) ? 0 : subscriptionId.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (equalsNoId(obj) == false) {
+            return false;
+        }
+        SubscriptionJsonNoEvents other = (SubscriptionJsonNoEvents) obj;
+        if (subscriptionId == null) {
+            if (other.subscriptionId != null)
+                return false;
+        } else if (!subscriptionId.equals(other.subscriptionId))
+            return false;
+        return true;
+    }
+
+    public boolean equalsNoId(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        SubscriptionJsonNoEvents other = (SubscriptionJsonNoEvents) obj;
+        if (billingPeriod == null) {
+            if (other.billingPeriod != null)
+                return false;
+        } else if (!billingPeriod.equals(other.billingPeriod))
+            return false;
+        if (bundleId == null) {
+            if (other.bundleId != null)
+                return false;
+        } else if (!bundleId.equals(other.bundleId))
+            return false;
+        if (priceList == null) {
+            if (other.priceList != null)
+                return false;
+        } else if (!priceList.equals(other.priceList))
+            return false;
+        if (productCategory == null) {
+            if (other.productCategory != null)
+                return false;
+        } else if (!productCategory.equals(other.productCategory))
+            return false;
+        if (productName == null) {
+            if (other.productName != null)
+                return false;
+        } else if (!productName.equals(other.productName))
+            return false;
+        return true;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonSimple.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonSimple.java
new file mode 100644
index 0000000..ebb86e4
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonSimple.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.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+public class SubscriptionJsonSimple {
+    
+    @JsonView(BundleTimelineViews.Base.class)
+    protected final String subscriptionId;
+
+    public SubscriptionJsonSimple() {
+        this.subscriptionId = null;
+    }
+
+    @JsonCreator
+    public SubscriptionJsonSimple(@JsonProperty("subscription_id") String subscriptionId) {
+        this.subscriptionId = subscriptionId;
+    }
+
+    public String getSubscriptionId() {
+        return subscriptionId;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
index bd7a22b..18c6dd4 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
@@ -19,7 +19,10 @@ package com.ning.billing.jaxrs.resources;
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 
 import java.net.URI;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
 
@@ -48,12 +51,20 @@ import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountData;
 import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.entitlement.api.timeline.EntitlementRepairException;
+import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.jaxrs.json.AccountJson;
-import com.ning.billing.jaxrs.json.BundleJson;
+import com.ning.billing.jaxrs.json.AccountTimelineJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
 import com.ning.billing.jaxrs.util.Context;
 import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentInfoEvent;
 
 
 @Singleton
@@ -64,14 +75,26 @@ public class AccountResource implements BaseJaxrsResource {
 
     private final AccountUserApi accountApi;
     private final EntitlementUserApi entitlementApi;
+    private final EntitlementTimelineApi timelineApi;
+    private final InvoiceUserApi invoiceApi;
+    private final PaymentApi paymentApi;
     private final Context context;
     private final JaxrsUriBuilder uriBuilder;
 
     @Inject
-    public AccountResource(final JaxrsUriBuilder uriBuilder, final AccountUserApi accountApi, final EntitlementUserApi entitlementApi, final Context context) {
+    public AccountResource(final JaxrsUriBuilder uriBuilder,
+            final AccountUserApi accountApi,
+            final EntitlementUserApi entitlementApi, 
+            final InvoiceUserApi invoiceApi,
+            final PaymentApi paymentApi,
+            final EntitlementTimelineApi timelineApi,
+            final Context context) {
         this.uriBuilder = uriBuilder;
     	this.accountApi = accountApi;
         this.entitlementApi = entitlementApi;
+        this.invoiceApi = invoiceApi;
+        this.paymentApi = paymentApi;
+        this.timelineApi = timelineApi;
         this.context = context;
     }
 
@@ -86,7 +109,7 @@ public class AccountResource implements BaseJaxrsResource {
             return Response.status(Status.OK).entity(json).build();
         } catch (AccountApiException e) {
             log.warn("Failed to find account.", e);
-            return Response.status(Status.NO_CONTENT).build();
+            return Response.status(Status.NO_CONTENT).build();            
         }
         
     }
@@ -95,16 +118,15 @@ public class AccountResource implements BaseJaxrsResource {
     @Path("/{accountId:" + UUID_PATTERN + "}/" + BUNDLES)
     @Produces(APPLICATION_JSON)
     public Response getAccountBundles(@PathParam("accountId") String accountId) {
-
         try {
             UUID uuid = UUID.fromString(accountId);
             accountApi.getAccountById(uuid);
 
             List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(uuid);
-            Collection<BundleJson> result = Collections2.transform(bundles, new Function<SubscriptionBundle, BundleJson>() {
+            Collection<BundleJsonNoSubsciptions> result = Collections2.transform(bundles, new Function<SubscriptionBundle, BundleJsonNoSubsciptions>() {
                 @Override
-                public BundleJson apply(SubscriptionBundle input) {
-                    return new BundleJson(input);
+                public BundleJsonNoSubsciptions apply(SubscriptionBundle input) {
+                    return new BundleJsonNoSubsciptions(input);
                 }
             });
             return Response.status(Status.OK).entity(result).build();
@@ -188,4 +210,43 @@ public class AccountResource implements BaseJaxrsResource {
        */
         return Response.status(Status.INTERNAL_SERVER_ERROR).build();
     }
+
+    @GET
+    @Path("/{accountId:" + UUID_PATTERN + "}/" + TIMELINE)
+    @Produces(APPLICATION_JSON)
+    public Response getAccountTimeline(@PathParam("accountId") String accountId) {
+        try {
+            Account account = accountApi.getAccountById(UUID.fromString(accountId));
+           
+            List<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId());
+            List<PaymentInfoEvent> payments = Collections.emptyList();
+
+            if (invoices.size() > 0) {
+                Collection<String> tmp = Collections2.transform(invoices, new Function<Invoice, String>() {
+                    @Override
+                    public String apply(Invoice input) {
+                        return input.getId().toString();
+                    }
+                });
+                List<String> invoicesId = new ArrayList<String>();
+                invoicesId.addAll(tmp);
+
+                payments = paymentApi.getPaymentInfo(invoicesId);
+            }
+
+            List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(account.getId());
+            List<BundleTimeline> bundlesTimeline = new LinkedList<BundleTimeline>();
+            for (SubscriptionBundle cur : bundles) {
+                bundlesTimeline.add(timelineApi.getBundleRepair(cur.getId()));
+            }
+            AccountTimelineJson json = new AccountTimelineJson(account, invoices, payments, bundlesTimeline);
+            return Response.status(Status.OK).entity(json).build();
+        } catch (AccountApiException e) {
+            log.warn("Failed to find account.", e);
+            return Response.status(Status.NO_CONTENT).build();
+        } catch (EntitlementRepairException e) {
+            log.error(e.getMessage());
+            return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+        }
+    }
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java
index 3fb0334..79f4b04 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java
@@ -20,6 +20,8 @@ public interface BaseJaxrsResource {
 	public static final String API_PREFIX = "";
 	public static final String API_VERSION = "/1.0";
 	
+	public static final String TIMELINE = "timeline";
+	
 	/*
 	 * Patterns
 	 */
@@ -35,7 +37,7 @@ public interface BaseJaxrsResource {
 	
 	public static final String ACCOUNTS = "accounts";	
 	public static final String ACCOUNTS_PATH = API_PREFIX + API_VERSION + "/" + ACCOUNTS;
-	
+
 	public static final String BUNDLES = "bundles";		
 	public static final String BUNDLES_PATH = API_PREFIX + API_VERSION + "/" + BUNDLES;
 
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
index 3b6665e..d3bb1a2 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
@@ -43,8 +43,8 @@ import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.jaxrs.json.BundleJson;
-import com.ning.billing.jaxrs.json.SubscriptionJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
 import com.ning.billing.jaxrs.util.Context;
 import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
 
@@ -70,7 +70,7 @@ public class BundleResource implements BaseJaxrsResource {
     public Response getBundle(@PathParam("bundleId") final String bundleId) throws EntitlementUserApiException {
         try {
             SubscriptionBundle bundle = entitlementApi.getBundleFromId(UUID.fromString(bundleId));
-            BundleJson json = new BundleJson(bundle);
+            BundleJsonNoSubsciptions json = new BundleJsonNoSubsciptions(bundle);
             return Response.status(Status.OK).entity(json).build();
         } catch (EntitlementUserApiException e) {
             if (e.getCode() == ErrorCode.ENT_GET_INVALID_BUNDLE_ID.getCode()) {
@@ -87,7 +87,7 @@ public class BundleResource implements BaseJaxrsResource {
     public Response getBundleByKey(@QueryParam(QUERY_EXTERNAL_KEY) final String externalKey) throws EntitlementUserApiException {
         try {
             SubscriptionBundle bundle = entitlementApi.getBundleForKey(externalKey);
-            BundleJson json = new BundleJson(bundle);
+            BundleJsonNoSubsciptions json = new BundleJsonNoSubsciptions(bundle);
             return Response.status(Status.OK).entity(json).build();
         } catch (EntitlementUserApiException e) {
             if (e.getCode() == ErrorCode.ENT_GET_INVALID_BUNDLE_KEY.getCode()) {
@@ -102,7 +102,7 @@ public class BundleResource implements BaseJaxrsResource {
     @POST
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
-    public Response createBundle(final BundleJson json) {
+    public Response createBundle(final BundleJsonNoSubsciptions json) {
         try {
             UUID accountId = UUID.fromString(json.getAccountId());
             final SubscriptionBundle bundle = entitlementApi.createBundleForAccount(accountId, json.getExternalKey(), context.createContext());
@@ -124,12 +124,12 @@ public class BundleResource implements BaseJaxrsResource {
                 return Response.status(Status.NO_CONTENT).build();
             }
             List<Subscription> bundles = entitlementApi.getSubscriptionsForBundle(uuid);
-            Collection<SubscriptionJson> result =  Collections2.transform(bundles, new Function<Subscription, SubscriptionJson>() {
+            Collection<SubscriptionJsonNoEvents> result =  Collections2.transform(bundles, new Function<Subscription, SubscriptionJsonNoEvents>() {
                 @Override
-                public SubscriptionJson apply(Subscription input) {
-                    return new SubscriptionJson(input, null, null, null);
+                public SubscriptionJsonNoEvents apply(Subscription input) {
+                    return new SubscriptionJsonNoEvents(input);
                 }
-            });
+             });
             return Response.status(Status.OK).entity(result).build();
         } catch (EntitlementUserApiException e) {
             if (e.getCode() == ErrorCode.ENT_GET_INVALID_BUNDLE_ID.getCode()) {
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
index 8a3b350..a8f12eb 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
@@ -52,7 +52,7 @@ import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionEventTransition;
 import com.ning.billing.invoice.api.EmptyInvoiceEvent;
 import com.ning.billing.invoice.api.InvoiceCreationEvent;
-import com.ning.billing.jaxrs.json.SubscriptionJson;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
 import com.ning.billing.jaxrs.util.Context;
 import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
 import com.ning.billing.jaxrs.util.KillbillEventHandler;
@@ -63,7 +63,7 @@ import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.userrequest.CompletionUserRequestBase;
 
 @Path(BaseJaxrsResource.SUBSCRIPTIONS_PATH)
-public class SubscriptionResource implements BaseJaxrsResource{
+public class SubscriptionResource implements BaseJaxrsResource {
 
     private static final Logger log = LoggerFactory.getLogger(SubscriptionResource.class);
 
@@ -73,7 +73,9 @@ public class SubscriptionResource implements BaseJaxrsResource{
     private final Context context;
     private final JaxrsUriBuilder uriBuilder;	
     private final KillbillEventHandler killbillHandler;
+    
 
+    
     @Inject
     public SubscriptionResource(final JaxrsUriBuilder uriBuilder, final EntitlementUserApi entitlementApi,
             final Clock clock, final Context context, final KillbillEventHandler killbillHandler) {
@@ -92,7 +94,7 @@ public class SubscriptionResource implements BaseJaxrsResource{
             UUID uuid = UUID.fromString(subscriptionId);
             Subscription subscription = entitlementApi.getSubscriptionFromId(uuid);
 
-            SubscriptionJson json = new SubscriptionJson(subscription, null, null, null);
+            SubscriptionJsonNoEvents json = new SubscriptionJsonNoEvents(subscription);
             return Response.status(Status.OK).entity(json).build();
         } catch (EntitlementUserApiException e) {
             if (e.getCode() == ErrorCode.ENT_INVALID_SUBSCRIPTION_ID.getCode()) {
@@ -103,11 +105,44 @@ public class SubscriptionResource implements BaseJaxrsResource{
 
         }
     }
+    
+    /*
+    @GET
+    @Path("/{subscriptionId:" + UUID_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public StreamingOutput getSubscription(@PathParam("subscriptionId") final String subscriptionId) {
+
+        UUID uuid = UUID.fromString(subscriptionId);
+        final Subscription subscription = entitlementApi.getSubscriptionFromId(uuid);
+        if (subscription == null) {
+            throw new WebApplicationException(Response.Status.NO_CONTENT);
+        }
+        final SubscriptionJson json = new SubscriptionJson(subscription, null, null, null);
+        return new StreamingOutput() {
+
+            final SubscriptionJson json = new SubscriptionJson(subscription, null, null, null);
+            
+            @Override
+            public void write(OutputStream output) throws IOException,
+                    WebApplicationException {
+                
+                final ObjectWriter objWriter = objectMapper.writerWithView(BundleTimelineViews.Base.class);
+                
+                Writer writer = new StringWriter();
+                objWriter.writeValue(writer, json);
+                String baseJson = writer.toString();
+                output.write(baseJson.getBytes());
+                output.flush();
+            }
+        };
+    }
+    */
+
 
     @POST
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
-    public Response createSubscription(final SubscriptionJson subscription,
+    public Response createSubscription(final SubscriptionJsonNoEvents subscription,
             final @QueryParam(QUERY_REQUESTED_DT) String requestedDate,
             final @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") Boolean callCompletion,
             final @QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") long timeoutSec) {
@@ -142,7 +177,7 @@ public class SubscriptionResource implements BaseJaxrsResource{
     @Produces(APPLICATION_JSON)
     @Consumes(APPLICATION_JSON)
     @Path("/{subscriptionId:" + UUID_PATTERN + "}")
-    public Response changeSubscriptionPlan(final SubscriptionJson subscription,
+    public Response changeSubscriptionPlan(final SubscriptionJsonNoEvents subscription,
             final @PathParam("subscriptionId") String subscriptionId,
             final @QueryParam(QUERY_REQUESTED_DT) String requestedDate,
             final @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") Boolean callCompletion,
@@ -156,14 +191,16 @@ public class SubscriptionResource implements BaseJaxrsResource{
             public Response doOperation(CallContext ctx)
                     throws EntitlementUserApiException, InterruptedException,
                     TimeoutException {
-                UUID uuid = UUID.fromString(subscriptionId);
-                Subscription current = entitlementApi.getSubscriptionFromId(uuid);
-                if (current == null) {
+                try {
+                    UUID uuid = UUID.fromString(subscriptionId);
+                    Subscription current = entitlementApi.getSubscriptionFromId(uuid);
+                    DateTime inputDate = (requestedDate != null) ? DATE_TIME_FORMATTER.parseDateTime(requestedDate) : null;
+                    isImmediateOp = current.changePlan(subscription.getProductName(),  BillingPeriod.valueOf(subscription.getBillingPeriod()), subscription.getPriceList(), inputDate, ctx);
+                    return Response.status(Status.OK).build();
+                } catch (EntitlementUserApiException e) {
+                    log.warn("Subscription not found: " + subscriptionId , e);
                     return Response.status(Status.NO_CONTENT).build();
                 }
-                DateTime inputDate = (requestedDate != null) ? DATE_TIME_FORMATTER.parseDateTime(requestedDate) : null;
-                isImmediateOp = current.changePlan(subscription.getProductName(),  BillingPeriod.valueOf(subscription.getBillingPeriod()), subscription.getPriceList(), inputDate, ctx);
-                return Response.status(Status.OK).build();
             }
             @Override
             public boolean isImmOperation() {
@@ -197,14 +234,17 @@ public class SubscriptionResource implements BaseJaxrsResource{
         try {
             UUID uuid = UUID.fromString(subscriptionId);
             Subscription current = entitlementApi.getSubscriptionFromId(uuid);
-            if (current == null) {
-                return Response.status(Status.NO_CONTENT).build();
-            }
+        
             current.uncancel(context.createContext());
             return Response.status(Status.OK).build();
         } catch (EntitlementUserApiException e) {
-            log.info(String.format("Failed to uncancel plan for subscription %s", subscriptionId), e);
-            return Response.status(Status.BAD_REQUEST).build();
+            if(e.getCode() == ErrorCode.ENT_INVALID_SUBSCRIPTION_ID.getCode()) {
+                log.info(String.format("Failed to find subscription %s", subscriptionId), e);
+                return Response.status(Status.NO_CONTENT).build();
+            } else {
+                log.info(String.format("Failed to uncancel plan for subscription %s", subscriptionId), e);
+                return Response.status(Status.BAD_REQUEST).build();
+            }
         }
     }
 
@@ -224,14 +264,22 @@ public class SubscriptionResource implements BaseJaxrsResource{
             public Response doOperation(CallContext ctx)
                     throws EntitlementUserApiException, InterruptedException,
                     TimeoutException {
-                UUID uuid = UUID.fromString(subscriptionId);
-                Subscription current = entitlementApi.getSubscriptionFromId(uuid);
-                if (current == null) {
-                    return Response.status(Status.NO_CONTENT).build();
+                try {
+                    UUID uuid = UUID.fromString(subscriptionId);
+
+                    Subscription current = entitlementApi.getSubscriptionFromId(uuid);
+
+                    DateTime inputDate = (requestedDate != null) ? DATE_TIME_FORMATTER.parseDateTime(requestedDate) : null;
+                    isImmediateOp = current.cancel(inputDate, false, ctx);
+                    return Response.status(Status.OK).build();
+                } catch (EntitlementUserApiException e) {
+                    if(e.getCode() == ErrorCode.ENT_INVALID_SUBSCRIPTION_ID.getCode()) {
+                        log.info(String.format("Failed to find subscription %s", subscriptionId), e);
+                        return Response.status(Status.NO_CONTENT).build();
+                    } else {
+                        throw e;
+                    }
                 }
-                DateTime inputDate = (requestedDate != null) ? DATE_TIME_FORMATTER.parseDateTime(requestedDate) : null;
-                isImmediateOp = current.cancel(inputDate, false, ctx);
-                return Response.status(Status.OK).build();
             }
             @Override
             public boolean isImmOperation() {
@@ -253,29 +301,29 @@ public class SubscriptionResource implements BaseJaxrsResource{
         }
         @Override
         public void onSubscriptionTransition(SubscriptionEventTransition curEvent) {
-            log.info(String.format("Got event SubscriptionTransition token = %s, type = %s, remaining = %d ", 
+            log.debug(String.format("Got event SubscriptionTransition token = %s, type = %s, remaining = %d ", 
                     curEvent.getUserToken(), curEvent.getTransitionType(),  curEvent.getRemainingEventsForUserOperation())); 
         }
         @Override
         public void onEmptyInvoice(final EmptyInvoiceEvent curEvent) {
-            log.info(String.format("Got event EmptyInvoiceNotification token = %s ", curEvent.getUserToken())); 
+            log.debug(String.format("Got event EmptyInvoiceNotification token = %s ", curEvent.getUserToken())); 
             notifyForCompletion();
         }
         @Override
         public void onInvoiceCreation(InvoiceCreationEvent curEvent) {
-            log.info(String.format("Got event InvoiceCreationNotification token = %s ", curEvent.getUserToken())); 
+            log.debug(String.format("Got event InvoiceCreationNotification token = %s ", curEvent.getUserToken())); 
             if (curEvent.getAmountOwed().compareTo(BigDecimal.ZERO) <= 0) {
                 notifyForCompletion();
             }
         }
         @Override
         public void onPaymentInfo(PaymentInfoEvent curEvent) {
-            log.info(String.format("Got event PaymentInfo token = %s ", curEvent.getUserToken()));  
+            log.debug(String.format("Got event PaymentInfo token = %s ", curEvent.getUserToken()));  
             notifyForCompletion();
         }
         @Override
         public void onPaymentError(PaymentErrorEvent curEvent) {
-            log.info(String.format("Got event PaymentError token = %s ", curEvent.getUserToken())); 
+            log.debug(String.format("Got event PaymentError token = %s ", curEvent.getUserToken())); 
             notifyForCompletion();
         }
     }
@@ -303,10 +351,13 @@ public class SubscriptionResource implements BaseJaxrsResource{
                 return callback.doResponseOk(operationValue);
             } catch (EntitlementUserApiException e) {
                 log.info(String.format("Failed to complete operation"), e);
+                //throw new WebApplicationException(Response.Status.BAD_REQUEST);
                 return Response.status(Status.BAD_REQUEST).build();
             } catch (InterruptedException e) {
+                //throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);                
                 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
             } catch (TimeoutException e) {
+                //throw new WebApplicationException(Response.Status.fromStatusCode(408));                
                 return Response.status(Status.fromStatusCode(408)).build();   
             } finally {
                 if (waiter != null) {
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/KillbillEventHandler.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/KillbillEventHandler.java
index 7d835e8..dc18416 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/KillbillEventHandler.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/KillbillEventHandler.java
@@ -15,6 +15,7 @@
  */
 package com.ning.billing.jaxrs.util;
 
+import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -22,7 +23,6 @@ import com.google.common.eventbus.Subscribe;
 import com.ning.billing.util.bus.BusEvent;
 import com.ning.billing.util.userrequest.CompletionUserRequest;
 import com.ning.billing.util.userrequest.CompletionUserRequestNotifier;
-import com.ning.billing.util.userrequest.CompletionUserRequestWaiter;
 
 public class KillbillEventHandler {
     
@@ -56,10 +56,14 @@ public class KillbillEventHandler {
      */
     @Subscribe
     public void handleEntitlementevents(final BusEvent event) {
-        if (activeWaiters.size() == 0) {
+        List<CompletionUserRequestNotifier> runningWaiters = new ArrayList<CompletionUserRequestNotifier>();
+        synchronized(activeWaiters) {
+            runningWaiters.addAll(activeWaiters);
+        }
+        if (runningWaiters.size() == 0) {
             return;
         }
-        for (CompletionUserRequestNotifier cur : activeWaiters) {
+        for (CompletionUserRequestNotifier cur : runningWaiters) {
             cur.onBusEvent(event);
         }
     }

junction/pom.xml 7(+0 -7)

diff --git a/junction/pom.xml b/junction/pom.xml
index b5dfd03..a33d843 100644
--- a/junction/pom.xml
+++ b/junction/pom.xml
@@ -110,13 +110,6 @@
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-surefire-plugin</artifactId>
-                <configuration>
-                    <groups>fast,slow, stress</groups>
-                </configuration>
-            </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
                 <executions>
                     <execution>
diff --git a/junction/src/main/java/com/ning/billing/junction/glue/JunctionModule.java b/junction/src/main/java/com/ning/billing/junction/glue/JunctionModule.java
index a44e757..2cf3de7 100644
--- a/junction/src/main/java/com/ning/billing/junction/glue/JunctionModule.java
+++ b/junction/src/main/java/com/ning/billing/junction/glue/JunctionModule.java
@@ -88,8 +88,6 @@ public class JunctionModule extends AbstractModule {
         public BlockingDaoProvider(IDBI dbi){
             this.dbi = dbi;
         }
-        
-
         @Override
         public BlockingStateDao get() {
             return dbi.onDemand(BlockingStateSqlDao.class);
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
index 0a0ddfc..f07fa04 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
@@ -32,11 +32,11 @@ import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.Product;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionEventTransition;
-import com.ning.billing.entitlement.api.user.SubscriptionEventTransition.SubscriptionTransitionType;
 
 public class BillCycleDayCalculator {
 	private static final Logger log = LoggerFactory.getLogger(BillCycleDayCalculator.class);
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java
index ff38df9..9c1aab1 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java
@@ -32,10 +32,10 @@ import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.billing.BillingModeType;
 import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionEventTransition.SubscriptionTransitionType;
 import com.ning.billing.junction.api.Blockable;
 import com.ning.billing.junction.api.BlockingApi;
 import com.ning.billing.junction.api.BlockingState;
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingEvent.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingEvent.java
index ec1a575..bee9113 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingEvent.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingEvent.java
@@ -26,11 +26,12 @@ import com.ning.billing.catalog.api.CatalogApiException;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
+
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.billing.BillingModeType;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionEventTransition;
-import com.ning.billing.entitlement.api.user.SubscriptionEventTransition.SubscriptionTransitionType;
 
 public class DefaultBillingEvent implements BillingEvent {
     final private Account account;
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java
index 874ba13..435ee50 100644
--- a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java
@@ -25,7 +25,6 @@ import java.util.ArrayList;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.UUID;
@@ -44,10 +43,10 @@ import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.billing.BillingModeType;
 import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionEventTransition.SubscriptionTransitionType;
 import com.ning.billing.junction.api.Blockable;
 import com.ning.billing.junction.api.Blockable.Type;
 import com.ning.billing.junction.api.BlockingApi;
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultBillingEvent.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultBillingEvent.java
index bd7d159..92e2bf2 100644
--- a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultBillingEvent.java
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultBillingEvent.java
@@ -37,11 +37,14 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
+
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.Subscription;
+
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.billing.BillingModeType;
-import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionEventTransition.SubscriptionTransitionType;
 import com.ning.billing.junction.plumbing.billing.DefaultBillingEvent;
+
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultEntitlementBillingApi.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultEntitlementBillingApi.java
index d43af31..c69c566 100644
--- a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultEntitlementBillingApi.java
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultEntitlementBillingApi.java
@@ -23,6 +23,7 @@ import static org.testng.Assert.assertTrue;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.SortedSet;
 import java.util.TreeSet;
@@ -52,6 +53,7 @@ import com.ning.billing.catalog.api.PriceList;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
@@ -59,16 +61,15 @@ import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.entitlement.api.user.SubscriptionEventTransition;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
 import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
 import com.ning.billing.entitlement.events.user.ApiEventType;
 import com.ning.billing.junction.api.BillingApi;
 import com.ning.billing.junction.api.Blockable;
-import com.ning.billing.junction.api.DefaultBlockingState;
 import com.ning.billing.junction.api.Blockable.Type;
 import com.ning.billing.junction.api.BlockingApi;
 import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
 import com.ning.billing.lifecycle.KillbillService.ServiceException;
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
@@ -124,9 +125,9 @@ public class TestDefaultEntitlementBillingApi {
 	private static final UUID bunId = new UUID(2L,0L);
 
 	private CatalogService catalogService;
-	private ArrayList<SubscriptionBundle> bundles;
-	private ArrayList<Subscription> subscriptions;
-	private ArrayList<SubscriptionEventTransition> transitions;
+	private List<SubscriptionBundle> bundles;
+	private List<Subscription> subscriptions;
+	private List<SubscriptionEventTransition> subscriptionTransitions;
     private EntitlementUserApi entitlementApi;
     private BlockingCalculator blockCalculator = new BlockingCalculator(null) {
         @Override
@@ -136,6 +137,7 @@ public class TestDefaultEntitlementBillingApi {
         
     };
 
+
 	private Clock clock;
 	private Subscription subscription;
 	private DateTime subscriptionStartDate;
@@ -153,8 +155,8 @@ public class TestDefaultEntitlementBillingApi {
 		bundles.add(bundle);
 
 
-		transitions = new ArrayList<SubscriptionEventTransition>();
-		subscriptions = new ArrayList<Subscription>();
+		subscriptionTransitions = new LinkedList<SubscriptionEventTransition>();
+		subscriptions = new LinkedList<Subscription>();
 
 		SubscriptionBuilder builder = new SubscriptionBuilder();
 		subscriptionStartDate = clock.getUTCNow().minusDays(3);
@@ -162,7 +164,7 @@ public class TestDefaultEntitlementBillingApi {
 		subscription = new SubscriptionData(builder) {
 		    @Override
             public List<SubscriptionEventTransition> getBillingTransitions() {
-		    	return transitions;
+		    	return subscriptionTransitions;
 		    }
 		};
 
@@ -202,8 +204,8 @@ public class TestDefaultEntitlementBillingApi {
 		PlanPhase nextPhase = nextPlan.getAllPhases()[0]; // The trial has no billing period
         PriceList nextPriceList = catalogService.getFullCatalog().findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
 		SubscriptionEventTransition t = new SubscriptionTransitionData(
-				eventId, subId, bunId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList, 1, null, true);
-		transitions.add(t);
+		        eventId, subId, bunId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList, 1, null, true);
+		subscriptionTransitions.add(t);
 
 
         AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
@@ -228,8 +230,8 @@ public class TestDefaultEntitlementBillingApi {
 		PlanPhase nextPhase = nextPlan.getAllPhases()[1];
 		PriceList nextPriceList = catalogService.getFullCatalog().findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
 		SubscriptionEventTransition t = new SubscriptionTransitionData(
-				eventId, subId, bunId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList, 1, null, true);
-		transitions.add(t);
+		        eventId, subId, bunId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList, 1, null, true);
+		subscriptionTransitions.add(t);
 
 		Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
 		((ZombieControl)account).addResult("getBillCycleDay", 1).addResult("getTimeZone", DateTimeZone.UTC)
@@ -256,8 +258,8 @@ public class TestDefaultEntitlementBillingApi {
 		PlanPhase nextPhase = nextPlan.getAllPhases()[1];
         PriceList nextPriceList = catalogService.getFullCatalog().findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
 		SubscriptionEventTransition t = new SubscriptionTransitionData(
-				eventId, subId, bunId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList, 1, null, true);
-		transitions.add(t);
+		        eventId, subId, bunId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList, 1, null, true);
+		subscriptionTransitions.add(t);
 
         AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
         Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
@@ -283,8 +285,8 @@ public class TestDefaultEntitlementBillingApi {
 		PlanPhase nextPhase = nextPlan.getAllPhases()[0];
         PriceList nextPriceList = catalogService.getFullCatalog().findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
 		SubscriptionEventTransition t = new SubscriptionTransitionData(
-				eventId, subId, bunId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList, 1, null, true);
-		transitions.add(t);
+		        eventId, subId, bunId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList, 1, null, true);
+		subscriptionTransitions.add(t);
 
 		Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
 		((ZombieControl)account).addResult("getBillCycleDay", 1).addResult("getTimeZone", DateTimeZone.UTC);
@@ -312,7 +314,7 @@ public class TestDefaultEntitlementBillingApi {
         PriceList nextPriceList = catalogService.getFullCatalog().findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
         SubscriptionEventTransition t = new SubscriptionTransitionData(
                 eventId, subId, bunId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList, 1, null, true);
-        transitions.add(t);
+        subscriptionTransitions.add(t);
 
         AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
         Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
@@ -400,6 +402,4 @@ public class TestDefaultEntitlementBillingApi {
 		Assert.assertEquals(BillingModeType.IN_ADVANCE, event.getBillingMode());
 		Assert.assertEquals(desc, event.getTransitionType().toString());
 	}
-
-
 }

pom.xml 5(+1 -4)

diff --git a/pom.xml b/pom.xml
index 73d1456..a36879b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -446,9 +446,6 @@
                                 <exclude>**/target/**</exclude>
                                 <exclude>**/.settings/**</exclude>
                                 <exclude>.travis.yml</exclude>
-                                <!--  until we merge from server branch we disable rat for those -->
-                                <exclude>jaxrs/**</exclude>
-                                <exclude>server/**</exclude>
                                 <exclude>bin/**</exclude>
 
                             </excludes>
@@ -534,7 +531,7 @@
                             <systemPropertyVariables>
                                 <log4j.configuration>file:${project.basedir}/src/test/resources/log4j.xml</log4j.configuration>
                                 <com.ning.billing.dbi.test.useLocalDb>true</com.ning.billing.dbi.test.useLocalDb>
-                                <com.ning.billing.dbi.jdbc.url>jdbc:mysql://127.0.0.1:3306/test_killbill</com.ning.billing.dbi.jdbc.url>
+                                <com.ning.billing.dbi.jdbc.url>jdbc:mysql://127.0.0.1:3306/killbill</com.ning.billing.dbi.jdbc.url>
                                 <file.encoding>UTF-8</file.encoding>
                                 <user.timezone>GMT</user.timezone>
                             </systemPropertyVariables>

server/pom.xml 14(+9 -5)

diff --git a/server/pom.xml b/server/pom.xml
index ea39345..4bc0388 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -34,8 +34,7 @@
 	</properties>
 
 	<dependencies>
-
-
+                <!-- Jetty provided scope -->
 		<dependency>
 			<groupId>org.eclipse.jetty</groupId>
 			<artifactId>jetty-http</artifactId>
@@ -80,8 +79,6 @@
 			<scope>test</scope>
 		</dependency>
 
-
-
 		<!-- NOT in master POM; include version as well -->
 		<dependency>
 			<groupId>org.weakref</groupId>
@@ -118,7 +115,6 @@
 			<artifactId>javax.servlet-api</artifactId>
 			<version>3.0.1</version>
 		</dependency>
-		<!-- Do we want to depend on skeleton -->
 		<dependency>
 			<groupId>com.ning.jetty</groupId>
 			<artifactId>ning-service-skeleton-base</artifactId>
@@ -195,6 +191,10 @@
 			<groupId>com.ning.billing</groupId>
 			<artifactId>killbill-analytics</artifactId>
 		</dependency>
+                <dependency>
+                        <groupId>com.ning.billing</groupId>
+                        <artifactId>killbill-junction</artifactId>
+                </dependency>
 		<dependency>
 			<groupId>com.google.guava</groupId>
 			<artifactId>guava</artifactId>
@@ -233,6 +233,10 @@
 			<groupId>joda-time</groupId>
 			<artifactId>joda-time</artifactId>
 		</dependency>
+                <dependency>
+                        <groupId>commons-io</groupId>
+                        <artifactId>commons-io</artifactId>
+                </dependency>
 		<dependency>
 			<groupId>com.ning</groupId>
 			<artifactId>async-http-client</artifactId>
diff --git a/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java b/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
index 11de6ec..3ec9e22 100644
--- a/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
+++ b/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
@@ -29,6 +29,8 @@ import com.ning.jetty.core.listeners.SetupServer;
 import com.google.inject.Injector;
 import com.google.inject.Module;
 
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -42,8 +44,7 @@ public class KillbillGuiceListener extends SetupServer
     private BusService killbillBusService;
     private KillbillEventHandler killbilleventHandler;
 
-    protected Injector theInjector;
-    
+
     protected Module getModule() {
     	return new KillbillServerModule();
     }
@@ -66,11 +67,11 @@ public class KillbillGuiceListener extends SetupServer
         super.contextInitialized(event);
 
         logger.info("KillbillLifecycleListener : contextInitialized");
-        theInjector = injector(event);
+        Injector theInjector = injector(event);
         killbillLifecycle = theInjector.getInstance(DefaultLifecycle.class);
         killbillBusService = theInjector.getInstance(BusService.class);
         killbilleventHandler = theInjector.getInstance(KillbillEventHandler.class); 
-
+        
         //
         // Fire all Startup levels up to service start
         //
diff --git a/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
index 2cdced0..5d76b4e 100644
--- a/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
+++ b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
@@ -33,6 +33,7 @@ import com.ning.billing.jaxrs.resources.InvoiceResource;
 import com.ning.billing.jaxrs.resources.PaymentResource;
 import com.ning.billing.jaxrs.resources.SubscriptionResource;
 import com.ning.billing.jaxrs.util.KillbillEventHandler;
+import com.ning.billing.junction.glue.JunctionModule;
 import com.ning.billing.payment.setup.PaymentModule;
 import com.ning.billing.util.glue.BusModule;
 import com.ning.billing.util.glue.CallContextModule;
@@ -83,6 +84,7 @@ public class KillbillServerModule extends AbstractModule
         install(new AnalyticsModule());
         install(new PaymentModule());
         install(new BeatrixModule());
+        install(new JunctionModule());        
         installClock();
     }
 }
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java b/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
index 4f0a4b2..8d971ec 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
@@ -16,6 +16,8 @@
 package com.ning.billing.jaxrs;
 
 
+import static org.testng.Assert.assertNotNull;
+
 import java.util.HashMap;
 import java.util.Map;
 
@@ -23,13 +25,20 @@ import java.util.Map;
 import javax.ws.rs.core.Response.Status;
 
 
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
 
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.AccountTimelineJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
 import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
 import com.ning.http.client.Response;
 
@@ -91,4 +100,44 @@ public class TestAccount extends TestJaxrsBase {
 		Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
 		Assert.assertEquals(response.getStatusCode(), Status.NOT_FOUND.getStatusCode());
 	}
+	
+	@Test(groups="slow", enabled=true)
+	public void testAccountTimeline() throws Exception {
+	    
+	    DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+        
+        
+	    AccountJson accountJson = createAccount("poney", "shdddqgfhwe", "poney@yahoo.com");
+	    assertNotNull(accountJson);
+	    
+	    BundleJsonNoSubsciptions bundleJson = createBundle(accountJson.getAcountId(), "996599");
+	    assertNotNull(bundleJson);
+	    
+        SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), "Shotgun", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), true);
+        assertNotNull(subscriptionJson);
+        
+        // MOVE AFTER TRIAL
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(3).plusDays(1));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        crappyWaitForLackOfProperSynchonization();
+        
+        
+        final String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + accountJson.getAcountId() + "/" + BaseJaxrsResource.TIMELINE;
+        
+        Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        String baseJson = response.getResponseBody();
+        AccountTimelineJson objFromJson = mapper.readValue(baseJson, AccountTimelineJson.class);
+        assertNotNull(objFromJson);
+        log.info(baseJson);
+        
+        Assert.assertEquals(objFromJson.getPayments().size(), 3);
+        Assert.assertEquals(objFromJson.getInvoices().size(), 4);   
+        Assert.assertEquals(objFromJson.getBundles().size(), 1); 
+        Assert.assertEquals(objFromJson.getBundles().get(0).getSubscriptions().size(), 1);
+        Assert.assertEquals(objFromJson.getBundles().get(0).getSubscriptions().get(0).getEvents().size(), 2);        
+ 
+	}
 }
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java b/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
index d340ab8..0d1f1cd 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
@@ -30,7 +30,7 @@ import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.ning.billing.jaxrs.json.AccountJson;
-import com.ning.billing.jaxrs.json.BundleJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
 import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
 import com.ning.http.client.Response;
 
@@ -44,7 +44,7 @@ public class TestBundle extends TestJaxrsBase {
 	public void testBundleOk() throws Exception {
 
 		AccountJson accountJson = createAccount("xlxl", "shdgfhkkl", "xlxl@yahoo.com");
-		BundleJson bundleJson = createBundle(accountJson.getAcountId(), "12345");
+		BundleJsonNoSubsciptions bundleJson = createBundle(accountJson.getAcountId(), "12345");
 		
 		// Retrieves by external key
 		Map<String, String> queryParams = new HashMap<String, String>();
@@ -52,7 +52,7 @@ public class TestBundle extends TestJaxrsBase {
 		Response response = doGet(BaseJaxrsResource.BUNDLES_PATH, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
 		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
 		String baseJson = response.getResponseBody();
-		BundleJson objFromJson = mapper.readValue(baseJson, BundleJson.class);
+		BundleJsonNoSubsciptions objFromJson = mapper.readValue(baseJson, BundleJsonNoSubsciptions.class);
 		Assert.assertTrue(objFromJson.equals(bundleJson));
 	}
 	
@@ -61,18 +61,18 @@ public class TestBundle extends TestJaxrsBase {
 	public void testBundleFromAccount() throws Exception {
 
 		AccountJson accountJson = createAccount("xaxa", "saagfhkkl", "xaxa@yahoo.com");
-		BundleJson bundleJson1 = createBundle(accountJson.getAcountId(), "156567");
-		BundleJson bundleJson2 = createBundle(accountJson.getAcountId(), "265658");
+		BundleJsonNoSubsciptions bundleJson1 = createBundle(accountJson.getAcountId(), "156567");
+		BundleJsonNoSubsciptions bundleJson2 = createBundle(accountJson.getAcountId(), "265658");
 
 		String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + accountJson.getAcountId().toString() + "/" + BaseJaxrsResource.BUNDLES;
 		Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
 		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
 		String baseJson = response.getResponseBody();
-		List<BundleJson> objFromJson = mapper.readValue(baseJson, new TypeReference<List<BundleJson>>() {});
+		List<BundleJsonNoSubsciptions> objFromJson = mapper.readValue(baseJson, new TypeReference<List<BundleJsonNoSubsciptions>>() {});
 		
-		Collections.sort(objFromJson, new Comparator<BundleJson>() {
+		Collections.sort(objFromJson, new Comparator<BundleJsonNoSubsciptions>() {
 			@Override
-			public int compare(BundleJson o1, BundleJson o2) {
+			public int compare(BundleJsonNoSubsciptions o1, BundleJsonNoSubsciptions o2) {
 				return o1.getExternalKey().compareTo(o2.getExternalKey());
 			}
 		});
@@ -100,7 +100,7 @@ public class TestBundle extends TestJaxrsBase {
 		response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
 		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
 		String baseJson = response.getResponseBody();
-		List<BundleJson> objFromJson = mapper.readValue(baseJson, new TypeReference<List<BundleJson>>() {});
+		List<BundleJsonNoSubsciptions> objFromJson = mapper.readValue(baseJson, new TypeReference<List<BundleJsonNoSubsciptions>>() {});
 		Assert.assertNotNull(objFromJson);
 		Assert.assertEquals(objFromJson.size(), 0);
 	}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
index 2853598..db6206b 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -30,6 +30,7 @@ import java.util.concurrent.TimeUnit;
 
 import javax.ws.rs.core.Response.Status;
 
+import org.apache.commons.io.IOUtils;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.eclipse.jetty.servlet.FilterHolder;
 import org.skife.config.ConfigurationObjectFactory;
@@ -42,12 +43,12 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.BeforeSuite;
 
-import com.google.inject.Injector;
 import com.google.inject.Module;
 import com.ning.billing.account.glue.AccountModule;
 import com.ning.billing.analytics.setup.AnalyticsModule;
 import com.ning.billing.beatrix.glue.BeatrixModule;
 import com.ning.billing.beatrix.integration.TestBusHandler;
+import com.ning.billing.beatrix.integration.TestIntegration;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.glue.CatalogModule;
 import com.ning.billing.config.PaymentConfig;
@@ -57,8 +58,8 @@ import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.entitlement.glue.EntitlementModule;
 import com.ning.billing.invoice.glue.InvoiceModule;
 import com.ning.billing.jaxrs.json.AccountJson;
-import com.ning.billing.jaxrs.json.BundleJson;
-import com.ning.billing.jaxrs.json.SubscriptionJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
 import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
 import com.ning.billing.junction.glue.JunctionModule;
 import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
@@ -93,13 +94,12 @@ public class TestJaxrsBase {
     public static final String HEADER_CONTENT_TYPE = "Content-type";
     public static final String CONTENT_TYPE = "application/json";
 
+    private static TestKillbillGuiceListener listener;
+    
+    
     private MysqlTestingHelper helper;
     private HttpServer server;
 
-
-    // YAck...
-    private static Injector injector;
-
     protected CoreConfig config;
     protected AsyncHttpClient httpClient;	
     protected ObjectMapper mapper;
@@ -117,23 +117,48 @@ public class TestJaxrsBase {
     }
 
     public static class TestKillbillGuiceListener extends KillbillGuiceListener {
-        public TestKillbillGuiceListener() {
+        
+        private final MysqlTestingHelper helper;
+        private final Clock clock;
+        
+        public TestKillbillGuiceListener(final MysqlTestingHelper helper, final Clock clock) {
             super();
+            this.helper = helper;
+            this.clock = clock;
         }
         @Override
         protected Module getModule() {
-            return new TestKillbillServerModule();
+            return new TestKillbillServerModule(helper, clock);
+        }
+        
+        //
+        // Listener is created once before Suite and keeps pointer to helper and clock so they can get
+        // reset for each test Class-- needed in order to ONLY start Jetty once across all the test classes
+        // while still being able to start mysql before Jetty is started
+        //
+        public MysqlTestingHelper getMysqlTestingHelper() {
+            return helper;
         }
-        public Injector getTheInjector() {
-            return theInjector;
+        
+        public Clock getClock() {
+            return clock;
         }
     }
 
     public static class TestKillbillServerModule extends KillbillServerModule {
 
+        private final MysqlTestingHelper helper;
+        private final Clock clock;
+        
+        public TestKillbillServerModule(final MysqlTestingHelper helper, final Clock clock) {
+            super();
+            this.helper = helper;
+            this.clock = clock;
+        }
+        
         @Override
         protected void installClock() {
-            bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+            bind(Clock.class).toInstance(clock);
         }
 
 
@@ -170,7 +195,6 @@ public class TestJaxrsBase {
 
         @Override
         protected void configureDao() {
-            final MysqlTestingHelper helper = new MysqlTestingHelper();
             bind(MysqlTestingHelper.class).toInstance(helper);
             if (helper.isUsingLocalInstance()) {
                 bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
@@ -184,21 +208,40 @@ public class TestJaxrsBase {
     }
 
     @BeforeMethod(groups="slow")
-    public void cleanupTables() {
-        helper.cleanupAllTables();
+    public void cleanupBeforeMethod() {
         busHandler.reset();
+        helper.cleanupAllTables();
     }
 
     @BeforeClass(groups="slow")
-    public void setupClass() {
-
+    public void setupClass() throws IOException {
         loadConfig();
         httpClient = new AsyncHttpClient();
         mapper = new ObjectMapper();
-        helper = injector.getInstance(MysqlTestingHelper.class);
-        clock = (ClockMock) injector.getInstance(Clock.class);
-        busHandler = new TestBusHandler();
+        busHandler = new TestBusHandler(null);
+        this.helper = listener.getMysqlTestingHelper();
+        this.clock =  (ClockMock) listener.getClock();
     }
+    
+    private void setupMySQL() throws IOException {
+        final String accountDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
+        final String entitlementDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+        final String invoiceDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+        final String paymentDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+        final String utilDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+        final String analyticsDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/analytics/ddl.sql"));        
+        final String junctionDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/junction/ddl.sql"));        
+
+        helper.startMysql();
+
+        helper.initDb(accountDdl);
+        helper.initDb(entitlementDdl);
+        helper.initDb(invoiceDdl);
+        helper.initDb(paymentDdl);
+        helper.initDb(utilDdl);
+        helper.initDb(analyticsDdl);        
+        helper.initDb(junctionDdl);        
+   }
 
 
     private void loadConfig() {
@@ -211,33 +254,39 @@ public class TestJaxrsBase {
     public void setup() throws Exception {
 
         loadSystemPropertiesFromClasspath("/killbill.properties");
-
-        final EventListener eventListener = new TestKillbillGuiceListener();
-        server = new HttpServer();
         loadConfig();
+        
+        this.helper = new MysqlTestingHelper();
+        this.clock = new ClockMock();
+        listener = new TestKillbillGuiceListener(helper, clock);
+        server = new HttpServer();
+
         final Iterable<EventListener> eventListeners = new Iterable<EventListener>() {
             @Override
             public Iterator<EventListener> iterator() {
                 ArrayList<EventListener> array = new ArrayList<EventListener>();
-                array.add(eventListener);
+                array.add(listener);
                 return array.iterator();
             }
         };
         server.configure(config, eventListeners, new HashMap<FilterHolder, String>());
+
+        setupMySQL();
+        helper.cleanupAllTables();
+
         server.start();
-        injector = ((TestKillbillGuiceListener) eventListener).getTheInjector();		
     }
 
     @AfterSuite(groups="slow")
     public void tearDown() {
-        if (helper != null) {
-            helper.stopMysql();
-        }
         try {
             server.stop();
         } catch (Exception e) {
 
         }
+        if (helper != null) {
+            helper.stopMysql();
+        }
     }
 
 
@@ -262,8 +311,8 @@ public class TestJaxrsBase {
 
 
 
-    protected BundleJson createBundle(String accountId, String key) throws Exception {
-        BundleJson input = new BundleJson(null, accountId, key, null);
+    protected BundleJsonNoSubsciptions createBundle(String accountId, String key) throws Exception {
+        BundleJsonNoSubsciptions input = new BundleJsonNoSubsciptions(null, accountId, key, null);
         String baseJson = mapper.writeValueAsString(input);
         Response response = doPost(BaseJaxrsResource.BUNDLES_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
         Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
@@ -276,14 +325,14 @@ public class TestJaxrsBase {
         Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
 
         baseJson = response.getResponseBody();
-        BundleJson objFromJson = mapper.readValue(baseJson, BundleJson.class);
+        BundleJsonNoSubsciptions objFromJson = mapper.readValue(baseJson, BundleJsonNoSubsciptions.class);
         Assert.assertTrue(objFromJson.equalsNoId(input));
         return objFromJson;
     }
 
-    protected SubscriptionJson createSubscription(final String bundleId, final String productName, final String productCategory, final String billingPeriod, final boolean waitCompletion) throws Exception {
+    protected SubscriptionJsonNoEvents createSubscription(final String bundleId, final String productName, final String productCategory, final String billingPeriod, final boolean waitCompletion) throws Exception {
 
-        SubscriptionJson input = new SubscriptionJson(null, bundleId, productName, productCategory, billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, null, null, null);
+        SubscriptionJsonNoEvents input = new SubscriptionJsonNoEvents(null, bundleId, null, productName, productCategory, billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, null);
         String baseJson = mapper.writeValueAsString(input);
 
 
@@ -300,7 +349,7 @@ public class TestJaxrsBase {
         Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
 
         baseJson = response.getResponseBody();
-        SubscriptionJson objFromJson = mapper.readValue(baseJson, SubscriptionJson.class);
+        SubscriptionJsonNoEvents objFromJson = mapper.readValue(baseJson, SubscriptionJsonNoEvents.class);
         Assert.assertTrue(objFromJson.equalsNoId(input));
         return objFromJson;
     }
@@ -405,4 +454,14 @@ public class TestJaxrsBase {
         AccountJson accountJson = new AccountJson(accountId, name, length, externalKey, email, billCycleDay, currency, paymentProvider, timeZone, address1, address2, company, state, country, phone);
         return accountJson;
     }
+    
+    /**
+     * 
+     * We could implement a ClockResource in jaxrs with the ability to sync on user token
+     * but until we have a strong need for it, this is in the TODO list...
+     */
+    protected void crappyWaitForLackOfProperSynchonization() throws Exception {
+        Thread.sleep(5000);
+    }
+
 }
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java b/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
index 3bd2524..d520d49 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
@@ -15,11 +15,16 @@
  */
 package com.ning.billing.jaxrs;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
 import java.util.Map;
+import java.util.UUID;
 
 import javax.ws.rs.core.Response.Status;
 
 import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
@@ -27,11 +32,12 @@ import org.testng.annotations.Test;
 
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.catalog.api.TimeUnit;
 import com.ning.billing.jaxrs.json.AccountJson;
-import com.ning.billing.jaxrs.json.BundleJson;
-import com.ning.billing.jaxrs.json.SubscriptionJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
 import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
 import com.ning.http.client.Response;
 
@@ -39,54 +45,57 @@ public class TestSubscription extends TestJaxrsBase {
 
     private static final Logger log = LoggerFactory.getLogger(TestSubscription.class);
 
-    private static final long DELAY = 5000;
     private static final String CALL_COMPLETION_TIMEOUT_SEC = "5";
     
 
     @Test(groups="slow", enabled=true)
     public void testSubscriptionInTrialOk() throws Exception {
 
+        DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+        
         AccountJson accountJson = createAccount("xil", "shdxilhkkl", "xil@yahoo.com");
-        BundleJson bundleJson = createBundle(accountJson.getAcountId(), "99999");
+        BundleJsonNoSubsciptions bundleJson = createBundle(accountJson.getAcountId(), "99999");
 
         String productName = "Shotgun";
         BillingPeriod term = BillingPeriod.MONTHLY;
 
-        SubscriptionJson subscriptionJson = createSubscription(bundleJson.getBundleId(), productName, ProductCategory.BASE.toString(), term.toString(), true);
-
+        SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), productName, ProductCategory.BASE.toString(), term.toString(), true);
+        Assert.assertNotNull(subscriptionJson.getChargedThroughDate());
+        Assert.assertEquals(subscriptionJson.getChargedThroughDate(), subscriptionJson.getStartDate().plusDays(30));        
+        
+        String uri = BaseJaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId().toString();
+ 
+        
+        // Retrieves with GET
+        Response response =  doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        String baseJson = response.getResponseBody();
+        SubscriptionJsonNoEvents objFromJson = mapper.readValue(baseJson, SubscriptionJsonNoEvents.class);
+        Assert.assertTrue(objFromJson.equals(subscriptionJson));
+        
         // Change plan IMM
         String newProductName = "Assault-Rifle";
 
-        SubscriptionJson newInput = new SubscriptionJson(subscriptionJson.getSubscriptionId(),
+        SubscriptionJsonNoEvents newInput = new SubscriptionJsonNoEvents(subscriptionJson.getSubscriptionId(),
                 subscriptionJson.getBundleId(),
+                null,
                 newProductName,
                 subscriptionJson.getProductCategory(), 
                 subscriptionJson.getBillingPeriod(), 
-                subscriptionJson.getPriceList(), null, null, null);
-        String baseJson = mapper.writeValueAsString(newInput);
+                subscriptionJson.getPriceList(), null);
+        baseJson = mapper.writeValueAsString(newInput);
 
-        String uri = BaseJaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId().toString();
         Map<String, String> queryParams = getQueryParamsForCallCompletion(CALL_COMPLETION_TIMEOUT_SEC);
-        Response response = doPut(uri, baseJson, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
-        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        response = doPut(uri, baseJson, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
         baseJson = response.getResponseBody();
-        SubscriptionJson objFromJson = mapper.readValue(baseJson, SubscriptionJson.class);
-        Assert.assertTrue(objFromJson.equals(newInput));
-
-        clock.setDeltaFromReality(new Duration() {
-            @Override
-            public TimeUnit getUnit() {
-                return TimeUnit.MONTHS;
-            }
-            @Override
-            public int getNumber() {
-                return 1;
-            }
-            @Override
-            public DateTime addToDateTime(DateTime dateTime) {
-                return null;
-            }
-        }, 1000);
+        objFromJson = mapper.readValue(baseJson, SubscriptionJsonNoEvents.class);
+        assertTrue(objFromJson.equals(newInput));
+
+        // MOVE AFTER TRIAL
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(3).plusDays(1));
+        clock.addDeltaFromReality(it.toDurationMillis());
 
         crappyWaitForLackOfProperSynchonization();
 
@@ -94,22 +103,32 @@ public class TestSubscription extends TestJaxrsBase {
         // Cancel EOT
         uri = BaseJaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId().toString();
         response = doDelete(uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
-        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
 
         // Uncancel
         uri = BaseJaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId().toString() + "/uncancel";
         response = doPut(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
         Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
     }
- 
-
-    /**
-     * 
-     * We could implement a ClockResource in jaxrs with the ability to sync on user token
-     * but until we have a strong need for it, this is in the TODO list...
-     */
-    private void crappyWaitForLackOfProperSynchonization() throws Exception {
-        Thread.sleep(DELAY);
+    
+    @Test(groups="slow", enabled=true)
+    public void testWithNonExistentSubscription() throws Exception {
+        String uri = BaseJaxrsResource.SUBSCRIPTIONS_PATH + "/" + UUID.randomUUID().toString();
+        SubscriptionJsonNoEvents subscriptionJson = new SubscriptionJsonNoEvents(null, UUID.randomUUID().toString(), null, "Pistol", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), PriceListSet.DEFAULT_PRICELIST_NAME, null);
+        String baseJson = mapper.writeValueAsString(subscriptionJson);
+        
+        Response response = doPut(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+        String body = response.getResponseBody();
+        Assert.assertEquals(body, "");
+        
+        response = doDelete(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+        body = response.getResponseBody();
+        Assert.assertEquals(body, "");
+        
+        response =  doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
     }
 
 }
diff --git a/server/src/test/resources/killbill.properties b/server/src/test/resources/killbill.properties
index 2ff0814..3463266 100644
--- a/server/src/test/resources/killbill.properties
+++ b/server/src/test/resources/killbill.properties
@@ -1,5 +1,5 @@
-# Use killbill util test properties (DbiProvider/MysqltestingHelper) on the test side configured with test_killbill
-com.ning.billing.dbi.jdbc.url=jdbc:mysql://127.0.0.1:3306/test_killbill
+# Use killbill util test properties (DbiProvider/MysqltestingHelper) on the test side configured with killbill
+com.ning.billing.dbi.jdbc.url=jdbc:mysql://127.0.0.1:3306/killbill
 
 killbill.catalog.uri=file:src/test/resources/catalog-weapons.xml
 
@@ -8,9 +8,10 @@ killbill.payment.retry.days=8,8,8
 
 user.timezone=UTC
 
-com.ning.billing.dbi.test.useLocalDb=true
-
 com.ning.core.server.jetty.logPath=/var/tmp/.logs
 
+# Local DB 
+#com.ning.billing.dbi.test.useLocalDb=true
+
 
 
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 9995229..1b0d9f6 100644
--- a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
+++ b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
@@ -46,7 +46,7 @@ public class MysqlTestingHelper
 
     private static final Logger log = LoggerFactory.getLogger(MysqlTestingHelper.class);
 
-    private static final String DB_NAME = "test_killbill";
+    private static final String DB_NAME = "killbill";
     private static final String USERNAME = "root";
     private static final String PASSWORD = "root";