killbill-aplcache
Changes
entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementPluginExecution.java 47(+42 -5)
entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java 188(+114 -74)
subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java 5(+4 -1)
Details
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
index 5679190..f6a6c8f 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
@@ -25,6 +25,7 @@ import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun;
@@ -44,6 +45,8 @@ public interface SubscriptionBaseInternalApi {
public SubscriptionBase createBaseSubscriptionWithAddOns(UUID bundleId, Iterable<EntitlementSpecifier> entitlements, DateTime requestedDateWithMs,
InternalCallContext context) throws SubscriptionBaseApiException;
+ public void cancelBaseSubscriptions(Iterable<SubscriptionBase> subscriptions, BillingActionPolicy policy, InternalCallContext context) throws SubscriptionBaseApiException;
+
public SubscriptionBaseBundle createBundleForAccount(UUID accountId, String bundleName, InternalCallContext context)
throws SubscriptionBaseApiException;
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementPluginExecution.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementPluginExecution.java
index 61f6307..82d9278 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementPluginExecution.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementPluginExecution.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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,6 +17,10 @@
package org.killbill.billing.entitlement.api;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Callable;
+
import javax.inject.Inject;
import org.killbill.billing.ErrorCode;
@@ -41,15 +45,48 @@ public class EntitlementPluginExecution {
T doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException;
}
-
@Inject
public EntitlementPluginExecution(final EntitlementApi entitlementApi, final OSGIServiceRegistration<EntitlementPluginApi> pluginRegistry) {
this.entitlementApi = entitlementApi;
this.pluginRegistry = pluginRegistry;
}
- public <T> T executeWithPlugin(final WithEntitlementPlugin<T> callback, final EntitlementContext pluginContext) throws EntitlementApiException {
+ public void executeWithPlugin(final Callable<Void> preCallbacksCallback, final List<WithEntitlementPlugin> callbacks, final Iterable<EntitlementContext> pluginContexts) throws EntitlementApiException {
+ final List<EntitlementContext> updatedPluginContexts = new LinkedList<EntitlementContext>();
+
+ try {
+ for (final EntitlementContext pluginContext : pluginContexts) {
+ final PriorEntitlementResult priorEntitlementResult = executePluginPriorCalls(pluginContext);
+ if (priorEntitlementResult != null && priorEntitlementResult.isAborted()) {
+ throw new EntitlementApiException(ErrorCode.ENT_PLUGIN_API_ABORTED);
+ }
+ updatedPluginContexts.add(new DefaultEntitlementContext(pluginContext, priorEntitlementResult));
+ }
+
+ preCallbacksCallback.call();
+ try {
+ for (int i = 0; i < updatedPluginContexts.size(); i++) {
+ final EntitlementContext updatedPluginContext = updatedPluginContexts.get(i);
+ final WithEntitlementPlugin callback = callbacks.get(i);
+
+ callback.doCall(entitlementApi, updatedPluginContext);
+ executePluginOnSuccessCalls(updatedPluginContext);
+ }
+ } catch (final EntitlementApiException e) {
+ for (final EntitlementContext updatedPluginContext : updatedPluginContexts) {
+ executePluginOnFailureCalls(updatedPluginContext);
+ }
+ throw e;
+ }
+ } catch (final EntitlementPluginApiException e) {
+ throw new EntitlementApiException(ErrorCode.ENT_PLUGIN_API_ABORTED, e.getMessage());
+ } catch (final Exception e) {
+ throw new EntitlementApiException(ErrorCode.ENT_PLUGIN_API_ABORTED, e.getMessage());
+ }
+ }
+
+ public <T> T executeWithPlugin(final WithEntitlementPlugin<T> callback, final EntitlementContext pluginContext) throws EntitlementApiException {
try {
final PriorEntitlementResult priorEntitlementResult = executePluginPriorCalls(pluginContext);
if (priorEntitlementResult != null && priorEntitlementResult.isAborted()) {
@@ -57,7 +94,7 @@ public class EntitlementPluginExecution {
}
final EntitlementContext updatedPluginContext = new DefaultEntitlementContext(pluginContext, priorEntitlementResult);
try {
- T result = callback.doCall(entitlementApi, updatedPluginContext);
+ final T result = callback.doCall(entitlementApi, updatedPluginContext);
executePluginOnSuccessCalls(updatedPluginContext);
return result;
} catch (final EntitlementApiException e) {
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
index 69ad149..3c48358 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementInternalApi.java
@@ -22,14 +22,16 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import java.util.UUID;
+import java.util.concurrent.Callable;
import javax.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
-import org.killbill.billing.ErrorCode;
import org.killbill.billing.account.api.AccountInternalApi;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
@@ -57,6 +59,7 @@ import org.killbill.billing.entitlement.plugin.api.OperationType;
import org.killbill.billing.junction.DefaultBlockingState;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.security.api.SecurityApi;
+import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
import org.killbill.billing.util.callcontext.CallContext;
@@ -91,28 +94,48 @@ public class DefaultEntitlementInternalApi extends DefaultEntitlementApiBase imp
public void cancel(final Iterable<Entitlement> entitlements, final LocalDate effectiveDate, final BillingActionPolicy billingPolicy, final Iterable<PluginProperty> properties, final InternalCallContext internalCallContext) throws EntitlementApiException {
final CallContext callContext = internalCallContextFactory.createCallContext(internalCallContext);
- final ImmutableMap.Builder<BlockingState, Optional<UUID>> states = new ImmutableMap.Builder<BlockingState, Optional<UUID>>();
+ final ImmutableMap.Builder<BlockingState, Optional<UUID>> blockingStates = new ImmutableMap.Builder<BlockingState, Optional<UUID>>();
final Map<DateTime, Collection<NotificationEvent>> notificationEvents = new HashMap<DateTime, Collection<NotificationEvent>>();
- for (final Entitlement entitlement : entitlements) {
- final DefaultEntitlement defaultEntitlement = getDefaultEntitlement(entitlement, internalCallContext);
- final Collection<BlockingState> blockingStates = new ArrayList<BlockingState>();
+ final Collection<EntitlementContext> pluginContexts = new LinkedList<EntitlementContext>();
+ final List<WithEntitlementPlugin> callbacks = new LinkedList<WithEntitlementPlugin>();
+ final List<SubscriptionBase> subscriptions = new LinkedList<SubscriptionBase>();
- try {
- cancelEntitlementWithDateOverrideBillingPolicy(defaultEntitlement, effectiveDate, billingPolicy, blockingStates, notificationEvents, properties, callContext, internalCallContext);
- } catch (final EntitlementApiException e) {
+ for (final Entitlement entitlement : entitlements) {
+ if (entitlement.getState() == EntitlementState.CANCELLED) {
// If subscription has already been cancelled, we ignore and carry on
- if (e.getCode() != ErrorCode.SUB_CANCEL_BAD_STATE.getCode()) {
- throw e;
- }
+ continue;
}
- for (final BlockingState blockingState : blockingStates) {
- states.put(blockingState, Optional.<UUID>fromNullable(entitlement.getBundleId()));
- }
+ final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CANCEL_SUBSCRIPTION,
+ entitlement.getAccountId(),
+ null,
+ entitlement.getBundleId(),
+ entitlement.getExternalKey(),
+ null,
+ effectiveDate,
+ properties,
+ callContext);
+ pluginContexts.add(pluginContext);
+
+ final DefaultEntitlement defaultEntitlement = getDefaultEntitlement(entitlement, internalCallContext);
+ final WithEntitlementPlugin<Entitlement> cancelEntitlementWithPlugin = new WithDateOverrideBillingPolicyEntitlementCanceler(defaultEntitlement,
+ blockingStates,
+ notificationEvents,
+ callContext,
+ internalCallContext);
+ callbacks.add(cancelEntitlementWithPlugin);
+
+ subscriptions.add(defaultEntitlement.getSubscriptionBase());
}
+ final Callable<Void> preCallbacksCallback = new BulkSubscriptionBaseCancellation(subscriptions,
+ billingPolicy,
+ internalCallContext);
+
+ pluginExecution.executeWithPlugin(preCallbacksCallback, callbacks, pluginContexts);
+
// Record the new states first, then insert the notifications to avoid race conditions
- blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(states.build(), internalCallContext);
+ blockingStateDao.setBlockingStatesAndPostBlockingTransitionEvent(blockingStates.build(), internalCallContext);
for (final DateTime effectiveDateForNotification : notificationEvents.keySet()) {
for (final NotificationEvent notificationEvent : notificationEvents.get(effectiveDateForNotification)) {
recordFutureNotification(effectiveDateForNotification, notificationEvent, internalCallContext);
@@ -120,65 +143,6 @@ public class DefaultEntitlementInternalApi extends DefaultEntitlementApiBase imp
}
}
- // Note that the implementation is similar to DefaultEntitlement#cancelEntitlementWithDateOverrideBillingPolicy but state isn't persisted on disk
- private void cancelEntitlementWithDateOverrideBillingPolicy(final DefaultEntitlement entitlement,
- final LocalDate localCancelDate,
- final BillingActionPolicy billingPolicy,
- final Collection<BlockingState> blockingStates,
- final Map<DateTime, Collection<NotificationEvent>> notificationEventsWithEffectiveDate,
- final Iterable<PluginProperty> properties,
- final CallContext callContext,
- final InternalCallContext internalCallContext) throws EntitlementApiException {
- final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CANCEL_SUBSCRIPTION,
- entitlement.getAccountId(),
- null,
- entitlement.getBundleId(),
- entitlement.getExternalKey(),
- null,
- localCancelDate,
- properties,
- callContext);
-
- final WithEntitlementPlugin<Entitlement> cancelEntitlementWithPlugin = new WithEntitlementPlugin<Entitlement>() {
- @Override
- public Entitlement doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
- if (entitlement.getState() == EntitlementState.CANCELLED) {
- throw new EntitlementApiException(ErrorCode.SUB_CANCEL_BAD_STATE, entitlement.getId(), EntitlementState.CANCELLED);
- }
-
- // Make sure to compute the entitlement effective date first to avoid timing issues for IMM cancellations
- // (we don't want an entitlement cancel date one second or so after the subscription cancel date or add-ons cancellations
- // computations won't work).
- final LocalDate effectiveLocalDate = new LocalDate(updatedPluginContext.getEffectiveDate(), entitlement.getAccountTimeZone());
- final DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(effectiveLocalDate, entitlement.getSubscriptionBase().getStartDate(), internalCallContext);
-
- try {
- // Cancel subscription base first, to correctly compute the add-ons entitlements we need to cancel (see below)
- entitlement.getSubscriptionBase().cancelWithPolicy(billingPolicy, callContext);
- } catch (final SubscriptionBaseApiException e) {
- throw new EntitlementApiException(e);
- }
-
- final BlockingState newBlockingState = new DefaultBlockingState(entitlement.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, effectiveDate);
- final Collection<NotificationEvent> notificationEvents = new ArrayList<NotificationEvent>();
- final Collection<BlockingState> addOnsBlockingStates = entitlement.computeAddOnBlockingStates(effectiveDate, notificationEvents, callContext, internalCallContext);
-
- blockingStates.add(newBlockingState);
- blockingStates.addAll(addOnsBlockingStates);
-
- if (notificationEventsWithEffectiveDate.get(effectiveDate) == null) {
- notificationEventsWithEffectiveDate.put(effectiveDate, notificationEvents);
- } else {
- notificationEventsWithEffectiveDate.get(effectiveDate).addAll(notificationEvents);
- }
-
- // Unable to return the new state (not on disk yet)
- return null;
- }
- };
- pluginExecution.executeWithPlugin(cancelEntitlementWithPlugin, pluginContext);
- }
-
private void recordFutureNotification(final DateTime effectiveDate,
final NotificationEvent notificationEvent,
final InternalCallContext context) {
@@ -202,4 +166,80 @@ public class DefaultEntitlementInternalApi extends DefaultEntitlementApiBase imp
return (DefaultEntitlement) getEntitlementForId(entitlement.getId(), context);
}
}
+
+ private class BulkSubscriptionBaseCancellation implements Callable<Void> {
+
+ private final Iterable<SubscriptionBase> subscriptions;
+ private final BillingActionPolicy billingPolicy;
+ private final InternalCallContext callContext;
+
+ public BulkSubscriptionBaseCancellation(final Iterable<SubscriptionBase> subscriptions,
+ final BillingActionPolicy billingPolicy,
+ final InternalCallContext callContext) {
+ this.subscriptions = subscriptions;
+ this.billingPolicy = billingPolicy;
+ this.callContext = callContext;
+ }
+
+ @Override
+ public Void call() throws Exception {
+ try {
+ subscriptionInternalApi.cancelBaseSubscriptions(subscriptions, billingPolicy, callContext);
+ } catch (final SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
+ }
+
+ return null;
+ }
+ }
+
+ // Note that the implementation is similar to DefaultEntitlement#cancelEntitlementWithDateOverrideBillingPolicy but state isn't persisted on disk
+ private class WithDateOverrideBillingPolicyEntitlementCanceler implements WithEntitlementPlugin<Entitlement> {
+
+ private final DefaultEntitlement entitlement;
+ private final ImmutableMap.Builder<BlockingState, Optional<UUID>> blockingStates;
+ private final Map<DateTime, Collection<NotificationEvent>> notificationEventsWithEffectiveDate;
+ private final CallContext callContext;
+ private final InternalCallContext internalCallContext;
+
+ public WithDateOverrideBillingPolicyEntitlementCanceler(final DefaultEntitlement entitlement,
+ final ImmutableMap.Builder<BlockingState, Optional<UUID>> blockingStates,
+ final Map<DateTime, Collection<NotificationEvent>> notificationEventsWithEffectiveDate,
+ final CallContext callContext,
+ final InternalCallContext internalCallContext) {
+ this.entitlement = entitlement;
+ this.blockingStates = blockingStates;
+ this.notificationEventsWithEffectiveDate = notificationEventsWithEffectiveDate;
+ this.callContext = callContext;
+ this.internalCallContext = internalCallContext;
+ }
+
+ @Override
+ public Entitlement doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
+ // Make sure to compute the entitlement effective date first to avoid timing issues for IMM cancellations
+ // (we don't want an entitlement cancel date one second or so after the subscription cancel date or add-ons cancellations
+ // computations won't work).
+ final LocalDate effectiveLocalDate = new LocalDate(updatedPluginContext.getEffectiveDate(), entitlement.getAccountTimeZone());
+ final DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(effectiveLocalDate, entitlement.getSubscriptionBase().getStartDate(), internalCallContext);
+
+ final BlockingState newBlockingState = new DefaultBlockingState(entitlement.getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, effectiveDate);
+ final Collection<NotificationEvent> notificationEvents = new ArrayList<NotificationEvent>();
+ final Collection<BlockingState> addOnsBlockingStates = entitlement.computeAddOnBlockingStates(effectiveDate, notificationEvents, callContext, internalCallContext);
+
+ final Optional<UUID> bundleIdOptional = Optional.<UUID>fromNullable(entitlement.getBundleId());
+ blockingStates.put(newBlockingState, bundleIdOptional);
+ for (final BlockingState blockingState : addOnsBlockingStates) {
+ blockingStates.put(blockingState, bundleIdOptional);
+ }
+
+ if (notificationEventsWithEffectiveDate.get(effectiveDate) == null) {
+ notificationEventsWithEffectiveDate.put(effectiveDate, notificationEvents);
+ } else {
+ notificationEventsWithEffectiveDate.get(effectiveDate).addAll(notificationEvents);
+ }
+
+ // Unable to return the new state (not on disk yet)
+ return null;
+ }
+ }
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
index 200a0fc..9b3d03d 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
@@ -22,6 +22,7 @@ import java.util.List;
import java.util.UUID;
import org.joda.time.DateTime;
+import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
@@ -31,7 +32,6 @@ import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanChangeResult;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
-import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
import org.killbill.billing.subscription.api.user.SubscriptionBuilder;
@@ -63,6 +63,9 @@ public interface SubscriptionBaseApiService {
public boolean cancelWithPolicy(DefaultSubscriptionBase subscription, BillingActionPolicy policy, CallContext context)
throws SubscriptionBaseApiException;
+ public boolean cancelWithPolicyNoValidation(Iterable<DefaultSubscriptionBase> subscriptions, BillingActionPolicy policy, InternalCallContext context)
+ throws SubscriptionBaseApiException;
+
public boolean uncancel(DefaultSubscriptionBase subscription, CallContext context)
throws SubscriptionBaseApiException;
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
index 64c4d39..5d97842 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
@@ -215,6 +215,23 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
@Override
+ public void cancelBaseSubscriptions(final Iterable<SubscriptionBase> subscriptions, final BillingActionPolicy policy, final InternalCallContext context) throws SubscriptionBaseApiException {
+ apiService.cancelWithPolicyNoValidation(Iterables.<SubscriptionBase, DefaultSubscriptionBase>transform(subscriptions,
+ new Function<SubscriptionBase, DefaultSubscriptionBase>() {
+ @Override
+ public DefaultSubscriptionBase apply(final SubscriptionBase subscriptionBase) {
+ try {
+ return getDefaultSubscriptionBase(subscriptionBase, context);
+ } catch (final CatalogApiException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }),
+ policy,
+ context);
+ }
+
+ @Override
public SubscriptionBaseBundle createBundleForAccount(final UUID accountId, final String bundleKey, final InternalCallContext context) throws SubscriptionBaseApiException {
final List<SubscriptionBaseBundle> existingBundles = dao.getSubscriptionBundlesForKey(bundleKey, context);
@@ -627,4 +644,14 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
}));
}
+
+ // For forward-compatibility
+ private DefaultSubscriptionBase getDefaultSubscriptionBase(final SubscriptionBase subscriptionBase, final InternalTenantContext context) throws CatalogApiException {
+ if (subscriptionBase instanceof DefaultSubscriptionBase) {
+ return (DefaultSubscriptionBase) subscriptionBase;
+ } else {
+ // Safe cast, see above
+ return (DefaultSubscriptionBase) dao.getSubscriptionFromId(subscriptionBase.getId(), context);
+ }
+ }
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
index cb7804e..3a079aa 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
@@ -75,6 +75,7 @@ import org.killbill.clock.DefaultClock;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
@@ -221,11 +222,11 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
subscription.getCurrentPhase().getPhaseType());
try {
- final InternalTenantContext internalCallContext = createTenantContextFromBundleId(subscription.getBundleId(), context);
+ final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
final BillingActionPolicy policy = catalogService.getFullCatalog(internalCallContext).planCancelPolicy(planPhase, now);
final DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy);
- return doCancelPlan(subscription, now, effectiveDate, context);
+ return doCancelPlan(ImmutableMap.<DefaultSubscriptionBase, DateTime>of(subscription, effectiveDate), now, internalCallContext);
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}
@@ -239,7 +240,9 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
final DateTime now = clock.getUTCNow();
final DateTime effectiveDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
- return doCancelPlan(subscription, now, effectiveDate, context);
+
+ final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
+ return doCancelPlan(ImmutableMap.<DefaultSubscriptionBase, DateTime>of(subscription, effectiveDate), now, internalCallContext);
}
@Override
@@ -248,33 +251,51 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
if (currentState != null && currentState != EntitlementState.ACTIVE) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CANCEL_BAD_STATE, subscription.getId(), currentState);
}
- final DateTime now = clock.getUTCNow();
- final DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy);
- return doCancelPlan(subscription, now, effectiveDate, context);
+ final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
+ return cancelWithPolicyNoValidation(ImmutableList.<DefaultSubscriptionBase>of(subscription), policy, internalCallContext);
}
- private boolean doCancelPlan(final DefaultSubscriptionBase subscription, final DateTime now, final DateTime effectiveDate, final CallContext context) throws SubscriptionBaseApiException {
- validateEffectiveDate(subscription, effectiveDate);
+ @Override
+ public boolean cancelWithPolicyNoValidation(final Iterable<DefaultSubscriptionBase> subscriptions, final BillingActionPolicy policy, final InternalCallContext context) throws SubscriptionBaseApiException {
+ final Map<DefaultSubscriptionBase, DateTime> subscriptionsWithEffectiveDate = new HashMap<DefaultSubscriptionBase, DateTime>();
+ final DateTime now = clock.getUTCNow();
- final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
+ for (final DefaultSubscriptionBase subscription : subscriptions) {
+ final DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy);
+ subscriptionsWithEffectiveDate.put(subscription, effectiveDate);
+ }
+
+ return doCancelPlan(subscriptionsWithEffectiveDate, now, context);
+ }
+
+ private boolean doCancelPlan(final Map<DefaultSubscriptionBase, DateTime> subscriptions, final DateTime now, final InternalCallContext internalCallContext) throws SubscriptionBaseApiException {
final List<DefaultSubscriptionBase> subscriptionsToBeCancelled = new LinkedList<DefaultSubscriptionBase>();
final List<SubscriptionBaseEvent> cancelEvents = new LinkedList<SubscriptionBaseEvent>();
try {
- subscriptionsToBeCancelled.add(subscription);
- cancelEvents.addAll(getEventsOnCancelPlan(subscription, effectiveDate, now, false, internalCallContext));
+ for (final DefaultSubscriptionBase subscription : subscriptions.keySet()) {
+ final DateTime effectiveDate = subscriptions.get(subscription);
+ validateEffectiveDate(subscription, effectiveDate);
- if (subscription.getCategory() == ProductCategory.BASE) {
- subscriptionsToBeCancelled.addAll(computeAddOnsToCancel(cancelEvents, null, subscription.getBundleId(), effectiveDate, internalCallContext));
+ subscriptionsToBeCancelled.add(subscription);
+ cancelEvents.addAll(getEventsOnCancelPlan(subscription, effectiveDate, now, false, internalCallContext));
+
+ if (subscription.getCategory() == ProductCategory.BASE) {
+ subscriptionsToBeCancelled.addAll(computeAddOnsToCancel(cancelEvents, null, subscription.getBundleId(), effectiveDate, internalCallContext));
+ }
}
dao.cancelSubscriptions(subscriptionsToBeCancelled, cancelEvents, internalCallContext);
- final Catalog fullCatalog = catalogService.getFullCatalog(internalCallContext);
- subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), fullCatalog);
+ boolean allSubscriptionsCancelled = true;
+ for (final DefaultSubscriptionBase subscription : subscriptions.keySet()) {
+ final Catalog fullCatalog = catalogService.getFullCatalog(internalCallContext);
+ subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), fullCatalog);
+ allSubscriptionsCancelled = allSubscriptionsCancelled && (subscription.getState() == EntitlementState.CANCELLED);
+ }
- return subscription.getState() == EntitlementState.CANCELLED;
+ return allSubscriptionsCancelled;
} catch (final CatalogApiException e) {
throw new SubscriptionBaseApiException(e);
}