killbill-memoizeit

entitlement: simplify ordering building logic of SubscriptionEvent Signed-off-by:

5/1/2017 2:09:34 AM

Details

diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/BlockingStateOrdering.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/BlockingStateOrdering.java
index 0c1c5b7..6648e03 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/BlockingStateOrdering.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/BlockingStateOrdering.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project 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
@@ -19,6 +19,7 @@ package org.killbill.billing.entitlement.api;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -42,6 +43,7 @@ import org.killbill.billing.entitlement.block.BlockingChecker.BlockingAggregator
 import org.killbill.billing.entitlement.block.DefaultBlockingChecker.DefaultBlockingAggregator;
 import org.killbill.billing.junction.DefaultBlockingState;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
@@ -53,13 +55,13 @@ import com.google.common.collect.Sets;
 // Given an event stream (across one or multiple entitlements), insert the blocking events at the right place
 public class BlockingStateOrdering extends EntitlementOrderingBase {
 
-    private static final BlockingStateOrdering INSTANCE = new BlockingStateOrdering();
+    @VisibleForTesting
+    static final BlockingStateOrdering INSTANCE = new BlockingStateOrdering();
 
     private BlockingStateOrdering() {}
 
     public static void insertSorted(final Iterable<Entitlement> entitlements, final InternalTenantContext internalTenantContext, final LinkedList<SubscriptionEvent> inputAndOutputResult) {
         INSTANCE.computeEvents(entitlements, internalTenantContext, inputAndOutputResult);
-
     }
 
     private void computeEvents(final Iterable<Entitlement> entitlements, final InternalTenantContext internalTenantContext, final LinkedList<SubscriptionEvent> inputAndOutputResult) {
@@ -71,6 +73,14 @@ public class BlockingStateOrdering extends EntitlementOrderingBase {
             blockingStates.addAll(((DefaultEntitlement) entitlement).getEventsStream().getBlockingStates());
         }
 
+        computeEvents(new LinkedList<UUID>(allEntitlementUUIDs), blockingStates, internalTenantContext, inputAndOutputResult);
+    }
+
+    @VisibleForTesting
+    void computeEvents(final LinkedList<UUID> allEntitlementUUIDs, final Collection<BlockingState> blockingStates, final InternalTenantContext internalTenantContext, final LinkedList<SubscriptionEvent> inputAndOutputResult) {
+        // Make sure the ordering is stable
+        Collections.sort(allEntitlementUUIDs);
+
         final SupportForOlderVersionThan_0_17_X backwardCompatibleContext = new SupportForOlderVersionThan_0_17_X(inputAndOutputResult, blockingStates);
 
         // Trust the incoming ordering here: blocking states were sorted using ProxyBlockingStateDao#sortedCopy
@@ -107,12 +117,7 @@ public class BlockingStateOrdering extends EntitlementOrderingBase {
                     shouldContinue = false;
                     break;
                 case 0:
-                    // In case of exact same date, we want to make sure that a START_ENTITLEMENT event gets correctly populated when the STOP_BILLING is also on the same date
-                    if (currentBlockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_START) && cur.getSubscriptionEventType() != SubscriptionEventType.STOP_BILLING) {
-                        shouldContinue = false;
-                    } else {
-                        shouldContinue = true;
-                    }
+                    shouldContinue = compareBlockingStateWithNextSubscriptionEvent(currentBlockingState, cur) > 0;
                     break;
                 case 1:
                     shouldContinue = true;
@@ -169,9 +174,75 @@ public class BlockingStateOrdering extends EntitlementOrderingBase {
                 outputNewEvents.add(toSubscriptionEvent(prevNext[0], prevNext[1], targetEntitlementId, currentBlockingState, t, internalTenantContext));
             }
         }
+
         return index;
     }
 
+    private int compareBlockingStateWithNextSubscriptionEvent(final BlockingState blockingState, final SubscriptionEvent next) {
+        final String serviceName = blockingState.getService();
+
+        // For consistency, make sure entitlement-service and billing-service events always happen in a
+        // deterministic order (e.g. after other services for STOP events and before for START events)
+        if ((DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(serviceName) ||
+             BILLING_SERVICE_NAME.equals(serviceName) ||
+             ENT_BILLING_SERVICE_NAME.equals(serviceName)) &&
+            !(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(next.getServiceName()) ||
+              BILLING_SERVICE_NAME.equals(next.getServiceName()) ||
+              ENT_BILLING_SERVICE_NAME.equals(next.getServiceName()))) {
+            // first is an entitlement-service or billing-service event, but not second
+            if (blockingState.isBlockBilling() || blockingState.isBlockEntitlement()) {
+                // PAUSE_ and STOP_ events go last
+                return 1;
+            } else {
+                return -1;
+            }
+        } else if ((DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(next.getServiceName()) ||
+                    BILLING_SERVICE_NAME.equals(next.getServiceName()) ||
+                    ENT_BILLING_SERVICE_NAME.equals(next.getServiceName())) &&
+                   !(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(serviceName) ||
+                     BILLING_SERVICE_NAME.equals(serviceName) ||
+                     ENT_BILLING_SERVICE_NAME.equals(serviceName))) {
+            // second is an entitlement-service or billing-service event, but not first
+            if (next.getSubscriptionEventType().equals(SubscriptionEventType.START_ENTITLEMENT) ||
+                next.getSubscriptionEventType().equals(SubscriptionEventType.START_BILLING) ||
+                next.getSubscriptionEventType().equals(SubscriptionEventType.RESUME_ENTITLEMENT) ||
+                next.getSubscriptionEventType().equals(SubscriptionEventType.RESUME_BILLING) ||
+                next.getSubscriptionEventType().equals(SubscriptionEventType.PHASE) ||
+                next.getSubscriptionEventType().equals(SubscriptionEventType.CHANGE)) {
+                return 1;
+            } else if (next.getSubscriptionEventType().equals(SubscriptionEventType.PAUSE_ENTITLEMENT) ||
+                       next.getSubscriptionEventType().equals(SubscriptionEventType.PAUSE_BILLING) ||
+                       next.getSubscriptionEventType().equals(SubscriptionEventType.STOP_ENTITLEMENT) ||
+                       next.getSubscriptionEventType().equals(SubscriptionEventType.STOP_BILLING)) {
+                return -1;
+            } else {
+                // Default behavior
+                return 1;
+            }
+        } else if (isStartEntitlement(blockingState)) {
+            // START_ENTITLEMENT is always first
+            return -1;
+        } else if (next.getSubscriptionEventType().equals(SubscriptionEventType.START_ENTITLEMENT)) {
+            // START_ENTITLEMENT is always first
+            return 1;
+        } else if (next.getSubscriptionEventType().equals(SubscriptionEventType.STOP_BILLING)) {
+            // STOP_BILLING is always last
+            return -1;
+        } else if (next.getSubscriptionEventType().equals(SubscriptionEventType.START_BILLING)) {
+            // START_BILLING is first after START_ENTITLEMENT
+            return 1;
+        } else if (isStopEntitlement(blockingState)) {
+            // STOP_ENTITLEMENT is last after STOP_BILLING
+            return 1;
+        } else if (next.getSubscriptionEventType().equals(SubscriptionEventType.STOP_ENTITLEMENT)) {
+            // STOP_ENTITLEMENT is last after STOP_BILLING
+            return -1;
+        } else {
+            // Trust the current ordering
+            return 1;
+        }
+    }
+
     // Extract prev and next events in the stream events for that particular target subscription from the insertionEvent
     private SubscriptionEvent[] findPrevNext(final List<SubscriptionEvent> events, final UUID targetEntitlementId, final SubscriptionEvent insertionEvent) {
         // Find prev/next event for the same entitlement
@@ -390,11 +461,11 @@ public class BlockingStateOrdering extends EntitlementOrderingBase {
                                                                               bs.getEffectiveDate());
 
             final List<SubscriptionEventType> result = new ArrayList<SubscriptionEventType>(4);
-            if (fixedBlockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_START)) {
+            if (isStartEntitlement(fixedBlockingState)) {
                 isEntitlementStarted = true;
                 result.add(SubscriptionEventType.START_ENTITLEMENT);
                 return result;
-            } else if (fixedBlockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_CANCELLED)) {
+            } else if (isStopEntitlement(fixedBlockingState)) {
                 isEntitlementStopped = true;
                 result.add(SubscriptionEventType.STOP_ENTITLEMENT);
                 return result;
@@ -450,6 +521,16 @@ public class BlockingStateOrdering extends EntitlementOrderingBase {
         }
     }
 
+    private static boolean isStartEntitlement(final BlockingState blockingState) {
+        return DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(blockingState.getService()) &&
+               DefaultEntitlementApi.ENT_STATE_START.equals(blockingState.getStateName());
+    }
+
+    private static boolean isStopEntitlement(final BlockingState blockingState) {
+        return DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(blockingState.getService()) &&
+               DefaultEntitlementApi.ENT_STATE_CANCELLED.equals(blockingState.getStateName());
+    }
+
     //
     // The logic to add the missing START_ENTITLEMENT for older subscriptions is contained in this class. When we want/need to drop backward compatibility we can
     // simply drop this class and where it is called.
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/SubscriptionEventOrdering.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/SubscriptionEventOrdering.java
index 9283363..96b3d26 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/SubscriptionEventOrdering.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/SubscriptionEventOrdering.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project 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
@@ -17,7 +17,6 @@
 
 package org.killbill.billing.entitlement.api;
 
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -25,7 +24,6 @@ import java.util.List;
 import java.util.Map;
 
 import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.entitlement.DefaultEntitlementService;
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseTransition;
@@ -42,8 +40,7 @@ import com.google.common.collect.ImmutableList;
 //
 public class SubscriptionEventOrdering extends EntitlementOrderingBase {
 
-    @VisibleForTesting
-    static final SubscriptionEventOrdering INSTANCE = new SubscriptionEventOrdering();
+    private static final SubscriptionEventOrdering INSTANCE = new SubscriptionEventOrdering();
 
     private SubscriptionEventOrdering() {}
 
@@ -63,7 +60,6 @@ public class SubscriptionEventOrdering extends EntitlementOrderingBase {
         BlockingStateOrdering.insertSorted(entitlements, internalTenantContext, result);
 
         // Final cleanups
-        reOrderSubscriptionEventsOnSameDateByType(result);
         removeOverlappingSubscriptionEvents(result);
 
         return result;
@@ -146,7 +142,8 @@ public class SubscriptionEventOrdering extends EntitlementOrderingBase {
         result.add(index, event);
     }
 
-    private SubscriptionEvent toSubscriptionEvent(final SubscriptionBaseTransition in, final SubscriptionEventType eventType, final InternalTenantContext internalTenantContext) {
+    @VisibleForTesting
+    static SubscriptionEvent toSubscriptionEvent(final SubscriptionBaseTransition in, final SubscriptionEventType eventType, final InternalTenantContext internalTenantContext) {
         return new DefaultSubscriptionEvent(in.getId(),
                                             in.getSubscriptionId(),
                                             in.getEffectiveTransitionTime(),
@@ -169,47 +166,6 @@ public class SubscriptionEventOrdering extends EntitlementOrderingBase {
                                             internalTenantContext);
     }
 
-    //
-    // All events have been inserted and should be at the right place, except that we want to ensure that events for a given subscription,
-    // and for a given time are ordered by SubscriptionEventType.
-    //
-    // All this seems a little over complicated, and one wonders why we don't just shove all events and call Collections.sort on the list prior
-    // to return:
-    // - One explanation is that we don't know the events in advance and each time the new events to be inserted are computed from the current state
-    //   of the stream, which requires ordering all along
-    // - A careful reader will notice that the algorithm is N^2, -- so that we care so much considering we have very events -- but in addition to that
-    //   the recursive path will be used very infrequently and when it is used, this will be probably just reorder with the prev event and that's it.
-    //
-    @VisibleForTesting
-    protected void reOrderSubscriptionEventsOnSameDateByType(final List<SubscriptionEvent> events) {
-        final int size = events.size();
-        for (int i = 0; i < size; i++) {
-            final SubscriptionEvent cur = events.get(i);
-            final SubscriptionEvent next = (i < (size - 1)) ? events.get(i + 1) : null;
-
-            final boolean shouldSwap = (next != null && shouldSwap(cur, next, true));
-            final boolean shouldReverseSort = (next == null || shouldSwap);
-
-            int currentIndex = i;
-            if (shouldSwap) {
-                Collections.swap(events, i, i + 1);
-            }
-            if (shouldReverseSort) {
-                while (currentIndex >= 1) {
-                    final SubscriptionEvent revCur = events.get(currentIndex);
-                    final SubscriptionEvent other = events.get(currentIndex - 1);
-                    if (shouldSwap(revCur, other, false)) {
-                        Collections.swap(events, currentIndex, currentIndex - 1);
-                    }
-                    if (revCur.getEffectiveDate().compareTo(other.getEffectiveDate()) != 0) {
-                        break;
-                    }
-                    currentIndex--;
-                }
-            }
-        }
-    }
-
     // Make sure the argument supports the remove operation - hence expect a LinkedList, not a List
     private void removeOverlappingSubscriptionEvents(final LinkedList<SubscriptionEvent> events) {
         final Iterator<SubscriptionEvent> iterator = events.iterator();
@@ -228,92 +184,4 @@ public class SubscriptionEventOrdering extends EntitlementOrderingBase {
             }
         }
     }
-
-    private boolean shouldSwap(final SubscriptionEvent cur, final SubscriptionEvent other, final boolean isAscending) {
-        // For a given date, order by subscriptionId, and within subscription by event type
-        final int idComp = cur.getEntitlementId().compareTo(other.getEntitlementId());
-        final Integer comparison = compareSubscriptionEventsForSameEffectiveDateAndEntitlementId(cur, other);
-        return (cur.getEffectiveDate().compareTo(other.getEffectiveDate()) == 0 &&
-                ((isAscending &&
-                  ((idComp > 0) ||
-                   (idComp == 0 && comparison != null && comparison > 0))) ||
-                 (!isAscending &&
-                  ((idComp < 0) ||
-                   (idComp == 0 && comparison != null && comparison < 0)))));
-    }
-
-    private Integer compareSubscriptionEventsForSameEffectiveDateAndEntitlementId(final SubscriptionEvent first, final SubscriptionEvent second) {
-        // For consistency, make sure entitlement-service and billing-service events always happen in a
-        // deterministic order (e.g. after other services for STOP events and before for START events)
-        if ((DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(first.getServiceName()) ||
-             BILLING_SERVICE_NAME.equals(first.getServiceName())) &&
-            !(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(second.getServiceName()) ||
-              BILLING_SERVICE_NAME.equals(second.getServiceName()))) {
-            // first is an entitlement-service or billing-service event, but not second
-            if (first.getSubscriptionEventType().equals(SubscriptionEventType.START_ENTITLEMENT) ||
-                first.getSubscriptionEventType().equals(SubscriptionEventType.START_BILLING) ||
-                first.getSubscriptionEventType().equals(SubscriptionEventType.RESUME_ENTITLEMENT) ||
-                first.getSubscriptionEventType().equals(SubscriptionEventType.RESUME_BILLING) ||
-                first.getSubscriptionEventType().equals(SubscriptionEventType.PHASE) ||
-                first.getSubscriptionEventType().equals(SubscriptionEventType.CHANGE)) {
-                return -1;
-            } else if (first.getSubscriptionEventType().equals(SubscriptionEventType.PAUSE_ENTITLEMENT) ||
-                       first.getSubscriptionEventType().equals(SubscriptionEventType.PAUSE_BILLING) ||
-                       first.getSubscriptionEventType().equals(SubscriptionEventType.STOP_ENTITLEMENT) ||
-                       first.getSubscriptionEventType().equals(SubscriptionEventType.STOP_BILLING)) {
-                return 1;
-            } else {
-                // Default behavior
-                return -1;
-            }
-        } else if ((DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(second.getServiceName()) ||
-                    BILLING_SERVICE_NAME.equals(second.getServiceName())) &&
-                   !(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(first.getServiceName()) ||
-                     BILLING_SERVICE_NAME.equals(first.getServiceName()))) {
-            // second is an entitlement-service or billing-service event, but not first
-            if (second.getSubscriptionEventType().equals(SubscriptionEventType.START_ENTITLEMENT) ||
-                second.getSubscriptionEventType().equals(SubscriptionEventType.START_BILLING) ||
-                second.getSubscriptionEventType().equals(SubscriptionEventType.RESUME_ENTITLEMENT) ||
-                second.getSubscriptionEventType().equals(SubscriptionEventType.RESUME_BILLING) ||
-                second.getSubscriptionEventType().equals(SubscriptionEventType.PHASE) ||
-                second.getSubscriptionEventType().equals(SubscriptionEventType.CHANGE)) {
-                return 1;
-            } else if (second.getSubscriptionEventType().equals(SubscriptionEventType.PAUSE_ENTITLEMENT) ||
-                       second.getSubscriptionEventType().equals(SubscriptionEventType.PAUSE_BILLING) ||
-                       second.getSubscriptionEventType().equals(SubscriptionEventType.STOP_ENTITLEMENT) ||
-                       second.getSubscriptionEventType().equals(SubscriptionEventType.STOP_BILLING)) {
-                return -1;
-            } else {
-                // Default behavior
-                return 1;
-            }
-        } else if (first.getSubscriptionEventType().equals(SubscriptionEventType.START_ENTITLEMENT)) {
-            // START_ENTITLEMENT is always first
-            return -1;
-        } else if (second.getSubscriptionEventType().equals(SubscriptionEventType.START_ENTITLEMENT)) {
-            // START_ENTITLEMENT is always first
-            return 1;
-        } else if (first.getSubscriptionEventType().equals(SubscriptionEventType.STOP_BILLING)) {
-            // STOP_BILLING is always last
-            return 1;
-        } else if (second.getSubscriptionEventType().equals(SubscriptionEventType.STOP_BILLING)) {
-            // STOP_BILLING is always last
-            return -1;
-        } else if (first.getSubscriptionEventType().equals(SubscriptionEventType.START_BILLING)) {
-            // START_BILLING is first after START_ENTITLEMENT
-            return -1;
-        } else if (second.getSubscriptionEventType().equals(SubscriptionEventType.START_BILLING)) {
-            // START_BILLING is first after START_ENTITLEMENT
-            return 1;
-        } else if (first.getSubscriptionEventType().equals(SubscriptionEventType.STOP_ENTITLEMENT)) {
-            // STOP_ENTITLEMENT is last after STOP_BILLING
-            return 1;
-        } else if (second.getSubscriptionEventType().equals(SubscriptionEventType.STOP_ENTITLEMENT)) {
-            // STOP_ENTITLEMENT is last after STOP_BILLING
-            return -1;
-        } else {
-            // Trust the current ordering
-            return null;
-        }
-    }
 }
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestBlockingStateOrdering.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestBlockingStateOrdering.java
new file mode 100644
index 0000000..02a7581
--- /dev/null
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestBlockingStateOrdering.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
+ *
+ * The Billing Project 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 org.killbill.billing.entitlement.api;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.entitlement.DefaultEntitlementService;
+import org.killbill.billing.entitlement.EntitlementTestSuiteNoDB;
+import org.killbill.billing.junction.DefaultBlockingState;
+import org.killbill.billing.subscription.api.user.SubscriptionBaseTransition;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+// invocationCount > 1 to verify flakiness
+public class TestBlockingStateOrdering extends EntitlementTestSuiteNoDB {
+
+    private long globalOrdering = 0;
+
+    @Test(groups = "fast", invocationCount = 10)
+    public void testIgnore_ENTITLEMENT_SERVICE_NAME_WithNoFlag() throws Exception {
+        final DateTime now = clock.getUTCNow();
+        final UUID subscriptionId1 = UUID.randomUUID();
+
+        final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now.plusDays(1)));
+
+        final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+        computeEvents(allEvents, blockingStates);
+
+        Assert.assertEquals(allEvents.size(), 3);
+        Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+        Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+    }
+
+    @Test(groups = "fast", invocationCount = 10)
+    public void test_ENT_STATE_IsNotInterpreted() throws Exception {
+        final DateTime now = clock.getUTCNow();
+        final UUID subscriptionId1 = UUID.randomUUID();
+
+        final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, "svc1", false, false, now.plusDays(1)));
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, "svc1", false, false, now.plusDays(2)));
+
+        final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+        computeEvents(allEvents, blockingStates);
+
+        Assert.assertEquals(allEvents.size(), 5);
+        Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+        Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+        Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+        Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+    }
+
+    @Test(groups = "fast", invocationCount = 10)
+    public void testPauseAtStart() throws Exception {
+        final DateTime now = clock.getUTCNow();
+        final UUID subscriptionId1 = UUID.randomUUID();
+
+        final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc1", true, true, now));
+
+        final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+        computeEvents(allEvents, blockingStates);
+
+        Assert.assertEquals(allEvents.size(), 5);
+        Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+        Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+        Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+    }
+
+    @Test(groups = "fast", invocationCount = 10)
+    public void testPausePostPhase_0_17_X() throws Exception {
+        final DateTime now = clock.getUTCNow();
+        final UUID subscriptionId1 = UUID.randomUUID();
+
+        final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc1", true, true, now.plusDays(40)));
+
+        final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+        computeEvents(allEvents, blockingStates);
+
+        Assert.assertEquals(allEvents.size(), 5);
+        Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+        Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+        Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+    }
+
+    @Test(groups = "fast", invocationCount = 10)
+    public void testPausePostPhase() throws Exception {
+        final DateTime now = clock.getUTCNow();
+        final UUID subscriptionId1 = UUID.randomUUID();
+
+        final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc1", true, true, now.plusDays(40)));
+
+        final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+        computeEvents(allEvents, blockingStates);
+
+        Assert.assertEquals(allEvents.size(), 5);
+        Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+        Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+        Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+    }
+
+    @Test(groups = "fast", invocationCount = 10)
+    public void testPauseAtPhase() throws Exception {
+        final DateTime now = clock.getUTCNow();
+        final UUID subscriptionId1 = UUID.randomUUID();
+
+        final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc1", true, true, now.plusDays(30)));
+
+        final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+        computeEvents(allEvents, blockingStates);
+
+        Assert.assertEquals(allEvents.size(), 5);
+        Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+        Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+        Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+    }
+
+    @Test(groups = "fast", invocationCount = 10)
+    public void testPauseResumeAtPhase() throws Exception {
+        final DateTime now = clock.getUTCNow();
+        final UUID subscriptionId1 = UUID.randomUUID();
+
+        final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc1", true, true, now.plusDays(30)));
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc1", false, false, now.plusDays(30)));
+
+        final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+        computeEvents(allEvents, blockingStates);
+
+        Assert.assertEquals(allEvents.size(), 7);
+        Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+        Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+        Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+        Assert.assertEquals(allEvents.get(5).getSubscriptionEventType(), SubscriptionEventType.RESUME_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(6).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
+    }
+
+    @Test(groups = "fast", invocationCount = 10)
+    public void testPauseAccountAtPhase() throws Exception {
+        final DateTime now = clock.getUTCNow();
+        final UUID subscriptionId1 = UUID.randomUUID();
+
+        final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+        blockingStates.add(createBlockingState(UUID.randomUUID(), BlockingStateType.ACCOUNT, "stuff", "svc1", true, true, now.plusDays(30)));
+
+        final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+        computeEvents(allEvents, blockingStates);
+
+        Assert.assertEquals(allEvents.size(), 5);
+        Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+        Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+        Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+    }
+
+    @Test(groups = "fast", invocationCount = 10)
+    public void testDifferentTypesOfBlockingSameService() throws Exception {
+        final DateTime now = clock.getUTCNow();
+        final UUID subscriptionId1 = UUID.randomUUID();
+
+        final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+        blockingStates.add(createBlockingState(UUID.randomUUID(), BlockingStateType.ACCOUNT, "stuff", "svc1", false, true, now.plusDays(10)));
+        // Same service
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc1", true, false, now.plusDays(15)));
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc1", false, false, now.plusDays(20)));
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.ACCOUNT, "stuff", "svc1", false, false, now.plusDays(30)));
+
+        final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+        computeEvents(allEvents, blockingStates);
+
+        Assert.assertEquals(allEvents.size(), 8);
+        Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+        Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+        Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
+        Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(5).getSubscriptionEventType(), SubscriptionEventType.RESUME_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(6).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+        Assert.assertEquals(allEvents.get(7).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+    }
+
+    @Test(groups = "fast", invocationCount = 10)
+    public void testDifferentTypesOfBlockingDifferentServices() throws Exception {
+        final DateTime now = clock.getUTCNow();
+        final UUID subscriptionId1 = UUID.randomUUID();
+
+        final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+        blockingStates.add(createBlockingState(UUID.randomUUID(), BlockingStateType.ACCOUNT, "stuff", "svc1", false, true, now.plusDays(10)));
+        // Different service
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc2", true, false, now.plusDays(15)));
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, "stuff", "svc2", false, false, now.plusDays(20)));
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.ACCOUNT, "stuff", "svc1", false, false, now.plusDays(30)));
+
+        final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+
+        computeEvents(allEvents, blockingStates);
+
+        Assert.assertEquals(allEvents.size(), 7);
+        Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+        Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+        Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.RESUME_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(5).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+        Assert.assertEquals(allEvents.get(6).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
+    }
+
+    @Test(groups = "fast", invocationCount = 10)
+    public void testPauseAccountAtPhaseAndPauseOtherSubscriptionFutureStartedV1() throws Exception {
+        final UUID subscriptionId1 = UUID.randomUUID();
+        UUID subscriptionId2 = UUID.randomUUID();
+        while (subscriptionId2.compareTo(subscriptionId1) <= 0) {
+            subscriptionId2 = UUID.randomUUID();
+        }
+        testPauseAccountAtPhaseAndPauseOtherSubscriptionFutureStarted(subscriptionId1, subscriptionId2);
+    }
+
+    @Test(groups = "fast", invocationCount = 10)
+    public void testPauseAccountAtPhaseAndPauseOtherSubscriptionFutureStartedV2() throws Exception {
+        final UUID subscriptionId1 = UUID.randomUUID();
+        UUID subscriptionId2 = UUID.randomUUID();
+        while (subscriptionId2.compareTo(subscriptionId1) >= 0) {
+            subscriptionId2 = UUID.randomUUID();
+        }
+        testPauseAccountAtPhaseAndPauseOtherSubscriptionFutureStarted(subscriptionId1, subscriptionId2);
+    }
+
+    private void testPauseAccountAtPhaseAndPauseOtherSubscriptionFutureStarted(final UUID subscriptionId1, final UUID subscriptionId2) throws Exception {
+        final DateTime now = clock.getUTCNow();
+
+        final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+        blockingStates.add(createBlockingState(subscriptionId1, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+        blockingStates.add(createBlockingState(subscriptionId2, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, now));
+        blockingStates.add(createBlockingState(UUID.randomUUID(), BlockingStateType.ACCOUNT, "stuff", "svc1", true, true, now.plusDays(30)));
+
+        final LinkedList<SubscriptionEvent> allEvents = new LinkedList<SubscriptionEvent>();
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.START_BILLING, now));
+        allEvents.add(createEvent(subscriptionId1, SubscriptionEventType.PHASE, now.plusDays(30)));
+        allEvents.add(createEvent(subscriptionId2, SubscriptionEventType.START_BILLING, now.plusDays(40)));
+
+        computeEvents(allEvents, blockingStates);
+
+        Assert.assertEquals(allEvents.size(), 8);
+        Assert.assertEquals(allEvents.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(1).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+        Assert.assertEquals(allEvents.get(2).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+        Assert.assertEquals(allEvents.get(3).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+        if (subscriptionId1.compareTo(subscriptionId2) >= 0) {
+            Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+            Assert.assertEquals(allEvents.get(5).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+            Assert.assertEquals(allEvents.get(6).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+        } else {
+            Assert.assertEquals(allEvents.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+            Assert.assertEquals(allEvents.get(5).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+            Assert.assertEquals(allEvents.get(6).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+        }
+        Assert.assertEquals(allEvents.get(7).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+    }
+
+    private BlockingState createBlockingState(final UUID blockedId,
+                                              final BlockingStateType blockingStateType,
+                                              final String stateName,
+                                              final String service,
+                                              final boolean blockEntitlement,
+                                              final boolean blockBilling,
+                                              final DateTime effectiveDate) {
+        return new DefaultBlockingState(UUID.randomUUID(),
+                                        blockedId,
+                                        blockingStateType,
+                                        stateName,
+                                        service,
+                                        false,
+                                        blockEntitlement,
+                                        blockBilling,
+                                        effectiveDate,
+                                        effectiveDate,
+                                        effectiveDate,
+                                        globalOrdering++);
+    }
+
+    // Re-use SubscriptionEventOrdering method, as it's the input of BlockingStateOrdering
+    private SubscriptionEvent createEvent(final UUID subscriptionId, final SubscriptionEventType type, final DateTime effectiveDate) {
+        final SubscriptionBaseTransition subscriptionBaseTransition = Mockito.mock(SubscriptionBaseTransition.class);
+        Mockito.when(subscriptionBaseTransition.getId()).thenReturn(UUID.randomUUID());
+        Mockito.when(subscriptionBaseTransition.getSubscriptionId()).thenReturn(subscriptionId);
+        Mockito.when(subscriptionBaseTransition.getEffectiveTransitionTime()).thenReturn(effectiveDate);
+        return SubscriptionEventOrdering.toSubscriptionEvent(subscriptionBaseTransition, type, internalCallContext);
+    }
+
+    private void computeEvents(final LinkedList<SubscriptionEvent> allEvents, final Collection<BlockingState> blockingStates) {
+        final Collection<UUID> allEntitlementUUIDs = new HashSet<UUID>();
+        for (final SubscriptionEvent subscriptionEvent : allEvents) {
+            allEntitlementUUIDs.add(subscriptionEvent.getEntitlementId());
+        }
+        for (final BlockingState blockingState : blockingStates) {
+            if (blockingState.getType() == BlockingStateType.SUBSCRIPTION) {
+                allEntitlementUUIDs.add(blockingState.getBlockedId());
+            }
+        }
+
+        BlockingStateOrdering.INSTANCE.computeEvents(new LinkedList<UUID>(allEntitlementUUIDs), blockingStates, internalCallContext, allEvents);
+    }
+}
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
index fc7fb25..b82d6e8 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
  *
  * The Billing Project 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
@@ -41,7 +41,6 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData
 import org.killbill.billing.subscription.events.SubscriptionBaseEvent.EventType;
 import org.killbill.billing.subscription.events.user.ApiEventType;
 import org.mockito.Mockito;
-import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
@@ -62,209 +61,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
         bundleExternalKey = bundleId.toString();
     }
 
-    public class TestSubscriptionBundleTimeline extends DefaultSubscriptionBundleTimeline {
-
-        public TestSubscriptionBundleTimeline(final UUID accountId, final UUID bundleId, final String externalKey, final Iterable<Entitlement> entitlements) {
-            super(accountId, bundleId, externalKey, entitlements, internalCallContext);
-        }
-
-        public SubscriptionEvent createEvent(final UUID subscriptionId, final SubscriptionEventType type, final DateTime effectiveDate) {
-            return new DefaultSubscriptionEvent(UUID.randomUUID(),
-                                                subscriptionId,
-                                                effectiveDate,
-                                                type,
-                                                true,
-                                                true,
-                                                "foo",
-                                                "bar",
-                                                null,
-                                                null,
-                                                null,
-                                                null,
-                                                null,
-                                                null,
-                                                null,
-                                                null,
-                                                null,
-                                                null,
-                                                null,
-                                                internalCallContext);
-
-        }
-    }
-
-    @Test(groups = "fast")
-    public void testReOrderSubscriptionEventsOnInvalidOrder1() {
-        final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, new ArrayList<Entitlement>());
-
-        final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
-        final UUID subscriptionId = UUID.randomUUID();
-        final DateTime effectiveDate = clock.getUTCNow();
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_BILLING, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_ENTITLEMENT, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_ENTITLEMENT, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
-
-        SubscriptionEventOrdering.INSTANCE.reOrderSubscriptionEventsOnSameDateByType(events);
-
-        Assert.assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
-        Assert.assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
-        Assert.assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
-        Assert.assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
-    }
-
-    @Test(groups = "fast")
-    public void testReOrderSubscriptionEventsOnInvalidOrder2() {
-        final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, new ArrayList<Entitlement>());
-
-        final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
-        final UUID subscriptionId = UUID.randomUUID();
-        final DateTime effectiveDate = clock.getUTCNow();
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_BILLING, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_ENTITLEMENT, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_ENTITLEMENT, effectiveDate));
-
-        SubscriptionEventOrdering.INSTANCE.reOrderSubscriptionEventsOnSameDateByType(events);
-
-        Assert.assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
-        Assert.assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
-        Assert.assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
-        Assert.assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
-    }
-
-    @Test(groups = "fast")
-    public void testReOrderSubscriptionEventsOnInvalidOrder3() {
-        final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, new ArrayList<Entitlement>());
-
-        final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
-        final UUID subscriptionId = UUID.randomUUID();
-        final DateTime effectiveDate = clock.getUTCNow();
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_BILLING, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_ENTITLEMENT, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_ENTITLEMENT, effectiveDate));
-
-        SubscriptionEventOrdering.INSTANCE.reOrderSubscriptionEventsOnSameDateByType(events);
-
-        Assert.assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
-        Assert.assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
-        Assert.assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
-        Assert.assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
-    }
-
-    @Test(groups = "fast")
-    public void testReOrderSubscriptionEventsOnInvalidOrderAndDifferentSubscriptionsSameDates1() {
-        final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, new ArrayList<Entitlement>());
-
-        final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
-        final UUID subscriptionId = UUID.fromString("60b64e0c-cefd-48c3-8de9-c731a9558165");
-
-        final UUID otherSubscriptionId = UUID.fromString("35b3b340-31b2-46ea-b062-e9fc9fab3bc9");
-        final DateTime effectiveDate = clock.getUTCNow();
-
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_ENTITLEMENT, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_BILLING, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
-        events.add(timeline.createEvent(otherSubscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_ENTITLEMENT, effectiveDate));
-
-        SubscriptionEventOrdering.INSTANCE.reOrderSubscriptionEventsOnSameDateByType(events);
-
-        Assert.assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
-        Assert.assertEquals(events.get(0).getEntitlementId(), otherSubscriptionId);
-        Assert.assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
-        Assert.assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
-        Assert.assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
-        Assert.assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
-        Assert.assertEquals(events.get(4).getEntitlementId(), subscriptionId);
-    }
-
-    @Test(groups = "fast")
-    public void testReOrderSubscriptionEventsOnInvalidOrderAndDifferentSubscriptionsSameDates2() {
-        final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, new ArrayList<Entitlement>());
-
-        final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
-        final UUID subscriptionId = UUID.fromString("35b3b340-31b2-46ea-b062-e9fc9fab3bc9");
-        final UUID otherSubscriptionId = UUID.fromString("60b64e0c-cefd-48c3-8de9-c731a9558165");
-
-        final DateTime effectiveDate = clock.getUTCNow();
-
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_ENTITLEMENT, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_BILLING, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
-        events.add(timeline.createEvent(otherSubscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_ENTITLEMENT, effectiveDate));
-
-        SubscriptionEventOrdering.INSTANCE.reOrderSubscriptionEventsOnSameDateByType(events);
-
-        Assert.assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
-        Assert.assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
-        Assert.assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
-        Assert.assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
-        Assert.assertEquals(events.get(3).getEntitlementId(), subscriptionId);
-        Assert.assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
-        Assert.assertEquals(events.get(4).getEntitlementId(), otherSubscriptionId);
-    }
-
-    @Test(groups = "fast")
-    public void testReOrderSubscriptionEventsOnInvalidOrderAndDifferentSubscriptionsDates() {
-        final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, new ArrayList<Entitlement>());
-
-        final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
-        final UUID subscriptionId = UUID.randomUUID();
-
-        final UUID otherSubscriptionId = UUID.randomUUID();
-        final DateTime effectiveDate = clock.getUTCNow();
-        final DateTime otherEffectiveDate = clock.getUTCNow().plusDays(1);
-
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_BILLING, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_ENTITLEMENT, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_ENTITLEMENT, effectiveDate));
-
-        events.add(timeline.createEvent(otherSubscriptionId, SubscriptionEventType.START_BILLING, otherEffectiveDate));
-        events.add(timeline.createEvent(otherSubscriptionId, SubscriptionEventType.START_ENTITLEMENT, otherEffectiveDate));
-        events.add(timeline.createEvent(otherSubscriptionId, SubscriptionEventType.STOP_ENTITLEMENT, otherEffectiveDate));
-        events.add(timeline.createEvent(otherSubscriptionId, SubscriptionEventType.STOP_BILLING, otherEffectiveDate));
-        events.add(timeline.createEvent(otherSubscriptionId, SubscriptionEventType.PAUSE_ENTITLEMENT, otherEffectiveDate));
-        events.add(timeline.createEvent(otherSubscriptionId, SubscriptionEventType.PAUSE_BILLING, otherEffectiveDate));
-
-        SubscriptionEventOrdering.INSTANCE.reOrderSubscriptionEventsOnSameDateByType(events);
-
-        Assert.assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
-        Assert.assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
-        Assert.assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
-        Assert.assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
-
-        Assert.assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
-        Assert.assertEquals(events.get(5).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
-        Assert.assertEquals(events.get(6).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
-        Assert.assertEquals(events.get(7).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
-        Assert.assertEquals(events.get(8).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
-        Assert.assertEquals(events.get(9).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
-    }
-
-    @Test(groups = "fast")
-    public void testReOrderSubscriptionEventsOnCorrectOrder() {
-        final TestSubscriptionBundleTimeline timeline = new TestSubscriptionBundleTimeline(null, null, null, new ArrayList<Entitlement>());
-
-        final List<SubscriptionEvent> events = new ArrayList<SubscriptionEvent>();
-        final UUID subscriptionId = UUID.randomUUID();
-        final DateTime effectiveDate = clock.getUTCNow();
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_ENTITLEMENT, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.START_BILLING, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_ENTITLEMENT, effectiveDate));
-        events.add(timeline.createEvent(subscriptionId, SubscriptionEventType.STOP_BILLING, effectiveDate));
-
-        SubscriptionEventOrdering.INSTANCE.reOrderSubscriptionEventsOnSameDateByType(events);
-
-        Assert.assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
-        Assert.assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
-        Assert.assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
-        Assert.assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
-    }
-
     @Test(groups = "fast")
     public void testOneSimpleEntitlement() throws CatalogApiException {
         testOneSimpleEntitlementImpl(false);
@@ -275,7 +71,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
         testOneSimpleEntitlementImpl(true);
     }
 
-
     private void testOneSimpleEntitlementImpl(boolean regressionFlagForOlderVersionThan_0_17_X) throws CatalogApiException {
         clock.setDay(new LocalDate(2013, 1, 1));
 
@@ -348,18 +143,16 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
         assertNull(events.get(3).getNextPhase());
     }
 
-
-    @Test(groups="fast", enabled = true)
+    @Test(groups="fast")
     public void testOneSimpleEntitlementCancelImmediately() throws CatalogApiException {
         testOneSimpleEntitlementCancelImmediatelyImpl(false);
     }
 
-    @Test(groups="fast", enabled = true)
+    @Test(groups="fast")
     public void testOneSimpleEntitlementCancelImmediatelyWithRegression() throws CatalogApiException {
         testOneSimpleEntitlementCancelImmediatelyImpl(true);
     }
 
-
     private void testOneSimpleEntitlementCancelImmediatelyImpl(boolean regressionFlagForOlderVersionThan_0_17_X) throws CatalogApiException {
 
         clock.setDay(new LocalDate(2013, 1, 1));
@@ -518,7 +311,6 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
         testOneEntitlementWithPauseResumeImpl(true);
     }
 
-
     private void testOneEntitlementWithPauseResumeImpl(final boolean regressionFlagForOlderVersionThan_0_17_X) throws CatalogApiException {
         clock.setDay(new LocalDate(2013, 1, 1));