killbill-memoizeit
Changes
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java 9(+3 -6)
subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java 51(+28 -23)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java 33(+24 -9)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 13(+13 -0)
Details
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
index 0f3c7e1..965a5fb 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
@@ -53,8 +53,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
return super.getConfigSource("/beatrixCatalogRetireElements.properties");
}
- // Flaky, see https://github.com/killbill/killbill/issues/860
- @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
+ @Test(groups = "slow")
public void testRetirePlan() throws Exception {
// Catalog v1 starts in 2011-01-01
// Catalog v2 starts in 2015-12-01
@@ -111,8 +110,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
}
- // Flaky, see https://github.com/killbill/killbill/issues/860
- @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
+ @Test(groups = "slow")
public void testRetirePlanWithUncancel() throws Exception {
// Catalog v1 starts in 2011-01-01
// Catalog v2 starts in 2015-12-01
@@ -176,8 +174,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
}
}
- // Flaky, see https://github.com/killbill/killbill/issues/860
- @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
+ @Test(groups = "slow")
public void testRetirePlanAfterChange() throws Exception {
// Catalog v1 starts in 2011-01-01
// Catalog v3 starts in 2016-01-01
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
index 4e8ecce..33fe5ab 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
@@ -308,6 +308,11 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
public void beforeClass() throws Exception {
final InvoiceConfig defaultInvoiceConfig = new ConfigurationObjectFactory(skifeConfigSource).build(InvoiceConfig.class);
invoiceConfig = new ConfigurableInvoiceConfig(defaultInvoiceConfig);
+ // The default value is 50, i.e. wait 50 x 100ms = 5s to get the lock. This isn't always enough and can lead to random tests failures
+ // in the listener status: after moving the clock, if there are two notifications triggering an invoice run, we typically expect
+ // both an INVOICE and a NULL_INVOICE event. If the invoice generation takes too long, the NULL_INVOICE event is never generated
+ // (LockFailedException): the test itself doesn't fail (the correct invoice is generated), but assertListenerStatus() would.
+ invoiceConfig.setMaxGlobalLockRetries(150);
final Injector g = Guice.createInjector(Stage.PRODUCTION, new BeatrixIntegrationModule(configSource, invoiceConfig));
g.injectMembers(this);
}
@@ -973,10 +978,12 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
private final InvoiceConfig defaultInvoiceConfig;
+ private int maxGlobalLockRetries;
private boolean isInvoicingSystemEnabled;
public ConfigurableInvoiceConfig(final InvoiceConfig defaultInvoiceConfig) {
this.defaultInvoiceConfig = defaultInvoiceConfig;
+ maxGlobalLockRetries = defaultInvoiceConfig.getMaxGlobalLockRetries();
isInvoicingSystemEnabled = defaultInvoiceConfig.isInvoicingSystemEnabled();
}
@@ -1032,7 +1039,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
@Override
public int getMaxGlobalLockRetries() {
- return defaultInvoiceConfig.getMaxGlobalLockRetries();
+ return maxGlobalLockRetries;
}
@Override
@@ -1080,6 +1087,10 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
return getItemResultBehaviorMode();
}
+ public void setMaxGlobalLockRetries(final int maxGlobalLockRetries) {
+ this.maxGlobalLockRetries = maxGlobalLockRetries;
+ }
+
public void setInvoicingSystemEnabled(final boolean invoicingSystemEnabled) {
isInvoicingSystemEnabled = invoicingSystemEnabled;
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
index a6ed103..dc76f96 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -643,10 +643,6 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
}
}
- protected void verifyNumberOfElements(int actual, int expected, String errorMessage) {
- Preconditions.checkArgument(actual == expected, errorMessage);
- }
-
protected void logDeprecationParameterWarningIfNeeded(@Nullable final String deprecatedParam, final String... replacementParams) {
if (deprecatedParam != null) {
log.warn(String.format("Parameter %s is being deprecated: Instead use parameters %s", deprecatedParam, Joiner.on(",").join(replacementParams)));
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
index 9bacaf2..7fdf952 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
@@ -28,6 +28,7 @@ import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
+import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -107,6 +108,7 @@ import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
@@ -329,8 +331,8 @@ public class SubscriptionResource extends JaxRsResourceBase {
final Account account = accountUserApi.getAccountById(entitlementsWithAddOns.get(0).getBaseEntitlementAndAddOns().get(0).getAccountId(), callContext);
- final List<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
- for (BulkSubscriptionsBundleJson bulkBaseEntitlementWithAddOns : entitlementsWithAddOns) {
+ final Collection<BaseEntitlementWithAddOnsSpecifier> baseEntitlementWithAddOnsSpecifierList = new ArrayList<BaseEntitlementWithAddOnsSpecifier>();
+ for (final BulkSubscriptionsBundleJson bulkBaseEntitlementWithAddOns : entitlementsWithAddOns) {
final Iterable<SubscriptionJson> baseEntitlements = Iterables.filter(
bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns(), new Predicate<SubscriptionJson>() {
@Override
@@ -338,26 +340,44 @@ public class SubscriptionResource extends JaxRsResourceBase {
return ProductCategory.BASE.toString().equalsIgnoreCase(subscription.getProductCategory());
}
});
- Preconditions.checkArgument(Iterables.size(baseEntitlements) > 0, "SubscriptionJson Base Entitlement needs to be provided");
- verifyNumberOfElements(Iterables.size(baseEntitlements), 1, "Only one BASE product is allowed per bundle.");
- final SubscriptionJson baseEntitlement = baseEntitlements.iterator().next();
- final Iterable<SubscriptionJson> addonEntitlements = Iterables.filter(
- bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns(), new Predicate<SubscriptionJson>() {
- @Override
- public boolean apply(final SubscriptionJson subscription) {
- return ProductCategory.ADD_ON.toString().equalsIgnoreCase(subscription.getProductCategory());
- }
- }
- );
-
- final List<EntitlementSpecifier> entitlementSpecifierList = buildEntitlementSpecifierList(baseEntitlement, addonEntitlements, account.getCurrency());
+ final List<EntitlementSpecifier> entitlementSpecifierList;
+ final String bundleExternalKey;
+ if (baseEntitlements.iterator().hasNext()) {
+ Preconditions.checkArgument(Iterables.size(baseEntitlements) == 1, "Only one BASE product is allowed per bundle.");
+
+ final SubscriptionJson baseEntitlement = baseEntitlements.iterator().next();
+ final Iterable<SubscriptionJson> addonEntitlements = Iterables.filter(bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns(),
+ new Predicate<SubscriptionJson>() {
+ @Override
+ public boolean apply(final SubscriptionJson subscription) {
+ return ProductCategory.ADD_ON.toString().equalsIgnoreCase(subscription.getProductCategory());
+ }
+ });
+
+ entitlementSpecifierList = buildEntitlementSpecifierList(baseEntitlement, addonEntitlements, account.getCurrency());
+ bundleExternalKey = baseEntitlement.getExternalKey();
+ } else {
+ final Collection<SubscriptionJson> standaloneEntitlements = Collections2.filter(bulkBaseEntitlementWithAddOns.getBaseEntitlementAndAddOns(),
+ new Predicate<SubscriptionJson>() {
+ @Override
+ public boolean apply(final SubscriptionJson subscription) {
+ return ProductCategory.STANDALONE.toString().equalsIgnoreCase(subscription.getProductCategory());
+ }
+ });
+ entitlementSpecifierList = buildEntitlementSpecifierList(standaloneEntitlements, account.getCurrency());
+ bundleExternalKey = standaloneEntitlements.isEmpty() ? null : standaloneEntitlements.iterator().next().getExternalKey();
+ }
// create the baseEntitlementSpecifierWithAddOns
final LocalDate resolvedEntitlementDate = requestedDate != null ? toLocalDate(requestedDate) : toLocalDate(entitlementDate);
final LocalDate resolvedBillingDate = requestedDate != null ? toLocalDate(requestedDate) : toLocalDate(billingDate);
- final BaseEntitlementWithAddOnsSpecifier baseEntitlementSpecifierWithAddOns = buildBaseEntitlementWithAddOnsSpecifier(entitlementSpecifierList, resolvedEntitlementDate, resolvedBillingDate, null, baseEntitlement, isMigrated);
+ final BaseEntitlementWithAddOnsSpecifier baseEntitlementSpecifierWithAddOns = buildBaseEntitlementWithAddOnsSpecifier(entitlementSpecifierList,
+ resolvedEntitlementDate,
+ resolvedBillingDate,
+ bundleExternalKey,
+ isMigrated);
baseEntitlementWithAddOnsSpecifierList.add(baseEntitlementSpecifierWithAddOns);
}
@@ -398,35 +418,15 @@ public class SubscriptionResource extends JaxRsResourceBase {
return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, callContext);
}
- private List<EntitlementSpecifier> buildEntitlementSpecifierList(final SubscriptionJson baseEntitlement, final Iterable<SubscriptionJson> addonEntitlements, final Currency currency) {
+ private List<EntitlementSpecifier> buildEntitlementSpecifierList(final SubscriptionJson baseEntitlement,
+ final Iterable<SubscriptionJson> addonEntitlements,
+ final Currency currency) {
final List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
//
- // BASE is fully specified we can add it
+ // BASE or STANDALONE is fully specified, we can add it
//
- if (baseEntitlement.getPlanName() != null ||
- (baseEntitlement.getProductName() != null &&
- baseEntitlement.getProductCategory() != null &&
- baseEntitlement.getBillingPeriod() != null &&
- baseEntitlement.getPriceList() != null)) {
- final PlanPhaseSpecifier planPhaseSpecifier = baseEntitlement.getPlanName() != null ?
- new PlanPhaseSpecifier(baseEntitlement.getPlanName(), null) :
- new PlanPhaseSpecifier(baseEntitlement.getProductName(),
- BillingPeriod.valueOf(baseEntitlement.getBillingPeriod()), baseEntitlement.getPriceList(), null);
- final List<PlanPhasePriceOverride> overrides = PhasePriceOverrideJson.toPlanPhasePriceOverrides(baseEntitlement.getPriceOverrides(), planPhaseSpecifier, currency);
-
- EntitlementSpecifier specifier = new EntitlementSpecifier() {
- @Override
- public PlanPhaseSpecifier getPlanPhaseSpecifier() {
- return planPhaseSpecifier;
- }
- @Override
- public List<PlanPhasePriceOverride> getOverrides() {
- return overrides;
- }
- };
- entitlementSpecifierList.add(specifier);
- }
+ buildEntitlementSpecifier(baseEntitlement, currency, entitlementSpecifierList);
for (final SubscriptionJson entitlement : addonEntitlements) {
// verifications
@@ -438,49 +438,96 @@ public class SubscriptionResource extends JaxRsResourceBase {
entitlement.getPriceList(), "SubscriptionJson priceList needs to be set for each element");
}
// create the entitlementSpecifier
- final PlanPhaseSpecifier planPhaseSpecifier = entitlement.getPlanName() != null ?
- new PlanPhaseSpecifier(entitlement.getPlanName(), null) :
- new PlanPhaseSpecifier(entitlement.getProductName(),
- BillingPeriod.valueOf(entitlement.getBillingPeriod()), entitlement.getPriceList(), null);
- final List<PlanPhasePriceOverride> overrides = PhasePriceOverrideJson.toPlanPhasePriceOverrides(entitlement.getPriceOverrides(), planPhaseSpecifier, currency);
-
- EntitlementSpecifier specifier = new EntitlementSpecifier() {
- @Override
- public PlanPhaseSpecifier getPlanPhaseSpecifier() {
- return planPhaseSpecifier;
- }
- @Override
- public List<PlanPhasePriceOverride> getOverrides() {
- return overrides;
- }
- };
- entitlementSpecifierList.add(specifier);
+ buildEntitlementSpecifier(entitlement, currency, entitlementSpecifierList);
+ }
+ return entitlementSpecifierList;
+ }
+
+ private List<EntitlementSpecifier> buildEntitlementSpecifierList(final Iterable<SubscriptionJson> standaloneEntitlements,
+ final Currency currency) {
+ final List<EntitlementSpecifier> entitlementSpecifierList = new ArrayList<EntitlementSpecifier>();
+
+ for (final SubscriptionJson standaloneEntitlement : standaloneEntitlements) {
+ // verifications
+ verifyNonNullOrEmpty(standaloneEntitlement, "SubscriptionJson body should be specified for each element");
+ if (standaloneEntitlement.getPlanName() == null) {
+ verifyNonNullOrEmpty(standaloneEntitlement.getProductName(), "SubscriptionJson productName needs to be set for each element",
+ standaloneEntitlement.getProductCategory(), "SubscriptionJson productCategory needs to be set for each element",
+ standaloneEntitlement.getBillingPeriod(), "SubscriptionJson billingPeriod needs to be set for each element",
+ standaloneEntitlement.getPriceList(), "SubscriptionJson priceList needs to be set for each element");
+ }
+ // create the entitlementSpecifier
+ buildEntitlementSpecifier(standaloneEntitlement, currency, entitlementSpecifierList);
}
return entitlementSpecifierList;
}
- private BaseEntitlementWithAddOnsSpecifier buildBaseEntitlementWithAddOnsSpecifier(final List<EntitlementSpecifier> entitlementSpecifierList, final LocalDate resolvedEntitlementDate, final LocalDate resolvedBillingDate, final UUID bundleId, final SubscriptionJson baseEntitlement, final @QueryParam(QUERY_MIGRATED) @DefaultValue("false") Boolean isMigrated) {
+ private void buildEntitlementSpecifier(final SubscriptionJson subscriptionJson,
+ final Currency currency,
+ final Collection<EntitlementSpecifier> entitlementSpecifierList) {
+ if (subscriptionJson.getPlanName() == null &&
+ (subscriptionJson.getProductName() == null ||
+ subscriptionJson.getProductCategory() == null ||
+ subscriptionJson.getBillingPeriod() == null ||
+ subscriptionJson.getPriceList() == null)) {
+ return;
+ }
+
+ final PlanPhaseSpecifier planPhaseSpecifier = subscriptionJson.getPlanName() != null ?
+ new PlanPhaseSpecifier(subscriptionJson.getPlanName(), null) :
+ new PlanPhaseSpecifier(subscriptionJson.getProductName(),
+ BillingPeriod.valueOf(subscriptionJson.getBillingPeriod()),
+ subscriptionJson.getPriceList(),
+ null);
+ final List<PlanPhasePriceOverride> overrides = PhasePriceOverrideJson.toPlanPhasePriceOverrides(subscriptionJson.getPriceOverrides(),
+ planPhaseSpecifier,
+ currency);
+
+ final EntitlementSpecifier specifier = new EntitlementSpecifier() {
+ @Override
+ public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+ return planPhaseSpecifier;
+ }
+
+ @Override
+ public List<PlanPhasePriceOverride> getOverrides() {
+ return overrides;
+ }
+ };
+ entitlementSpecifierList.add(specifier);
+ }
+
+ private BaseEntitlementWithAddOnsSpecifier buildBaseEntitlementWithAddOnsSpecifier(final Iterable<EntitlementSpecifier> entitlementSpecifierList,
+ final LocalDate resolvedEntitlementDate,
+ final LocalDate resolvedBillingDate,
+ @Nullable final String bundleExternalKey,
+ final Boolean isMigrated) {
return new BaseEntitlementWithAddOnsSpecifier() {
@Override
public UUID getBundleId() {
- return bundleId;
+ return null;
}
+
@Override
public String getExternalKey() {
- return baseEntitlement.getExternalKey();
+ return bundleExternalKey;
}
+
@Override
public Iterable<EntitlementSpecifier> getEntitlementSpecifier() {
return entitlementSpecifierList;
}
+
@Override
public LocalDate getEntitlementEffectiveDate() {
return resolvedEntitlementDate;
}
+
@Override
public LocalDate getBillingEffectiveDate() {
return resolvedBillingDate;
}
+
@Override
public boolean isMigrated() {
return isMigrated;
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 e6bdb7b..9794ac6 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
@@ -148,7 +148,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
private List<SubscriptionSpecifier> verifyAndBuildSubscriptionSpecifiers(final SubscriptionBaseBundle bundle,
- @Nullable final EntitlementSpecifier baseOrStandalonePlanSpecifier,
+ @Nullable final EntitlementSpecifier baseOrFirstStandalonePlanSpecifier,
final Iterable<EntitlementSpecifier> entitlements,
final boolean isMigrated,
final InternalCallContext context,
@@ -188,7 +188,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
final DateTime bundleStartDate;
- if (baseOrStandalonePlanSpecifier != null) {
+ if (baseOrFirstStandalonePlanSpecifier != null) {
bundleStartDate = effectiveDate;
} else {
final SubscriptionBase baseSubscription = dao.getBaseSubscription(bundle.getId(), catalog, context);
@@ -219,16 +219,18 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
final SubscriptionBaseWithAddOnsSpecifier subscriptionBaseWithAddOnsSpecifier,
final DateTime effectiveDate,
final Collection<EntitlementSpecifier> outputEntitlementSpecifier) throws SubscriptionBaseApiException {
- EntitlementSpecifier baseOrStandalonePlanSpecifier = null;
+ EntitlementSpecifier basePlanSpecifier = null;
final Collection<EntitlementSpecifier> addOnSpecifiers = new ArrayList<EntitlementSpecifier>();
+ final List<EntitlementSpecifier> standaloneSpecifiers = new ArrayList<EntitlementSpecifier>();
try {
for (final EntitlementSpecifier cur : subscriptionBaseWithAddOnsSpecifier.getEntitlementSpecifiers()) {
final boolean isBase = isBaseSpecifier(catalog, effectiveDate, cur);
final boolean isStandalone = isStandaloneSpecifier(catalog, effectiveDate, cur);
- final boolean isBaseOrStandaloneSpecifier = isBase || isStandalone;
- if (isBaseOrStandaloneSpecifier) {
- if (baseOrStandalonePlanSpecifier == null) {
- baseOrStandalonePlanSpecifier = cur;
+ if (isStandalone) {
+ standaloneSpecifiers.add(cur);
+ } else if (isBase) {
+ if (basePlanSpecifier == null) {
+ basePlanSpecifier = cur;
} else {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
}
@@ -240,11 +242,21 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
throw new SubscriptionBaseApiException(e);
}
- if (baseOrStandalonePlanSpecifier != null) {
- outputEntitlementSpecifier.add(baseOrStandalonePlanSpecifier);
+ if (basePlanSpecifier != null) {
+ outputEntitlementSpecifier.add(basePlanSpecifier);
}
outputEntitlementSpecifier.addAll(addOnSpecifiers);
- return baseOrStandalonePlanSpecifier;
+
+ if (!outputEntitlementSpecifier.isEmpty() && !standaloneSpecifiers.isEmpty()) {
+ throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
+ }
+
+ if (standaloneSpecifiers.isEmpty()) {
+ return basePlanSpecifier;
+ } else {
+ outputEntitlementSpecifier.addAll(standaloneSpecifiers);
+ return standaloneSpecifiers.get(0);
+ }
}
private boolean isBaseSpecifier(final Catalog catalog, final DateTime effectiveDate, final EntitlementSpecifier cur) throws CatalogApiException {
@@ -271,7 +283,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
final Collection<EntitlementSpecifier> reorderedSpecifiers = new ArrayList<EntitlementSpecifier>();
// Note: billingRequestedDateRaw might not be accurate here (add-on with a too early date passed)?
- final EntitlementSpecifier baseOrStandalonePlanSpecifier = sanityAndReorderBPOrStandaloneSpecFirst(catalog, subscriptionBaseWithAddOnsSpecifier, billingRequestedDateRaw, reorderedSpecifiers);
+ final EntitlementSpecifier baseOrFirstStandalonePlanSpecifier = sanityAndReorderBPOrStandaloneSpecFirst(catalog, subscriptionBaseWithAddOnsSpecifier, billingRequestedDateRaw, reorderedSpecifiers);
DateTime billingRequestedDate = billingRequestedDateRaw;
SubscriptionBaseBundle bundle = null;
@@ -282,7 +294,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
}
} else if (subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey() != null &&
- baseOrStandalonePlanSpecifier == null) { // Skip the expensive checks if we are about to create the bundle (validation will be done in SubscriptionDao#createSubscriptionBundle)
+ 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);
if (tmp == null) {
@@ -303,12 +315,12 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
}
- if (bundle == null && baseOrStandalonePlanSpecifier != null) {
+ if (bundle == null && baseOrFirstStandalonePlanSpecifier != null) {
bundle = createBundleForAccount(accountId,
subscriptionBaseWithAddOnsSpecifier.getBundleExternalKey(),
renameCancelledBundleIfExist,
context);
- } else if (bundle != null && baseSubscription != null && baseOrStandalonePlanSpecifier != null && isBaseSpecifier(catalog, billingRequestedDateRaw, baseOrStandalonePlanSpecifier)) {
+ } else if (bundle != null && baseSubscription != null && baseOrFirstStandalonePlanSpecifier != null && isBaseSpecifier(catalog, billingRequestedDateRaw, baseOrFirstStandalonePlanSpecifier)) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_BP_EXISTS, bundle.getExternalKey());
} else if (bundle == null) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_INVALID_ENTITLEMENT_SPECIFIER);
@@ -317,7 +329,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
final SubscriptionAndAddOnsSpecifier subscriptionAndAddOnsSpecifier = new SubscriptionAndAddOnsSpecifier(bundle,
billingRequestedDate,
verifyAndBuildSubscriptionSpecifiers(bundle,
- baseOrStandalonePlanSpecifier,
+ baseOrFirstStandalonePlanSpecifier,
reorderedSpecifiers,
subscriptionBaseWithAddOnsSpecifier.isMigrated(),
context,
@@ -850,14 +862,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
private CacheLoaderArgument createBundleIdFromSubscriptionIdCacheLoaderArgument(final InternalTenantContext internalTenantContext) {
final BundleIdFromSubscriptionIdCacheLoader.LoaderCallback loaderCallback = new BundleIdFromSubscriptionIdCacheLoader.LoaderCallback() {
public UUID loadBundleId(final UUID subscriptionId, final InternalTenantContext internalTenantContext) {
- final SubscriptionBase subscriptionBase;
- try {
- subscriptionBase = getSubscriptionFromId(subscriptionId, internalTenantContext);
- } catch (final SubscriptionBaseApiException e) {
- log.warn("Unable to retrieve subscription for id='{}'", subscriptionId);
- return null;
- }
- return subscriptionBase.getBundleId();
+ return dao.getBundleIdFromSubscriptionId(subscriptionId, internalTenantContext);
}
};
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 27b31f3..29cedce 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
@@ -144,10 +144,15 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
try {
final DefaultSubscriptionBase subscriptionBase = new DefaultSubscriptionBase(subscription.getBuilder(), this, clock);
final InternalCallContext internalCallContext = createCallContextFromBundleId(subscriptionBase.getBundleId(), context);
- final List<SubscriptionBaseEvent> events = getEventsOnCreation(subscriptionBase.getId(), subscriptionBase.getAlignStartDate(),
- subscriptionBase.getBundleStartDate(), subscription.getPlan(),
- subscription.getInitialPhase(), subscription.getRealPriceList(),
- subscription.getEffectiveDate(), fullCatalog, internalCallContext);
+ final List<SubscriptionBaseEvent> events = getEventsOnCreation(subscriptionBase.getId(),
+ subscriptionBase.getAlignStartDate(),
+ subscriptionBase.getBundleStartDate(),
+ subscription.getPlan(),
+ subscription.getInitialPhase(),
+ subscription.getRealPriceList(),
+ subscription.getEffectiveDate(),
+ fullCatalog,
+ internalCallContext);
eventsMap.put(subscriptionBase.getId(), events);
subscriptionBaseList.add(subscriptionBase);
} catch (final CatalogApiException e) {
@@ -434,13 +439,23 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
}
@Override
- public List<SubscriptionBaseEvent> getEventsOnCreation(final UUID subscriptionId, final DateTime alignStartDate, final DateTime bundleStartDate,
- final Plan plan, final PhaseType initialPhase,
- final String realPriceList, final DateTime effectiveDate,
+ public List<SubscriptionBaseEvent> getEventsOnCreation(final UUID subscriptionId,
+ final DateTime alignStartDate,
+ final DateTime bundleStartDate,
+ final Plan plan,
+ final PhaseType initialPhase,
+ final String realPriceList,
+ final DateTime effectiveDate,
final Catalog fullCatalog,
final InternalTenantContext internalTenantContext) throws CatalogApiException, SubscriptionBaseApiException {
- final TimedPhase[] curAndNextPhases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate, bundleStartDate, plan, initialPhase,
- realPriceList, effectiveDate, fullCatalog, internalTenantContext);
+ final TimedPhase[] curAndNextPhases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate,
+ bundleStartDate,
+ plan,
+ initialPhase,
+ realPriceList,
+ effectiveDate,
+ fullCatalog,
+ internalTenantContext);
final ApiEventBuilder createBuilder = new ApiEventBuilder()
.setSubscriptionId(subscriptionId)
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index 1c11dae..065e0c8 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -301,6 +301,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
final SubscriptionBaseBundle unusedBundle = findExistingUnusedBundleForExternalKeyAndAccount(existingBundles, entitySqlDaoWrapperFactory);
if (unusedBundle != null) {
+ log.info("Found unused bundle for externalKey='{}': bundleId='{}'", bundle.getExternalKey(), unusedBundle.getId());
return unusedBundle;
}
final BundleSqlDao bundleSqlDao = entitySqlDaoWrapperFactory.become(BundleSqlDao.class);
@@ -319,6 +320,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
if (s.getState() != EntitlementState.CANCELLED) {
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS, bundle.getExternalKey());
} else if (renameCancelledBundleIfExist) {
+ log.info("Renaming bundles with externalKey='{}', prefix='cncl'", bundle.getExternalKey());
// Note that if bundle belongs to a different account, context is not the context for this target account,
// but the underlying sql operation does not use the account info
bundleSqlDao.renameBundleExternalKey(bundle.getExternalKey(), "cncl", context);
@@ -361,6 +363,17 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
+ public UUID getBundleIdFromSubscriptionId(final UUID subscriptionId, final InternalTenantContext context) {
+ return transactionalSqlDao.execute(true, new EntitySqlDaoTransactionWrapper<UUID>() {
+ @Override
+ public UUID inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ final SubscriptionModelDao subscriptionModel = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class).getById(subscriptionId.toString(), context);
+ return subscriptionModel.getBundleId();
+ }
+ });
+ }
+
+ @Override
public List<SubscriptionBase> getSubscriptions(final UUID bundleId, final List<SubscriptionBaseEvent> dryRunEvents, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
return buildBundleSubscriptions(getSubscriptionFromBundleId(bundleId, context), null, dryRunEvents, catalog, context);
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
index 3e6dd6c..242676b 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
@@ -59,6 +59,8 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
public SubscriptionBase getSubscriptionFromId(UUID subscriptionId, final Catalog catalog, InternalTenantContext context) throws CatalogApiException;
+ public UUID getBundleIdFromSubscriptionId(UUID subscriptionId, InternalTenantContext context);
+
// SubscriptionBase retrieval
public SubscriptionBase getBaseSubscription(UUID bundleId, final Catalog catalog, InternalTenantContext context) throws CatalogApiException;
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
index 19dbef1..8b5a74e 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
@@ -198,6 +198,11 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
@Override
+ public UUID getBundleIdFromSubscriptionId(final UUID subscriptionId, final InternalTenantContext context) {
+ return getSubscriptionFromId(subscriptionId, null, context).getBundleId();
+ }
+
+ @Override
public List<SubscriptionBaseEvent> createSubscriptionsWithAddOns(final List<SubscriptionBaseWithAddOns> subscriptions,
final Map<UUID, List<SubscriptionBaseEvent>> initialEventsMap,
final Catalog catalog,