killbill-aplcache
Merge pull request #1100 from killbill/fix-for-InvoiceTrackingSqlDao-auditing Audit …
Changes
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/BundleSqlDao.java 12(+10 -2)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 22(+18 -4)
subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg 9(+9 -0)
subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionDao.java 46(+43 -3)
Details
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
index b0fe792..07f22f0 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
@@ -773,7 +773,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
throw new InvoiceApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT, paymentId);
}
- transactional.updateAttempt(invoicePayment.getRecordId(),
+ transactional.updateAttempt(invoicePayment.getId().toString(),
invoicePayment.getPaymentId().toString(),
invoicePayment.getPaymentDate().toDate(),
invoicePayment.getAmount(),
@@ -932,7 +932,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
if (existingAttempt == null) {
createAndRefresh(transactional, invoicePayment, context);
} else {
- transactional.updateAttempt(existingAttempt.getRecordId(),
+ transactional.updateAttempt(existingAttempt.getId().toString(),
invoicePayment.getPaymentId().toString(),
invoicePayment.getPaymentDate().toDate(),
invoicePayment.getAmount(),
@@ -1219,7 +1219,17 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
// Deactivate any usage trackingIds if necessary
} else if (InvoiceStatus.VOID.equals(newStatus)) {
final InvoiceTrackingSqlDao trackingSqlDao = entitySqlDaoWrapperFactory.become(InvoiceTrackingSqlDao.class);
- trackingSqlDao.deactivateForInvoice(invoiceId.toString(), context);
+ final List<InvoiceTrackingModelDao> invoiceTrackingModelDaos = trackingSqlDao.getTrackingsForInvoice(invoiceId.toString(), context);
+ if (!invoiceTrackingModelDaos.isEmpty()) {
+ final Collection<String> invoiceTrackingIdsToDeactivate = Collections2.<InvoiceTrackingModelDao, String>transform(invoiceTrackingModelDaos,
+ new Function<InvoiceTrackingModelDao, String>() {
+ @Override
+ public String apply(final InvoiceTrackingModelDao input) {
+ return input.getId().toString();
+ }
+ });
+ trackingSqlDao.deactivateByIds(invoiceTrackingIdsToDeactivate, context);
+ }
}
return null;
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
index 2723b68..b530b30 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.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
@@ -26,6 +26,8 @@ import java.util.UUID;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.invoice.api.InvoicePayment;
+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;
@@ -69,7 +71,8 @@ public interface InvoicePaymentSqlDao extends EntitySqlDao<InvoicePaymentModelDa
@SmartBindBean final InternalTenantContext context);
@SqlUpdate
- void updateAttempt(@Bind("recordId") Long recordId,
+ @Audited(ChangeType.UPDATE)
+ void updateAttempt(@Bind("id") String id,
@Bind("paymentId") final String paymentId,
@Bind("paymentDate") final Date paymentDate,
@Bind("amount") final BigDecimal amount,
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.java
index 49e8b8e..f1eeab5 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.java
@@ -17,6 +17,7 @@
package org.killbill.billing.invoice.dao;
+import java.util.Collection;
import java.util.Date;
import java.util.List;
@@ -31,14 +32,15 @@ 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.unstable.BindIn;
@KillBillSqlDaoStringTemplate
public interface InvoiceTrackingSqlDao extends EntitySqlDao<InvoiceTrackingModelDao, Entity> {
@SqlUpdate
@Audited(ChangeType.DELETE)
- public void deactivateForInvoice(@Bind("invoiceId") String invoiceId,
- @SmartBindBean final InternalCallContext context);
+ public void deactivateByIds(@BindIn("ids") final Collection<String> ids,
+ @SmartBindBean final InternalCallContext context);
@SqlQuery
List<InvoiceTrackingModelDao> getTrackingsByDateRange(@Bind("startDate") final Date startDate,
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
index 3552f3c..23776fa 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
@@ -131,7 +131,7 @@ updateAttempt() ::= <<
payment_cookie_id = :paymentCookieId,
linked_invoice_payment_id := :linkedInvoicePaymentId,
success := :success
- WHERE record_id = :recordId
+ WHERE id = :id
<AND_CHECK_TENANT("")>
;
>>
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.sql.stg
index 9add3fe..b20293a 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.sql.stg
@@ -28,14 +28,13 @@ tableValues() ::= <<
, :updatedDate
>>
-deactivateForInvoice() ::= <<
+deactivateByIds() ::= <<
update <tableName()>
set
-is_active = false
+ is_active = false
, updated_by = :createdBy
, updated_date = :updatedDate
-where
-invoice_id = :invoiceId
+where <idField("")> in (<ids>)
<AND_CHECK_TENANT("")>
;
>>
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceTrackingSqlDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceTrackingSqlDao.java
index 0072f3d..8c42ce5 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceTrackingSqlDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceTrackingSqlDao.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
@@ -24,15 +24,33 @@ import java.util.UUID;
import org.joda.time.LocalDate;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
+import org.killbill.billing.util.audit.ChangeType;
+import org.killbill.billing.util.audit.dao.AuditLogModelDao;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.dao.TableName;
+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.testng.Assert;
+import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.google.common.collect.ImmutableList;
+
public class TestInvoiceTrackingSqlDao extends InvoiceTestSuiteWithEmbeddedDB {
+ private EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
+
+ @BeforeMethod(groups = "slow")
+ public void setUp() {
+ if (hasFailed()) {
+ return;
+ }
+ transactionalSqlDao = new EntitySqlDaoTransactionalJdbiWrapper(dbi, roDbi, clock, new CacheControllerDispatcher(), nonEntityDao, internalCallContextFactory);
+ }
+
@Test(groups = "slow")
public void testBasicTrackingIds() {
- final InvoiceTrackingSqlDao dao = dbi.onDemand(InvoiceTrackingSqlDao.class);
-
LocalDate startRange = new LocalDate(2018, 8, 1);
LocalDate endRange = new LocalDate(2018, 11, 23);
@@ -57,32 +75,39 @@ public class TestInvoiceTrackingSqlDao extends InvoiceTestSuiteWithEmbeddedDB {
inputs.add(input3);
inputs.add(input4);
- dao.create(inputs, internalCallContext);
+ transactionalSqlDao.execute(false,
+ new EntitySqlDaoTransactionWrapper<Void>() {
+ @Override
+ public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ final InvoiceTrackingSqlDao dao = entitySqlDaoWrapperFactory.become(InvoiceTrackingSqlDao.class);
+
+ dao.create(inputs, internalCallContext);
- final List<InvoiceTrackingModelDao> result = dao.getTrackingsByDateRange(startRange.toDate(), endRange.toDate(), internalCallContext);
- Assert.assertEquals(result.size(), 3);
+ final List<InvoiceTrackingModelDao> result = dao.getTrackingsByDateRange(startRange.toDate(), endRange.toDate(), internalCallContext);
+ Assert.assertEquals(result.size(), 3);
- Assert.assertEquals(result.get(0).getTrackingId(), "trackingId1");
- Assert.assertEquals(result.get(0).getInvoiceId(), invoiceId1);
- Assert.assertEquals(result.get(0).getRecordDate(), startRange);
- Assert.assertEquals(result.get(0).getSubscriptionId(), subscriptionId);
+ Assert.assertEquals(result.get(0).getTrackingId(), "trackingId1");
+ Assert.assertEquals(result.get(0).getInvoiceId(), invoiceId1);
+ Assert.assertEquals(result.get(0).getRecordDate(), startRange);
+ Assert.assertEquals(result.get(0).getSubscriptionId(), subscriptionId);
- Assert.assertEquals(result.get(1).getTrackingId(), "trackingId2");
- Assert.assertEquals(result.get(1).getInvoiceId(), invoiceId1);
- Assert.assertEquals(result.get(1).getRecordDate(), new LocalDate(2018, 8, 5));
- Assert.assertEquals(result.get(1).getSubscriptionId(), subscriptionId);
+ Assert.assertEquals(result.get(1).getTrackingId(), "trackingId2");
+ Assert.assertEquals(result.get(1).getInvoiceId(), invoiceId1);
+ Assert.assertEquals(result.get(1).getRecordDate(), new LocalDate(2018, 8, 5));
+ Assert.assertEquals(result.get(1).getSubscriptionId(), subscriptionId);
- Assert.assertEquals(result.get(2).getTrackingId(), "trackingId3");
- Assert.assertEquals(result.get(2).getInvoiceId(), invoiceId2);
- Assert.assertEquals(result.get(2).getRecordDate(), new LocalDate(2018, 9, 1));
- Assert.assertEquals(result.get(2).getSubscriptionId(), subscriptionId);
+ Assert.assertEquals(result.get(2).getTrackingId(), "trackingId3");
+ Assert.assertEquals(result.get(2).getInvoiceId(), invoiceId2);
+ Assert.assertEquals(result.get(2).getRecordDate(), new LocalDate(2018, 9, 1));
+ Assert.assertEquals(result.get(2).getSubscriptionId(), subscriptionId);
+ return null;
+ }
+ });
}
@Test(groups = "slow")
public void testInvalidation() {
- final InvoiceTrackingSqlDao dao = dbi.onDemand(InvoiceTrackingSqlDao.class);
-
LocalDate startRange = new LocalDate(2019, 1, 1);
LocalDate endRange = new LocalDate(2019, 1, 31);
@@ -104,29 +129,56 @@ public class TestInvoiceTrackingSqlDao extends InvoiceTestSuiteWithEmbeddedDB {
inputs.add(input3);
inputs.add(input4);
- dao.create(inputs, internalCallContext);
-
- final List<InvoiceTrackingModelDao> result = dao.getTrackingsByDateRange(startRange.toDate(), endRange.toDate(), internalCallContext);
- Assert.assertEquals(result.size(), 4);
-
- clock.addDays(1);
- final InternalCallContext updatedContext = new InternalCallContext(internalCallContext.getTenantRecordId(),
- internalCallContext.getAccountRecordId(),
- internalCallContext.getFixedOffsetTimeZone(),
- clock.getUTCNow(),
- internalCallContext.getUserToken(),
- "invalidation-user",
- internalCallContext.getCallOrigin(),
- internalCallContext.getContextUserType(),
- internalCallContext.getReasonCode(),
- internalCallContext.getComments(),
- internalCallContext.getCreatedDate(),
- clock.getUTCNow());
-
- dao.deactivateForInvoice(invoiceId1.toString(), updatedContext);
-
- final List<InvoiceTrackingModelDao> result2 = dao.getTrackingsByDateRange(startRange.toDate(), endRange.toDate(), internalCallContext);
- Assert.assertEquals(result2.size(), 1);
-
+ transactionalSqlDao.execute(false,
+ new EntitySqlDaoTransactionWrapper<Void>() {
+ @Override
+ public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ final InvoiceTrackingSqlDao dao = entitySqlDaoWrapperFactory.become(InvoiceTrackingSqlDao.class);
+
+ dao.create(inputs, internalCallContext);
+
+ final List<InvoiceTrackingModelDao> result = dao.getTrackingsByDateRange(startRange.toDate(), endRange.toDate(), internalCallContext);
+ Assert.assertEquals(result.size(), 4);
+
+ final List<AuditLogModelDao> auditLogsPostCreate = ImmutableList.<AuditLogModelDao>copyOf(dao.getAuditLogsForTableNameAndAccountRecordId(TableName.INVOICE_TRACKING_IDS.toString(), internalCallContext));
+ Assert.assertEquals(auditLogsPostCreate.size(), 4);
+ for (int i = 0; i < 4; i++) {
+ Assert.assertEquals(auditLogsPostCreate.get(i).getChangeType(), ChangeType.INSERT);
+ Assert.assertEquals(auditLogsPostCreate.get(i).getTargetRecordId(), result.get(i).getRecordId());
+ }
+
+ clock.addDays(1);
+ final InternalCallContext updatedContext = new InternalCallContext(internalCallContext.getTenantRecordId(),
+ internalCallContext.getAccountRecordId(),
+ internalCallContext.getFixedOffsetTimeZone(),
+ clock.getUTCNow(),
+ internalCallContext.getUserToken(),
+ "invalidation-user",
+ internalCallContext.getCallOrigin(),
+ internalCallContext.getContextUserType(),
+ internalCallContext.getReasonCode(),
+ internalCallContext.getComments(),
+ internalCallContext.getCreatedDate(),
+ clock.getUTCNow());
+
+ dao.deactivateByIds(ImmutableList.<String>of(input1.getId().toString(), input2.getId().toString(), input3.getId().toString()), updatedContext);
+
+ final List<InvoiceTrackingModelDao> result2 = dao.getTrackingsByDateRange(startRange.toDate(), endRange.toDate(), internalCallContext);
+ Assert.assertEquals(result2.size(), 1);
+
+ final List<AuditLogModelDao> auditLogsPostDelete = ImmutableList.<AuditLogModelDao>copyOf(dao.getAuditLogsForTableNameAndAccountRecordId(TableName.INVOICE_TRACKING_IDS.toString(), internalCallContext));
+ Assert.assertEquals(auditLogsPostDelete.size(), 7);
+ for (int i = 0; i < 4; i++) {
+ Assert.assertEquals(auditLogsPostDelete.get(i).getChangeType(), ChangeType.INSERT);
+ Assert.assertEquals(auditLogsPostDelete.get(i).getTargetRecordId(), result.get(i).getRecordId());
+ }
+ for (int i = 4; i < 7; i++) {
+ Assert.assertEquals(auditLogsPostDelete.get(i).getChangeType(), ChangeType.DELETE);
+ Assert.assertEquals(auditLogsPostDelete.get(i).getTargetRecordId(), result.get(i - 4).getRecordId());
+ }
+
+ return null;
+ }
+ });
}
}
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;
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
index 0378027..b4b8a0e 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
@@ -59,6 +59,7 @@ import org.skife.jdbi.v2.exceptions.DBIException;
import org.skife.jdbi.v2.exceptions.StatementException;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlBatch;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.unstable.BindIn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -184,6 +185,9 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
private Object invokeSafely(final Method method, final Object[] args) throws Throwable {
final Audited auditedAnnotation = method.getAnnotation(Audited.class);
+ final boolean isROQuery = method.getAnnotation(SqlQuery.class) != null;
+ Preconditions.checkState(auditedAnnotation != null || isROQuery, "Non-@SqlQuery method %s without @Audited annotation", method);
+
// This can't be AUDIT'ed and CACHABLE'd at the same time as we only cache 'get'
if (auditedAnnotation != null) {
return invokeWithAuditAndHistory(auditedAnnotation, method, args);
@@ -213,6 +217,7 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
private Object invokeWithAuditAndHistory(final Audited auditedAnnotation, final Method method, final Object[] args) throws Throwable {
final InternalCallContext contextMaybeWithoutAccountRecordId = retrieveContextFromArguments(args);
final List<String> entityIds = retrieveEntityIdsFromArguments(method, args);
+ Preconditions.checkState(!entityIds.isEmpty(), "@Audited Sql method must have entities (@Bind(\"id\")) as arguments");
// We cannot always infer the TableName from the signature
TableName tableName = retrieveTableNameFromArgumentsIfPossible(Arrays.asList(args));
final ChangeType changeType = auditedAnnotation.value();
@@ -221,13 +226,10 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
// Get the current state before deletion for the history tables
final Map<Long, M> deletedAndUpdatedEntities = new HashMap<Long, M>();
if (changeType == ChangeType.DELETE) {
- // TODO FIXME: this shouldn't happen (auditing for InvoiceTrackingSqlDao is broken)
- if (!entityIds.isEmpty()) {
- final List<M> entitiesToBeDeleted = sqlDao.getByIds(entityIds, contextMaybeWithoutAccountRecordId);
- printSQLWarnings();
- for (final M entityToBeDeleted : entitiesToBeDeleted) {
- deletedAndUpdatedEntities.put(entityToBeDeleted.getRecordId(), entityToBeDeleted);
- }
+ final List<M> entitiesToBeDeleted = sqlDao.getByIds(entityIds, contextMaybeWithoutAccountRecordId);
+ printSQLWarnings();
+ for (final M entityToBeDeleted : entitiesToBeDeleted) {
+ deletedAndUpdatedEntities.put(entityToBeDeleted.getRecordId(), entityToBeDeleted);
}
}