killbill-uncached

Details

diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java
index 2e8ab03..f6343f5 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java
@@ -129,7 +129,7 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
 
 
         // Keep the current state per entitlement
-        final Map<UUID, TargetState> targetStates= new HashMap<UUID, TargetState>();
+        final Map<UUID, TargetState> targetStates = new HashMap<UUID, TargetState>();
         for (UUID cur : allEntitlementUUIDs) {
             targetStates.put(cur, new TargetState());
         }
@@ -140,38 +140,38 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
         int index = -1;
         final Iterator<SubscriptionEvent> it = result.iterator();
         // Where we need to insert in that stream
-        DefaultSubscriptionEvent cur = null;
+        DefaultSubscriptionEvent curInsertion = null;
         while (it.hasNext()) {
-            cur = (DefaultSubscriptionEvent) it.next();
+            DefaultSubscriptionEvent cur = (DefaultSubscriptionEvent) it.next();
             index++;
 
             final int compEffectiveDate = bsEffectiveDate.compareTo(cur.getEffectiveDate());
             final boolean shouldContinue = (compEffectiveDate > 0 ||
                                             (compEffectiveDate == 0 && bs.getCreatedDate().compareTo(cur.getCreatedDate()) >= 0));
+            if (!shouldContinue) {
+                break;
+            }
 
             final TargetState curTargetState = targetStates.get(cur.getEntitlementId());
-            if (shouldContinue) {
-                switch (cur.getSubscriptionEventType()) {
-                    case START_ENTITLEMENT:
-                        curTargetState.setEntitlementStarted();
-                        break;
-                    case STOP_ENTITLEMENT:
-                        curTargetState.setEntitlementStopped();
-                        break;
-                    case START_BILLING:
-                        curTargetState.setBillingStarted();
-                        break;
-                    case PAUSE_BILLING:
-                    case PAUSE_ENTITLEMENT:
-                        curTargetState.addEntitlementEvent(cur);
-                        break;
-                    case STOP_BILLING:
-                        curTargetState.setBillingStopped();
-                        break;
-                }
-            } else {
-                break;
+            switch (cur.getSubscriptionEventType()) {
+                case START_ENTITLEMENT:
+                    curTargetState.setEntitlementStarted();
+                    break;
+                case STOP_ENTITLEMENT:
+                    curTargetState.setEntitlementStopped();
+                    break;
+                case START_BILLING:
+                    curTargetState.setBillingStarted();
+                    break;
+                case PAUSE_BILLING:
+                case PAUSE_ENTITLEMENT:
+                    curTargetState.addEntitlementEvent(cur);
+                    break;
+                case STOP_BILLING:
+                    curTargetState.setBillingStopped();
+                    break;
             }
+            curInsertion = cur;
         }
 
         // Extract the list of targets based on the type of blocking state
@@ -181,7 +181,7 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
         // For each target compute the new events that should be inserted in the stream
         for (UUID target : targetEntitlementIds) {
 
-            final SubscriptionEvent[] prevNext = findPrevNext(result, target, cur, bs);
+            final SubscriptionEvent[] prevNext = findPrevNext(result, target, curInsertion);
             final TargetState curTargetState = targetStates.get(target);
 
             final List<SubscriptionEventType> eventTypes = curTargetState.addStateAndReturnEventTypes(bs);
@@ -192,10 +192,16 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
         return index;
     }
 
-    private SubscriptionEvent[] findPrevNext(final List<SubscriptionEvent> events, final UUID targetEntitlementId, final SubscriptionEvent insertionEvent, final BlockingState bs) {
+    private SubscriptionEvent[] findPrevNext(final List<SubscriptionEvent> events, final UUID targetEntitlementId, final SubscriptionEvent insertionEvent) {
 
         // Find prev/next event for the same entitlement
-        final SubscriptionEvent[] result =  new DefaultSubscriptionEvent[2];
+        final SubscriptionEvent[] result = new DefaultSubscriptionEvent[2];
+        if (insertionEvent == null) {
+            result[0] = null;
+            result[1] = events.size() > 0 ? events.get(0) : null;
+            return result;
+        }
+
         final Iterator<SubscriptionEvent> it = events.iterator();
         DefaultSubscriptionEvent prev = null;
         DefaultSubscriptionEvent next = null;
@@ -251,16 +257,16 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
     private void sanitizeForBaseRecreateEvents(final LinkedList<SubscriptionEvent> input) {
 
         final Set<UUID> guiltyEntitlementIds = new TreeSet<UUID>();
-        ListIterator<SubscriptionEvent> it = input.listIterator(input.size() - 1);
+        ListIterator<SubscriptionEvent> it = input.listIterator(input.size());
         while (it.hasPrevious()) {
             final SubscriptionEvent cur = it.previous();
             if (cur.getSubscriptionEventType() == SubscriptionEventType.RESUME_BILLING) {
-                guiltyEntitlementIds.add(cur.getId());
+                guiltyEntitlementIds.add(cur.getEntitlementId());
                 continue;
             }
             if (cur.getSubscriptionEventType() == SubscriptionEventType.STOP_BILLING &&
-                guiltyEntitlementIds.contains(cur.getId())) {
-                guiltyEntitlementIds.remove(cur.getId());
+                guiltyEntitlementIds.contains(cur.getEntitlementId())) {
+                guiltyEntitlementIds.remove(cur.getEntitlementId());
                 final SubscriptionEvent correctedEvent = new DefaultSubscriptionEvent((DefaultSubscriptionEvent) cur, SubscriptionEventType.PAUSE_BILLING);
                 it.set(correctedEvent);
             }
@@ -314,7 +320,7 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
                                             prev != null ? (prev.getNextPlan() != null ? prev.getNextPlan() : prev.getPrevPlan()) : null,
                                             prev != null ? (prev.getNextPhase() != null ? prev.getNextPhase() : prev.getPrevPhase()) : null,
                                             prev != null ? (prev.getNextPriceList() != null ? prev.getNextPriceList() : prev.getPrevPriceList()) : null,
-                                            prev != null ? (prev.getNextBillingPeriod() != null ? prev.getNextBillingPeriod() : prev.getPrevBillingPeriod())  : null,
+                                            prev != null ? (prev.getNextBillingPeriod() != null ? prev.getNextBillingPeriod() : prev.getPrevBillingPeriod()) : null,
                                             next != null ? next.getPrevProduct() : null,
                                             next != null ? next.getPrevPlan() : null,
                                             next != null ? next.getPrevPhase() : null,
@@ -401,6 +407,9 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
         return events;
     }
 
+    //
+    // Internal class to keep the state associated with each subscription
+    //
     private final static class TargetState {
 
         private boolean isEntitlementStarted;
@@ -425,7 +434,6 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
             isEntitlementStopped = true;
         }
 
-
         public void setBillingStarted() {
             isBillingStarted = true;
         }
@@ -441,17 +449,28 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
             perServiceBlockingState.put(converted.getService(), converted);
 
         }
+
         public List<SubscriptionEventType> addStateAndReturnEventTypes(final BlockingState bs) {
 
-            final List<SubscriptionEventType> result  = new ArrayList<SubscriptionEventType>(4);
-            if (bs.getStateName().equals(DefaultEntitlementApi.ENT_STATE_CANCELLED)) {
+            // Turn off isBlockedEntitlement and isBlockedBilling if there was not start event
+            final BlockingState fixedBlockingState = new DefaultBlockingState(bs.getBlockedId(),
+                                                                              bs.getType(),
+                                                                              bs.getStateName(),
+                                                                              bs.getService(),
+                                                                              bs.isBlockChange(),
+                                                                              (bs.isBlockEntitlement() && isEntitlementStarted && !isEntitlementStopped),
+                                                                              (bs.isBlockBilling() && isBillingStarted && !isBillingStopped),
+                                                                              bs.getEffectiveDate());
+
+            final List<SubscriptionEventType> result = new ArrayList<SubscriptionEventType>(4);
+            if (fixedBlockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_CANCELLED)) {
                 isEntitlementStopped = true;
                 result.add(SubscriptionEventType.STOP_ENTITLEMENT);
                 return result;
             }
 
             final BlockingAggregator stateBefore = getState();
-            perServiceBlockingState.put(bs.getService(), bs);
+            perServiceBlockingState.put(fixedBlockingState.getService(), fixedBlockingState);
             final BlockingAggregator stateAfter = getState();
 
             final boolean shouldResumeEntitlement = isEntitlementStarted && !isEntitlementStopped && stateBefore.isBlockEntitlement() && !stateAfter.isBlockEntitlement();
@@ -472,7 +491,7 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
                 result.add(SubscriptionEventType.PAUSE_BILLING);
             }
 
-            if (!shouldResumeEntitlement && !shouldBlockEntitlement && !shouldBlockEntitlement && !shouldBlockBilling && !bs.getService().equals(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME)) {
+            if (!shouldResumeEntitlement && !shouldBlockEntitlement && !shouldBlockEntitlement && !shouldBlockBilling && !fixedBlockingState.getService().equals(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME)) {
                 result.add(SubscriptionEventType.SERVICE_STATE_CHANGE);
             }
             return result;
@@ -578,7 +597,7 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
                  copy.getNextPriceList(),
                  copy.getNextBillingPeriod(),
                  copy.getCreatedDate(),
-                copy.getAccountTimeZone());
+                 copy.getAccountTimeZone());
         }
 
         public DateTimeZone getAccountTimeZone() {
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
index 2eac11a..a76882e 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
@@ -117,6 +117,159 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
     }
 
 
+
+    @Test(groups = "fast")
+    public void testOneEntitlementWithRecreate() throws CatalogApiException {
+
+        clock.setDay(new LocalDate(2013, 1, 1));
+
+        final DateTimeZone accountTimeZone = DateTimeZone.UTC;
+        final UUID accountId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
+        final String externalKey = "foo";
+
+
+        final UUID entitlementId = UUID.randomUUID();
+
+        final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
+
+        final DateTime requestedDate = new DateTime();
+        DateTime effectiveDate = new DateTime(2013, 1, 1, 15, 43, 25, 0, DateTimeZone.UTC);
+        final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "trial");
+        allTransitions.add(tr1);
+
+        effectiveDate = effectiveDate.plusDays(30);
+        clock.addDays(30);
+        final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, requestedDate, effectiveDate, clock.getUTCNow(), "trial", "phase");
+        allTransitions.add(tr2);
+
+
+        effectiveDate = effectiveDate.plusDays(15);
+        clock.addDays(15);
+        final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, requestedDate, effectiveDate, clock.getUTCNow(), "phase", null);
+        allTransitions.add(tr3);
+
+        effectiveDate = effectiveDate.plusDays(15);
+        clock.addDays(15);
+        final SubscriptionBaseTransition tr4 = createTransition(entitlementId, EventType.API_USER, ApiEventType.RE_CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "phase");
+        allTransitions.add(tr4);
+
+
+        final List<Entitlement> entitlements = new ArrayList<Entitlement>();
+        final Entitlement entitlement = createEntitlement(entitlementId, allTransitions);
+        entitlements.add(entitlement);
+
+        final DefaultSubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, accountId, bundleId, externalKey, entitlements, Collections.<BlockingState>emptyList());
+
+        assertEquals(timeline.getAccountId(), accountId);
+        assertEquals(timeline.getBundleId(), bundleId);
+        assertEquals(timeline.getExternalKey(), externalKey);
+
+        List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
+        assertEquals(events.size(), 5);
+
+        assertEquals(events.get(0).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(1).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(2).getEffectiveDate().compareTo(new LocalDate(tr2.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(3).getEffectiveDate().compareTo(new LocalDate(tr3.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(4).getEffectiveDate().compareTo(new LocalDate(tr4.getEffectiveTransitionTime(), accountTimeZone)), 0);
+
+        assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+        assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+        assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+        assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+        assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
+
+        assertEquals(events.get(0).getNextPhase().getName(), "trial");
+        assertEquals(events.get(1).getNextPhase().getName(), "trial");
+        assertEquals(events.get(2).getNextPhase().getName(), "phase");
+        assertEquals(events.get(3).getNextPhase(), null);
+        assertEquals(events.get(4).getNextPhase().getName(), "phase");
+    }
+
+
+
+    @Test(groups = "fast")
+    public void testOneEntitlementWithInitialBlockingState() throws CatalogApiException {
+
+        clock.setDay(new LocalDate(2013, 1, 1));
+
+        final DateTimeZone accountTimeZone = DateTimeZone.UTC;
+        final UUID accountId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
+        final String externalKey = "foo";
+
+
+        final UUID entitlementId = UUID.randomUUID();
+
+        final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
+        final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
+        final BlockingState bs1 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
+                                                           DefaultEntitlementApi.ENT_STATE_BLOCKED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+                                                           true, true, false, clock.getUTCNow(), clock.getUTCNow(), clock.getUTCNow());
+
+        blockingStates.add(bs1);
+
+        clock.addDays(1);
+
+        final DateTime requestedDate = new DateTime();
+        DateTime effectiveDate = new DateTime(2013, 1, 1, 15, 43, 25, 0, DateTimeZone.UTC);
+        final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "trial");
+        allTransitions.add(tr1);
+
+        effectiveDate = effectiveDate.plusDays(30);
+        clock.addDays(30);
+        final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, requestedDate, effectiveDate, clock.getUTCNow(), "trial", "phase");
+        allTransitions.add(tr2);
+
+
+        final BlockingState bs2 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
+                                                           "NothingUseful", "boo",
+                                                           false, false, false, clock.getUTCNow(), clock.getUTCNow(), clock.getUTCNow());
+
+        blockingStates.add(bs2);
+
+
+        effectiveDate = effectiveDate.plusDays(15);
+        clock.addDays(15);
+        final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, requestedDate, effectiveDate, clock.getUTCNow(), "phase", null);
+        allTransitions.add(tr3);
+
+
+        final List<Entitlement> entitlements = new ArrayList<Entitlement>();
+        final Entitlement entitlement = createEntitlement(entitlementId, allTransitions);
+        entitlements.add(entitlement);
+
+        final DefaultSubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, accountId, bundleId, externalKey, entitlements, blockingStates);
+
+        assertEquals(timeline.getAccountId(), accountId);
+        assertEquals(timeline.getBundleId(), bundleId);
+        assertEquals(timeline.getExternalKey(), externalKey);
+
+        List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
+        assertEquals(events.size(), 5);
+
+        assertEquals(events.get(0).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(1).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(2).getEffectiveDate().compareTo(new LocalDate(tr2.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(3).getEffectiveDate().compareTo(new LocalDate(bs2.getEffectiveDate(), accountTimeZone)), 0);
+        assertEquals(events.get(4).getEffectiveDate().compareTo(new LocalDate(tr3.getEffectiveTransitionTime(), accountTimeZone)), 0);
+
+        assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+        assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+        assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+        assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+        assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
+
+        assertEquals(events.get(0).getNextPhase().getName(), "trial");
+        assertEquals(events.get(1).getNextPhase().getName(), "trial");
+        assertEquals(events.get(2).getNextPhase().getName(), "phase");
+        assertEquals(events.get(3).getNextPhase().getName(), "phase");
+        assertEquals(events.get(4).getNextPhase(), null);
+    }
+
+
+
     @Test(groups = "fast")
     public void testOneEntitlementWithBlockingStatesSubscription() throws CatalogApiException {
 
@@ -191,7 +344,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
         assertEquals(events.get(2).getPrevPhase().getName(), "trial");
         assertEquals(events.get(2).getNextPhase().getName(), "phase");
         assertEquals(events.get(3).getPrevPhase().getName(), "phase");
-        assertEquals(events.get(3).getNextPhase(), null);
+        assertEquals(events.get(3).getNextPhase().getName(), "phase");
         assertEquals(events.get(4).getPrevPhase().getName(), "phase");
         assertEquals(events.get(4).getNextPhase(), null);
         assertEquals(events.get(5).getPrevPhase().getName(), "phase");
@@ -289,15 +442,29 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
         assertEquals(events.get(7).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
         assertEquals(events.get(8).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
 
-        assertEquals(events.get(0).getNextPhase().getName(), "trial");
+        assertEquals(events.get(0).getPrevPhase(), null);
+        assertEquals(events.get(0).getNextPhase().getName(), "trial1");
+        assertEquals(events.get(1).getPrevPhase(), null);
         assertEquals(events.get(1).getNextPhase().getName(), "trial1");
+        assertEquals(events.get(2).getPrevPhase(), null);
         assertEquals(events.get(2).getNextPhase().getName(), "phase2");
+        assertEquals(events.get(3).getPrevPhase(), null);
         assertEquals(events.get(3).getNextPhase().getName(), "phase2");
+
+        assertEquals(events.get(4).getPrevPhase().getName(), "trial1");
         assertEquals(events.get(4).getNextPhase().getName(), "phase1");
+
+        assertEquals(events.get(5).getPrevPhase().getName(), "phase1");
         assertEquals(events.get(5).getNextPhase().getName(), "phase1");
-        assertEquals(events.get(6).getNextPhase().getName(), "phase2");
-        assertEquals(events.get(7).getNextPhase().getName(), "phase1");
-        assertEquals(events.get(8).getNextPhase().getName(), "phase1");
+
+        assertEquals(events.get(6).getPrevPhase().getName(), "phase2");
+        assertEquals(events.get(6).getNextPhase(), null);
+
+        assertEquals(events.get(7).getPrevPhase().getName(), "phase1");
+        assertEquals(events.get(7).getNextPhase(), null);
+
+        assertEquals(events.get(8).getPrevPhase().getName(), "phase1");
+        assertEquals(events.get(8).getNextPhase(), null);
 
     }