killbill-aplcache
Changes
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java 2(+1 -1)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java 3(+3 -0)
subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java 8(+4 -4)
subscription/src/main/java/org/killbill/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java 7(+4 -3)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/BundleSqlDao.java 22(+14 -8)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 43(+22 -21)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java 15(+13 -2)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java 3(+1 -2)
subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg 15(+15 -0)
subscription/src/main/resources/org/killbill/billing/subscription/migration/V20170920200757__bundle_external_key.sql 2(+2 -0)
subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestTransfer.java 2(+1 -1)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java 2(+2 -0)
subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java 6(+3 -3)
Details
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
index 89507b5..b9f6047 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
@@ -510,40 +510,7 @@ public class TestIntegration extends TestIntegrationBase {
checkNoMoreInvoiceToGenerate(account);
}
- @Test(groups = "slow")
- public void testCreateMultipleBPWithSameExternalKey() throws Exception {
- final DateTime initialDate = new DateTime(2012, 4, 25, 0, 13, 42, 0, testTimeZone);
- clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
-
- final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(25));
- assertNotNull(account);
-
- final String productName = "Shotgun";
- final BillingPeriod term = BillingPeriod.MONTHLY;
-
- final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
- final SubscriptionBundle initialBundle = subscriptionApi.getActiveSubscriptionBundleForExternalKey("bundleKey", callContext);
-
- busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.NULL_INVOICE);
- baseEntitlement.cancelEntitlementWithPolicy(EntitlementActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
- assertListenerStatus();
-
- final String newProductName = "Pistol";
- final DefaultEntitlement newBaseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", newProductName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
-
- final SubscriptionBundle newBundle = subscriptionApi.getActiveSubscriptionBundleForExternalKey("bundleKey", callContext);
-
- assertNotEquals(initialBundle.getId(), newBundle.getId());
- assertEquals(initialBundle.getAccountId(), newBundle.getAccountId());
- assertEquals(initialBundle.getExternalKey(), newBundle.getExternalKey());
-
- final Entitlement refreshedBseEntitlement = entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
-
- assertEquals(refreshedBseEntitlement.getState(), EntitlementState.CANCELLED);
- assertEquals(newBaseEntitlement.getState(), EntitlementState.ACTIVE);
-
- checkNoMoreInvoiceToGenerate(account);
- }
+
@Test(groups = "slow")
public void testWithPauseResume() throws Exception {
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java
index a8fd0c1..324e747 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java
@@ -918,7 +918,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
clock.setTime(date);
assertListenerStatus();
- createBaseEntitlementAndCheckForCompletion(childAccount.getId(), "bundleKey1", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ createBaseEntitlementAndCheckForCompletion(childAccount.getId(), "bundleKey2", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
// Move through time and verify new parent Invoice.
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.INVOICE);
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
index f8b59ef..a9416a7 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
@@ -268,7 +268,7 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
logUpdateExternalKey(log, bundleId, newExternalKey);
- final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContextWithoutAccountRecordId(callContext);
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(bundleId, ObjectType.BUNDLE, callContext);
final SubscriptionBaseBundle bundle;
final ImmutableAccountData account;
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java
index c73dcdb..9c719bf 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionApi.java
@@ -134,6 +134,9 @@ public class TestDefaultSubscriptionApi extends EntitlementTestSuiteWithEmbedded
entitlement.cancelEntitlementWithDate(new LocalDate(clock.getUTCNow(), account.getTimeZone()), true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
+ // Update the bundle externalKey associated with previously cancelled subscription, so we can reuse it
+ subscriptionApi.updateExternalKey(bundles.get(0).getId(), "kbtest-123:" + externalKey, callContext);
+
try {
subscriptionApi.getActiveSubscriptionBundleForExternalKey(externalKey, callContext);
Assert.fail("Expected getActiveSubscriptionBundleForExternalKey to fail after cancellation");
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 a3a15cc..140d1a1 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
@@ -46,7 +46,6 @@ import org.killbill.billing.catalog.api.PlanPhase;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
-import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.BaseEntitlementWithAddOnsSpecifier;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
@@ -399,7 +398,8 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
@Override
public List<SubscriptionBaseBundle> getBundlesForAccountAndKey(final UUID accountId, final String bundleKey, final InternalTenantContext context) throws SubscriptionBaseApiException {
- return dao.getSubscriptionBundlesForAccountAndKey(accountId, bundleKey, context);
+ final SubscriptionBaseBundle subscriptionBundlesForAccountAndKey = dao.getSubscriptionBundlesForAccountAndKey(accountId, bundleKey, context);
+ return subscriptionBundlesForAccountAndKey != null ? ImmutableList.of(subscriptionBundlesForAccountAndKey) : ImmutableList.<SubscriptionBaseBundle>of();
}
@Override
@@ -424,7 +424,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
new Function<SubscriptionBundleModelDao, SubscriptionBaseBundle>() {
@Override
public SubscriptionBaseBundle apply(final SubscriptionBundleModelDao bundleModelDao) {
- return SubscriptionBundleModelDao.toSubscriptionbundle(bundleModelDao);
+ return SubscriptionBundleModelDao.toSubscriptionBundle(bundleModelDao);
}
}
);
@@ -442,7 +442,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
new Function<SubscriptionBundleModelDao, SubscriptionBaseBundle>() {
@Override
public SubscriptionBaseBundle apply(final SubscriptionBundleModelDao bundleModelDao) {
- return SubscriptionBundleModelDao.toSubscriptionbundle(bundleModelDao);
+ return SubscriptionBundleModelDao.toSubscriptionBundle(bundleModelDao);
}
}
);
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 61f34e9..f93883e 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
@@ -193,12 +193,13 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
throw new SubscriptionBaseTransferApiException(ErrorCode.SUB_TRANSFER_INVALID_EFF_DATE, effectiveTransferDate);
}
- final List<SubscriptionBaseBundle> bundlesForAccountAndKey = dao.getSubscriptionBundlesForAccountAndKey(sourceAccountId, bundleKey, fromInternalCallContext);
- final SubscriptionBaseBundle bundle = DefaultSubscriptionInternalApi.getActiveBundleForKeyNotException(bundlesForAccountAndKey, dao, clock, catalog, fromInternalCallContext);
+ final SubscriptionBaseBundle bundleForAccountAndKey = dao.getSubscriptionBundlesForAccountAndKey(sourceAccountId, bundleKey, fromInternalCallContext);
+ final SubscriptionBaseBundle bundle = DefaultSubscriptionInternalApi.getActiveBundleForKeyNotException(ImmutableList.of(bundleForAccountAndKey), dao, clock, 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);
@@ -260,7 +261,7 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
final SubscriptionTransferData curData = new SubscriptionTransferData(defaultSubscriptionBase, events, null);
subscriptionTransferDataList.add(curData);
}
- BundleTransferData bundleTransferData = new BundleTransferData(subscriptionBundleData, subscriptionTransferDataList);
+ final BundleTransferData bundleTransferData = new BundleTransferData(subscriptionBundleData, subscriptionTransferDataList);
// Atomically cancelWithRequestedDate all subscription on old account and create new bundle, subscriptions, events for new account
dao.transfer(sourceAccountId, destAccountId, bundleTransferData, transferCancelDataList, catalog, fromInternalCallContext, toInternalCallContext);
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/BundleSqlDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/BundleSqlDao.java
index bc8f9f2..79a0d28 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/BundleSqlDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/BundleSqlDao.java
@@ -19,11 +19,6 @@ package org.killbill.billing.subscription.engine.dao;
import java.util.Date;
import java.util.List;
-import org.skife.jdbi.v2.sqlobject.Bind;
-import org.killbill.commons.jdbi.binder.SmartBindBean;
-import org.skife.jdbi.v2.sqlobject.SqlQuery;
-import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
@@ -31,7 +26,12 @@ import org.killbill.billing.subscription.engine.dao.model.SubscriptionBundleMode
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.Define;
@KillBillSqlDaoStringTemplate
public interface BundleSqlDao extends EntitySqlDao<SubscriptionBundleModelDao, SubscriptionBaseBundle> {
@@ -44,14 +44,20 @@ public interface BundleSqlDao extends EntitySqlDao<SubscriptionBundleModelDao, S
@SqlUpdate
@Audited(ChangeType.UPDATE)
+ public void renameBundleExternalKey(@Bind("externalKey") String externalKey,
+ @Define("prefix") final String prefix,
+ @SmartBindBean final InternalCallContext context);
+
+ @SqlUpdate
+ @Audited(ChangeType.UPDATE)
public void updateBundleLastSysTime(@Bind("id") String id,
@Bind("lastSysUpdateDate") Date lastSysUpdate,
@SmartBindBean final InternalCallContext context);
@SqlQuery
- public List<SubscriptionBundleModelDao> getBundlesFromAccountAndKey(@Bind("accountId") String accountId,
- @Bind("externalKey") String externalKey,
- @SmartBindBean final InternalTenantContext context);
+ public SubscriptionBundleModelDao getBundlesFromAccountAndKey(@Bind("accountId") String accountId,
+ @Bind("externalKey") String externalKey,
+ @SmartBindBean final InternalTenantContext context);
@SqlQuery
public List<SubscriptionBundleModelDao> getBundleFromAccount(@Bind("accountId") String accountId,
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 ac018a3..67bdfb1 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
@@ -35,12 +35,10 @@ import javax.inject.Inject;
import org.joda.time.DateTime;
import org.killbill.billing.ErrorCode;
-import org.killbill.billing.ObjectType;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
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.Plan;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
@@ -140,17 +138,12 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public List<SubscriptionBaseBundle> getSubscriptionBundlesForAccountAndKey(final UUID accountId, final String bundleKey, final InternalTenantContext context) {
- return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<SubscriptionBaseBundle>>() {
+ public SubscriptionBaseBundle getSubscriptionBundlesForAccountAndKey(final UUID accountId, final String bundleKey, final InternalTenantContext context) {
+ return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<SubscriptionBaseBundle>() {
@Override
- public List<SubscriptionBaseBundle> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- final List<SubscriptionBundleModelDao> models = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getBundlesFromAccountAndKey(accountId.toString(), bundleKey, context);
- return new ArrayList<SubscriptionBaseBundle>(Collections2.transform(models, new Function<SubscriptionBundleModelDao, SubscriptionBaseBundle>() {
- @Override
- public SubscriptionBaseBundle apply(@Nullable final SubscriptionBundleModelDao input) {
- return SubscriptionBundleModelDao.toSubscriptionbundle(input);
- }
- }));
+ public SubscriptionBaseBundle inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ final SubscriptionBundleModelDao input = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getBundlesFromAccountAndKey(accountId.toString(), bundleKey, context);
+ return SubscriptionBundleModelDao.toSubscriptionBundle(input);
}
});
}
@@ -165,7 +158,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
return new ArrayList<SubscriptionBaseBundle>(Collections2.transform(models, new Function<SubscriptionBundleModelDao, SubscriptionBaseBundle>() {
@Override
public SubscriptionBaseBundle apply(@Nullable final SubscriptionBundleModelDao input) {
- return SubscriptionBundleModelDao.toSubscriptionbundle(input);
+ return SubscriptionBundleModelDao.toSubscriptionBundle(input);
}
}));
}
@@ -178,13 +171,14 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
@Override
public SubscriptionBaseBundle inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final SubscriptionBundleModelDao model = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getById(bundleId.toString(), context);
- return SubscriptionBundleModelDao.toSubscriptionbundle(model);
+ return SubscriptionBundleModelDao.toSubscriptionBundle(model);
}
});
}
@Override
public List<SubscriptionBaseBundle> getSubscriptionBundlesForKey(final String bundleKey, final InternalTenantContext context) {
+
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<SubscriptionBaseBundle>>() {
@Override
public List<SubscriptionBaseBundle> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
@@ -192,7 +186,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
return new ArrayList<SubscriptionBaseBundle>(Collections2.transform(models, new Function<SubscriptionBundleModelDao, SubscriptionBaseBundle>() {
@Override
public SubscriptionBaseBundle apply(@Nullable final SubscriptionBundleModelDao input) {
- return SubscriptionBundleModelDao.toSubscriptionbundle(input);
+ return SubscriptionBundleModelDao.toSubscriptionBundle(input);
}
}));
}
@@ -273,7 +267,9 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
final SubscriptionBundleModelDao existingBundleForAccount = Iterables.tryFind(existingBundles, new Predicate<SubscriptionBundleModelDao>() {
@Override
public boolean apply(final SubscriptionBundleModelDao input) {
- return input.getAccountId().equals(bundle.getAccountId());
+ return input.getAccountId().equals(bundle.getAccountId()) &&
+ // We look for strict equality ignoring tsf items with keys 'kbtsf-343453:'
+ bundle.getExternalKey().equals(input.getExternalKey());
}
}).orNull();
@@ -287,7 +283,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
return input.getBundleId().equals(existingBundleForAccount.getId());
}
})) {
- return SubscriptionBundleModelDao.toSubscriptionbundle(existingBundleForAccount);
+ return SubscriptionBundleModelDao.toSubscriptionBundle(existingBundleForAccount);
}
}
return null;
@@ -330,7 +326,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
final BundleSqlDao bundleSqlDao = entitySqlDaoWrapperFactory.become(BundleSqlDao.class);
final SubscriptionBundleModelDao result = createAndRefresh(bundleSqlDao, model, context);
- return SubscriptionBundleModelDao.toSubscriptionbundle(result);
+ return SubscriptionBundleModelDao.toSubscriptionBundle(result);
}
});
}
@@ -1043,13 +1039,18 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
// Cancel the subscriptions for the old bundle
for (final TransferCancelData cancel : transferCancelData) {
cancelSubscriptionFromTransaction(cancel.getSubscription(), cancel.getCancelEvent(), entitySqlDaoWrapperFactory, catalog, fromContext, 0);
}
+
+ // Rename externalKey from source bundle
+ final BundleSqlDao bundleSqlDao = entitySqlDaoWrapperFactory.become(BundleSqlDao.class);
+ bundleSqlDao.renameBundleExternalKey(bundleTransferData.getData().getExternalKey(), "tsf", fromContext);
+
+ final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
transferBundleDataFromTransaction(bundleTransferData, transactional, entitySqlDaoWrapperFactory, toContext);
return null;
}
@@ -1181,8 +1182,8 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
final DefaultSubscriptionBaseBundle bundleData = bundleTransferData.getData();
- final List<SubscriptionBundleModelDao> existingBundleModels = transBundleDao.getBundlesFromAccountAndKey(bundleData.getAccountId().toString(), bundleData.getExternalKey(), context);
- if (!existingBundleModels.isEmpty()) {
+ final SubscriptionBundleModelDao existingBundleForAccount = transBundleDao.getBundlesFromAccountAndKey(bundleData.getAccountId().toString(), bundleData.getExternalKey(), context);
+ if (existingBundleForAccount != null) {
log.warn("Bundle already exists for accountId='{}', bundleExternalKey='{}'", bundleData.getAccountId(), bundleData.getExternalKey());
return;
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java
index 2c9489f..e5938dc 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java
@@ -17,6 +17,8 @@
package org.killbill.billing.subscription.engine.dao.model;
import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.joda.time.DateTime;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
@@ -29,6 +31,11 @@ import com.google.common.base.MoreObjects;
public class SubscriptionBundleModelDao extends EntityModelDaoBase implements EntityModelDao<SubscriptionBaseBundle> {
+ // Any key that starts with kb<some_prefix>-<some_number>:<something> is interpreted as a <something> key that got renamed for internal purpose
+ // KB core currently only use the prefix 'tsf' for renaming such keys during bundle transfer
+ //
+ private static Pattern BUNDLE_KEY_PATTERN = Pattern.compile("kb(?:\\w+)-\\d+:(.*)");
+
private String externalKey;
private UUID accountId;
private DateTime lastSysUpdateDate;
@@ -81,11 +88,15 @@ public class SubscriptionBundleModelDao extends EntityModelDaoBase implements En
this.originalCreatedDate = originalCreatedDate;
}
- public static SubscriptionBaseBundle toSubscriptionbundle(final SubscriptionBundleModelDao src) {
+ public static SubscriptionBaseBundle toSubscriptionBundle(final SubscriptionBundleModelDao src) {
if (src == null) {
return null;
}
- return new DefaultSubscriptionBaseBundle(src.getId(), src.getExternalKey(), src.getAccountId(), src.getLastSysUpdateDate(), src.getOriginalCreatedDate(), src.getCreatedDate(), src.getUpdatedDate());
+
+ // Fix externalKey to remove internal prefix used for tsf
+ final Matcher m = BUNDLE_KEY_PATTERN.matcher(src.getExternalKey());
+ final String externalKey = m.matches() ? m.group(1) : src.getExternalKey();
+ return new DefaultSubscriptionBaseBundle(src.getId(), externalKey, src.getAccountId(), src.getLastSysUpdateDate(), src.getOriginalCreatedDate(), src.getCreatedDate(), src.getUpdatedDate());
}
@Override
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 ac499bf..accffd1 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
@@ -31,7 +31,6 @@ import org.killbill.billing.subscription.api.transfer.BundleTransferData;
import org.killbill.billing.subscription.api.transfer.TransferCancelData;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
-import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseWithAddOns;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
import org.killbill.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
@@ -50,7 +49,7 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
public Iterable<UUID> getNonAOSubscriptionIdsForKey(String bundleKey, InternalTenantContext context);
- public List<SubscriptionBaseBundle> getSubscriptionBundlesForAccountAndKey(UUID accountId, String bundleKey, InternalTenantContext context);
+ public SubscriptionBaseBundle getSubscriptionBundlesForAccountAndKey(UUID accountId, String bundleKey, InternalTenantContext context);
public SubscriptionBaseBundle getSubscriptionBundleFromId(UUID bundleId, InternalTenantContext context);
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/ddl.sql b/subscription/src/main/resources/org/killbill/billing/subscription/ddl.sql
index ac3b021..0807094 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/ddl.sql
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/ddl.sql
@@ -65,7 +65,7 @@ CREATE TABLE bundles (
PRIMARY KEY(record_id)
) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
CREATE UNIQUE INDEX bundles_id ON bundles(id);
-CREATE INDEX bundles_key ON bundles(external_key);
+CREATE UNIQUE INDEX bundles_external_key ON bundles(external_key, tenant_record_id);
CREATE INDEX bundles_account ON bundles(account_id);
CREATE INDEX bundles_tenant_account_record_id ON bundles(tenant_record_id, account_record_id);
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
index 9a5337c..874e2e7 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
@@ -47,11 +47,26 @@ where id = :id
;
>>
+
+renameBundleExternalKey(prefix) ::= <<
+update bundles b
+join (select
+ record_id
+ , external_key
+ from
+ bundles
+ where external_key = :externalKey <AND_CHECK_TENANT("")>) t
+on b.record_id = t.record_id
+set b.external_key = concat('kb', '<prefix>', '-', t.record_id, ':', t.external_key)
+;
+>>
+
getBundlesForKey() ::= <<
select <allTableFields("")>
from bundles
where
external_key = :externalKey
+or external_key like concat('kb%-%:', :externalKey)
<AND_CHECK_TENANT("")>
<defaultOrderBy("")>
;
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/migration/V20170920200757__bundle_external_key.sql b/subscription/src/main/resources/org/killbill/billing/subscription/migration/V20170920200757__bundle_external_key.sql
new file mode 100644
index 0000000..c175f86
--- /dev/null
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/migration/V20170920200757__bundle_external_key.sql
@@ -0,0 +1,2 @@
+drop index bundles_key on bundles;
+create unique index bundles_external_key on bundles(external_key, tenant_record_id);
\ No newline at end of file
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestTransfer.java b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestTransfer.java
index 3a0b350..ea1b9fa 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestTransfer.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestTransfer.java
@@ -64,7 +64,7 @@ public class TestTransfer extends SubscriptionTestSuiteWithEmbeddedDB {
final Account account2 = createAccount(accountData2);
finalNewAccountId = account2.getId();
- // internal context will be configured for newAccountId
+ // internal context will be configured for accountId
final AccountData accountData = subscriptionTestInitializer.initAccountData();
final Account account = createAccount(accountData);
newAccountId = account.getId();
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
index 36167c8..065ae2e 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
@@ -76,6 +76,7 @@ public class TestUserApiCreate extends SubscriptionTestSuiteWithEmbeddedDB {
subscription.cancelWithDate(clock.getUTCNow(), callContext);
assertListenerStatus();
+ /*
final SubscriptionBaseBundle newBundle = subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), DefaultSubscriptionTestInitializer.DEFAULT_BUNDLE_KEY, internalCallContext);
assertNotNull(newBundle);
assertEquals(newBundle.getOriginalCreatedDate().compareTo(bundle.getCreatedDate()), 0);
@@ -92,6 +93,7 @@ public class TestUserApiCreate extends SubscriptionTestSuiteWithEmbeddedDB {
assertListenerStatus();
assertNotNull(newSubscription);
+ */
}
@Test(groups = "slow")
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 cbcb9a5..0e94c99 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
@@ -163,14 +163,14 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
@Override
- public List<SubscriptionBaseBundle> getSubscriptionBundlesForAccountAndKey(final UUID accountId, final String bundleKey, final InternalTenantContext context) {
+ public SubscriptionBaseBundle getSubscriptionBundlesForAccountAndKey(final UUID accountId, final String bundleKey, final InternalTenantContext context) {
final List<SubscriptionBaseBundle> results = new ArrayList<SubscriptionBaseBundle>();
for (final SubscriptionBaseBundle cur : bundles) {
if (cur.getExternalKey().equals(bundleKey) && cur.getAccountId().equals(accountId)) {
- results.add(cur);
+ return cur;
}
}
- return results;
+ return null;
}
@Override
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionDao.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionDao.java
new file mode 100644
index 0000000..cfe002b
--- /dev/null
+++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionDao.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 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
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.subscription.engine.dao;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountData;
+import org.killbill.billing.catalog.DefaultPriceListSet;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.subscription.SubscriptionTestSuiteWithEmbeddedDB;
+import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
+import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle;
+import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
+import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
+import org.killbill.billing.subscription.api.user.SubscriptionBuilder;
+import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
+import org.killbill.billing.subscription.events.user.ApiEventBuilder;
+import org.killbill.billing.subscription.events.user.ApiEventCreate;
+import org.killbill.billing.util.UUIDs;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestSubscriptionDao extends SubscriptionTestSuiteWithEmbeddedDB {
+
+ protected UUID accountId;
+
+ @Override
+ @BeforeMethod(groups = "slow")
+ public void beforeMethod() throws Exception {
+ // Note: this will cleanup all tables
+ super.beforeMethod();
+
+ // internal context will be configured for accountId
+ final AccountData accountData = subscriptionTestInitializer.initAccountData();
+ final Account account = createAccount(accountData);
+ accountId = account.getId();
+ }
+
+ @Override // to ignore events
+ @AfterMethod(groups = "slow")
+ public void afterMethod() throws Exception {
+ subscriptionTestInitializer.stopTestFramework(testListener, busService, subscriptionBaseService);
+ }
+
+ @Test(groups = "slow")
+ public void testBundleExternalKeyReused() throws Exception {
+
+ final String externalKey = "12345";
+ final DateTime startDate = clock.getUTCNow();
+ final DateTime createdDate = startDate.plusSeconds(10);
+
+ final DefaultSubscriptionBaseBundle bundleDef = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
+ final SubscriptionBaseBundle bundle = dao.createSubscriptionBundle(bundleDef, catalog, internalCallContext);
+
+ final List<SubscriptionBaseBundle> result = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
+ assertEquals(result.size(), 1);
+ assertEquals(result.get(0).getExternalKey(), bundle.getExternalKey());
+
+ // Operation succeeds but nothing new got created because bundle is empty
+ dao.createSubscriptionBundle(bundleDef, catalog, internalCallContext);
+ final List<SubscriptionBaseBundle> result2 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
+ assertEquals(result2.size(), 1);
+
+ // Create a subscription and this time operation should fail
+ final SubscriptionBuilder builder = new SubscriptionBuilder()
+ .setId(UUIDs.randomUUID())
+ .setBundleId(bundle.getId())
+ .setBundleExternalKey(bundle.getExternalKey())
+ .setCategory(ProductCategory.BASE)
+ .setBundleStartDate(startDate)
+ .setAlignStartDate(startDate)
+ .setMigrated(false);
+
+ final ApiEventBuilder createBuilder = new ApiEventBuilder()
+ .setSubscriptionId(builder.getId())
+ .setEventPlan("shotgun-monthly")
+ .setEventPlanPhase("shotgun-monthly-trial")
+ .setEventPriceList(DefaultPriceListSet.DEFAULT_PRICELIST_NAME)
+ .setEffectiveDate(startDate)
+ .setFromDisk(true);
+ final SubscriptionBaseEvent creationEvent = new ApiEventCreate(createBuilder);
+
+ final DefaultSubscriptionBase subscription = new DefaultSubscriptionBase(builder);
+ dao.createSubscription(subscription, ImmutableList.of(creationEvent), catalog, internalCallContext);
+
+ // Operation Should now fail
+ try {
+ dao.createSubscriptionBundle(bundleDef, catalog, internalCallContext);
+ Assert.fail("Should fail to create new subscription bundle with existing key");
+ } catch (SubscriptionBaseApiException e) {
+ assertEquals(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS.getCode(), e.getCode());
+ }
+ }
+
+ @Test(groups = "slow")
+ public void testBundleExternalKeyTransferred() throws Exception {
+
+ final String externalKey = "2534125sdfsd";
+ final DateTime startDate = clock.getUTCNow();
+ final DateTime createdDate = startDate.plusSeconds(10);
+
+ final DefaultSubscriptionBaseBundle bundleDef = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
+ final SubscriptionBaseBundle bundle = dao.createSubscriptionBundle(bundleDef, catalog, internalCallContext);
+
+ final List<SubscriptionBaseBundle> result = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
+ assertEquals(result.size(), 1);
+ assertEquals(result.get(0).getExternalKey(), bundle.getExternalKey());
+
+ // Update key to 'internal KB value 'kbtsf-12345:'
+ dao.updateBundleExternalKey(bundle.getId(), "kbtsf-12345:" + bundle.getExternalKey(), internalCallContext);
+ final List<SubscriptionBaseBundle> result2 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
+ assertEquals(result2.size(), 1);
+
+ // Create new bundle with original key, verify all results show original key, stripping down internal prefix
+ final DefaultSubscriptionBaseBundle bundleDef2 = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
+ final SubscriptionBaseBundle bundle2 = dao.createSubscriptionBundle(bundleDef2, catalog, internalCallContext);
+ final List<SubscriptionBaseBundle> result3 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
+ assertEquals(result3.size(), 2);
+ assertEquals(result3.get(0).getExternalKey(), bundle2.getExternalKey());
+ assertEquals(result3.get(1).getExternalKey(), bundle2.getExternalKey());
+
+ // This time we call the lower SqlDao to rename the bundle automatically and verify we still get same # results,
+ // with original key
+ dbi.onDemand(BundleSqlDao.class).renameBundleExternalKey(externalKey, "foo", internalCallContext);
+ final List<SubscriptionBaseBundle> result4 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
+ assertEquals(result4.size(), 2);
+ assertEquals(result4.get(0).getExternalKey(), bundle2.getExternalKey());
+ assertEquals(result4.get(1).getExternalKey(), bundle2.getExternalKey());
+
+ // Create bundle one more time
+ final DefaultSubscriptionBaseBundle bundleDef3 = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
+ final SubscriptionBaseBundle bundle3 = dao.createSubscriptionBundle(bundleDef3, catalog, internalCallContext);
+ final List<SubscriptionBaseBundle> result5 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
+ assertEquals(result5.size(), 3);
+ assertEquals(result5.get(0).getExternalKey(), bundle2.getExternalKey());
+ assertEquals(result5.get(1).getExternalKey(), bundle2.getExternalKey());
+ assertEquals(result5.get(2).getExternalKey(), bundle2.getExternalKey());
+
+
+ }
+}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionModelDao.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionModelDao.java
new file mode 100644
index 0000000..2f6bf69
--- /dev/null
+++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionModelDao.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 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
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.subscription.engine.dao;
+
+import org.killbill.billing.subscription.SubscriptionTestSuiteNoDB;
+import org.killbill.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestSubscriptionModelDao extends SubscriptionTestSuiteNoDB {
+
+ @Test(groups = "fast")
+ public void testBundleExternalKeyPattern1() throws Exception {
+ final SubscriptionBundleModelDao b = new SubscriptionBundleModelDao();
+ b.setExternalKey("1235");
+
+ assertEquals(SubscriptionBundleModelDao.toSubscriptionBundle(b).getExternalKey(), "1235");
+ }
+
+
+ @Test(groups = "fast")
+ public void testBundleExternalKeyPattern2() throws Exception {
+ final SubscriptionBundleModelDao b = new SubscriptionBundleModelDao();
+ b.setExternalKey("kbtsf-343453:1235");
+ assertEquals(SubscriptionBundleModelDao.toSubscriptionBundle(b).getExternalKey(), "1235");
+ }
+
+ @Test(groups = "fast")
+ public void testBundleExternalKeyPattern3() throws Exception {
+ final SubscriptionBundleModelDao b = new SubscriptionBundleModelDao();
+ b.setExternalKey("kbXXXX-343453:1235");
+ assertEquals(SubscriptionBundleModelDao.toSubscriptionBundle(b).getExternalKey(), "1235");
+ }
+
+}