killbill-aplcache
Changes
entitlement/pom.xml 8(+8 -0)
entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java 3(+2 -1)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java 5(+3 -2)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java 12(+8 -4)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java 25(+16 -9)
entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java 131(+113 -18)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java 9(+7 -2)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java 8(+4 -4)
Details
diff --git a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
index c413f0d..7153f48 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
@@ -180,7 +180,8 @@ public class TestAnalyticsService
Subscription.SubscriptionState.ACTIVE,
plan,
phase,
- priceList
+ priceList,
+ true
);
expectedTransition = new BusinessSubscriptionTransition(
ID,
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
index f592c41..6bc4d7a 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
@@ -19,6 +19,7 @@ package com.ning.billing.analytics;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionTransition;
@@ -146,11 +147,16 @@ public class MockSubscription implements Subscription
@Override
public DateTime getPaidThroughDate() {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException();
}
@Override
public SubscriptionTransition getPreviousTransition() {
return null;
}
+
+ @Override
+ public ProductCategory getCategory() {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
index a9a8293..0c3cf62 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
@@ -175,7 +175,8 @@ public class TestAnalyticsListener
nextState,
plan,
phase,
- priceList
+ priceList,
+ true
);
}
@@ -212,7 +213,8 @@ public class TestAnalyticsListener
null,
null,
null,
- null
+ null,
+ true
);
}
@@ -239,7 +241,8 @@ public class TestAnalyticsListener
nextState,
plan,
phase,
- priceList
+ priceList,
+ true
);
}
}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java b/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
index 5c626fc..0765f30 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
@@ -19,6 +19,8 @@ package com.ning.billing.entitlement.api.user;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.ProductCategory;
+
import org.joda.time.DateTime;
import java.util.List;
@@ -69,6 +71,7 @@ public interface Subscription {
public DateTime getPaidThroughDate();
+ public ProductCategory getCategory();
public List<SubscriptionTransition> getActiveTransitions();
entitlement/pom.xml 8(+8 -0)
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 363e948..137fb52 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -83,6 +83,14 @@
<artifactId>management-dbfiles</artifactId>
<scope>test</scope>
</dependency>
+ <!-- Same here, this is really debatable whether or not we should keep that here -->
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-account</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
index 26248e7..0b372e8 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
@@ -189,7 +189,8 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
.setActiveVersion(subscriptionData.getActiveVersion())
.setEffectiveDate(cur.getEventTime())
.setProcessedDate(now)
- .setRequestedDate(now);
+ .setRequestedDate(now)
+ .setFromDisk(true);
switch(cur.getApiEventType()) {
case MIGRATE_ENTITLEMENT:
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
index d265697..5bd8596 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
@@ -168,12 +168,13 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_BP_NON_ACTIVE, targetAddOnPlan.getName());
}
- if (addonUtils.isAddonIncluded(baseSubscription, targetAddOnPlan)) {
+ Product baseProduct = baseSubscription.getCurrentPlan().getProduct();
+ if (addonUtils.isAddonIncluded(baseProduct, targetAddOnPlan)) {
throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_ALREADY_INCLUDED,
targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
}
- if (!addonUtils.isAddonAvailable(baseSubscription, targetAddOnPlan)) {
+ if (!addonUtils.isAddonAvailable(baseProduct, targetAddOnPlan)) {
throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_NOT_AVAILABLE,
targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
index 1c523df..9a82a99 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
@@ -69,7 +69,8 @@ public class SubscriptionApiService {
.setActiveVersion(subscription.getActiveVersion())
.setProcessedDate(processedDate)
.setEffectiveDate(effectiveDate)
- .setRequestedDate(requestedDate));
+ .setRequestedDate(requestedDate)
+ .setFromDisk(true));
TimedPhase nextTimedPhase = curAndNextPhases[1];
PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
@@ -117,7 +118,8 @@ public class SubscriptionApiService {
.setActiveVersion(subscription.getActiveVersion())
.setProcessedDate(now)
.setEffectiveDate(effectiveDate)
- .setRequestedDate(now));
+ .setRequestedDate(now)
+ .setFromDisk(true));
dao.cancelSubscription(subscription.getId(), cancelEvent);
subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId()), catalogService.getFullCatalog());
@@ -140,7 +142,8 @@ public class SubscriptionApiService {
.setActiveVersion(subscription.getActiveVersion())
.setProcessedDate(now)
.setRequestedDate(now)
- .setEffectiveDate(now));
+ .setEffectiveDate(now)
+ .setFromDisk(true));
List<EntitlementEvent> uncancelEvents = new ArrayList<EntitlementEvent>();
uncancelEvents.add(uncancelEvent);
@@ -212,7 +215,8 @@ public class SubscriptionApiService {
.setActiveVersion(subscription.getActiveVersion())
.setProcessedDate(now)
.setEffectiveDate(effectiveDate)
- .setRequestedDate(now));
+ .setRequestedDate(now)
+ .setFromDisk(true));
TimedPhase nextTimedPhase = planAligner.getNextTimedPhaseOnChange(subscription, newPlan, newPriceList.getName(), requestedDate, effectiveDate);
PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
index d94af98..ce341fd 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
@@ -209,9 +209,11 @@ public class SubscriptionData implements Subscription {
}
// ensure that the latestSubscription is always set; prevents NPEs
- SubscriptionTransition latestSubscription = transitions.get(0);
- for (SubscriptionTransition cur : transitions) {
- if (cur.getEffectiveTransitionTime().isAfter(clock.getUTCNow())) {
+ SubscriptionTransitionData latestSubscription = transitions.get(0);
+ for (SubscriptionTransitionData cur : transitions) {
+ if (cur.getEffectiveTransitionTime().isAfter(clock.getUTCNow()) ||
+ // We are not looking at events that were patched on the fly-- such as future ADDON cancelation from Base Plan
+ !cur.isFromDisk()) {
break;
}
latestSubscription = cur;
@@ -236,6 +238,7 @@ public class SubscriptionData implements Subscription {
return activeVersion;
}
+ @Override
public ProductCategory getCategory() {
return category;
}
@@ -359,6 +362,8 @@ public class SubscriptionData implements Subscription {
ApiEventType apiEventType = null;
+ boolean isFromDisk = true;
+
switch (cur.getType()) {
case PHASE:
@@ -369,6 +374,7 @@ public class SubscriptionData implements Subscription {
case API_USER:
ApiEvent userEV = (ApiEvent) cur;
apiEventType = userEV.getEventType();
+ isFromDisk = userEV.isFromDisk();
switch(apiEventType) {
case MIGRATE_ENTITLEMENT:
case CREATE:
@@ -430,7 +436,8 @@ public class SubscriptionData implements Subscription {
nextState,
nextPlan,
nextPhase,
- nextPriceList);
+ nextPriceList,
+ isFromDisk);
transitions.add(transition);
previousState = nextState;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
index 5a0eba0..fb8e2f2 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
@@ -44,11 +44,12 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
private final String nextPriceList;
private final Plan nextPlan;
private final PlanPhase nextPhase;
+ private final boolean isFromDisk;
public SubscriptionTransitionData(UUID eventId, UUID subscriptionId, UUID bundleId, EventType eventType,
ApiEventType apiEventType, DateTime requestedTransitionTime, DateTime effectiveTransitionTime,
SubscriptionState previousState, Plan previousPlan, PlanPhase previousPhase, String previousPriceList,
- SubscriptionState nextState, Plan nextPlan, PlanPhase nextPhase, String nextPriceList) {
+ SubscriptionState nextState, Plan nextPlan, PlanPhase nextPhase, String nextPriceList, boolean isFromDisk) {
super();
this.eventId = eventId;
this.subscriptionId = subscriptionId;
@@ -65,6 +66,7 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
this.nextPlan = nextPlan;
this.nextPriceList = nextPriceList;
this.nextPhase = nextPhase;
+ this.isFromDisk = isFromDisk;
}
@Override
@@ -136,14 +138,6 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
}
}
- public ApiEventType getApiEventType() {
- return apiEventType;
- }
-
- public EventType getEventType() {
- return eventType;
- }
-
@Override
public DateTime getRequestedTransitionTime() {
return requestedTransitionTime;
@@ -154,6 +148,19 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
return effectiveTransitionTime;
}
+ public boolean isFromDisk() {
+ return isFromDisk;
+ }
+
+ public ApiEventType getApiEventType() {
+ return apiEventType;
+ }
+
+ public EventType getEventType() {
+ return eventType;
+ }
+
+
@Override
public String toString() {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
index 94c35f9..b2c9405 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
@@ -16,6 +16,10 @@
package com.ning.billing.entitlement.engine.addon;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import com.google.inject.Inject;
import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.CatalogApiException;
@@ -26,9 +30,11 @@ import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
import com.ning.billing.entitlement.api.user.SubscriptionData;
import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.exceptions.EntitlementError;
public class AddonUtils {
+ private static final Logger logger = LoggerFactory.getLogger(AddonUtils.class);
private final CatalogService catalogService;
@@ -37,14 +43,19 @@ public class AddonUtils {
this.catalogService = catalogService;
}
- public boolean isAddonAvailable(SubscriptionData baseSubscription, Plan targetAddOnPlan) {
- if (baseSubscription.getState() == SubscriptionState.CANCELLED) {
- return false;
+ public boolean isAddonAvailable(final String basePlanName, final DateTime requestedDate, final Plan targetAddOnPlan) {
+ try {
+ Plan plan = catalogService.getFullCatalog().findPlan(basePlanName, requestedDate);
+ Product product = plan.getProduct();
+ return isAddonAvailable(product, targetAddOnPlan);
+ } catch (CatalogApiException e) {
+ throw new EntitlementError(e);
}
+ }
+ public boolean isAddonAvailable(final Product baseProduct, final Plan targetAddOnPlan) {
Product targetAddonProduct = targetAddOnPlan.getProduct();
- Product baseProduct = baseSubscription.getCurrentPlan().getProduct();
Product[] availableAddOns = baseProduct.getAvailable();
for (Product curAv : availableAddOns) {
@@ -55,15 +66,18 @@ public class AddonUtils {
return false;
}
- public boolean isAddonIncluded(SubscriptionData baseSubscription, Plan targetAddOnPlan) {
-
- if (baseSubscription.getState() == SubscriptionState.CANCELLED) {
- return false;
+ public boolean isAddonIncluded(final String basePlanName, final DateTime requestedDate, final Plan targetAddOnPlan) {
+ try {
+ Plan plan = catalogService.getFullCatalog().findPlan(basePlanName, requestedDate);
+ Product product = plan.getProduct();
+ return isAddonIncluded(product, targetAddOnPlan);
+ } catch (CatalogApiException e) {
+ throw new EntitlementError(e);
}
+ }
+ public boolean isAddonIncluded(final Product baseProduct, final Plan targetAddOnPlan) {
Product targetAddonProduct = targetAddOnPlan.getProduct();
- Product baseProduct = baseSubscription.getCurrentPlan().getProduct();
-
Product[] includedAddOns = baseProduct.getIncluded();
for (Product curAv : includedAddOns) {
if (curAv.getName().equals(targetAddonProduct.getName())) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
index 427353a..55ea2cf 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
@@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.Product;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.config.EntitlementConfig;
import com.ning.billing.entitlement.alignment.PlanAligner;
@@ -225,7 +226,11 @@ public class Engine implements EventListener, EntitlementService {
DateTime now = clock.getUTCNow();
+ Product baseProduct = (baseSubscription.getState() == SubscriptionState.CANCELLED ) ?
+ null : baseSubscription.getCurrentPlan().getProduct();
+
List<Subscription> subscriptions = dao.getSubscriptions(baseSubscription.getBundleId());
+
Iterator<Subscription> it = subscriptions.iterator();
while (it.hasNext()) {
SubscriptionData cur = (SubscriptionData) it.next();
@@ -234,9 +239,9 @@ public class Engine implements EventListener, EntitlementService {
continue;
}
Plan addonCurrentPlan = cur.getCurrentPlan();
- // If base Plan has been canceled, that will cancel all the OA
- if (addonUtils.isAddonIncluded(baseSubscription, addonCurrentPlan) ||
- ! addonUtils.isAddonAvailable(baseSubscription, addonCurrentPlan)) {
+ if (baseProduct == null ||
+ addonUtils.isAddonIncluded(baseProduct, addonCurrentPlan) ||
+ ! addonUtils.isAddonAvailable(baseProduct, addonCurrentPlan)) {
//
// Perform AO cancellation using the effectiveDate of the BP
//
@@ -245,7 +250,8 @@ public class Engine implements EventListener, EntitlementService {
.setActiveVersion(cur.getActiveVersion())
.setProcessedDate(now)
.setEffectiveDate(event.getEffectiveDate())
- .setRequestedDate(now));
+ .setRequestedDate(now)
+ .setFromDisk(true));
dao.cancelSubscription(cur.getId(), cancelEvent);
}
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
index 9507061..ef3168f 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
@@ -17,7 +17,9 @@
package com.ning.billing.entitlement.engine.dao;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@@ -30,8 +32,12 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
import com.google.inject.Inject;
import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.Product;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.migration.AccountMigrationData;
import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
@@ -42,10 +48,14 @@ import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
import com.ning.billing.entitlement.api.user.SubscriptionData;
import com.ning.billing.entitlement.api.user.SubscriptionFactory;
import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.engine.addon.AddonUtils;
import com.ning.billing.entitlement.engine.core.Engine;
import com.ning.billing.entitlement.events.EntitlementEvent;
import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
import com.ning.billing.entitlement.events.user.ApiEvent;
+import com.ning.billing.entitlement.events.user.ApiEventBuilder;
+import com.ning.billing.entitlement.events.user.ApiEventCancel;
+import com.ning.billing.entitlement.events.user.ApiEventChange;
import com.ning.billing.entitlement.events.user.ApiEventType;
import com.ning.billing.entitlement.exceptions.EntitlementError;
import com.ning.billing.util.clock.Clock;
@@ -65,16 +75,18 @@ public class EntitlementSqlDao implements EntitlementDao {
private final EventSqlDao eventsDao;
private final SubscriptionFactory factory;
private final NotificationQueueService notificationQueueService;
+ private final AddonUtils addonUtils;
@Inject
public EntitlementSqlDao(final IDBI dbi, final Clock clock, final SubscriptionFactory factory,
- final NotificationQueueService notificationQueueService) {
+ final AddonUtils addonUtils, final NotificationQueueService notificationQueueService) {
this.clock = clock;
this.factory = factory;
this.subscriptionsDao = dbi.onDemand(SubscriptionSqlDao.class);
this.eventsDao = dbi.onDemand(EventSqlDao.class);
this.bundlesDao = dbi.onDemand(BundleSqlDao.class);
this.notificationQueueService = notificationQueueService;
+ this.addonUtils = addonUtils;
}
@Override
@@ -104,10 +116,6 @@ public class EntitlementSqlDao implements EntitlementDao {
});
}
- @Override
- public Subscription getSubscriptionFromId(final UUID subscriptionId) {
- return buildSubscription(subscriptionsDao.getSubscriptionFromId(subscriptionId.toString()));
- }
@Override
public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
@@ -134,19 +142,18 @@ public class EntitlementSqlDao implements EntitlementDao {
@Override
public Subscription getBaseSubscription(final UUID bundleId) {
+ return getBaseSubscription(bundleId, true);
+ }
- List<Subscription> subscriptions = subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString());
- for (Subscription cur : subscriptions) {
- if (((SubscriptionData)cur).getCategory() == ProductCategory.BASE) {
- return buildSubscription(cur);
- }
- }
- return null;
+
+ @Override
+ public Subscription getSubscriptionFromId(final UUID subscriptionId) {
+ return buildSubscription(subscriptionsDao.getSubscriptionFromId(subscriptionId.toString()));
}
@Override
public List<Subscription> getSubscriptions(UUID bundleId) {
- return buildSubscription(subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString()));
+ return buildBundleSubscriptions(subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString()));
}
@Override
@@ -155,7 +162,7 @@ public class EntitlementSqlDao implements EntitlementDao {
if (bundle == null) {
return Collections.emptyList();
}
- return buildSubscription(subscriptionsDao.getSubscriptionsFromBundleId(bundle.getId().toString()));
+ return getSubscriptions(bundle.getId());
}
@Override
@@ -359,18 +366,106 @@ public class EntitlementSqlDao implements EntitlementDao {
}
}
+ public Subscription getBaseSubscription(final UUID bundleId, boolean rebuildSubscription) {
+ List<Subscription> subscriptions = subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString());
+ for (Subscription cur : subscriptions) {
+ if (((SubscriptionData)cur).getCategory() == ProductCategory.BASE) {
+ return rebuildSubscription ? buildSubscription(cur) : cur;
+ }
+ }
+ return null;
+ }
+
+
private Subscription buildSubscription(Subscription input) {
if (input == null) {
return null;
}
- return buildSubscription(Collections.singletonList(input)).get(0);
+ List<Subscription> bundleInput = new ArrayList<Subscription>();
+ Subscription baseSubscription = null;
+ if (input.getCategory() == ProductCategory.ADD_ON) {
+ baseSubscription = getBaseSubscription(input.getBundleId(), false);
+ bundleInput.add(baseSubscription);
+ bundleInput.add(input);
+ } else {
+ bundleInput.add(input);
+ }
+ List<Subscription> reloadedSubscriptions = buildBundleSubscriptions(bundleInput);
+ for (Subscription cur : reloadedSubscriptions) {
+ if (cur.getId().equals(input.getId())) {
+ return cur;
+ }
+ }
+ throw new EntitlementError(String.format("Unexpected code path in buildSubscription"));
}
- private List<Subscription> buildSubscription(List<Subscription> input) {
- List<Subscription> result = new ArrayList<Subscription>(input.size());
+
+
+ private List<Subscription> buildBundleSubscriptions(List<Subscription> input) {
+
+ // Make sure BasePlan -- if exists-- is first
+ Collections.sort(input, new Comparator<Subscription>() {
+ @Override
+ public int compare(Subscription o1, Subscription o2) {
+ if (o1.getCategory() == ProductCategory.BASE) {
+ return -1;
+ } else if (o2.getCategory() == ProductCategory.BASE) {
+ return 1;
+ } else {
+ return o1.getStartDate().compareTo(o2.getStartDate());
+ }
+ }
+ });
+
+ EntitlementEvent futureBaseEvent = null;
+ List<Subscription> result = new ArrayList<Subscription>(input.size());
for (Subscription cur : input) {
+
List<EntitlementEvent> events = eventsDao.getEventsForSubscription(cur.getId().toString());
- Subscription reloaded = factory.createSubscription(new SubscriptionBuilder((SubscriptionData) cur), events);
+ Subscription reloaded = factory.createSubscription(new SubscriptionBuilder((SubscriptionData) cur), events);
+
+ switch (cur.getCategory()) {
+ case BASE:
+ Collection<EntitlementEvent> futureApiEvents = Collections2.filter(events, new Predicate<EntitlementEvent>() {
+ @Override
+ public boolean apply(EntitlementEvent input) {
+ return (input.getEffectiveDate().isAfter(clock.getUTCNow()) &&
+ ((input instanceof ApiEventCancel) || (input instanceof ApiEventChange)));
+ }
+ });
+ futureBaseEvent = (futureApiEvents.size() == 0) ? null : futureApiEvents.iterator().next();
+ break;
+
+ case ADD_ON:
+ Plan targetAddOnPlan = reloaded.getCurrentPlan();
+ String baseProductName = (futureBaseEvent instanceof ApiEventChange) ?
+ ((ApiEventChange) futureBaseEvent).getEventPlan() : null;
+
+ boolean createCancelEvent = (futureBaseEvent != null) &&
+ ((futureBaseEvent instanceof ApiEventCancel) ||
+ ((! addonUtils.isAddonAvailable(baseProductName, futureBaseEvent.getEffectiveDate(), targetAddOnPlan)) ||
+ (addonUtils.isAddonIncluded(baseProductName, futureBaseEvent.getEffectiveDate(), targetAddOnPlan))));
+
+ if (createCancelEvent) {
+ DateTime now = clock.getUTCNow();
+ EntitlementEvent addOnCancelEvent = new ApiEventCancel(new ApiEventBuilder()
+ .setSubscriptionId(reloaded.getId())
+ .setActiveVersion(((SubscriptionData) reloaded).getActiveVersion())
+ .setProcessedDate(now)
+ .setEffectiveDate(futureBaseEvent.getEffectiveDate())
+ .setRequestedDate(now)
+ // This event is only there to indicate the ADD_ON is future canceled, but it is not there
+ // on disk until the base plan cancellation becomes effective
+ .setFromDisk(false));
+
+ events.add(addOnCancelEvent);
+ // Finally reload subscription with full set of events
+ reloaded = factory.createSubscription(new SubscriptionBuilder((SubscriptionData) cur), events);
+ }
+ break;
+ default:
+ break;
+ }
result.add(reloaded);
}
return result;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java
index 5f485e5..4e2128e 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java
@@ -139,7 +139,8 @@ public interface EventSqlDao extends Transactional<EventSqlDao>, CloseMe, Transm
.setEventPlan(planName)
.setEventPlanPhase(phaseName)
.setEventPriceList(priceListName)
- .setEventType(userType);
+ .setEventType(userType)
+ .setFromDisk(true);
if (userType == ApiEventType.CREATE) {
result = new ApiEventCreate(builder);
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEvent.java
index ecd5aa1..c26b168 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEvent.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEvent.java
@@ -29,4 +29,6 @@ public interface ApiEvent extends EntitlementEvent {
public String getPriceList();
+ public boolean isFromDisk();
+
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBase.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBase.java
index 2438ddf..f67c6b7 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBase.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBase.java
@@ -28,7 +28,7 @@ public class ApiEventBase extends EventBase implements ApiEvent {
private final String eventPlan;
private final String eventPlanPhase;
private final String eventPriceList;
-
+ private final boolean fromDisk;
public ApiEventBase(ApiEventBuilder builder) {
super(builder);
@@ -36,9 +36,10 @@ public class ApiEventBase extends EventBase implements ApiEvent {
this.eventPriceList = builder.getEventPriceList();
this.eventPlan = builder.getEventPlan();
this.eventPlanPhase = builder.getEventPlanPhase();
+ this.fromDisk = builder.isFromDisk();
}
-
+/*
public ApiEventBase(UUID subscriptionId, DateTime bundleStartDate, DateTime processed, String planName, String phaseName,
String priceList, DateTime requestedDate, ApiEventType eventType, DateTime effectiveDate, long activeVersion) {
super(subscriptionId, requestedDate, effectiveDate, processed, activeVersion, true);
@@ -56,7 +57,7 @@ public class ApiEventBase extends EventBase implements ApiEvent {
this.eventPlan = null;
this.eventPlanPhase = null;
}
-
+*/
@Override
public ApiEventType getEventType() {
@@ -83,6 +84,11 @@ public class ApiEventBase extends EventBase implements ApiEvent {
return eventPriceList;
}
+ @Override
+ public boolean isFromDisk() {
+ return fromDisk;
+ }
+
@Override
public String toString() {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBuilder.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBuilder.java
index 2ff026b..b7e9764 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBuilder.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBuilder.java
@@ -24,6 +24,8 @@ public class ApiEventBuilder extends EventBaseBuilder<ApiEventBuilder> {
private String eventPlan;
private String eventPlanPhase;
private String eventPriceList;
+ private boolean fromDisk;
+
public ApiEventBuilder() {
super();
@@ -49,6 +51,15 @@ public class ApiEventBuilder extends EventBaseBuilder<ApiEventBuilder> {
return eventPriceList;
}
+ public boolean isFromDisk() {
+ return fromDisk;
+ }
+
+ public ApiEventBuilder setFromDisk(boolean fromDisk) {
+ this.fromDisk = fromDisk;
+ return this;
+ }
+
public ApiEventBuilder setEventType(ApiEventType eventType) {
this.eventType = eventType;
return this;
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java
index 98cc376..fab66dd 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java
@@ -24,6 +24,7 @@ import org.joda.time.DateTime;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionTransition;
@@ -131,14 +132,13 @@ public class BrainDeadSubscription implements Subscription {
@Override
public List<SubscriptionTransition> getAllTransitions() {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException();
}
@Override
public SubscriptionTransition getPendingTransition() {
throw new UnsupportedOperationException();
-
}
@Override
@@ -146,4 +146,9 @@ public class BrainDeadSubscription implements Subscription {
return null;
}
+ @Override
+ public ProductCategory getCategory() {
+ throw new UnsupportedOperationException();
+ }
+
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
index c62fa07..55d383a 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
@@ -174,7 +174,7 @@ public class TestDefaultEntitlementBillingApi {
PlanPhase nextPhase = nextPlan.getAllPhases()[0]; // The trial has no billing period
String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
SubscriptionTransition t = new SubscriptionTransitionData(
- zeroId, oneId, twoId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList);
+ zeroId, oneId, twoId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList, true);
transitions.add(t);
AccountUserApi accountApi = new BrainDeadAccountUserApi(){
@@ -199,7 +199,7 @@ public class TestDefaultEntitlementBillingApi {
PlanPhase nextPhase = nextPlan.getAllPhases()[1];
String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
SubscriptionTransition t = new SubscriptionTransitionData(
- zeroId, oneId, twoId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList);
+ zeroId, oneId, twoId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList, true);
transitions.add(t);
Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
@@ -221,7 +221,7 @@ public class TestDefaultEntitlementBillingApi {
PlanPhase nextPhase = nextPlan.getAllPhases()[1];
String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
SubscriptionTransition t = new SubscriptionTransitionData(
- zeroId, oneId, twoId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList);
+ zeroId, oneId, twoId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList, true);
transitions.add(t);
AccountUserApi accountApi = new BrainDeadAccountUserApi(){
@@ -246,7 +246,7 @@ public class TestDefaultEntitlementBillingApi {
PlanPhase nextPhase = nextPlan.getAllPhases()[0];
String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
SubscriptionTransition t = new SubscriptionTransitionData(
- zeroId, oneId, twoId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList);
+ zeroId, oneId, twoId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList, true);
transitions.add(t);
Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
index a5b0142..7db938e 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
@@ -123,6 +123,8 @@ public class TestUserApiAddOn extends TestApiBase {
// REFETCH AO SUBSCRIPTION AND CHECK THIS IS ACTIVE
aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
assertEquals(aoSubscription.getState(), SubscriptionState.ACTIVE);
+ assertTrue(aoSubscription.isSubscriptionFutureCancelled());
+
// MOVE AFTER CANCELLATION
testListener.reset();
@@ -168,7 +170,7 @@ public class TestUserApiAddOn extends TestApiBase {
clock.setDeltaFromReality(twoMonths, DAY_IN_MS);
assertTrue(testListener.isCompleted(5000));
- // SET CTD TO CANCEL IN FUTURE
+ // SET CTD TO CHANGE IN FUTURE
DateTime now = clock.getUTCNow();
Duration ctd = getDurationMonth(1);
DateTime newChargedThroughDate = DefaultClock.addDuration(now, ctd);
@@ -239,13 +241,13 @@ public class TestUserApiAddOn extends TestApiBase {
// REFETCH AO SUBSCRIPTION AND CHECK THIS IS ACTIVE
aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
assertEquals(aoSubscription.getState(), SubscriptionState.ACTIVE);
+ assertTrue(aoSubscription.isSubscriptionFutureCancelled());
// MOVE AFTER CHANGE
testListener.reset();
testListener.pushExpectedEvent(NextEvent.CHANGE);
testListener.pushExpectedEvent(NextEvent.CANCEL);
clock.addDeltaFromReality(ctd);
- now = clock.getUTCNow();
assertTrue(testListener.isCompleted(5000));
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
index c5881f9..cb87dc0 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
@@ -25,6 +25,7 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
import com.google.inject.Inject;
import com.ning.billing.entitlement.api.user.SubscriptionFactory;
+import com.ning.billing.entitlement.engine.addon.AddonUtils;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.notificationq.NotificationQueueService;
@@ -33,8 +34,8 @@ public class MockEntitlementDaoSql extends EntitlementSqlDao implements MockEnti
private final ResetSqlDao resetDao;
@Inject
- public MockEntitlementDaoSql(IDBI dbi, Clock clock, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
- super(dbi, clock, factory, notificationQueueService);
+ public MockEntitlementDaoSql(IDBI dbi, Clock clock, SubscriptionFactory factory, AddonUtils addonUtils, NotificationQueueService notificationQueueService) {
+ super(dbi, clock, factory, addonUtils, notificationQueueService);
this.resetDao = dbi.onDemand(ResetSqlDao.class);
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java
index 7a9253c..c0b054a 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java
@@ -19,6 +19,7 @@ package com.ning.billing.invoice.dao;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionTransition;
@@ -124,4 +125,9 @@ public class MockSubscription implements Subscription {
public SubscriptionTransition getPreviousTransition() {
return null;
}
+
+ @Override
+ public ProductCategory getCategory() {
+ throw new UnsupportedOperationException();
+ }
}
\ No newline at end of file
diff --git a/invoice/src/test/java/com/ning/billing/invoice/notification/BrainDeadSubscription.java b/invoice/src/test/java/com/ning/billing/invoice/notification/BrainDeadSubscription.java
index 587144b..05d7c79 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/notification/BrainDeadSubscription.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/BrainDeadSubscription.java
@@ -24,6 +24,7 @@ import org.joda.time.DateTime;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionTransition;
@@ -47,103 +48,108 @@ public class BrainDeadSubscription implements Subscription {
throws EntitlementUserApiException {
throw new UnsupportedOperationException();
-
+
}
@Override
public void pause() throws EntitlementUserApiException {
throw new UnsupportedOperationException();
-
+
}
@Override
public void resume() throws EntitlementUserApiException {
throw new UnsupportedOperationException();
-
+
}
@Override
public UUID getId() {
throw new UnsupportedOperationException();
-
+
}
@Override
public UUID getBundleId() {
throw new UnsupportedOperationException();
-
+
}
@Override
public SubscriptionState getState() {
throw new UnsupportedOperationException();
-
+
}
@Override
public DateTime getStartDate() {
throw new UnsupportedOperationException();
-
+
}
@Override
public DateTime getEndDate() {
throw new UnsupportedOperationException();
-
+
}
@Override
public Plan getCurrentPlan() {
throw new UnsupportedOperationException();
-
+
}
@Override
public String getCurrentPriceList() {
throw new UnsupportedOperationException();
-
+
}
@Override
public PlanPhase getCurrentPhase() {
throw new UnsupportedOperationException();
-
+
}
@Override
public DateTime getChargedThroughDate() {
throw new UnsupportedOperationException();
-
+
}
@Override
public DateTime getPaidThroughDate() {
throw new UnsupportedOperationException();
-
+
}
@Override
public List<SubscriptionTransition> getActiveTransitions() {
throw new UnsupportedOperationException();
-
+
}
@Override
public List<SubscriptionTransition> getAllTransitions() {
throw new UnsupportedOperationException();
-
+
}
@Override
public SubscriptionTransition getPendingTransition() {
throw new UnsupportedOperationException();
-
+
}
@Override
public SubscriptionTransition getPreviousTransition() {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException();
}
+ @Override
+ public ProductCategory getCategory() {
+ throw new UnsupportedOperationException();
+ }
+
}