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 99af794..255f8e9 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
@@ -334,6 +334,9 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
break;
case PAUSE_BILLING:
case PAUSE_ENTITLEMENT:
+ case RESUME_ENTITLEMENT:
+ case RESUME_BILLING:
+ case SERVICE_STATE_CHANGE:
curTargetState.addEntitlementEvent(cur);
break;
case STOP_BILLING:
@@ -507,12 +510,7 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
}
// See https://github.com/killbill/killbill/issues/135
- final String serviceName;
- if (DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(in.getService())) {
- serviceName = getServiceName(eventType);
- } else {
- serviceName = in.getService();
- }
+ final String serviceName = getRealServiceNameForEntitlementOrExternalServiceName(in.getService(), eventType);
return new DefaultSubscriptionEvent(in.getId(),
entitlementId,
@@ -537,6 +535,16 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
accountTimeZone);
}
+ private static String getRealServiceNameForEntitlementOrExternalServiceName(final String originalServiceName, final SubscriptionEventType eventType) {
+ final String serviceName;
+ if (DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(originalServiceName)) {
+ serviceName = getServiceName(eventType);
+ } else {
+ serviceName = originalServiceName;
+ }
+ return serviceName;
+ }
+
private SubscriptionEvent toSubscriptionEvent(final SubscriptionBaseTransition in, final SubscriptionEventType eventType, final DateTimeZone accountTimeZone) {
return new DefaultSubscriptionEvent(in.getId(),
in.getSubscriptionId(),
@@ -663,8 +671,22 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
}
public void addEntitlementEvent(final SubscriptionEvent e) {
- final BlockingState converted = new DefaultBlockingState(e.getEntitlementId(), BlockingStateType.SUBSCRIPTION,
- e.getServiceStateName(), e.getServiceName(), false, e.isBlockedEntitlement(), e.isBlockedBilling(),
+ final String serviceName = getRealServiceNameForEntitlementOrExternalServiceName(e.getServiceName(), e.getSubscriptionEventType());
+ final BlockingState lastBlockingStateForService = perServiceBlockingState.get(serviceName);
+
+ // Assume the event has no impact on changes - TODO this is wrong for SERVICE_STATE_CHANGE
+ final boolean blockChange = lastBlockingStateForService != null && lastBlockingStateForService.isBlockChange();
+ // For block entitlement or billing, override the previous state
+ final boolean blockedEntitlement = e.isBlockedEntitlement();
+ final boolean blockedBilling = e.isBlockedBilling();
+
+ final BlockingState converted = new DefaultBlockingState(e.getEntitlementId(),
+ BlockingStateType.SUBSCRIPTION,
+ e.getServiceStateName(),
+ serviceName,
+ blockChange,
+ blockedEntitlement,
+ blockedBilling,
((DefaultSubscriptionEvent) e).getEffectiveDateTime());
perServiceBlockingState.put(converted.getService(), converted);
}
@@ -724,7 +746,7 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
result.add(SubscriptionEventType.PAUSE_BILLING);
}
- if (!shouldResumeEntitlement && !shouldBlockEntitlement && !shouldBlockEntitlement && !shouldBlockBilling && !fixedBlockingState.getService().equals(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME)) {
+ if (!shouldResumeEntitlement && !shouldResumeBilling && !shouldBlockEntitlement && !shouldBlockBilling && !fixedBlockingState.getService().equals(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME)) {
result.add(SubscriptionEventType.SERVICE_STATE_CHANGE);
}
return result;
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 9932e3f..3f89870 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
@@ -368,7 +368,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
effectiveDate = effectiveDate.plusDays(15);
clock.addDays(15);
- final String service = "boo";
+ final String service = "boo-service-which-will-pause-billing";
final BlockingState bs3 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
"NothingUseful3", service,
false, false, true, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
@@ -416,8 +416,8 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
assertEquals(events.get(5).getSubscriptionEventType(), SubscriptionEventType.RESUME_ENTITLEMENT);
assertEquals(events.get(6).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
- assertEquals(events.get(7).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
- assertEquals(events.get(8).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+ assertEquals(events.get(7).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+ assertEquals(events.get(8).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
assertEquals(events.get(0).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
assertEquals(events.get(1).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
@@ -1081,6 +1081,143 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
assertNull(events.get(3).getNextPhase());
}
+ @Test(groups = "fast", description = "Test for https://github.com/killbill/killbill/issues/149")
+ public void testVariousBlockingStatesAtTheSameEffectiveDate() 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 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);
+
+ // 2013-02-10
+ effectiveDate = effectiveDate.plusDays(40);
+ clock.addDays(40);
+ final BlockingState bs1 = new DefaultBlockingState(UUID.randomUUID(), bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE,
+ DefaultEntitlementApi.ENT_STATE_BLOCKED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ true, true, true, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+ blockingStates.add(bs1);
+ // Same timestamp on purpose
+ final BlockingState bs2 = new DefaultBlockingState(UUID.randomUUID(), bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE,
+ DefaultEntitlementApi.ENT_STATE_CLEAR, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ false, false, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+ blockingStates.add(bs2);
+
+ // 2013-02-20
+ effectiveDate = effectiveDate.plusDays(10);
+ clock.addDays(10);
+ final BlockingState bs3 = new DefaultBlockingState(UUID.randomUUID(), bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE,
+ DefaultEntitlementApi.ENT_STATE_BLOCKED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ true, true, true, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+ blockingStates.add(bs3);
+
+ // 2013-03-02
+ effectiveDate = effectiveDate.plusDays(10);
+ clock.addDays(10);
+ final BlockingState bs4 = new DefaultBlockingState(UUID.randomUUID(), bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE,
+ DefaultEntitlementApi.ENT_STATE_CLEAR, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ false, false, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+ blockingStates.add(bs4);
+
+ final String overdueService = "overdue-service";
+ // 2013-03-04
+ effectiveDate = effectiveDate.plusDays(2);
+ clock.addDays(2);
+ final BlockingState bs5 = new DefaultBlockingState(UUID.randomUUID(), accountId, BlockingStateType.ACCOUNT,
+ "OD1", overdueService,
+ false, false, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+ blockingStates.add(bs5);
+
+ final List<Entitlement> entitlements = new ArrayList<Entitlement>();
+ final Entitlement entitlement = createEntitlement(entitlementId, allTransitions);
+ entitlements.add(entitlement);
+
+ final SubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, accountId, bundleId, externalKey, entitlements, blockingStates);
+
+ final List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
+ assertEquals(events.size(), 11);
+
+ 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(bs1.getEffectiveDate(), accountTimeZone)), 0);
+ assertEquals(events.get(3).getEffectiveDate().compareTo(new LocalDate(bs1.getEffectiveDate(), accountTimeZone)), 0);
+ assertEquals(events.get(4).getEffectiveDate().compareTo(new LocalDate(bs2.getEffectiveDate(), accountTimeZone)), 0);
+ assertEquals(events.get(5).getEffectiveDate().compareTo(new LocalDate(bs2.getEffectiveDate(), accountTimeZone)), 0);
+ assertEquals(events.get(6).getEffectiveDate().compareTo(new LocalDate(bs3.getEffectiveDate(), accountTimeZone)), 0);
+ assertEquals(events.get(7).getEffectiveDate().compareTo(new LocalDate(bs3.getEffectiveDate(), accountTimeZone)), 0);
+ assertEquals(events.get(8).getEffectiveDate().compareTo(new LocalDate(bs4.getEffectiveDate(), accountTimeZone)), 0);
+ assertEquals(events.get(9).getEffectiveDate().compareTo(new LocalDate(bs4.getEffectiveDate(), accountTimeZone)), 0);
+
+ assertEquals(events.get(10).getEffectiveDate().compareTo(new LocalDate(bs5.getEffectiveDate(), accountTimeZone)), 0);
+
+ assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+
+ assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+ assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+ assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.RESUME_ENTITLEMENT);
+ assertEquals(events.get(5).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
+
+ assertEquals(events.get(6).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+ assertEquals(events.get(7).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+ assertEquals(events.get(8).getSubscriptionEventType(), SubscriptionEventType.RESUME_ENTITLEMENT);
+ assertEquals(events.get(9).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
+
+ assertEquals(events.get(10).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+
+ assertEquals(events.get(0).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(1).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+
+ assertEquals(events.get(2).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(3).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+ assertEquals(events.get(4).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(5).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+
+ assertEquals(events.get(6).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(7).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+ assertEquals(events.get(8).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+ assertEquals(events.get(9).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+
+ assertEquals(events.get(10).getServiceName(), overdueService);
+
+ assertNull(events.get(0).getPrevPhase());
+ assertEquals(events.get(0).getNextPhase().getName(), "trial");
+ assertNull(events.get(1).getPrevPhase());
+ assertEquals(events.get(1).getNextPhase().getName(), "trial");
+
+ assertEquals(events.get(2).getPrevPhase().getName(), "trial");
+ assertEquals(events.get(2).getNextPhase().getName(), "trial");
+ assertEquals(events.get(3).getPrevPhase().getName(), "trial");
+ assertEquals(events.get(3).getNextPhase().getName(), "trial");
+ assertEquals(events.get(4).getPrevPhase().getName(), "trial");
+ assertEquals(events.get(4).getNextPhase().getName(), "trial");
+ assertEquals(events.get(5).getPrevPhase().getName(), "trial");
+ assertEquals(events.get(5).getNextPhase().getName(), "trial");
+
+ assertEquals(events.get(6).getPrevPhase().getName(), "trial");
+ assertEquals(events.get(6).getNextPhase().getName(), "trial");
+ assertEquals(events.get(7).getPrevPhase().getName(), "trial");
+ assertEquals(events.get(7).getNextPhase().getName(), "trial");
+ assertEquals(events.get(8).getPrevPhase().getName(), "trial");
+ assertEquals(events.get(8).getNextPhase().getName(), "trial");
+ assertEquals(events.get(9).getPrevPhase().getName(), "trial");
+ assertEquals(events.get(9).getNextPhase().getName(), "trial");
+
+ assertEquals(events.get(10).getPrevPhase().getName(), "trial");
+ assertEquals(events.get(10).getNextPhase().getName(), "trial");
+ }
+
private Entitlement createEntitlement(final UUID entitlementId, final List<SubscriptionBaseTransition> allTransitions) {
final DefaultEntitlement result = Mockito.mock(DefaultEntitlement.class);
Mockito.when(result.getId()).thenReturn(entitlementId);