killbill-memoizeit
Changes
entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java 409(+239 -170)
entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementSpecifier.java 12(+12 -0)
entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java 7(+7 -0)
subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java 7(+4 -3)
Details
diff --git a/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java b/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
index 20027f2..a62fc85 100644
--- a/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
+++ b/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -64,6 +64,8 @@ public interface EventsStream {
boolean isBlockChange(final DateTime effectiveDate);
+ boolean isBlockEntitlement(final DateTime effectiveDate);
+
int getDefaultBillCycleDayLocal();
Collection<BlockingState> getPendingEntitlementCancellationEvents();
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 ac1df1a..0728a12 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
@@ -65,6 +65,8 @@ public interface SubscriptionBaseInternalApi {
public Iterable<UUID> getNonAOSubscriptionIdsForKey(String bundleKey, InternalTenantContext context);
+ public SubscriptionBaseBundle getActiveBundleForKey(String bundleKey, Catalog catalog, InternalTenantContext context);
+
public List<SubscriptionBase> getSubscriptionsForBundle(UUID bundleId, DryRunArguments dryRunArguments, InternalTenantContext context)
throws SubscriptionBaseApiException;
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
index 475527c..9c7423e 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementApi.java
@@ -38,6 +38,9 @@ import org.killbill.billing.account.api.AccountInternalApi;
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.Catalog;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.CatalogInternalApi;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.ProductCategory;
@@ -72,12 +75,12 @@ import org.killbill.clock.Clock;
import org.killbill.notificationq.api.NotificationQueueService;
import com.google.common.base.Function;
+import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
-import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logCreateEntitlement;
import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logCreateEntitlementsWithAOs;
import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logPauseResumeEntitlement;
import static org.killbill.billing.entitlement.logging.EntitlementLoggingHelper.logTransferEntitlement;
@@ -102,6 +105,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
private final NotificationQueueService notificationQueueService;
private final EntitlementPluginExecution pluginExecution;
private final SecurityApi securityApi;
+ private final CatalogInternalApi catalogInternalApi;
@Inject
public DefaultEntitlementApi(final PersistentBus eventBus, final InternalCallContextFactory internalCallContextFactory,
@@ -110,6 +114,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
final BlockingChecker checker, final NotificationQueueService notificationQueueService,
final EventsStreamBuilder eventsStreamBuilder, final EntitlementUtils entitlementUtils,
final EntitlementPluginExecution pluginExecution,
+ final CatalogInternalApi catalogInternalApi,
final SecurityApi securityApi) {
super(eventBus, null, pluginExecution, internalCallContextFactory, subscriptionInternalApi, accountApi, blockingStateDao, clock, checker, notificationQueueService, eventsStreamBuilder, entitlementUtils, securityApi);
this.internalCallContextFactory = internalCallContextFactory;
@@ -123,6 +128,7 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
this.entitlementUtils = entitlementUtils;
this.pluginExecution = pluginExecution;
this.securityApi = securityApi;
+ this.catalogInternalApi = catalogInternalApi;
this.dateHelper = new EntitlementDateHelper();
}
@@ -137,7 +143,8 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
entitlementEffectiveDate,
billingEffectiveDate,
isMigrated);
- final List<UUID> createdEntitlements = createBaseEntitlementsWithAddOns(accountId,
+ final List<UUID> createdEntitlements = createBaseEntitlementsWithAddOns(OperationType.CREATE_SUBSCRIPTION,
+ accountId,
ImmutableList.<BaseEntitlementWithAddOnsSpecifier>of(baseEntitlementWithAddOnsSpecifier),
renameCancelledBundleIfExist,
properties,
@@ -145,182 +152,41 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
return createdEntitlements.get(0);
}
- private BaseEntitlementWithAddOnsSpecifier getFirstBaseEntitlementWithAddOnsSpecifier(final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiers) throws SubscriptionBaseApiException {
- if (baseEntitlementWithAddOnsSpecifiers == null) {
- throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
- }
-
- final Iterator<BaseEntitlementWithAddOnsSpecifier> iterator = baseEntitlementWithAddOnsSpecifiers.iterator();
- if (!iterator.hasNext()) {
- throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
- }
-
- return iterator.next();
- }
-
@Override
public List<UUID> createBaseEntitlementsWithAddOns(final UUID accountId, final Iterable<BaseEntitlementWithAddOnsSpecifier> originalBaseEntitlementWithAddOnsSpecifiers, final boolean renameCancelledBundleIfExist, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
- logCreateEntitlementsWithAOs(log, originalBaseEntitlementWithAddOnsSpecifiers);
-
- final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CREATE_SHOPPING_CART_SUBSCRIPTIONS,
- accountId,
- null,
- originalBaseEntitlementWithAddOnsSpecifiers,
- null,
- properties,
- callContext);
-
- final WithEntitlementPlugin<List<UUID>> createBaseEntitlementsWithAddOns = new WithEntitlementPlugin<List<UUID>>() {
- @Override
- public List<UUID> doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
- final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(accountId, callContext);
-
- final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiersAfterPlugins = updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers();
- final Collection<SubscriptionBaseWithAddOnsSpecifier> subscriptionBaseWithAddOnsSpecifiers = new LinkedList<SubscriptionBaseWithAddOnsSpecifier>();
- DateTime upTo = null;
- for (final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier : baseEntitlementWithAddOnsSpecifiersAfterPlugins) {
- // Entitlement
- final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(),
- updatedPluginContext.getCreatedDate(),
- contextWithValidAccountRecordId);
- upTo = upTo == null || upTo.compareTo(entitlementRequestedDate) < 0 ? entitlementRequestedDate : upTo;
-
- final SubscriptionBaseWithAddOnsSpecifier subscriptionBaseWithAddOnsSpecifier = new SubscriptionBaseWithAddOnsSpecifier(baseEntitlementWithAddOnsSpecifier.getBundleId(),
- baseEntitlementWithAddOnsSpecifier.getExternalKey(),
- baseEntitlementWithAddOnsSpecifier.getEntitlementSpecifier(),
- baseEntitlementWithAddOnsSpecifier.getBillingEffectiveDate(),
- baseEntitlementWithAddOnsSpecifier.isMigrated());
- subscriptionBaseWithAddOnsSpecifiers.add(subscriptionBaseWithAddOnsSpecifier);
- }
-
- try {
- // Verify if operation is allowed by looking for is_block_change on Account
- // Note that to fully check for block_change we should also look for BlockingState at the BUNDLE/SUBSCRIPTION level in case some of the input contain a BP that already exists.
- checkForAccountBlockingChange(accountId, upTo, contextWithValidAccountRecordId);
-
- final List<SubscriptionBaseWithAddOns> subscriptionsWithAddOns = subscriptionBaseInternalApi.createBaseSubscriptionsWithAddOns(subscriptionBaseWithAddOnsSpecifiers,
- renameCancelledBundleIfExist,
- contextWithValidAccountRecordId);
- final List<UUID> createdSubscriptionIds = new LinkedList<UUID>();
- final Map<BlockingState, UUID> blockingStateMap = new HashMap<BlockingState, UUID>();
- int i = 0;
- for (final Iterator<BaseEntitlementWithAddOnsSpecifier> it = baseEntitlementWithAddOnsSpecifiersAfterPlugins.iterator(); it.hasNext(); i++) {
- final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = it.next();
- for (final SubscriptionBase subscriptionBase : subscriptionsWithAddOns.get(i).getSubscriptionBaseList()) {
- final BlockingState blockingState = new DefaultBlockingState(subscriptionBase.getId(),
- BlockingStateType.SUBSCRIPTION,
- DefaultEntitlementApi.ENT_STATE_START,
- EntitlementService.ENTITLEMENT_SERVICE_NAME,
- false,
- false,
- false,
- dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), updatedPluginContext.getCreatedDate(), contextWithValidAccountRecordId));
- blockingStateMap.put(blockingState, subscriptionsWithAddOns.get(i).getBundle().getId());
-
- createdSubscriptionIds.add(subscriptionBase.getId());
- }
- }
- entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(blockingStateMap, contextWithValidAccountRecordId);
- return createdSubscriptionIds;
- } catch (final SubscriptionBaseApiException e) {
- throw new EntitlementApiException(e);
- }
- }
- };
- return pluginExecution.executeWithPlugin(createBaseEntitlementsWithAddOns, pluginContext);
+ return createBaseEntitlementsWithAddOns(OperationType.CREATE_SHOPPING_CART_SUBSCRIPTIONS,
+ accountId,
+ originalBaseEntitlementWithAddOnsSpecifiers,
+ renameCancelledBundleIfExist,
+ properties,
+ callContext);
}
@Override
public UUID addEntitlement(final UUID bundleId, final PlanPhaseSpecifier planPhaseSpecifier, final List<PlanPhasePriceOverride> overrides, @Nullable final LocalDate entitlementEffectiveDate, @Nullable final LocalDate billingEffectiveDate,
- final boolean isMigrated, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
- logCreateEntitlement(log, bundleId, planPhaseSpecifier, overrides, entitlementEffectiveDate, billingEffectiveDate);
-
+ final boolean isMigrated, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
final EntitlementSpecifier entitlementSpecifier = new DefaultEntitlementSpecifier(planPhaseSpecifier, overrides);
- final List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
- entitlementSpecifierList.add(entitlementSpecifier);
- final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(
- bundleId,
- null,
- entitlementSpecifierList,
- entitlementEffectiveDate,
- billingEffectiveDate,
- isMigrated);
- final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
- baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementWithAddOnsSpecifier);
-
- final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CREATE_SUBSCRIPTION,
- null,
- null,
- baseEntitlementWithAddOnsSpecifierList,
- null,
- properties,
- callContext);
-
- final WithEntitlementPlugin<UUID> addEntitlementWithPlugin = new WithEntitlementPlugin<UUID>() {
- @Override
- public UUID doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
- final InternalCallContext context = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, callContext);
-
- final List<SubscriptionBase> subscriptionsByBundle;
- try {
- subscriptionsByBundle = subscriptionBaseInternalApi.getSubscriptionsForBundle(bundleId, null, context);
-
- if (subscriptionsByBundle == null || subscriptionsByBundle.isEmpty()) {
- throw new EntitlementApiException(ErrorCode.SUB_NO_ACTIVE_SUBSCRIPTIONS, bundleId);
- }
- } catch (final SubscriptionBaseApiException e) {
- throw new EntitlementApiException(e);
- }
- final boolean isStandalone = ProductCategory.STANDALONE.equals(subscriptionsByBundle.get(0).getCategory());
-
- final EventsStream eventsStreamForBaseSubscription = isStandalone ? null : eventsStreamBuilder.buildForBaseSubscription(bundleId, callContext);
-
- final DateTime entitlementRequestedDateRaw = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), updatedPluginContext.getCreatedDate(), context);
- DateTime entitlementRequestedDate = entitlementRequestedDateRaw;
- if (!isStandalone) {
- final DateTime baseEntitlementStartDate = eventsStreamForBaseSubscription.getEntitlementEffectiveStartDateTime();
- entitlementRequestedDate = entitlementRequestedDateRaw.isBefore(baseEntitlementStartDate) ? baseEntitlementStartDate : entitlementRequestedDateRaw;
- preCheckAddEntitlement(bundleId, entitlementRequestedDate, baseEntitlementWithAddOnsSpecifier, eventsStreamForBaseSubscription, callContext);
- }
-
- try {
- final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifierAfterPlugins = getFirstBaseEntitlementWithAddOnsSpecifier(updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers());
- final SubscriptionBaseWithAddOnsSpecifier subscriptionBaseWithAddOnsSpecifier = new SubscriptionBaseWithAddOnsSpecifier(baseEntitlementWithAddOnsSpecifierAfterPlugins.getBundleId(),
- baseEntitlementWithAddOnsSpecifierAfterPlugins.getExternalKey(),
- baseEntitlementWithAddOnsSpecifierAfterPlugins.getEntitlementSpecifier(),
- baseEntitlementWithAddOnsSpecifierAfterPlugins.getBillingEffectiveDate(),
- baseEntitlementWithAddOnsSpecifierAfterPlugins.isMigrated());
-
- final List<SubscriptionBaseWithAddOns> subscriptionsWithAddOns = subscriptionBaseInternalApi.createBaseSubscriptionsWithAddOns(ImmutableList.<SubscriptionBaseWithAddOnsSpecifier>of(subscriptionBaseWithAddOnsSpecifier),
- false,
- context);
- final SubscriptionBaseWithAddOns subscriptionBaseWithAddOns = subscriptionsWithAddOns.get(0);
- final UUID subscriptionId = subscriptionBaseWithAddOns.getSubscriptionBaseList().get(0).getId();
-
- final BlockingState newBlockingState = new DefaultBlockingState(subscriptionId, BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_START, EntitlementService.ENTITLEMENT_SERVICE_NAME, false, false, false, entitlementRequestedDate);
- entitlementUtils.setBlockingStatesAndPostBlockingTransitionEvent(ImmutableList.<BlockingState>of(newBlockingState), subscriptionBaseWithAddOns.getBundle().getId(), context);
-
- return subscriptionId;
- } catch (final SubscriptionBaseApiException e) {
- throw new EntitlementApiException(e);
- }
- }
- };
- return pluginExecution.executeWithPlugin(addEntitlementWithPlugin, pluginContext);
- }
-
- private void preCheckAddEntitlement(final UUID bundleId, final DateTime entitlementRequestedDate, final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier, final EventsStream eventsStreamForBaseSubscription, final CallContext callContext) throws EntitlementApiException {
- if (eventsStreamForBaseSubscription.isEntitlementCancelled() ||
- (eventsStreamForBaseSubscription.isEntitlementPending() &&
- (baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate() == null ||
- baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate().compareTo(eventsStreamForBaseSubscription.getEntitlementEffectiveStartDate()) < 0))) {
- throw new EntitlementApiException(ErrorCode.SUB_GET_NO_SUCH_BASE_SUBSCRIPTION, bundleId);
+ final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = new DefaultBaseEntitlementWithAddOnsSpecifier(bundleId,
+ null,
+ ImmutableList.<EntitlementSpecifier>of(entitlementSpecifier),
+ entitlementEffectiveDate,
+ billingEffectiveDate,
+ isMigrated);
+ final InternalCallContext context = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, callContext);
+ final UUID accountId;
+ try {
+ accountId = subscriptionBaseInternalApi.getAccountIdFromBundleId(bundleId, context);
+ } catch (final SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
}
- // Check the base entitlement state is not blocked
- if (eventsStreamForBaseSubscription.isBlockChange(entitlementRequestedDate)) {
- throw new EntitlementApiException(new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, BlockingChecker.ACTION_CHANGE, BlockingChecker.TYPE_SUBSCRIPTION, eventsStreamForBaseSubscription.getEntitlementId().toString()));
- }
+ final List<UUID> createdEntitlements = createBaseEntitlementsWithAddOns(OperationType.CREATE_SUBSCRIPTION,
+ accountId,
+ ImmutableList.<BaseEntitlementWithAddOnsSpecifier>of(baseEntitlementWithAddOnsSpecifier),
+ false,
+ properties,
+ callContext);
+ return createdEntitlements.get(0);
}
@Override
@@ -506,8 +372,184 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
return pluginExecution.executeWithPlugin(transferWithPlugin, pluginContext);
}
- private void checkForAccountBlockingChange(final UUID accountId, @Nullable final DateTime upTo, final InternalCallContext context) throws EntitlementApiException {
+ private List<UUID> createBaseEntitlementsWithAddOns(final OperationType operationType,
+ final UUID accountId,
+ final Iterable<BaseEntitlementWithAddOnsSpecifier> originalBaseEntitlementWithAddOnsSpecifiers,
+ final boolean renameCancelledBundleIfExist,
+ final Iterable<PluginProperty> properties,
+ final CallContext callContext) throws EntitlementApiException {
+ logCreateEntitlementsWithAOs(log, originalBaseEntitlementWithAddOnsSpecifiers);
+
+ final EntitlementContext pluginContext = new DefaultEntitlementContext(operationType,
+ accountId,
+ null,
+ originalBaseEntitlementWithAddOnsSpecifiers,
+ null,
+ properties,
+ callContext);
+
+ final WithEntitlementPlugin<List<UUID>> createBaseEntitlementsWithAddOns = new WithEntitlementPlugin<List<UUID>>() {
+ @Override
+ public List<UUID> doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
+ final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(accountId, callContext);
+
+ final Catalog catalog;
+ try {
+ catalog = catalogInternalApi.getFullCatalog(true, true, contextWithValidAccountRecordId);
+ } catch (final CatalogApiException e) {
+ throw new EntitlementApiException(e);
+ }
+
+ final Map<UUID, Optional<EventsStream>> eventsStreamForBaseSubscriptionPerBundle = new HashMap<UUID, Optional<EventsStream>>();
+ final Map<String, Optional<UUID>> bundleKeyToIdMapping = new HashMap<String, Optional<UUID>>();
+ final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiersAfterPlugins = updatedPluginContext.getBaseEntitlementWithAddOnsSpecifiers();
+ final Collection<SubscriptionBaseWithAddOnsSpecifier> subscriptionBaseWithAddOnsSpecifiers = new LinkedList<SubscriptionBaseWithAddOnsSpecifier>();
+ DateTime upTo = null;
+ for (final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier : baseEntitlementWithAddOnsSpecifiersAfterPlugins) {
+ // Entitlement
+ final DateTime entitlementRequestedDate = dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(),
+ updatedPluginContext.getCreatedDate(),
+ contextWithValidAccountRecordId);
+ upTo = upTo == null || upTo.compareTo(entitlementRequestedDate) < 0 ? entitlementRequestedDate : upTo;
+
+ // Verify if the operation is valid for that bundle
+ preCheckAddEntitlement(baseEntitlementWithAddOnsSpecifier,
+ entitlementRequestedDate,
+ eventsStreamForBaseSubscriptionPerBundle,
+ bundleKeyToIdMapping,
+ catalog,
+ callContext,
+ contextWithValidAccountRecordId);
+
+ final SubscriptionBaseWithAddOnsSpecifier subscriptionBaseWithAddOnsSpecifier = new SubscriptionBaseWithAddOnsSpecifier(baseEntitlementWithAddOnsSpecifier.getBundleId(),
+ baseEntitlementWithAddOnsSpecifier.getExternalKey(),
+ baseEntitlementWithAddOnsSpecifier.getEntitlementSpecifier(),
+ baseEntitlementWithAddOnsSpecifier.getBillingEffectiveDate(),
+ baseEntitlementWithAddOnsSpecifier.isMigrated());
+ subscriptionBaseWithAddOnsSpecifiers.add(subscriptionBaseWithAddOnsSpecifier);
+ }
+
+ // Verify if operation is allowed by looking for is_block_change on Account
+ // Note that to fully check for block_change we should also look for BlockingState at the BUNDLE/SUBSCRIPTION level in case some of the input contain a BP that already exists.
+ checkForAccountBlockingChange(accountId, upTo, contextWithValidAccountRecordId);
+
+ final List<SubscriptionBaseWithAddOns> subscriptionsWithAddOns;
+ try {
+ subscriptionsWithAddOns = subscriptionBaseInternalApi.createBaseSubscriptionsWithAddOns(subscriptionBaseWithAddOnsSpecifiers,
+ renameCancelledBundleIfExist,
+ contextWithValidAccountRecordId);
+ } catch (final SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
+ }
+
+ return createEntitlementEvents(baseEntitlementWithAddOnsSpecifiersAfterPlugins, subscriptionsWithAddOns, updatedPluginContext, contextWithValidAccountRecordId);
+ }
+ };
+ return pluginExecution.executeWithPlugin(createBaseEntitlementsWithAddOns, pluginContext);
+ }
+ private BaseEntitlementWithAddOnsSpecifier getFirstBaseEntitlementWithAddOnsSpecifier(final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiers) throws SubscriptionBaseApiException {
+ if (baseEntitlementWithAddOnsSpecifiers == null) {
+ throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
+ }
+
+ final Iterator<BaseEntitlementWithAddOnsSpecifier> iterator = baseEntitlementWithAddOnsSpecifiers.iterator();
+ if (!iterator.hasNext()) {
+ throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
+ }
+
+ return iterator.next();
+ }
+
+ private void preCheckAddEntitlement(final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier,
+ final DateTime entitlementRequestedDate,
+ final Map<UUID, Optional<EventsStream>> eventsStreamForBaseSubscriptionPerBundle,
+ final Map<String, Optional<UUID>> bundleKeyToIdMapping,
+ final Catalog catalog,
+ final TenantContext callContext,
+ final InternalCallContext contextWithValidAccountRecordId) throws EntitlementApiException {
+ // In the addEntitlement codepath, bundleId is always set. But, technically, an existing bundle could be specified by externalKey in
+ // the createBaseEntitlementsWithAddOns codepath. In that case, we should also check if that bundle is blocked.
+ UUID bundleId = baseEntitlementWithAddOnsSpecifier.getBundleId();
+ if (bundleId == null && baseEntitlementWithAddOnsSpecifier.getExternalKey() != null) {
+ populateBundleKeyToIdMappingCache(baseEntitlementWithAddOnsSpecifier, bundleKeyToIdMapping, catalog, contextWithValidAccountRecordId);
+
+ final Optional<UUID> bundleIdForKey = bundleKeyToIdMapping.get(baseEntitlementWithAddOnsSpecifier.getExternalKey());
+ if (bundleIdForKey.isPresent()) {
+ bundleId = bundleIdForKey.get();
+ }
+ }
+
+ if (bundleId == null) {
+ return;
+ }
+
+ populateEventsStreamForBaseSubscriptionPerBundleCache(bundleId, eventsStreamForBaseSubscriptionPerBundle, callContext, contextWithValidAccountRecordId);
+
+ final Optional<EventsStream> eventsStreamForBaseSubscription = eventsStreamForBaseSubscriptionPerBundle.get(bundleId);
+ if (eventsStreamForBaseSubscription.isPresent()) {
+ preCheckAddEntitlement(bundleId, entitlementRequestedDate, baseEntitlementWithAddOnsSpecifier, eventsStreamForBaseSubscription.get());
+ }
+ }
+
+ private void populateBundleKeyToIdMappingCache(final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier, final Map<String, Optional<UUID>> bundleKeyToIdMapping, final Catalog catalog, final InternalCallContext contextWithValidAccountRecordId) throws EntitlementApiException {
+ if (bundleKeyToIdMapping.get(baseEntitlementWithAddOnsSpecifier.getExternalKey()) == null) {
+ final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.getActiveBundleForKey(baseEntitlementWithAddOnsSpecifier.getExternalKey(), catalog, contextWithValidAccountRecordId);
+ if (bundle != null) {
+ bundleKeyToIdMapping.put(baseEntitlementWithAddOnsSpecifier.getExternalKey(), Optional.<UUID>of(bundle.getId()));
+ } else {
+ bundleKeyToIdMapping.put(baseEntitlementWithAddOnsSpecifier.getExternalKey(), Optional.<UUID>absent());
+ }
+ }
+ }
+
+ private void populateEventsStreamForBaseSubscriptionPerBundleCache(final UUID bundleId, final Map<UUID, Optional<EventsStream>> eventsStreamForBaseSubscriptionPerBundle, final TenantContext callContext, final InternalCallContext contextWithValidAccountRecordId) throws EntitlementApiException {
+ if (eventsStreamForBaseSubscriptionPerBundle.get(bundleId) == null) {
+ final List<SubscriptionBase> subscriptionsByBundle;
+ try {
+ subscriptionsByBundle = subscriptionBaseInternalApi.getSubscriptionsForBundle(bundleId, null, contextWithValidAccountRecordId);
+
+ if (subscriptionsByBundle == null || subscriptionsByBundle.isEmpty()) {
+ throw new EntitlementApiException(ErrorCode.SUB_NO_ACTIVE_SUBSCRIPTIONS, bundleId);
+ }
+ } catch (final SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
+ }
+
+ final boolean isStandalone = Iterables.any(subscriptionsByBundle,
+ new Predicate<SubscriptionBase>() {
+ @Override
+ public boolean apply(final SubscriptionBase input) {
+ return ProductCategory.STANDALONE.equals(input.getCategory());
+ }
+ });
+
+ if (!isStandalone) {
+ final EventsStream eventsStreamForBaseSubscription = eventsStreamBuilder.buildForBaseSubscription(bundleId, callContext);
+ eventsStreamForBaseSubscriptionPerBundle.put(bundleId, Optional.<EventsStream>of(eventsStreamForBaseSubscription));
+ } else {
+ eventsStreamForBaseSubscriptionPerBundle.put(bundleId, Optional.<EventsStream>absent());
+ }
+ }
+ }
+
+ private void preCheckAddEntitlement(final UUID bundleId, final DateTime entitlementRequestedDate, final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier, final EventsStream eventsStreamForBaseSubscription) throws EntitlementApiException {
+ if (eventsStreamForBaseSubscription.isEntitlementCancelled() ||
+ (eventsStreamForBaseSubscription.isEntitlementPending() &&
+ (baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate() == null ||
+ baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate().compareTo(eventsStreamForBaseSubscription.getEntitlementEffectiveStartDate()) < 0))) {
+ throw new EntitlementApiException(ErrorCode.SUB_GET_NO_SUCH_BASE_SUBSCRIPTION, bundleId);
+ }
+
+ // Check the base entitlement state is not blocked
+ if (eventsStreamForBaseSubscription.isBlockChange(entitlementRequestedDate)) {
+ throw new EntitlementApiException(new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, BlockingChecker.ACTION_CHANGE, BlockingChecker.TYPE_SUBSCRIPTION, eventsStreamForBaseSubscription.getEntitlementId().toString()));
+ } else if (eventsStreamForBaseSubscription.isBlockEntitlement(entitlementRequestedDate)) {
+ throw new EntitlementApiException(new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION, BlockingChecker.ACTION_ENTITLEMENT, BlockingChecker.TYPE_SUBSCRIPTION, eventsStreamForBaseSubscription.getEntitlementId().toString()));
+ }
+ }
+
+ private void checkForAccountBlockingChange(final UUID accountId, @Nullable final DateTime upTo, final InternalCallContext context) throws EntitlementApiException {
try {
final BlockingAggregator blockingAggregator = checker.getBlockedStatus(accountId, BlockingStateType.ACCOUNT, upTo, context);
if (blockingAggregator.isBlockChange()) {
@@ -517,4 +559,31 @@ public class DefaultEntitlementApi extends DefaultEntitlementApiBase implements
throw new EntitlementApiException(e);
}
}
+
+ private List<UUID> createEntitlementEvents(final Iterable<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifiersAfterPlugins,
+ final List<SubscriptionBaseWithAddOns> subscriptionsWithAddOns,
+ final CallContext updatedPluginContext,
+ final InternalCallContext contextWithValidAccountRecordId) throws EntitlementApiException {
+ final List<UUID> createdSubscriptionIds = new LinkedList<UUID>();
+ final Map<BlockingState, UUID> blockingStateMap = new HashMap<BlockingState, UUID>();
+ int i = 0;
+ for (final Iterator<BaseEntitlementWithAddOnsSpecifier> it = baseEntitlementWithAddOnsSpecifiersAfterPlugins.iterator(); it.hasNext(); i++) {
+ final BaseEntitlementWithAddOnsSpecifier baseEntitlementWithAddOnsSpecifier = it.next();
+ for (final SubscriptionBase subscriptionBase : subscriptionsWithAddOns.get(i).getSubscriptionBaseList()) {
+ final BlockingState blockingState = new DefaultBlockingState(subscriptionBase.getId(),
+ BlockingStateType.SUBSCRIPTION,
+ DefaultEntitlementApi.ENT_STATE_START,
+ EntitlementService.ENTITLEMENT_SERVICE_NAME,
+ false,
+ false,
+ false,
+ dateHelper.fromLocalDateAndReferenceTime(baseEntitlementWithAddOnsSpecifier.getEntitlementEffectiveDate(), updatedPluginContext.getCreatedDate(), contextWithValidAccountRecordId));
+ blockingStateMap.put(blockingState, subscriptionsWithAddOns.get(i).getBundle().getId());
+
+ createdSubscriptionIds.add(subscriptionBase.getId());
+ }
+ }
+ entitlementUtils.setBlockingStateAndPostBlockingTransitionEvent(blockingStateMap, contextWithValidAccountRecordId);
+ return createdSubscriptionIds;
+ }
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementSpecifier.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementSpecifier.java
index dbf7876..0fc92b0 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementSpecifier.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlementSpecifier.java
@@ -42,4 +42,16 @@ public class DefaultEntitlementSpecifier implements EntitlementSpecifier {
return overrides;
}
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("DefaultEntitlementSpecifier{");
+ sb.append("planName=").append(planPhaseSpecifier.getPlanName());
+ sb.append(", productName=").append(planPhaseSpecifier.getProductName());
+ sb.append(", billingPeriod=").append(planPhaseSpecifier.getBillingPeriod());
+ sb.append(", phaseType=").append(planPhaseSpecifier.getPhaseType());
+ sb.append(", priceListName=").append(planPhaseSpecifier.getPriceListName());
+ sb.append(", overrides=").append(overrides);
+ sb.append('}');
+ return sb.toString();
+ }
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
index 5ff2339..9c5a89b 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
@@ -226,6 +226,13 @@ public class DefaultEventsStream implements EventsStream {
}
@Override
+ public boolean isBlockEntitlement(final DateTime effectiveDate) {
+ Preconditions.checkState(effectiveDate != null);
+ final BlockingAggregator aggregator = getBlockingAggregator(effectiveDate);
+ return aggregator.isBlockEntitlement();
+ }
+
+ @Override
public int getDefaultBillCycleDayLocal() {
return defaultBillCycleDayLocal;
}
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 10a2b1b..4734da9 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
@@ -295,8 +295,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
} else if (subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey() != null &&
baseOrFirstStandalonePlanSpecifier == null) { // Skip the expensive checks if we are about to create the bundle (validation will be done in SubscriptionDao#createSubscriptionBundle)
- final List<SubscriptionBaseBundle> existingBundles = dao.getSubscriptionBundlesForKey(subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey(), context);
- final SubscriptionBaseBundle tmp = getActiveBundleForKeyNotException(existingBundles, dao, clock, catalog, context);
+ final SubscriptionBaseBundle tmp = getActiveBundleForKey(subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey(), catalog, context);
if (tmp == null) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BP, subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey());
} else if (!tmp.getAccountId().equals(accountId)) {
@@ -474,7 +473,9 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
return dao.getNonAOSubscriptionIdsForKey(bundleKey, context);
}
- public static SubscriptionBaseBundle getActiveBundleForKeyNotException(final Iterable<SubscriptionBaseBundle> existingBundles, final SubscriptionDao dao, final Clock clock, final Catalog catalog, final InternalTenantContext context) {
+ @Override
+ public SubscriptionBaseBundle getActiveBundleForKey(final String bundleKey, final Catalog catalog, final InternalTenantContext context) {
+ final List<SubscriptionBaseBundle> existingBundles = dao.getSubscriptionBundlesForKey(bundleKey, context);
for (final SubscriptionBaseBundle cur : existingBundles) {
final List<SubscriptionBase> subscriptions;
try {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java
index 2d73917..a075ae9 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -35,6 +35,7 @@ import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.subscription.api.SubscriptionApiBase;
import org.killbill.billing.subscription.api.SubscriptionBaseApiService;
+import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
import org.killbill.billing.subscription.api.svcs.DefaultSubscriptionInternalApi;
import org.killbill.billing.subscription.api.timeline.BundleBaseTimeline;
import org.killbill.billing.subscription.api.timeline.SubscriptionBaseRepairException;
@@ -66,14 +67,16 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
private final CatalogInternalApi catalogInternalApi;
private final SubscriptionBaseTimelineApi timelineApi;
+ private final SubscriptionBaseInternalApi subscriptionBaseInternalApi;
private final InternalCallContextFactory internalCallContextFactory;
@Inject
public DefaultSubscriptionBaseTransferApi(final Clock clock, final SubscriptionDao dao, final SubscriptionBaseTimelineApi timelineApi, final CatalogInternalApi catalogInternalApi,
- final SubscriptionBaseApiService apiService, final InternalCallContextFactory internalCallContextFactory) {
+ final SubscriptionBaseInternalApi subscriptionBaseInternalApi, final SubscriptionBaseApiService apiService, final InternalCallContextFactory internalCallContextFactory) {
super(dao, apiService, clock);
this.catalogInternalApi = catalogInternalApi;
this.timelineApi = timelineApi;
+ this.subscriptionBaseInternalApi = subscriptionBaseInternalApi;
this.internalCallContextFactory = internalCallContextFactory;
}
@@ -192,13 +195,11 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
throw new SubscriptionBaseTransferApiException(ErrorCode.SUB_TRANSFER_INVALID_EFF_DATE, effectiveTransferDate);
}
- final SubscriptionBaseBundle bundleForAccountAndKey = dao.getSubscriptionBundlesForAccountAndKey(sourceAccountId, bundleKey, fromInternalCallContext);
- final SubscriptionBaseBundle bundle = DefaultSubscriptionInternalApi.getActiveBundleForKeyNotException(ImmutableList.of(bundleForAccountAndKey), dao, clock, catalog, fromInternalCallContext);
+ final SubscriptionBaseBundle bundle = subscriptionBaseInternalApi.getActiveBundleForKey(bundleKey, catalog, fromInternalCallContext);
if (bundle == null) {
throw new SubscriptionBaseTransferApiException(ErrorCode.SUB_CREATE_NO_BUNDLE, bundleKey);
}
-
// Get the bundle timeline for the old account
final BundleBaseTimeline bundleBaseTimeline = timelineApi.getBundleTimeline(bundle, context);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java
index 95ce7e2..9c3818b 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java
@@ -73,7 +73,7 @@ public class TestDefaultSubscriptionTransferApi extends SubscriptionTestSuiteNoD
final CatalogInternalApi catalogInternalApiWithMockCatalogService = new DefaultCatalogInternalApi(catalogService, internalCallContextFactory);
final SubscriptionBaseApiService apiService = Mockito.mock(SubscriptionBaseApiService.class);
final SubscriptionBaseTimelineApi timelineApi = Mockito.mock(SubscriptionBaseTimelineApi.class);
- transferApi = new DefaultSubscriptionBaseTransferApi(clock, dao, timelineApi, catalogInternalApiWithMockCatalogService, apiService, internalCallContextFactory);
+ transferApi = new DefaultSubscriptionBaseTransferApi(clock, dao, timelineApi, catalogInternalApiWithMockCatalogService, subscriptionInternalApi, apiService, internalCallContextFactory);
// Overrride catalog with our Mock CatalogService
this.catalog = catalogInternalApiWithMockCatalogService.getFullCatalog(true, true, internalCallContext);
}