killbill-aplcache

Modify code to use raw entitlement events rather that SubscripyionTransition--

4/25/2012 3:37:32 PM

Details

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
index e7cd999..9813448 100644
--- 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
@@ -32,6 +32,9 @@ import org.joda.time.DateTime;
 import com.google.inject.Inject;
 import com.google.inject.name.Named;
 import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.CatalogUserApi;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.SubscriptionFactory;
 import com.ning.billing.entitlement.api.SubscriptionTransitionType;
@@ -42,6 +45,7 @@ import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.SubscriptionEventTransition;
 import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.events.EntitlementEvent;
@@ -55,18 +59,20 @@ public class DefaultEntitlementRepairApi implements EntitlementRepairApi {
     private final EntitlementDao dao;
     private final SubscriptionFactory factory;
     private final RepairEntitlementLifecycleDao repairDao;
+    private final CatalogService catalogService;
+
 
-    
     private enum RepairType  {
         BASE_REPAIR,
         ADD_ON_REPAIR,
         STANDALONE_REPAIR
     }
 
-    
+
     @Inject
-    public DefaultEntitlementRepairApi(@Named(EntitlementModule.REPAIR_NAMED) final SubscriptionFactory factory,
+    public DefaultEntitlementRepairApi(@Named(EntitlementModule.REPAIR_NAMED) final SubscriptionFactory factory, final CatalogService catalogService,
             @Named(EntitlementModule.REPAIR_NAMED) final RepairEntitlementLifecycleDao repairDao, final EntitlementDao dao) {
+        this.catalogService = catalogService;
         this.dao = dao;
         this.repairDao = repairDao;
         this.factory = factory;
@@ -77,20 +83,24 @@ public class DefaultEntitlementRepairApi implements EntitlementRepairApi {
     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);
+        try {
+            SubscriptionBundle bundle = dao.getSubscriptionBundleFromId(bundleId);
+            if (bundle == null) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_BUNDLE, bundleId);
+            }
+            final List<Subscription> subscriptions = dao.getSubscriptions(factory, bundleId);
+            if (subscriptions.size() == 0) {
+                throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_NO_ACTIVE_SUBSCRIPTIONS, bundleId);
+            }
+            final String viewId = getViewId(((SubscriptionBundleData) bundle).getLastSysUpdateTime(), subscriptions);
+            final List<SubscriptionRepair> repairs = createGetSubscriptionRepairList(subscriptions, Collections.<SubscriptionRepair>emptyList()); 
+            return createGetBundleRepair(bundleId, viewId, repairs);
+        } catch (CatalogApiException e) {
+            throw new EntitlementRepairException(e);
         }
-        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)
@@ -130,21 +140,21 @@ public class DefaultEntitlementRepairApi implements EntitlementRepairApi {
                 if (curRepair != null) {
                     SubscriptionDataRepair curInputRepair = ((SubscriptionDataRepair) cur);
                     final List<EntitlementEvent> remaining = getRemainingEventsAndValidateDeletedEvents(curInputRepair, firstDeletedBPEventTime, curRepair.getDeletedEvents());
-                    
+
                     final boolean isPlanRecreate = (curRepair.getNewEvents().size() > 0 
                             && (curRepair.getNewEvents().get(0).getSubscriptionTransitionType() == SubscriptionTransitionType.CREATE 
                                     || curRepair.getNewEvents().get(0).getSubscriptionTransitionType() == SubscriptionTransitionType.RE_CREATE));
-                    
+
                     final DateTime newSubscriptionStartDate = isPlanRecreate ? curRepair.getNewEvents().get(0).getRequestedDate() : null;
-                    
+
                     if (isPlanRecreate && remaining.size() != 0) {
                         throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_SUB_RECREATE_NOT_EMPTY, cur.getId(), cur.getBundleId());
                     }
-                    
+
                     if (!isPlanRecreate && remaining.size() == 0) {
                         throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_SUB_EMPTY, cur.getId(), cur.getBundleId());
                     }
-                    
+
                     if (cur.getCategory() == ProductCategory.BASE) {
 
                         int bpTransitionSize =((SubscriptionData) cur).getAllTransitions().size();
@@ -175,30 +185,30 @@ public class DefaultEntitlementRepairApi implements EntitlementRepairApi {
                     }
                 }
             }
-            
+
             final RepairType repairType = getRepairType(subscriptions.get(0), (baseSubscriptionRepair != null));
             switch(repairType) {
-                case BASE_REPAIR:
-                   // We need to add any existing addon that are not in the input repair list
-                    for (Subscription cur : subscriptions) {
-                        if (cur.getCategory() == ProductCategory.ADD_ON && !inRepair.contains(cur)) {
-                            SubscriptionDataRepair curOutputRepair = createSubscriptionDataRepair((SubscriptionDataRepair) cur, newBundleStartDate, null, ((SubscriptionDataRepair) cur).getEvents());
-                            repairDao.initializeRepair(curOutputRepair.getId(), ((SubscriptionDataRepair) cur).getEvents());
-                            inRepair.add(curOutputRepair);
-                            addOnSubscriptionInRepair.add(curOutputRepair);
-                        }
+            case BASE_REPAIR:
+                // We need to add any existing addon that are not in the input repair list
+                for (Subscription cur : subscriptions) {
+                    if (cur.getCategory() == ProductCategory.ADD_ON && !inRepair.contains(cur)) {
+                        SubscriptionDataRepair curOutputRepair = createSubscriptionDataRepair((SubscriptionDataRepair) cur, newBundleStartDate, null, ((SubscriptionDataRepair) cur).getEvents());
+                        repairDao.initializeRepair(curOutputRepair.getId(), ((SubscriptionDataRepair) cur).getEvents());
+                        inRepair.add(curOutputRepair);
+                        addOnSubscriptionInRepair.add(curOutputRepair);
                     }
-                    
-                    break;
-                case ADD_ON_REPAIR:
-                    // We need to set the baseSubscription as it is useful to calculate addon validity
-                    SubscriptionDataRepair baseSubscription =  (SubscriptionDataRepair) subscriptions.get(0);
-                    baseSubscriptionRepair = createSubscriptionDataRepair(baseSubscription, baseSubscription.getBundleStartDate(), baseSubscription.getStartDate(), baseSubscription.getEvents());
-                    break;
-                case STANDALONE_REPAIR:
-                default:
-                    break;
-                    
+                }
+
+                break;
+            case ADD_ON_REPAIR:
+                // We need to set the baseSubscription as it is useful to calculate addon validity
+                SubscriptionDataRepair baseSubscription =  (SubscriptionDataRepair) subscriptions.get(0);
+                baseSubscriptionRepair = createSubscriptionDataRepair(baseSubscription, baseSubscription.getBundleStartDate(), baseSubscription.getStartDate(), baseSubscription.getEvents());
+                break;
+            case STANDALONE_REPAIR:
+            default:
+                break;
+
             }
 
             validateBasePlanRecreate(isBasePlanRecreate, subscriptions, input.getSubscriptions());
@@ -215,19 +225,27 @@ public class DefaultEntitlementRepairApi implements EntitlementRepairApi {
                 }
                 curDataRepair.addNewRepairEvent(cur, baseSubscriptionRepair, addOnSubscriptionInRepair, context);
             }
-
+            
             if (dryRun) {
+                
+                baseSubscriptionRepair.addFutureAddonCancellation(addOnSubscriptionInRepair, context);
+
                 final List<SubscriptionRepair> repairs = createGetSubscriptionRepairList(subscriptions, convertDataRepair(inRepair)); 
                 return createGetBundleRepair(input.getBundleId(), input.getViewId(), repairs);
             } else {
                 dao.repair(input.getBundleId(), inRepair, context);
                 return getBundleRepair(input.getBundleId());
             }
+        } catch (CatalogApiException e) {
+            throw new EntitlementRepairException(e);
         } finally {
             repairDao.cleanup();
         }
     }
 
+ 
+    
+    
     private RepairType getRepairType(final Subscription firstSubscription, final boolean gotBaseSubscription) {
         if (firstSubscription.getCategory() == ProductCategory.BASE) {
             return gotBaseSubscription ? RepairType.BASE_REPAIR : RepairType.ADD_ON_REPAIR;
@@ -235,10 +253,10 @@ public class DefaultEntitlementRepairApi implements EntitlementRepairApi {
             return RepairType.STANDALONE_REPAIR;
         }
     }
-    
+
     private void validateBasePlanRecreate(boolean isBasePlanRecreate, List<Subscription> subscriptions, List<SubscriptionRepair> input) 
-        throws EntitlementRepairException  {
-        
+    throws EntitlementRepairException  {
+
         if (!isBasePlanRecreate) {
             return;
         }
@@ -253,10 +271,10 @@ public class DefaultEntitlementRepairApi implements EntitlementRepairApi {
             }
         }
     }
-    
-    
+
+
     private void validateInputSubscriptionsKnown(List<Subscription> subscriptions, List<SubscriptionRepair> input)
-        throws EntitlementRepairException {
+    throws EntitlementRepairException {
 
         for (SubscriptionRepair cur : input) {
             boolean found = false;
@@ -284,7 +302,7 @@ public class DefaultEntitlementRepairApi implements EntitlementRepairApi {
         }
 
     }
-    
+
     private Collection<NewEvent> createOrderedNewEventInput(List<SubscriptionRepair> subscriptionsReapir) {
         TreeSet<NewEvent> newEventSet = new TreeSet<SubscriptionRepair.NewEvent>(new Comparator<NewEvent>() {
             @Override
@@ -352,7 +370,7 @@ public class DefaultEntitlementRepairApi implements EntitlementRepairApi {
         return result;
     }
 
-    
+
     private String getViewId(DateTime lastUpdateBundleDate, List<Subscription> subscriptions) {
         StringBuilder tmp = new StringBuilder();
         long lastOrderedId = -1;
@@ -383,7 +401,7 @@ public class DefaultEntitlementRepairApi implements EntitlementRepairApi {
 
     }
 
-    private List<SubscriptionRepair> createGetSubscriptionRepairList(final List<Subscription> subscriptions, final List<SubscriptionRepair> inRepair) {
+    private List<SubscriptionRepair> createGetSubscriptionRepairList(final List<Subscription> subscriptions, final List<SubscriptionRepair> inRepair) throws CatalogApiException {
 
         final List<SubscriptionRepair> result = new LinkedList<SubscriptionRepair>();
         Set<UUID> repairIds = new TreeSet<UUID>();
@@ -393,17 +411,17 @@ public class DefaultEntitlementRepairApi implements EntitlementRepairApi {
         }
         for (final Subscription cur : subscriptions) {
             if ( !repairIds.contains(cur.getId())) { 
-                result.add(new DefaultSubscriptionRepair((SubscriptionData) cur));
+                result.add(new DefaultSubscriptionRepair((SubscriptionDataRepair) cur, catalogService.getFullCatalog()));
             }
         }
         return result;
     }
 
 
-    private List<SubscriptionRepair> convertDataRepair(List<SubscriptionDataRepair> input) {
+    private List<SubscriptionRepair> convertDataRepair(List<SubscriptionDataRepair> input) throws CatalogApiException  {
         List<SubscriptionRepair> result = new LinkedList<SubscriptionRepair>();
         for (SubscriptionDataRepair cur : input) {
-            result.add(new DefaultSubscriptionRepair(cur));
+            result.add(new DefaultSubscriptionRepair(cur, catalogService.getFullCatalog()));
         }
         return result;
     }
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
index 297825c..7444385 100644
--- 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
@@ -25,7 +25,12 @@ import java.util.UUID;
 import org.joda.time.DateTime;
 
 import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.CatalogUserApi;
 import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.SubscriptionTransitionType;
@@ -33,6 +38,12 @@ 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;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
+import com.ning.billing.entitlement.events.phase.PhaseEvent;
+import com.ning.billing.entitlement.events.user.ApiEvent;
+import com.ning.billing.entitlement.events.user.ApiEventType;
+import com.sun.org.apache.xml.internal.resolver.CatalogException;
 
 public class DefaultSubscriptionRepair implements SubscriptionRepair  {
 
@@ -61,37 +72,74 @@ public class DefaultSubscriptionRepair implements SubscriptionRepair  {
     }
     
      // CTOR for returning events only
-    public DefaultSubscriptionRepair(SubscriptionData input) {
+    public DefaultSubscriptionRepair(SubscriptionDataRepair input, Catalog catalog) throws CatalogApiException {
         this.id = input.getId();
-        this.existingEvents = toExistingEvents(input.getCategory(), input.getAllTransitions());
+        this.existingEvents = toExistingEvents(catalog, input.getActiveVersion(), input.getCategory(), input.getEvents());
         this.deletedEvents = null;
         this.newEvents = null;
     }
     
-    private List<ExistingEvent> toExistingEvents(final ProductCategory category, final List<SubscriptionTransitionData> transitions) {
+   private List<ExistingEvent> toExistingEvents(final Catalog catalog, final long activeVersion, final ProductCategory category, final List<EntitlementEvent> events) 
+       throws CatalogApiException {
+        
         List<ExistingEvent> result = new LinkedList<SubscriptionRepair.ExistingEvent>();
-        for (final SubscriptionTransitionData cur : transitions) {
+        
+        String prevProductName = null; 
+        BillingPeriod prevBillingPeriod = null;
+        String prevPriceListName = null;
+        PhaseType prevPhaseType = null;
+        
+        DateTime startDate = null;
+        
+        for (final EntitlementEvent cur : events) {
             
-            String productName = null;
+            // First active event is used to figure out which catalog version to use.
+            //startDate = (startDate == null && cur.getActiveVersion() == activeVersion) ?  cur.getEffectiveDate() : startDate;
+            
+            // STEPH that needs tp be reviewed if we support mutli version events
+            if (cur.getActiveVersion() != activeVersion) {
+                continue;
+            }
+            startDate = (startDate == null) ?  cur.getEffectiveDate() : startDate;
+            
+            
+            String productName = null; 
             BillingPeriod billingPeriod = null;
             String priceListName = null;
             PhaseType phaseType = null;
-            if (cur.getTransitionType() != SubscriptionTransitionType.CANCEL) {
-                productName = cur.getNextPlan().getProduct().getName();
-                billingPeriod = cur.getNextPhase().getBillingPeriod(); 
-                priceListName = cur.getNextPriceList(); 
-                phaseType = cur.getNextPhase().getPhaseType();
+
+            ApiEventType apiType = null;
+            switch (cur.getType()) {
+            case PHASE:
+                PhaseEvent phaseEV = (PhaseEvent) cur;
+                phaseType = catalog.findPhase(phaseEV.getPhase(), cur.getEffectiveDate(), startDate).getPhaseType();
+                productName = prevProductName;
+                billingPeriod = catalog.findPhase(phaseEV.getPhase(), cur.getEffectiveDate(), startDate).getBillingPeriod();
+                priceListName = prevPriceListName;
+                break;
+
+            case API_USER:
+                ApiEvent userEV = (ApiEvent) cur;
+                apiType = userEV.getEventType();
+                Plan plan =  (userEV.getEventPlan() != null) ? catalog.findPlan(userEV.getEventPlan(), cur.getRequestedDate(), startDate) : null;
+                phaseType = (userEV.getEventPlanPhase() != null) ? catalog.findPhase(userEV.getEventPlanPhase(), cur.getEffectiveDate(), startDate).getPhaseType() : prevPhaseType;
+                productName = (plan != null) ? plan.getProduct().getName() : prevProductName;
+                billingPeriod = (userEV.getEventPlanPhase() != null) ? catalog.findPhase(userEV.getEventPlanPhase(), cur.getEffectiveDate(), startDate).getBillingPeriod() : prevBillingPeriod;
+                priceListName = (userEV.getPriceList() != null) ? userEV.getPriceList() : prevPriceListName;
+                break;
             }
+
+            final SubscriptionTransitionType transitionType = SubscriptionTransitionData.toSubscriptionTransitionType(cur.getType(), apiType);
             
             final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
             result.add(new ExistingEvent() {
                 @Override
                 public SubscriptionTransitionType getSubscriptionTransitionType() {
-                    return cur.getTransitionType();
+                    return transitionType;
                 }
                 @Override
                 public DateTime getRequestedDate() {
-                    return cur.getRequestedTransitionTime();
+                    return cur.getRequestedDate();
                 }
                 @Override
                 public PlanPhaseSpecifier getPlanPhaseSpecifier() {
@@ -103,13 +151,106 @@ public class DefaultSubscriptionRepair implements SubscriptionRepair  {
                 }
                 @Override
                 public DateTime getEffectiveDate() {
-                    return cur.getEffectiveTransitionTime();
+                    return cur.getEffectiveDate();
                 }
             });
+            
+            prevProductName = productName; 
+            prevBillingPeriod = billingPeriod;
+            prevPriceListName = priceListName;
+            prevPhaseType = phaseType;
+
         }
         sortExistingEvent(result);
         return result;
     }
+   
+   
+   /*
+   
+   private List<ExistingEvent> toExistingEvents(final Catalog catalog, final long processingVersion, final ProductCategory category, final List<EntitlementEvent> events, List<ExistingEvent> result)
+       throws CatalogApiException {
+       
+       
+       String prevProductName = null; 
+       BillingPeriod prevBillingPeriod = null;
+       String prevPriceListName = null;
+       PhaseType prevPhaseType = null;
+       
+       DateTime startDate = null;
+       
+       for (final EntitlementEvent cur : events) {
+           
+           if (processingVersion != cur.getActiveVersion()) {
+               continue;
+           }
+           
+           // First active event is used to figure out which catalog version to use.
+           startDate = (startDate == null && cur.getActiveVersion() == processingVersion) ?  cur.getEffectiveDate() : startDate;
+           
+           String productName = null; 
+           BillingPeriod billingPeriod = null;
+           String priceListName = null;
+           PhaseType phaseType = null;
+
+           ApiEventType apiType = null;
+           switch (cur.getType()) {
+           case PHASE:
+               PhaseEvent phaseEV = (PhaseEvent) cur;
+               phaseType = catalog.findPhase(phaseEV.getPhase(), cur.getEffectiveDate(), startDate).getPhaseType();
+               productName = prevProductName;
+               billingPeriod = prevBillingPeriod;
+               priceListName = prevPriceListName;
+               break;
+
+           case API_USER:
+               ApiEvent userEV = (ApiEvent) cur;
+               apiType = userEV.getEventType();
+               Plan plan =  (userEV.getEventPlan() != null) ? catalog.findPlan(userEV.getEventPlan(), cur.getRequestedDate(), startDate) : null;
+               phaseType = (userEV.getEventPlanPhase() != null) ? catalog.findPhase(userEV.getEventPlanPhase(), cur.getEffectiveDate(), startDate).getPhaseType() : prevPhaseType;
+               productName = (plan != null) ? plan.getProduct().getName() : prevProductName;
+               billingPeriod = (plan != null) ? plan.getBillingPeriod() : prevBillingPeriod;
+               priceListName = (userEV.getPriceList() != null) ? userEV.getPriceList() : prevPriceListName;
+               break;
+           }
+
+           final SubscriptionTransitionType transitionType = SubscriptionTransitionData.toSubscriptionTransitionType(cur.getType(), apiType);
+           
+           final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
+           result.add(new ExistingEvent() {
+               @Override
+               public SubscriptionTransitionType getSubscriptionTransitionType() {
+                   return transitionType;
+               }
+               @Override
+               public DateTime getRequestedDate() {
+                   return cur.getRequestedDate();
+               }
+               @Override
+               public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+                   return spec;
+               }
+               @Override
+               public UUID getEventId() {
+                   return cur.getId();
+               }
+               @Override
+               public DateTime getEffectiveDate() {
+                   return cur.getEffectiveDate();
+               }
+           });
+           prevProductName = productName; 
+           prevBillingPeriod = billingPeriod;
+           prevPriceListName = priceListName;
+           prevPhaseType = phaseType;
+       }
+   }
+   */
+   
+   
+    
+    
+    
     
     @Override
     public UUID getId() {
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
index 809c23c..b79d658 100644
--- 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
@@ -34,8 +34,10 @@ import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.SubscriptionApiService;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionEventTransition;
 import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.engine.addon.AddonUtils;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
@@ -110,7 +112,6 @@ public class SubscriptionDataRepair extends SubscriptionData {
             default:
                 throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_TYPE, input.getSubscriptionTransitionType(), id);
             }
-            
         } catch (EntitlementUserApiException e) {
             throw new EntitlementRepairException(e);
         } catch (CatalogApiException e) {
@@ -119,18 +120,40 @@ public class SubscriptionDataRepair extends SubscriptionData {
     }
 
 
-    private void trickleDownBPEffectForAddon(final List<SubscriptionDataRepair> addonSubscriptions, final DateTime effectiveDate, final CallContext context)
+    public void addFutureAddonCancellation(List<SubscriptionDataRepair> addOnSubscriptionInRepair, final CallContext context) {
+
+        if (category != ProductCategory.BASE) {
+            return;
+        }
+
+        SubscriptionEventTransition pendingTransition = getPendingTransition();
+        if (pendingTransition == null) {
+            return;
+        }
+        Product baseProduct = (pendingTransition.getTransitionType() == SubscriptionTransitionType.CANCEL) ? null : 
+            pendingTransition.getNextPlan().getProduct();
+
+        addAddonCancellationIfRequired(addOnSubscriptionInRepair, baseProduct, pendingTransition.getEffectiveTransitionTime(), context);
+    }
+    
+    private void trickleDownBPEffectForAddon(final List<SubscriptionDataRepair> addOnSubscriptionInRepair, final DateTime effectiveDate, final CallContext context)
      throws EntitlementUserApiException {
 
         if (category != ProductCategory.BASE) {
             return;
         }
-        
-        DateTime now = clock.getUTCNow();
+
         Product baseProduct = (getState() == SubscriptionState.CANCELLED ) ?
                 null : getCurrentPlan().getProduct();
+        addAddonCancellationIfRequired(addOnSubscriptionInRepair, baseProduct, effectiveDate, context);
+    }
+    
+    
+    
+    private void addAddonCancellationIfRequired(final List<SubscriptionDataRepair> addOnSubscriptionInRepair, Product baseProduct, final DateTime effectiveDate, final CallContext context) {
 
-        Iterator<SubscriptionDataRepair> it = addonSubscriptions.iterator();
+        DateTime now = clock.getUTCNow();
+        Iterator<SubscriptionDataRepair> it = addOnSubscriptionInRepair.iterator();
         while (it.hasNext()) {
             SubscriptionDataRepair cur = it.next();
             if (cur.getState() == SubscriptionState.CANCELLED ||
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 9bfaa70..80f25ab 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
@@ -475,11 +475,9 @@ public class SubscriptionData extends ExtendedEntityBase implements
             Plan nextPlan = null;
             PlanPhase nextPhase = null;
             try {
-                nextPlan = (nextPlanName != null) ? catalog.findPlan(
-                        nextPlanName, cur.getRequestedDate(), getStartDate())
+                nextPlan = (nextPlanName != null) ? catalog.findPlan(nextPlanName, cur.getRequestedDate(), getStartDate())
                         : null;
-                nextPhase = (nextPhaseName != null) ? catalog.findPhase(
-                        nextPhaseName, cur.getRequestedDate(), getStartDate())
+                nextPhase = (nextPhaseName != null) ? catalog.findPhase(nextPhaseName, cur.getRequestedDate(), getStartDate())
                         : null;
             } catch (CatalogApiException e) {
                 log.error(String.format(
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/repair/TestApiBaseRepair.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/repair/TestApiBaseRepair.java
index 31e178d..b908bb6 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/repair/TestApiBaseRepair.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/repair/TestApiBaseRepair.java
@@ -23,8 +23,11 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 
+
 import com.ning.billing.ErrorCode;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.PhaseType;
@@ -40,7 +43,8 @@ import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 
 public abstract class TestApiBaseRepair extends TestApiBase {
 
-
+    protected final static Logger log = LoggerFactory.getLogger(TestApiBaseRepair.class);
+    
     public interface TestWithExceptionCallback {
         public void doTest() throws EntitlementRepairException, EntitlementUserApiException;
     }
@@ -135,11 +139,18 @@ public abstract class TestApiBaseRepair extends TestApiBase {
         return null;
     }
     protected void validateExistingEventForAssertion(final ExistingEvent expected, final ExistingEvent input) {
+        
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName()));
         assertEquals(input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName());
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType()));
         assertEquals(input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType());
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory()));
         assertEquals(input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory());                    
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName()));
         assertEquals(input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName());                    
+        log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod()));
         assertEquals(input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod());
+        log.info(String.format("Got %s -> Expected %s", input.getEffectiveDate(), expected.getEffectiveDate()));
         assertEquals(input.getEffectiveDate(), expected.getEffectiveDate());        
     }
     
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/repair/TestRepairBP.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/repair/TestRepairBP.java
index fe25958..08bf2d0 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/repair/TestRepairBP.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/repair/TestRepairBP.java
@@ -162,8 +162,9 @@ public class TestRepairBP extends TestApiBaseRepair {
         List<ExistingEvent> expected = new LinkedList<SubscriptionRepair.ExistingEvent>();
         expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, baseProduct, PhaseType.TRIAL,
                 ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate()));
-        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, null, null,
-                    ProductCategory.BASE, null, null, baseSubscription.getStartDate()));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, baseProduct, PhaseType.TRIAL,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD,baseSubscription.getStartDate()));
+
         for (ExistingEvent e : expected) {
            validateExistingEventForAssertion(e, events.get(index++));           
         }
@@ -364,7 +365,10 @@ public class TestRepairBP extends TestApiBaseRepair {
         assertEquals(cur.getId(), baseSubscription.getId());
 
         events = cur.getExistingEvents();
-        assertEquals(expectedEvents.size(), events.size());
+        for (ExistingEvent e : events) {
+            log.info(String.format("%s, %s, %s, %s", e.getSubscriptionTransitionType(), e.getEffectiveDate(), e.getPlanPhaseSpecifier().getProductName(),  e.getPlanPhaseSpecifier().getPhaseType()));
+        }
+        assertEquals(events.size(), expectedEvents.size());
         index = 0;
         for (ExistingEvent e : expectedEvents) {
            validateExistingEventForAssertion(e, events.get(index++));           
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/repair/TestRepairWithAO.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/repair/TestRepairWithAO.java
index d238a6a..a4cfcd4 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/repair/TestRepairWithAO.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/repair/TestRepairWithAO.java
@@ -52,7 +52,7 @@ public class TestRepairWithAO extends TestApiBaseRepair {
     @Override
     public Injector getInjector() {
         return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
-    }
+    }    
 
     @Test(groups={"slow"})
     public void testRepairChangeBPWithAddonIncluded() throws Exception {
@@ -116,8 +116,8 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         List<ExistingEvent> expectedAO = new LinkedList<SubscriptionRepair.ExistingEvent>();
         expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
                 ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
-        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, null, null,
-                ProductCategory.ADD_ON, null, null, bpChangeDate));
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, bpChangeDate));
         int index = 0;
         for (ExistingEvent e : expectedAO) {
            validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
@@ -262,8 +262,8 @@ public class TestRepairWithAO extends TestApiBaseRepair {
                 ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
         expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.EVERGREEN,
                 ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusMonths(1)));
-        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, null, null,
-                ProductCategory.ADD_ON, null, null, bpChangeDate));
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Telescopic-Scope", PhaseType.EVERGREEN,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, bpChangeDate));
         int index = 0;
         for (ExistingEvent e : expectedAO) {
            validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
@@ -369,7 +369,7 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         BundleRepair dryRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
                 
         aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
-        assertEquals(aoRepair.getExistingEvents().size(), 2);
+        assertEquals(aoRepair.getExistingEvents().size(), 3);
 
         bpRepair = getSubscriptionRepair(baseSubscription.getId(), dryRunBundleRepair);
         assertEquals(bpRepair.getExistingEvents().size(), 3);        
@@ -380,10 +380,9 @@ public class TestRepairWithAO extends TestApiBaseRepair {
                 ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
         expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Telescopic-Scope", PhaseType.EVERGREEN,
                 ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusMonths(1)));
-        /*
-        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, null, null,
-                ProductCategory.ADD_ON, null, null, newChargedThroughDate));
-                */
+        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Telescopic-Scope", PhaseType.EVERGREEN,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, newChargedThroughDate));
+
         int index = 0;
         for (ExistingEvent e : expectedAO) {
            validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
@@ -395,8 +394,8 @@ public class TestRepairWithAO extends TestApiBaseRepair {
                 ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate()));
         expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Shotgun", PhaseType.EVERGREEN,
                 ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusDays(30)));
-        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, null, null,
-                ProductCategory.BASE, null, null, newChargedThroughDate));
+        expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Shotgun", PhaseType.EVERGREEN,
+                ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, newChargedThroughDate));
         index = 0;
         for (ExistingEvent e : expectedBP) {
            validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));           
@@ -421,9 +420,6 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         bpRepair = getSubscriptionRepair(baseSubscription.getId(), realRunBundleRepair);
         assertEquals(bpRepair.getExistingEvents().size(), 3);        
         
-        // STEPH BUG -- WE ONLY SEE THAT AFTER WE HIT DISK.
-        expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, null, null,
-                ProductCategory.ADD_ON, null, null, newChargedThroughDate));
         index = 0;
         for (ExistingEvent e : expectedAO) {
            validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));           
@@ -515,8 +511,8 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         List<ExistingEvent> expected = new LinkedList<SubscriptionRepair.ExistingEvent>();
         expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
                 ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
-        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, null, null,
-                ProductCategory.ADD_ON, null, null, aoCancelDate));
+        expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Telescopic-Scope", PhaseType.DISCOUNT,
+                ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoCancelDate));
         int index = 0;
         for (ExistingEvent e : expected) {
            validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));