killbill-aplcache

subscription: fix bundle renaming auditing Signed-off-by:

2/12/2019 1:44:33 PM

Details

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 587d390..8d5de31 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
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2019 Groupon, Inc
+ * Copyright 2014-2019 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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:
  *
@@ -16,6 +18,7 @@
 
 package org.killbill.billing.subscription.engine.dao;
 
+import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 
@@ -32,6 +35,7 @@ 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;
+import org.skife.jdbi.v2.unstable.BindIn;
 
 @KillBillSqlDaoStringTemplate
 public interface BundleSqlDao extends EntitySqlDao<SubscriptionBundleModelDao, SubscriptionBaseBundle> {
@@ -44,7 +48,7 @@ public interface BundleSqlDao extends EntitySqlDao<SubscriptionBundleModelDao, S
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
-    public void renameBundleExternalKey(@Bind("externalKey") String externalKey,
+    public void renameBundleExternalKey(@BindIn("ids") final Collection<String> ids,
                                         @Define("prefix") final String prefix,
                                         @SmartBindBean final InternalCallContext context);
 
@@ -64,6 +68,10 @@ public interface BundleSqlDao extends EntitySqlDao<SubscriptionBundleModelDao, S
                                                                  @SmartBindBean final InternalTenantContext context);
 
     @SqlQuery
+    public List<SubscriptionBundleModelDao> getBundlesForKey(@Bind("externalKey") String externalKey,
+                                                             @SmartBindBean final InternalTenantContext context);
+
+    @SqlQuery
     public List<SubscriptionBundleModelDao> getBundlesForLikeKey(@Bind("externalKey") String externalKey,
                                                                  @SmartBindBean final InternalTenantContext context);
 }
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 31d1c4d..fe31f97 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
@@ -323,9 +323,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
                                 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);
+                                renameBundleExternalKey(bundleSqlDao, bundle.getExternalKey(), "cncl", context);
                             } /* else {
                                 Code will throw SQLIntegrityConstraintViolationException because of unique constraint on externalKey; might be worth having an ErrorCode just for that
                             } */
@@ -346,6 +344,22 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
         });
     }
 
+    // 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
+    private void renameBundleExternalKey(final BundleSqlDao bundleSqlDao, final String externalKey, final String prefix, final InternalCallContext context) {
+        final List<SubscriptionBundleModelDao> bundleModelDaos = bundleSqlDao.getBundlesForKey(externalKey, context);
+        if (!bundleModelDaos.isEmpty()) {
+            final Collection<String> bundleIdsToRename = Collections2.<SubscriptionBundleModelDao, String>transform(bundleModelDaos,
+                                                                                                                    new Function<SubscriptionBundleModelDao, String>() {
+                                                                                                                        @Override
+                                                                                                                        public String apply(final SubscriptionBundleModelDao input) {
+                                                                                                                            return input.getId().toString();
+                                                                                                                        }
+                                                                                                                    });
+            bundleSqlDao.renameBundleExternalKey(bundleIdsToRename, prefix, context);
+        }
+    }
+
     @Override
     public SubscriptionBase getBaseSubscription(final UUID bundleId, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
         return getBaseSubscription(bundleId, true, catalog, context);
@@ -1012,7 +1026,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
 
                 // Rename externalKey from source bundle
                 final BundleSqlDao bundleSqlDao = entitySqlDaoWrapperFactory.become(BundleSqlDao.class);
-                bundleSqlDao.renameBundleExternalKey(bundleTransferData.getData().getExternalKey(), "tsf", fromContext);
+                renameBundleExternalKey(bundleSqlDao, bundleTransferData.getData().getExternalKey(), "tsf", fromContext);
 
                 final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
                 transferBundleDataFromTransaction(bundleTransferData, transactional, entitySqlDaoWrapperFactory, toContext);
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 ecfeb0f..886d776 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
@@ -51,8 +51,17 @@ where id = :id
 renameBundleExternalKey(prefix)  ::= <<
 update bundles
 set external_key = concat('kb', '<prefix>', '-', record_id, ':', external_key)
+where <idField("")> in (<ids>)
+<AND_CHECK_TENANT("")>
+;
+>>
+
+getBundlesForKey() ::= <<
+select <allTableFields("")>
+from bundles
 where external_key = :externalKey
 <AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
 ;
 >>
 
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
index 7ca3747..4e0eef5 100644
--- 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
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2018 Groupon, Inc
- * Copyright 2014-2018 The Billing Project, LLC
+ * Copyright 2014-2019 Groupon, Inc
+ * Copyright 2014-2019 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
@@ -22,6 +22,7 @@ import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.killbill.billing.ErrorCode;
+import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountData;
 import org.killbill.billing.api.TestApiListener.NextEvent;
@@ -40,7 +41,14 @@ 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.killbill.billing.util.api.AuditLevel;
+import org.killbill.billing.util.audit.AuditLog;
+import org.killbill.billing.util.audit.ChangeType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.entity.dao.DBRouterUntyped;
+import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
+import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
+import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
 import org.killbill.commons.profiling.Profiling.WithProfilingCallback;
 import org.mockito.Mockito;
 import org.skife.jdbi.v2.IDBI;
@@ -56,6 +64,7 @@ import static org.testng.Assert.assertEquals;
 
 public class TestSubscriptionDao extends SubscriptionTestSuiteWithEmbeddedDB {
 
+    private EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
     protected UUID accountId;
 
     @Override
@@ -72,6 +81,8 @@ public class TestSubscriptionDao extends SubscriptionTestSuiteWithEmbeddedDB {
         final AccountData accountData = subscriptionTestInitializer.initAccountData(clock);
         final Account account = createAccount(accountData);
         accountId = account.getId();
+
+        transactionalSqlDao = new EntitySqlDaoTransactionalJdbiWrapper(dbi, roDbi, clock, new CacheControllerDispatcher(), nonEntityDao, internalCallContextFactory);
     }
 
     @Override // to ignore events
@@ -154,26 +165,55 @@ public class TestSubscriptionDao extends SubscriptionTestSuiteWithEmbeddedDB {
         assertEquals(result.size(), 1);
         assertEquals(result.get(0).getExternalKey(), bundle.getExternalKey());
 
+        final List<AuditLog> auditLogsBeforeRenaming = auditUserApi.getAuditLogs(bundle.getId(), ObjectType.BUNDLE, AuditLevel.FULL, callContext);
+        assertEquals(auditLogsBeforeRenaming.size(), 1);
+        assertEquals(auditLogsBeforeRenaming.get(0).getChangeType(), ChangeType.INSERT);
+
         // 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);
 
+        final List<AuditLog> auditLogsAfterRenaming = auditUserApi.getAuditLogs(bundle.getId(), ObjectType.BUNDLE, AuditLevel.FULL, callContext);
+        assertEquals(auditLogsAfterRenaming.size(), 2);
+        assertEquals(auditLogsAfterRenaming.get(0).getChangeType(), ChangeType.INSERT);
+        assertEquals(auditLogsAfterRenaming.get(1).getChangeType(), ChangeType.UPDATE);
+
         // 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, true, internalCallContext);
         final List<SubscriptionBaseBundle> result3 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
         assertEquals(result3.size(), 2);
+        assertEquals(result3.get(0).getId(), bundle.getId());
         assertEquals(result3.get(0).getExternalKey(), bundle2.getExternalKey());
+        assertEquals(result3.get(1).getId(), bundle2.getId());
         assertEquals(result3.get(1).getExternalKey(), bundle2.getExternalKey());
 
+        final List<AuditLog> auditLogs2BeforeRenaming = auditUserApi.getAuditLogs(bundle2.getId(), ObjectType.BUNDLE, AuditLevel.FULL, callContext);
+        assertEquals(auditLogs2BeforeRenaming.size(), 1);
+        assertEquals(auditLogs2BeforeRenaming.get(0).getChangeType(), ChangeType.INSERT);
+
         // 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);
+        transactionalSqlDao.execute(false,
+                                    new EntitySqlDaoTransactionWrapper<Void>() {
+                                        @Override
+                                        public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+                                            entitySqlDaoWrapperFactory.become(BundleSqlDao.class).renameBundleExternalKey(ImmutableList.<String>of(bundle2.getId().toString()), "foo", internalCallContext);
+                                            return null;
+                                        }
+                                    });
         final List<SubscriptionBaseBundle> result4 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
         assertEquals(result4.size(), 2);
         assertEquals(result4.get(0).getExternalKey(), bundle2.getExternalKey());
+        assertEquals(result4.get(0).getId(), bundle.getId());
         assertEquals(result4.get(1).getExternalKey(), bundle2.getExternalKey());
+        assertEquals(result4.get(1).getId(), bundle2.getId());
+
+        final List<AuditLog> auditLogs2AfterRenaming = auditUserApi.getAuditLogs(bundle2.getId(), ObjectType.BUNDLE, AuditLevel.FULL, callContext);
+        assertEquals(auditLogs2AfterRenaming.size(), 2);
+        assertEquals(auditLogs2AfterRenaming.get(0).getChangeType(), ChangeType.INSERT);
+        assertEquals(auditLogs2AfterRenaming.get(1).getChangeType(), ChangeType.UPDATE);
 
         // Create bundle one more time
         final DefaultSubscriptionBaseBundle bundleDef3 = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
index 5a59f67..260c0da 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2018 Groupon, Inc
- * Copyright 2014-2018 The Billing Project, LLC
+ * Copyright 2014-2019 Groupon, Inc
+ * Copyright 2014-2019 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
@@ -41,6 +41,7 @@ import org.killbill.billing.subscription.api.user.TestSubscriptionHelper;
 import org.killbill.billing.subscription.engine.addon.AddonUtils;
 import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
 import org.killbill.billing.subscription.glue.TestDefaultSubscriptionModuleWithEmbeddedDB;
+import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.config.definition.SubscriptionConfig;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.bus.api.PersistentBus;
@@ -91,6 +92,8 @@ public class SubscriptionTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteW
     protected TestApiListener testListener;
     @Inject
     protected SubscriptionTestInitializer subscriptionTestInitializer;
+    @Inject
+    protected AuditUserApi auditUserApi;
 
     @Inject
     protected NonEntityDao nonEntityDao;