killbill-aplcache

Changes

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 4f2e0d9..4c33dac 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
@@ -23,8 +23,8 @@ import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.InternationalPrice;
 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;
 
 import java.math.BigDecimal;
 
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/repair/BundleRepair.java b/api/src/main/java/com/ning/billing/entitlement/api/repair/BundleRepair.java
new file mode 100644
index 0000000..27dadd6
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/repair/BundleRepair.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.repair;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface BundleRepair {
+
+    String getViewId();
+    
+    UUID getBundleId();
+
+    List<SubscriptionRepair> getSubscriptions();
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/repair/EntitlementRepairApi.java b/api/src/main/java/com/ning/billing/entitlement/api/repair/EntitlementRepairApi.java
new file mode 100644
index 0000000..de6ae59
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/repair/EntitlementRepairApi.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.repair;
+
+import java.util.UUID;
+
+import com.ning.billing.util.callcontext.CallContext;
+
+public interface EntitlementRepairApi {
+    
+    public BundleRepair getBundleRepair(final UUID bundleId) throws EntitlementRepairException;
+    
+    public BundleRepair repairBundle(final BundleRepair input, final boolean dryRun, final CallContext context) throws EntitlementRepairException;
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/repair/EntitlementRepairException.java b/api/src/main/java/com/ning/billing/entitlement/api/repair/EntitlementRepairException.java
new file mode 100644
index 0000000..e99a283
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/repair/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.repair;
+
+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/repair/SubscriptionRepair.java b/api/src/main/java/com/ning/billing/entitlement/api/repair/SubscriptionRepair.java
new file mode 100644
index 0000000..adab3b5
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/repair/SubscriptionRepair.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.repair;
+
+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 SubscriptionRepair {
+
+    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/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/user/EntitlementUserApiException.java b/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApiException.java
index 693938b..08f2906 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
@@ -34,5 +34,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 b1efaad..7521f92 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
@@ -18,6 +18,7 @@ package com.ning.billing.entitlement.api.user;
 
 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.SubscriptionState;
 import com.ning.billing.util.bus.BusEvent;
 import org.joda.time.DateTime;
@@ -26,17 +27,6 @@ import java.util.UUID;
 
 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 57ea8d9..8a833ec 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
      */
@@ -56,6 +58,19 @@ public enum ErrorCode {
     ENT_GET_NO_BUNDLE_FOR_SUBSCRIPTION(1080, "Could not find a bundle for subscription %s"),
     ENT_GET_INVALID_BUNDLE_ID(1081, "Could not find a bundle matching id %s"),
     ENT_INVALID_SUBSCRIPTION_ID(1082, "Unknown subscription %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_AO_EVENT_BEFORE_BP(1094, "New event %s for subscription %s is before last remaining event for BP"),
+    ENT_REPAIR_INVALID_NEW_AO_EVENT(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"),             
+    
     /*
     *
     * Range 2000 : CATALOG
@@ -153,6 +168,7 @@ public enum ErrorCode {
     INVOICE_TARGET_DATE_TOO_FAR_IN_THE_FUTURE(4005, "The target date was too far in the future. Target Date: %s")
     ;
 
+    
     private int code;
     private String format;
 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultBillingEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultBillingEvent.java
index bc89040..6acc98d 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultBillingEvent.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultBillingEvent.java
@@ -23,9 +23,9 @@ 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;
-import com.ning.billing.entitlement.api.user.SubscriptionEventTransition.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
 
 import java.math.BigDecimal;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java
index 8c3e83f..a3a22cf 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java
@@ -45,18 +45,17 @@ import com.ning.billing.catalog.api.BillingAlignment;
 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.Currency;
 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.SubscriptionFactory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 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.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.api.user.SubscriptionEventTransition;
-import com.ning.billing.entitlement.api.user.SubscriptionEventTransition.SubscriptionTransitionType;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.engine.dao.SubscriptionSqlDao;
 
@@ -72,7 +71,8 @@ public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
     private static final String SUBSCRIPTION_TABLE_NAME = "subscriptions";
 
     @Inject
-    public DefaultEntitlementBillingApi(final CallContextFactory factory, final SubscriptionFactory subscriptionFactory, final EntitlementDao dao, final AccountUserApi accountApi, final CatalogService catalogService) {
+    public DefaultEntitlementBillingApi(final CallContextFactory factory, final SubscriptionFactory subscriptionFactory,
+            final EntitlementDao dao, final AccountUserApi accountApi, final CatalogService catalogService) {
         super();
         this.factory = factory;
         this.subscriptionFactory = subscriptionFactory;
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/repair/DefaultDeletedEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/DefaultDeletedEvent.java
new file mode 100644
index 0000000..0a74fda
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/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.repair;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.entitlement.api.repair.SubscriptionRepair.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/repair/DefaultEntitlementRepairApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/DefaultEntitlementRepairApi.java
new file mode 100644
index 0000000..5dbb3a2
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/DefaultEntitlementRepairApi.java
@@ -0,0 +1,341 @@
+/* 
+ * 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.repair;
+
+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.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
+import com.ning.billing.entitlement.api.repair.SubscriptionRepair.ExistingEvent;
+import com.ning.billing.entitlement.api.repair.SubscriptionRepair.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.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 DefaultEntitlementRepairApi implements EntitlementRepairApi {
+
+    private final EntitlementDao dao;
+    private final SubscriptionFactory factory;
+    private final RepairEntitlementLifecycleDao repairDao;
+
+    @Inject
+    public DefaultEntitlementRepairApi(@Named(EntitlementModule.REPAIR_NAMED) final SubscriptionFactory factory,
+            @Named(EntitlementModule.REPAIR_NAMED) final RepairEntitlementLifecycleDao repairDao, final EntitlementDao dao) {
+        this.dao = dao;
+        this.repairDao = repairDao;
+        this.factory = factory;
+    }
+    
+
+    @Override
+    public BundleRepair getBundleRepair(final UUID bundleId) 
+    throws EntitlementRepairException {
+
+        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<SubscriptionRepair> repairs = createGetSubscriptionRepairList(subscriptions, Collections.<SubscriptionRepair>emptyList()); 
+        return createGetBundleRepair(bundleId, viewId, repairs);
+    }
+
+
+
+    @Override
+    public BundleRepair repairBundle(final BundleRepair 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_UNKNOWN_BUNDLE,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;
+
+            SubscriptionDataRepair baseSubscriptionRepair = null;
+            List<SubscriptionDataRepair> addOnSubscriptionInRepair = new LinkedList<SubscriptionDataRepair>();
+            List<SubscriptionDataRepair> inRepair =  new LinkedList<SubscriptionDataRepair>();
+            for (Subscription cur : subscriptions) {
+
+                SubscriptionRepair curRepair = findAndCreateSubscriptionRepair(cur.getId(), input.getSubscriptions());
+                if (curRepair != null) {
+                    SubscriptionDataRepair curData = ((SubscriptionDataRepair) cur);
+                    List<EntitlementEvent> remaining = getRemainingEventsAndValidateDeletedEvents(curData, firstDeletedBPEventTime, curRepair.getDeletedEvents());
+
+                    if (cur.getCategory() == ProductCategory.BASE) {
+                        int bpTransitionSize =((SubscriptionData) cur).getAllTransitions().size();
+                        lastRemainingBPEventTime = (remaining.size() > 0) ? curData.getAllTransitions().get(remaining.size() - 1).getEffectiveTransitionTime() : null;
+                        firstDeletedBPEventTime =  (remaining.size() < bpTransitionSize) ? curData.getAllTransitions().get(remaining.size()).getEffectiveTransitionTime() : null;
+                    }
+
+                    if (curRepair.getNewEvents() != null && curRepair.getNewEvents().size() > 0) {
+                        Collections.sort(curRepair.getNewEvents(), new Comparator<NewEvent>() {
+                            @Override
+                            public int compare(NewEvent o1, NewEvent o2) {
+                                return o1.getRequestedDate().compareTo(o2.getRequestedDate());
+                            }
+                        });
+                        DateTime lastRemainingEventTime = (remaining.size() == 0) ? null : curData.getAllTransitions().get(remaining.size() - 1).getEffectiveTransitionTime();
+                        validateFirstNewEvent(curData, curRepair.getNewEvents().get(0), lastRemainingBPEventTime, lastRemainingEventTime);
+                    }
+                    SubscriptionDataRepair sRepair = createSubscriptionDataRepair(curData, remaining);
+                    repairDao.initializeRepair(curData.getId(), remaining);
+                    inRepair.add(sRepair);
+                    if (sRepair.getCategory() == ProductCategory.ADD_ON) {
+                        addOnSubscriptionInRepair.add(sRepair);
+                    } else if (sRepair.getCategory() == ProductCategory.BASE) {
+                        baseSubscriptionRepair = sRepair;
+                    }
+                }
+            }
+
+            if (input.getSubscriptions().size() != inRepair.size()) {
+                for (SubscriptionRepair cur : input.getSubscriptions()) {
+                    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());
+                    }
+                }
+            }
+
+            TreeSet<NewEvent> newEventSet = new TreeSet<SubscriptionRepair.NewEvent>(new Comparator<NewEvent>() {
+                @Override
+                public int compare(NewEvent o1, NewEvent o2) {
+                    return o1.getRequestedDate().compareTo(o2.getRequestedDate());
+                }
+            });
+            for (SubscriptionRepair cur : input.getSubscriptions()) {
+                for (NewEvent e : cur.getNewEvents()) {
+                    newEventSet.add(new DefaultNewEvent(cur.getId(), e.getPlanPhaseSpecifier(), e.getRequestedDate(), e.getSubscriptionTransitionType()));    
+                }
+            }
+
+            Iterator<NewEvent> it = newEventSet.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) {
+                final List<SubscriptionRepair> repairs = createGetSubscriptionRepairList(subscriptions, convertDataRepair(inRepair)); 
+                return createGetBundleRepair(input.getBundleId(), input.getViewId(), repairs);
+            } else {
+                // STEPH no implemented yet
+                return null;
+            }
+        } finally {
+            repairDao.cleanup();
+        }
+    }
+    
+    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 BundleRepair createGetBundleRepair(final UUID bundleId, final String viewId, final List<SubscriptionRepair> repairList) {
+        return new BundleRepair() {
+            @Override
+            public String getViewId() {
+                return viewId;
+            }
+            @Override
+            public List<SubscriptionRepair> getSubscriptions() {
+                return repairList;
+            }
+            @Override
+            public UUID getBundleId() {
+                return bundleId;
+            }
+        };
+
+    }
+    
+    private List<SubscriptionRepair> createGetSubscriptionRepairList(final List<Subscription> subscriptions, final List<SubscriptionRepair> inRepair) {
+
+        final List<SubscriptionRepair> result = new LinkedList<SubscriptionRepair>();
+        Set<UUID> repairIds = new TreeSet<UUID>();
+        for (final SubscriptionRepair cur : inRepair) {
+            repairIds.add(cur.getId());
+            result.add(cur);
+        }
+        for (final Subscription cur : subscriptions) {
+            if ( !repairIds.contains(cur.getId())) { 
+                result.add(new DefaultSubscriptionRepair((SubscriptionData) cur));
+            }
+        }
+        return result;
+    }
+
+
+    private List<SubscriptionRepair> convertDataRepair(List<SubscriptionDataRepair> input) {
+        List<SubscriptionRepair> result = new LinkedList<SubscriptionRepair>();
+        for (SubscriptionDataRepair cur : input) {
+            result.add(new DefaultSubscriptionRepair(cur));
+        }
+        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 List<EntitlementEvent> initialEvents) {
+        SubscriptionBuilder builder = new SubscriptionBuilder(curData);
+        builder.setActiveVersion(curData.getActiveVersion() + 1);
+        SubscriptionDataRepair result = (SubscriptionDataRepair) factory.createSubscription(builder, initialEvents);
+        return result;
+    }
+
+    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_AO_EVENT_BEFORE_BP, firstNewEvent.getPlanPhaseSpecifier().toString(), data.getId());
+        }
+        if (lastRemainingTime != null &&
+                firstNewEvent.getRequestedDate().isBefore(lastRemainingTime)) {
+            throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_INVALID_NEW_AO_EVENT, firstNewEvent.getPlanPhaseSpecifier().toString(), data.getId());
+        }
+
+    }
+
+    private List<EntitlementEvent> getRemainingEventsAndValidateDeletedEvents(final SubscriptionDataRepair data, final DateTime firstBPDeletedTime,
+            final List<SubscriptionRepair.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 (SubscriptionRepair.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 (SubscriptionRepair.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 SubscriptionRepair findAndCreateSubscriptionRepair(final UUID target, final List<SubscriptionRepair> input) {
+        for (SubscriptionRepair cur : input) {
+            if (target.equals(cur.getId())) {
+                return new DefaultSubscriptionRepair(cur);
+            }
+        }
+        return null;
+    }
+}
+
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/DefaultNewEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/DefaultNewEvent.java
new file mode 100644
index 0000000..8b2e3bf
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/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.repair;
+
+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.repair.SubscriptionRepair.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/repair/DefaultSubscriptionRepair.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/DefaultSubscriptionRepair.java
new file mode 100644
index 0000000..6048ff1
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/DefaultSubscriptionRepair.java
@@ -0,0 +1,145 @@
+/* 
+ * 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.repair;
+
+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.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.repair.SubscriptionRepair.ExistingEvent;
+import com.ning.billing.entitlement.api.repair.SubscriptionRepair.NewEvent;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
+
+public class DefaultSubscriptionRepair implements SubscriptionRepair  {
+
+    private final UUID id;
+    private final List<ExistingEvent> existingEvents;
+    private final List<NewEvent> newEvents;
+    private final List<DeletedEvent> deletedEvents;    
+    
+    
+    public DefaultSubscriptionRepair(SubscriptionRepair input) {
+        this.id = input.getId();
+        this.existingEvents = (input.getExistingEvents() != null) ? new ArrayList<SubscriptionRepair.ExistingEvent>(input.getExistingEvents()) : null;
+        sortExistingEvent(this.existingEvents);
+        this.deletedEvents = (input.getDeletedEvents() != null) ? new ArrayList<SubscriptionRepair.DeletedEvent>(input.getDeletedEvents()) : null;
+        this.newEvents = (input.getNewEvents() != null) ? new ArrayList<SubscriptionRepair.NewEvent>(input.getNewEvents()) : null;
+        sortNewEvent(this.newEvents);
+    }
+    
+     // CTOR for returning events only
+    public DefaultSubscriptionRepair(SubscriptionData input) {
+        this.id = input.getId();
+        this.existingEvents = toExistingEvents(input.getCategory(), input.getAllTransitions());
+        this.deletedEvents = null;
+        this.newEvents = null;
+    }
+    
+    private List<ExistingEvent> toExistingEvents(final ProductCategory category, final List<SubscriptionTransitionData> transitions) {
+        List<ExistingEvent> result = new LinkedList<SubscriptionRepair.ExistingEvent>();
+        for (final SubscriptionTransitionData cur : transitions) {
+            
+            String productName = null;
+            BillingPeriod billingPeriod = null;
+            String priceListName = null;
+            PhaseType phaseType = null;
+            if (cur.getTransitionType() != SubscriptionTransitionType.CANCEL) {
+                productName = cur.getNextPlan().getProduct().getName();
+                billingPeriod = cur.getNextPhase().getBillingPeriod(); 
+                priceListName = cur.getNextPriceList(); 
+                phaseType = cur.getNextPhase().getPhaseType();
+            }
+            
+            final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
+            result.add(new ExistingEvent() {
+                @Override
+                public SubscriptionTransitionType getSubscriptionTransitionType() {
+                    return cur.getTransitionType();
+                }
+                @Override
+                public DateTime getRequestedDate() {
+                    return cur.getRequestedTransitionTime();
+                }
+                @Override
+                public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+                    return spec;
+                }
+                @Override
+                public UUID getEventId() {
+                    return cur.getId();
+                }
+                @Override
+                public DateTime getEffectiveDate() {
+                    return cur.getEffectiveTransitionTime();
+                }
+            });
+        }
+        sortExistingEvent(result);
+        return result;
+    }
+    
+    @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/repair/RepairEntitlementLifecycleDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/RepairEntitlementLifecycleDao.java
new file mode 100644
index 0000000..cf482e5
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/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.repair;
+
+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/repair/RepairSubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/RepairSubscriptionApiService.java
new file mode 100644
index 0000000..dbcca32
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/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.repair;
+
+
+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/repair/RepairSubscriptionFactory.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/RepairSubscriptionFactory.java
new file mode 100644
index 0000000..eb32bb0
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/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.repair;
+
+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.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;
+    
+    @Inject
+    public RepairSubscriptionFactory(@Named(EntitlementModule.REPAIR_NAMED) SubscriptionApiService apiService, Clock clock, CatalogService catalogService, AddonUtils addonUtils) {
+        super(apiService, clock, catalogService);
+        this.addonUtils = addonUtils;
+    }
+     
+    @Override
+    public SubscriptionData createSubscription(SubscriptionBuilder builder,
+            List<EntitlementEvent> events) {
+        SubscriptionData subscription = new SubscriptionDataRepair(builder, apiService, clock, addonUtils);
+        if (events.size() > 0) {
+            for (EntitlementEvent cur : events) {
+                cur.setActiveVersion(builder.getActiveVersion());
+            }
+            subscription.rebuildTransitions(events, catalogService.getFullCatalog());
+        }
+        return subscription;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/SubscriptionDataRepair.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/SubscriptionDataRepair.java
new file mode 100644
index 0000000..7b1f02b
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/repair/SubscriptionDataRepair.java
@@ -0,0 +1,145 @@
+/* 
+ * 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.repair;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+
+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.Plan;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PlanSpecifier;
+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.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.engine.addon.AddonUtils;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+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;
+import com.sun.org.apache.xml.internal.resolver.CatalogException;
+
+public class SubscriptionDataRepair extends SubscriptionData {
+
+    private final AddonUtils addonUtils;
+    private final Clock clock;
+
+    private final List<EntitlementEvent> newEvents;
+
+
+    // Low level events are ONLY used for Repair APIs
+    protected List<EntitlementEvent> events;
+
+
+    public SubscriptionDataRepair(SubscriptionBuilder builder, SubscriptionApiService apiService,
+            Clock clock, AddonUtils addonUtils) {
+        super(builder, apiService, clock);
+        this.addonUtils = addonUtils;
+        this.clock = clock;
+        this.newEvents = new LinkedList<EntitlementEvent>();
+    }
+
+    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);
+                break;
+            case CHANGE:
+                changePlan(spec.getProductName(), spec.getBillingPeriod(), spec.getPriceListName(), input.getRequestedDate(), context);
+                break;
+            case CANCEL:
+                cancel(input.getRequestedDate(), false, context);
+                break;
+            case PHASE:
+                break;
+            default:
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_TYPE, input.getSubscriptionTransitionType(), id);
+            }
+            
+            trickleDownBPEffectForAddon(addonSubscriptions, input.getRequestedDate(), context);
+            checkAddonRights(baseSubscription);
+            
+        } catch (EntitlementUserApiException e) {
+            throw new EntitlementRepairException(e);
+        } catch (CatalogApiException e) {
+            throw new EntitlementRepairException(e);
+        }
+    }
+
+
+    private void trickleDownBPEffectForAddon(final List<SubscriptionDataRepair> addonSubscriptions, final DateTime requestedDate, final CallContext context)
+     throws EntitlementUserApiException {
+
+        if (category != ProductCategory.BASE) {
+            return;
+        }
+        
+        DateTime now = clock.getUTCNow();
+        Product baseProduct = (getState() == SubscriptionState.CANCELLED ) ?
+                null : getCurrentPlan().getProduct();
+
+        Iterator<SubscriptionDataRepair> it = addonSubscriptions.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)) {
+
+                cur.cancel(requestedDate, false, context);
+            }
+        }
+    }
+
+    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;
+    }
+}
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/user/DefaultEntitlementUserApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
index 5554c9c..16f75d3 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;
@@ -43,13 +44,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;
@@ -92,7 +93,7 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
     @Override
     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);
     }
 
@@ -141,7 +142,7 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
                 if (baseSubscription == null) {
                     throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_NO_BP, bundleId);
                 }
-                checkAddonCreationRights(baseSubscription, plan);
+                addonUtils.checkAddonCreationRights(baseSubscription, plan);
                 bundleStartDate = baseSubscription.getStartDate();
                 break;
             case STANDALONE:
@@ -170,26 +171,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 8cc2573..75a4da1 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
@@ -26,17 +26,19 @@ public class SubscriptionBundleData implements SubscriptionBundle {
     private final String key;
     private final UUID accountId;
     private final DateTime startDate;
+    private final DateTime lastSysTimeUpdate; 
 
-    public SubscriptionBundleData(String name, UUID accountId) {
-        this(UUID.randomUUID(), name, accountId, null);
+    public SubscriptionBundleData(String name, UUID accountId, DateTime now) {
+        this(UUID.randomUUID(), name, accountId, null, now);
     }
 
-    public SubscriptionBundleData(UUID id, String key, UUID accountId, DateTime startDate) {
+    public SubscriptionBundleData(UUID id, String key, UUID accountId, DateTime startDate, DateTime now) {
         super();
         this.id = id;
         this.key = key;
         this.accountId = accountId;
         this.startDate = startDate;
+        this.lastSysTimeUpdate = now;
     }
 
     @Override
@@ -60,4 +62,8 @@ public class SubscriptionBundleData implements SubscriptionBundle {
     public DateTime getStartDate() {
         return startDate;
     }
+    
+    public DateTime getLastSysUpdateTime() {
+        return lastSysTimeUpdate;
+    }
 }
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 103860f..701323e 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
@@ -24,8 +24,9 @@ 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.ProductCategory;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
-import com.ning.billing.entitlement.api.user.SubscriptionEventTransition.SubscriptionTransitionType;
+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;
@@ -51,41 +52,45 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
 
-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;
@@ -104,7 +109,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);
     }
@@ -133,26 +139,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 String getCurrentPriceList() {
-        return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPriceList();
+        return (getPreviousTransition() == null) ? null
+                : getPreviousTransition().getNextPriceList();
     }
 
-
     @Override
     public DateTime getEndDate() {
         SubscriptionEventTransition latestTransition = getPreviousTransition();
@@ -162,26 +170,29 @@ 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);
     }
 
@@ -191,8 +202,9 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
             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);
+        SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+                clock, transitions, Order.ASC_FROM_PAST, Kind.BILLING,
+                Visibility.ALL, TimeLimit.ALL);
         while (it.hasNext()) {
             result.add(it.next());
         }
@@ -205,8 +217,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;
     }
 
@@ -215,23 +228,31 @@ 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) {
+    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 new SubscriptionTransitionData(
+                        (SubscriptionTransitionData) cur, seqId);
             }
         }
         return null;
     }
 
+    public long getLastEventOrderedId() {
+        return (getPreviousTransition() == null) ? null
+                : ((SubscriptionTransitionData) getPreviousTransition()).getTotalOrdering();
+    }
+    
     public long getActiveVersion() {
         return activeVersion;
     }
@@ -255,32 +276,40 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
         return paidThroughDate;
     }
 
+    public List<SubscriptionTransitionData> getAllTransitions() {
+        return transitions;
+    }
+
     public SubscriptionTransitionData getInitialTransitionForCurrentPlan() {
         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.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) {
@@ -290,46 +319,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;
         }
 
@@ -338,7 +374,7 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
         String nextPhaseName = null;
         String nextPriceList = null;
         UUID nextUserToken = null;
-        
+
         SubscriptionState previousState = null;
         String previousPriceList = null;
 
@@ -346,7 +382,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;
@@ -368,8 +404,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:
@@ -396,43 +432,37 @@ 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;
             try {
-                nextPlan = (nextPlanName != null) ? catalog.findPlan(nextPlanName, cur.getRequestedDate(), getStartDate()) : null;
-                nextPhase = (nextPhaseName != null) ? catalog.findPhase(nextPhaseName, cur.getRequestedDate(), getStartDate()) : null;
+                nextPlan = (nextPlanName != null) ? catalog.findPlan(
+                        nextPlanName, cur.getRequestedDate(), getStartDate())
+                        : null;
+                nextPhase = (nextPhaseName != null) ? catalog.findPhase(
+                        nextPhaseName, cur.getRequestedDate(), getStartDate())
+                        : 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 80990cb..6b28140 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
@@ -18,6 +18,7 @@ package com.ning.billing.entitlement.api.user;
 
 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.SubscriptionState;
 import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
 import com.ning.billing.entitlement.events.user.ApiEventType;
@@ -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 2984ac3..f22b2a3 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
@@ -36,6 +36,7 @@ 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;
@@ -45,6 +46,7 @@ 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.SubscriptionFactory;
 import com.ning.billing.entitlement.api.billing.DefaultEntitlementBillingApi;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
 import com.ning.billing.entitlement.api.migration.DefaultEntitlementMigrationApi;
@@ -52,7 +54,7 @@ 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.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.entitlement.engine.addon.AddonUtils;
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 bccd559..29cc30e 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
@@ -38,6 +38,7 @@ import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.Date;
 import java.util.List;
 import java.util.UUID;
 
@@ -48,13 +49,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)
@@ -65,8 +69,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,10 +81,11 @@ public interface BundleSqlDao extends Transactional<BundleSqlDao>, CloseMe, Tran
                 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);
+            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..2373588 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,17 @@
 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.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 {
@@ -59,6 +60,8 @@ public interface EntitlementDao {
 
     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);
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 55238f5..fbb2cba 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,7 +21,10 @@ 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 com.ning.billing.util.ChangeType;
@@ -42,6 +45,7 @@ import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 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;
@@ -49,8 +53,7 @@ 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;
@@ -197,6 +200,8 @@ public class EntitlementSqlDao implements EntitlementDao {
                     TransactionStatus status) throws Exception {
                 transactionalDao.updateSubscription(subscription.getId().toString(), subscription.getActiveVersion(), ctd, ptd, 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);
@@ -235,6 +240,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);
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..85247ec
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/RepairEntitlementDao.java
@@ -0,0 +1,230 @@
+/* 
+ * 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.UUID;
+
+import com.ning.billing.entitlement.api.SubscriptionFactory;
+import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.repair.RepairEntitlementLifecycleDao;
+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 List<EntitlementEvent> events;
+        
+        public SubscriptionRepairEvent(List<EntitlementEvent> initialEvents) {
+            events = new LinkedList<EntitlementEvent>();
+            if (initialEvents != null) {
+                events.addAll(initialEvents);
+            }
+        }
+        public List<EntitlementEvent> getEvents() {
+            Collections.sort(events, new Comparator<EntitlementEvent>() {
+                @Override
+                public int compare(EntitlementEvent o1, EntitlementEvent o2) {
+                    return o1.compareTo(o2);
+                }
+            });
+            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 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) {
+        addEvents(subscriptionId, Collections.singletonList(cancelEvent));        
+    }
+
+    
+    @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 updateSubscription(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");
+    }
+}
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..d50572e 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;
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 e167807..a79705a 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,24 +19,36 @@ 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.DefaultEntitlementBillingApi;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
 import com.ning.billing.entitlement.api.migration.DefaultEntitlementMigrationApi;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
+import com.ning.billing.entitlement.api.repair.DefaultEntitlementRepairApi;
+import com.ning.billing.entitlement.api.repair.EntitlementRepairApi;
+import com.ning.billing.entitlement.api.repair.RepairEntitlementLifecycleDao;
+import com.ning.billing.entitlement.api.repair.RepairSubscriptionApiService;
+import com.ning.billing.entitlement.api.repair.RepairSubscriptionFactory;
 import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
 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.api.user.DefaultSubscriptionApiService;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory;
 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;
 
 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);
@@ -44,16 +56,26 @@ 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(EntitlementRepairApi.class).to(DefaultEntitlementRepairApi.class).asEagerSingleton();
         bind(EntitlementUserApi.class).to(DefaultEntitlementUserApi.class).asEagerSingleton();
         bind(EntitlementBillingApi.class).to(DefaultEntitlementBillingApi.class).asEagerSingleton();
         bind(EntitlementMigrationApi.class).to(DefaultEntitlementMigrationApi.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/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java
index 0c16089..df4c822 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java
@@ -37,8 +37,8 @@ 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.user.SubscriptionEventTransition.SubscriptionTransitionType;
 import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
index 3650bf9..559b9f9 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
@@ -21,6 +21,7 @@ import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
 import java.util.ArrayList;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.SortedSet;
 import java.util.UUID;
@@ -54,7 +55,7 @@ import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
 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.SubscriptionBuilder;
+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;
@@ -72,9 +73,9 @@ public class TestDefaultEntitlementBillingApi {
 	private static final UUID twoId = 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 EntitlementDao dao;
 
 	private Clock clock;
@@ -97,12 +98,12 @@ public class TestDefaultEntitlementBillingApi {
 	@BeforeMethod(alwaysRun=true)
 	public void setupEveryTime() {
 		bundles = new ArrayList<SubscriptionBundle>();
-		final SubscriptionBundle bundle = new SubscriptionBundleData( zeroId,"TestKey", oneId,  clock.getUTCNow().minusDays(4));
+		final SubscriptionBundle bundle = new SubscriptionBundleData( zeroId,"TestKey", oneId,  clock.getUTCNow().minusDays(4), null);
 		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);
@@ -110,7 +111,7 @@ public class TestDefaultEntitlementBillingApi {
 		subscription = new SubscriptionData(builder) {
 		    @Override
             public List<SubscriptionEventTransition> getBillingTransitions() {
-		    	return transitions;
+		    	return subscriptionTransitions;
 		    }
 		};
 
@@ -153,7 +154,7 @@ public class TestDefaultEntitlementBillingApi {
 		String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
 		SubscriptionEventTransition t = new SubscriptionTransitionData(
 				zeroId, oneId, twoId, 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);
@@ -175,7 +176,7 @@ public class TestDefaultEntitlementBillingApi {
 		String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
 		SubscriptionEventTransition t = new SubscriptionTransitionData(
 				zeroId, oneId, twoId, 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);
 
 		Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
 		((ZombieControl)account).addResult("getBillCycleDay", 1).addResult("getTimeZone", DateTimeZone.UTC)
@@ -200,7 +201,7 @@ public class TestDefaultEntitlementBillingApi {
 		String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
 		SubscriptionEventTransition t = new SubscriptionTransitionData(
 				zeroId, oneId, twoId, 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);
@@ -222,7 +223,7 @@ public class TestDefaultEntitlementBillingApi {
 		String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
 		SubscriptionEventTransition t = new SubscriptionTransitionData(
 				zeroId, oneId, twoId, 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);
 
 		Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
 		((ZombieControl)account).addResult("getBillCycleDay", 1).addResult("getTimeZone", DateTimeZone.UTC);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/repair/TestRepair.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/repair/TestRepair.java
new file mode 100644
index 0000000..da619ec
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/repair/TestRepair.java
@@ -0,0 +1,306 @@
+/* 
+ * 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.repair;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.util.Collections;
+import java.util.Comparator;
+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.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.TestApiBase;
+import com.ning.billing.entitlement.api.repair.SubscriptionRepair.DeletedEvent;
+import com.ning.billing.entitlement.api.repair.SubscriptionRepair.ExistingEvent;
+import com.ning.billing.entitlement.api.repair.SubscriptionRepair.NewEvent;
+import com.ning.billing.entitlement.api.user.Subscription;
+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 TestRepair extends TestApiBase {
+
+    @Override
+    public Injector getInjector() {
+        return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
+    }
+
+    @Test(groups={"slow"})
+    public void testFetchBundleRepair() {
+        try {
+
+            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);
+
+            BundleRepair bundleRepair = repairApi.getBundleRepair(bundle.getId());
+            List<SubscriptionRepair> subscriptionRepair = bundleRepair.getSubscriptions();
+            assertEquals(subscriptionRepair.size(), 2);
+
+            for (SubscriptionRepair 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);                    
+                }
+            }
+        } catch (Exception e) {
+            Assert.fail(e.getMessage());
+        }
+    }
+
+
+
+    @Test(groups={"slow"})
+    public void testSimpleBPRepairAddChangeBeforePhase() throws Exception {
+
+        String baseProduct = "Shotgun";
+        BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+        String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        // CREATE BP
+        Subscription baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+        BundleRepair bundleRepair = repairApi.getBundleRepair(bundle.getId());
+        sortEventsOnBundle(bundleRepair);
+
+        // MOVE CLOCK-- STAYS in TRIAL
+        Duration moveTenDays = getDurationDay(10);
+        clock.setDeltaFromReality(moveTenDays, 0);
+
+
+        DateTime changeTime = baseSubscription.getStartDate().plusDays(3);
+        String newBaseProduct = "Assault-Rifle";
+        BillingPeriod newBaseTerm = BillingPeriod.MONTHLY;
+        String newBasePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+        PlanPhaseSpecifier spec = new PlanPhaseSpecifier(newBaseProduct, ProductCategory.BASE, newBaseTerm, newBasePriceList, PhaseType.TRIAL);
+
+
+        NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, changeTime, spec);
+        DeletedEvent de = createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId());
+        SubscriptionRepair sRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.singletonList(de), Collections.singletonList(ne));
+        
+        // FIRST ISSUE DRY RUN
+        BundleRepair bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+        
+        boolean dryRun = true;
+        BundleRepair dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        List<SubscriptionRepair> subscriptionRepair = dryRunBundleRepair.getSubscriptions();
+        assertEquals(subscriptionRepair.size(), 1);
+        SubscriptionRepair cur = subscriptionRepair.get(0);
+        assertEquals(cur.getId(), baseSubscription.getId());
+
+        List<ExistingEvent> events = cur.getExistingEvents();
+       assertEquals(events.size(), 3);
+        
+       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(), newBaseProduct);
+       assertEquals(events.get(1).getPlanPhaseSpecifier().getPhaseType(), PhaseType.TRIAL);
+       assertEquals(events.get(1).getPlanPhaseSpecifier().getProductCategory(),ProductCategory.BASE);                    
+       assertEquals(events.get(1).getPlanPhaseSpecifier().getPriceListName(), basePriceList);                    
+       assertEquals(events.get(1).getPlanPhaseSpecifier().getBillingPeriod(), BillingPeriod.NO_BILLING_PERIOD);
+
+        assertEquals(events.get(2).getPlanPhaseSpecifier().getProductName(), newBaseProduct);
+        assertEquals(events.get(2).getPlanPhaseSpecifier().getPhaseType(), PhaseType.EVERGREEN);
+        assertEquals(events.get(2).getPlanPhaseSpecifier().getProductCategory(),ProductCategory.BASE);                    
+        assertEquals(events.get(2).getPlanPhaseSpecifier().getPriceListName(), basePriceList);                    
+        assertEquals(events.get(2).getPlanPhaseSpecifier().getBillingPeriod(), baseTerm);
+     
+        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(), baseTerm);
+
+        PlanPhase currentPhase = dryRunBaseSubscription.getCurrentPhase();
+        assertNotNull(currentPhase);
+        assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
+        
+        
+       // SECOND RE-ISSUE CALL-- NON DRY RUN
+        
+    }
+
+
+    private SubscriptionRepair createSubscriptionReapir(final UUID id, final List<DeletedEvent> deletedEvents, final List<NewEvent> newEvents) {
+        return new SubscriptionRepair() {
+            @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;
+            }
+        };
+    }
+
+    private BundleRepair createBundleRepair(final UUID bundleId, final String viewId, final List<SubscriptionRepair> subscriptionRepair) {
+        return new BundleRepair() {
+            @Override
+            public String getViewId() {
+                return viewId;
+            }
+            @Override
+            public List<SubscriptionRepair> getSubscriptions() {
+                return subscriptionRepair;
+            }
+            @Override
+            public UUID getBundleId() {
+                return bundleId;
+            }
+        };
+    }
+
+    private DeletedEvent createDeletedEvent(final UUID eventId) {
+        return new DeletedEvent() {
+            @Override
+            public UUID getEventId() {
+                return eventId;
+            }
+        };
+    }
+
+    private 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;
+            }
+        };
+    }
+
+    private void sortEventsOnBundle(final BundleRepair bundle) {
+        if (bundle.getSubscriptions() == null) {
+            return;
+        }
+        for (SubscriptionRepair cur : bundle.getSubscriptions()) {
+            if (cur.getExistingEvents() != null) {
+                sortExistingEvent(cur.getExistingEvents());
+            }
+            if (cur.getNewEvents() != null) {
+                sortNewEvent(cur.getNewEvents());
+            }
+        }
+    }
+
+    private 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());
+            }
+        });
+    }
+    private 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/TestApiBase.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
index 0a65693..eebd3e9 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
@@ -59,6 +59,7 @@ import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
+import com.ning.billing.entitlement.api.repair.EntitlementRepairApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
@@ -90,6 +91,7 @@ public abstract class TestApiBase {
     protected EntitlementBillingApi billingApi;
 
     protected EntitlementMigrationApi migrationApi;
+    protected EntitlementRepairApi repairApi;
 
     protected CatalogService catalogService;
     protected EntitlementConfig config;
@@ -148,7 +150,7 @@ public abstract class TestApiBase {
             ((DefaultCatalogService) catalogService).loadCatalog();
             ((DefaultBusService) busService).startBus();
             ((Engine) entitlementService).initialize();
-            init();
+            init(g);
         } catch (Exception e) {
         }
     }
@@ -167,7 +169,7 @@ public abstract class TestApiBase {
         }
     }
 
-    private void init() throws Exception {
+    private void init(Injector g) throws Exception {
 
         setupMySQL();
 
@@ -182,6 +184,8 @@ public abstract class TestApiBase {
         entitlementApi = entitlementService.getUserApi();
         billingApi = entitlementService.getBillingApi();
         migrationApi = entitlementService.getMigrationApi();
+        
+        repairApi = g.getInstance(EntitlementRepairApi.class);
     }
 
     @BeforeMethod(alwaysRun = true)
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 14d6653..258308a 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
@@ -354,6 +354,7 @@ public class TestUserApiAddOn extends TestApiBase {
            // MOVE THROUGH TIME TO GO INTO EVERGREEN
            someTimeLater = aoCurrentPhase.getDuration();
            clock.addDeltaFromReality(someTimeLater);
+           clock.addDeltaFromReality(getDurationDay(1));
            assertTrue(testListener.isCompleted(5000));
 
 
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 1872305..18c3ee3 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;
 
@@ -36,6 +37,7 @@ 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;
@@ -43,8 +45,7 @@ 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;
@@ -446,4 +447,11 @@ 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;
+    }
+
 }
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 4733f4a..5c5b2b5 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
@@ -26,7 +26,7 @@ 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.entitlement.api.user.SubscriptionFactory;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory;
 import com.ning.billing.entitlement.engine.addon.AddonUtils;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.notificationq.NotificationQueueService;
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..20b91e1 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.repair.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 f774e58..ebe60c4 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,11 +16,15 @@
 
 package com.ning.billing.entitlement.glue;
 
+import com.google.inject.name.Names;
 import com.ning.billing.dbi.DBIProvider;
 import com.ning.billing.dbi.DbiConfig;
 import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.repair.RepairEntitlementLifecycleDao;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.engine.dao.EntitlementSqlDao;
 import com.ning.billing.entitlement.engine.dao.MockEntitlementDaoSql;
+import com.ning.billing.entitlement.engine.dao.RepairEntitlementDao;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
 import com.ning.billing.util.glue.FieldStoreModule;
@@ -35,7 +39,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/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 512f559..04af58b 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
@@ -48,12 +48,12 @@ 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.billing.DefaultBillingEvent;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
 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 907c8e3..aa8abb5 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
@@ -26,11 +26,11 @@ 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.billing.DefaultBillingEvent;
 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 26df2af..a15b2c1 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
@@ -26,6 +26,8 @@ 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.entitlement.api.billing.DefaultEntitlementBillingApi;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
 import com.ning.billing.invoice.InvoiceDispatcher;
@@ -56,16 +58,24 @@ import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
+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.repair.RepairEntitlementLifecycleDao;
+import com.ning.billing.entitlement.api.repair.RepairSubscriptionApiService;
+import com.ning.billing.entitlement.api.repair.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.entitlement.engine.dao.RepairEntitlementDao;
+import com.ning.billing.entitlement.glue.EntitlementModule;
 import com.ning.billing.invoice.InvoiceListener;
 import com.ning.billing.lifecycle.KillbillService.ServiceException;
 import com.ning.billing.mock.BrainDeadProxyFactory;
@@ -88,6 +98,7 @@ public class TestNextBillingDateNotifier {
 	private InvoiceListenerMock listener;
 	private NotificationQueueService notificationQueueService;
 
+	
 	private static final class InvoiceListenerMock extends InvoiceListener {
 		int eventCount = 0;
 		UUID latestSubscriptionId = null;
@@ -135,14 +146,21 @@ 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(AccountUserApi.class).to(MockAccountUserApi.class).asEagerSingleton();
                 bind(EntitlementBillingApi.class).to(DefaultEntitlementBillingApi.class).asEagerSingleton();
-                bind(EntitlementUserApi.class).to(DefaultEntitlementUserApi.class).asEagerSingleton();                
+                bind(EntitlementUserApi.class).to(DefaultEntitlementUserApi.class).asEagerSingleton();
+                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();
 			}
         });
 
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 b77f758..c13e386 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
@@ -48,12 +48,12 @@ 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.billing.DefaultBillingEvent;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
 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.InvoiceUserApi;
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 da29ce7..5792abd 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
@@ -27,13 +27,13 @@ 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.billing.DefaultBillingEvent;
 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.SubscriptionEventTransition.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.model.BillingEventSet;