killbill-aplcache
Changes
payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorWithDB.java 3(+1 -2)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java 2(+1 -1)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java 11(+3 -8)
util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java 141(+86 -55)
Details
diff --git a/account/src/test/java/org/killbill/billing/account/AccountTestSuiteNoDB.java b/account/src/test/java/org/killbill/billing/account/AccountTestSuiteNoDB.java
index 76beffd..cf1f2df 100644
--- a/account/src/test/java/org/killbill/billing/account/AccountTestSuiteNoDB.java
+++ b/account/src/test/java/org/killbill/billing/account/AccountTestSuiteNoDB.java
@@ -85,6 +85,10 @@ public abstract class AccountTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
@AfterMethod(groups = "fast")
public void afterMethod() throws Exception {
+ if (hasFailed()) {
+ return;
+ }
+
bus.stop();
}
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.java b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.java
index abc01c9..af6939f 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.java
@@ -60,7 +60,7 @@ public interface BlockingStateSqlDao extends EntitySqlDao<BlockingStateModelDao,
@SmartBindBean final InternalTenantContext context);
@SqlUpdate
- @Audited(ChangeType.UPDATE)
+ @Audited(ChangeType.DELETE)
public void unactiveEvent(@Bind("id") String id,
@SmartBindBean final InternalCallContext context);
}
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 7742de0..252e4ae 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
@@ -86,6 +86,7 @@ import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -309,20 +310,21 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
final FutureAccountNotifications callbackDateTimePerSubscriptions,
final ExistingInvoiceMetadata existingInvoiceMetadata,
final InternalCallContext context) {
- createInvoices(ImmutableList.<InvoiceModelDao>of(invoice), trackingIds, callbackDateTimePerSubscriptions, existingInvoiceMetadata, context);
+ createInvoices(ImmutableList.<InvoiceModelDao>of(invoice), trackingIds, callbackDateTimePerSubscriptions, existingInvoiceMetadata, false, context);
}
@Override
public List<InvoiceItemModelDao> createInvoices(final List<InvoiceModelDao> invoices,
final Set<InvoiceTrackingModelDao> trackingIds,
final InternalCallContext context) {
- return createInvoices(invoices, trackingIds, new FutureAccountNotifications(), null, context);
+ return createInvoices(invoices, trackingIds, new FutureAccountNotifications(), null, true, context);
}
private List<InvoiceItemModelDao> createInvoices(final Iterable<InvoiceModelDao> invoices,
final Set<InvoiceTrackingModelDao> trackingIds,
final FutureAccountNotifications callbackDateTimePerSubscriptions,
@Nullable final ExistingInvoiceMetadata existingInvoiceMetadataOrNull,
+ final boolean returnCreatedInvoiceItems,
final InternalCallContext context) {
// Track invoices that are being created
final Collection<UUID> createdInvoiceIds = new HashSet<UUID>();
@@ -361,7 +363,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
existingInvoiceMetadata = existingInvoiceMetadataOrNull;
}
- final List<InvoiceItemModelDao> createdInvoiceItems = new LinkedList<InvoiceItemModelDao>();
+ final Collection<InvoiceItemModelDao> invoiceItemsToCreate = new LinkedList<InvoiceItemModelDao>();
for (final InvoiceModelDao invoiceModelDao : invoices) {
invoiceByInvoiceId.put(invoiceModelDao.getId(), invoiceModelDao);
final boolean isNotShellInvoice = invoiceIdsReferencedFromItems.remove(invoiceModelDao.getId());
@@ -384,7 +386,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
// Because of AUTO_INVOICING_REUSE_DRAFT we expect an invoice were items might already exist.
// Also for ALLOWED_INVOICE_ITEM_TYPES, we expect plugins to potentially modify the amount
if (existingInvoiceItem == null) {
- createdInvoiceItems.add(createInvoiceItemFromTransaction(transInvoiceItemSqlDao, invoiceItemModelDao, context));
+ invoiceItemsToCreate.add(invoiceItemModelDao);
allInvoiceIds.add(invoiceItemModelDao.getInvoiceId());
} else if (InvoicePluginDispatcher.ALLOWED_INVOICE_ITEM_TYPES.contains(invoiceItemModelDao.getType()) &&
// The restriction on the amount is to deal with https://github.com/killbill/killbill/issues/993 - and esnure that duplicate
@@ -399,7 +401,6 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
final boolean wasInvoiceCreatedOrCommitted = createdInvoiceIds.contains(invoiceModelDao.getId()) ||
committedReusedInvoiceId.contains(invoiceModelDao.getId());
if (InvoiceStatus.COMMITTED.equals(invoiceModelDao.getStatus())) {
-
if (wasInvoiceCreatedOrCommitted) {
notifyBusOfInvoiceCreation(entitySqlDaoWrapperFactory, invoiceModelDao, context);
} else {
@@ -414,6 +415,9 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
notifyOfFutureBillingEvents(entitySqlDaoWrapperFactory, invoiceModelDao.getAccountId(), callbackDateTimePerSubscriptions, context);
}
+ // Bulk insert the invoice items
+ createInvoiceItemsFromTransaction(transInvoiceItemSqlDao, invoiceItemsToCreate, context);
+
for (final UUID adjustedInvoiceId : allInvoiceIds) {
final boolean newInvoice = createdInvoiceIds.contains(adjustedInvoiceId);
if (newInvoice) {
@@ -431,13 +435,27 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
}
}
-
if (trackingIds != null && !trackingIds.isEmpty()) {
final InvoiceTrackingSqlDao trackingIdsSqlDao = entitySqlDaoWrapperFactory.become(InvoiceTrackingSqlDao.class);
trackingIdsSqlDao.create(trackingIds, context);
}
- return createdInvoiceItems;
+ if (returnCreatedInvoiceItems) {
+ if (invoiceItemsToCreate.isEmpty()) {
+ return ImmutableList.<InvoiceItemModelDao>of();
+ } else {
+ return transInvoiceItemSqlDao.getByIds(Collections2.<InvoiceItemModelDao, String>transform(invoiceItemsToCreate,
+ new Function<InvoiceItemModelDao, String>() {
+ @Override
+ public String apply(final InvoiceItemModelDao input) {
+ return input.getId().toString();
+ }
+ }),
+ context);
+ }
+ } else {
+ return null;
+ }
}
});
}
@@ -1133,15 +1151,31 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
}
}
- private InvoiceItemModelDao createInvoiceItemFromTransaction(final InvoiceItemSqlDao invoiceItemSqlDao, final InvoiceItemModelDao invoiceItemModelDao, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
+ private void createInvoiceItemFromTransaction(final InvoiceItemSqlDao invoiceItemSqlDao,
+ final InvoiceItemModelDao invoiceItemModelDao,
+ final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
+ validateInvoiceItemToBeAdjustedIfNeeded(invoiceItemSqlDao, invoiceItemModelDao, context);
+
+ createAndRefresh(invoiceItemSqlDao, invoiceItemModelDao, context);
+ }
+
+ private void createInvoiceItemsFromTransaction(final InvoiceItemSqlDao invoiceItemSqlDao,
+ final Iterable<InvoiceItemModelDao> invoiceItemModelDaos,
+ final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
+ for (final InvoiceItemModelDao invoiceItemModelDao : invoiceItemModelDaos) {
+ validateInvoiceItemToBeAdjustedIfNeeded(invoiceItemSqlDao, invoiceItemModelDao, context);
+ }
+
+ bulkCreate(invoiceItemSqlDao, invoiceItemModelDaos, context);
+ }
+
+ private void validateInvoiceItemToBeAdjustedIfNeeded(final InvoiceItemSqlDao invoiceItemSqlDao, final InvoiceItemModelDao invoiceItemModelDao, final InternalCallContext context) throws InvoiceApiException {
// There is no efficient way to retrieve an invoice item given an ID today (and invoice plugins can put item adjustments
// on a different invoice than the original item), so it's easier to do the check in the DAO rather than in the API layer
// See also https://github.com/killbill/killbill/issues/7
if (InvoiceItemType.ITEM_ADJ.equals(invoiceItemModelDao.getType())) {
validateInvoiceItemToBeAdjusted(invoiceItemSqlDao, invoiceItemModelDao, context);
}
-
- return createAndRefresh(invoiceItemSqlDao, invoiceItemModelDao, context);
}
private void validateInvoiceItemToBeAdjusted(final InvoiceItemSqlDao invoiceItemSqlDao, final InvoiceItemModelDao invoiceItemModelDao, final InternalCallContext context) throws InvoiceApiException {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.java
index 47fde47..b1533de 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 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
@@ -19,6 +19,7 @@
package org.killbill.billing.invoice.dao;
import java.math.BigDecimal;
+import java.util.Collection;
import java.util.List;
import org.killbill.billing.callcontext.InternalCallContext;
@@ -32,6 +33,7 @@ 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 InvoiceItemSqlDao extends EntitySqlDao<InvoiceItemModelDao, InvoiceItem> {
@@ -39,14 +41,14 @@ public interface InvoiceItemSqlDao extends EntitySqlDao<InvoiceItemModelDao, Inv
@SqlQuery
List<InvoiceItemModelDao> getInvoiceItemsByInvoice(@Bind("invoiceId") final String invoiceId,
@SmartBindBean final InternalTenantContext context);
+
@SqlQuery
List<InvoiceItemModelDao> getInvoiceItemsBySubscription(@Bind("subscriptionId") final String subscriptionId,
@SmartBindBean final InternalTenantContext context);
-
@SqlQuery
List<InvoiceItemModelDao> getAdjustedOrRepairedInvoiceItemsByLinkedId(@Bind("linkedItemId") final String linkedItemId,
- @SmartBindBean final InternalTenantContext context);
+ @SmartBindBean final InternalTenantContext context);
@SqlUpdate
@Audited(ChangeType.UPDATE)
@@ -62,4 +64,8 @@ public interface InvoiceItemSqlDao extends EntitySqlDao<InvoiceItemModelDao, Inv
@SqlQuery
BigDecimal getAccountCBA(@SmartBindBean final InternalTenantContext context);
+
+ @SqlQuery
+ List<InvoiceItemModelDao> getByIds(@BindIn("ids") final Collection<String> invoiceItemIds,
+ @SmartBindBean final InternalTenantContext context);
}
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 a9525e2..49e8b8e 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
@@ -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
@@ -29,7 +29,6 @@ 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.SqlBatch;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
@@ -37,14 +36,10 @@ import org.skife.jdbi.v2.sqlobject.SqlUpdate;
public interface InvoiceTrackingSqlDao extends EntitySqlDao<InvoiceTrackingModelDao, Entity> {
@SqlUpdate
- @Audited(ChangeType.UPDATE)
+ @Audited(ChangeType.DELETE)
public void deactivateForInvoice(@Bind("invoiceId") String invoiceId,
@SmartBindBean final InternalCallContext context);
- @SqlBatch
- void create(@SmartBindBean Iterable<InvoiceTrackingModelDao> trackings,
- @SmartBindBean final InternalCallContext context);
-
@SqlQuery
List<InvoiceTrackingModelDao> getTrackingsByDateRange(@Bind("startDate") final Date startDate,
@Bind("endDate") final Date endDate,
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
index 8dfc05f..7ac7570 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
@@ -50,6 +50,14 @@ tableValues() ::= <<
, :createdDate
>>
+getByIds(ids) ::= <<
+select
+ <allTableFields("t.")>
+from <tableName()> t
+where <idField("t.")> in (<ids>)
+<AND_CHECK_TENANT("t.")>
+;
+>>
getInvoiceItemsByInvoice() ::= <<
SELECT <allTableFields("")>
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
index a36be45..c74a26d 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.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
@@ -46,6 +46,7 @@ import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.entity.EntityPersistenceException;
+import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
import org.killbill.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
import org.killbill.billing.invoice.MockBillingEventSet;
import org.killbill.billing.invoice.api.Invoice;
@@ -73,7 +74,6 @@ import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.util.currency.KillBillMoney;
import org.killbill.clock.ClockMock;
import org.mockito.Mockito;
-import org.skife.jdbi.v2.exceptions.TransactionFailedException;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@@ -117,6 +117,25 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
}
@Test(groups = "slow")
+ public void testSimpleInvoiceRun() throws Exception {
+ final UUID accountId = account.getId();
+
+ final InvoiceModelDao invoiceForExternalCharge = new InvoiceModelDao(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD, false);
+ final InvoiceItemModelDao externalCharge1 = new InvoiceItemModelDao(new ExternalChargeInvoiceItem(invoiceForExternalCharge.getId(), accountId, UUID.randomUUID(), UUID.randomUUID().toString(), clock.getUTCToday(), clock.getUTCToday(), new BigDecimal("15.0"), Currency.USD, null));
+ final InvoiceItemModelDao externalCharge2 = new InvoiceItemModelDao(new ExternalChargeInvoiceItem(invoiceForExternalCharge.getId(), accountId, UUID.randomUUID(), UUID.randomUUID().toString(), clock.getUTCToday(), clock.getUTCToday(), new BigDecimal("17.0"), Currency.USD, null));
+ invoiceForExternalCharge.addInvoiceItem(externalCharge1);
+ invoiceForExternalCharge.addInvoiceItem(externalCharge2);
+ invoiceDao.createInvoice(invoiceForExternalCharge,
+ ImmutableSet.<InvoiceTrackingModelDao>of(),
+ new FutureAccountNotifications(),
+ new ExistingInvoiceMetadata(ImmutableList.<Invoice>of()),
+ context);
+
+ final Invoice invoice = invoiceUserApi.getInvoice(invoiceForExternalCharge.getId(), callContext);
+ invoiceUtil.checkInvoicesEqual(invoiceForExternalCharge, invoice);
+ }
+
+ @Test(groups = "slow")
public void testCreationAndRetrievalByAccount() throws EntityPersistenceException {
final UUID accountId = account.getId();
final Invoice invoice = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
@@ -504,7 +523,6 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
assertEquals(invoices.size(), 0);
}
-
@Test(groups = "slow")
public void testAccountBalance() throws EntityPersistenceException {
final UUID accountId = account.getId();
@@ -564,7 +582,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
final UUID accountId = account.getId();
final UUID bundleId = UUID.randomUUID();
final LocalDate targetDate1 = new LocalDate(2011, 10, 6);
- final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1, Currency.USD);
+ final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1, Currency.USD);
invoiceUtil.createInvoice(invoice1, context);
final LocalDate startDate = new LocalDate(2011, 3, 1);
@@ -823,7 +841,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
final InvoiceModelDao invoiceForExternalCharge = new InvoiceModelDao(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD, false);
final InvoiceItemModelDao externalCharge = new InvoiceItemModelDao(new ExternalChargeInvoiceItem(invoiceForExternalCharge.getId(), accountId, bundleId, description, clock.getUTCToday(), clock.getUTCToday(), new BigDecimal("15.0"), Currency.USD, null));
invoiceForExternalCharge.addInvoiceItem(externalCharge);
- final InvoiceItemModelDao charge = invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceForExternalCharge), ImmutableSet.of(), context).get(0);
+ final InvoiceItemModelDao charge = invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceForExternalCharge), ImmutableSet.of(), context).get(0);
InvoiceModelDao newInvoice = invoiceDao.getById(charge.getInvoiceId(), context);
List<InvoiceItemModelDao> items = newInvoice.getInvoiceItems();
@@ -1593,7 +1611,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
// * $5 item
// * $-5 CBA used
final DefaultInvoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
- final InvoiceItem fixedItem2 = new FixedPriceInvoiceItem(invoice2.getId(), invoice1.getAccountId(), null, null, null, UUID.randomUUID().toString(),
+ final InvoiceItem fixedItem2 = new FixedPriceInvoiceItem(invoice2.getId(), invoice1.getAccountId(), null, null, null, UUID.randomUUID().toString(),
UUID.randomUUID().toString(), clock.getUTCToday(), new BigDecimal("5"), Currency.USD);
final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem2 = new CreditBalanceAdjInvoiceItem(fixedItem2.getInvoiceId(), fixedItem2.getAccountId(),
fixedItem2.getStartDate(), fixedItem2.getAmount().negate(),
@@ -1627,7 +1645,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
// * $-10 repair
// * $10 generated CBA due to the repair (assume previous payment)
final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
- final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null, null, UUID.randomUUID().toString(),
+ final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null, null, UUID.randomUUID().toString(),
UUID.randomUUID().toString(), clock.getUTCToday(), BigDecimal.TEN, Currency.USD);
final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
fixedItem1.getStartDate(), fixedItem1.getEndDate(),
@@ -1653,7 +1671,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
// * $5 item
// * $-5 CBA used
final DefaultInvoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
- final InvoiceItem fixedItem2 = new FixedPriceInvoiceItem(invoice2.getId(), invoice1.getAccountId(), null, null, null, UUID.randomUUID().toString(),
+ final InvoiceItem fixedItem2 = new FixedPriceInvoiceItem(invoice2.getId(), invoice1.getAccountId(), null, null, null, UUID.randomUUID().toString(),
UUID.randomUUID().toString(), clock.getUTCToday(), new BigDecimal("5"), Currency.USD);
final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem2 = new CreditBalanceAdjInvoiceItem(fixedItem2.getInvoiceId(), fixedItem2.getAccountId(),
fixedItem2.getStartDate(), fixedItem2.getAmount().negate(),
@@ -1667,7 +1685,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
// * $5 item
// * $-5 CBA used
final DefaultInvoice invoice3 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
- final InvoiceItem fixedItem3 = new FixedPriceInvoiceItem(invoice3.getId(), invoice1.getAccountId(), null, null, null, UUID.randomUUID().toString(),
+ final InvoiceItem fixedItem3 = new FixedPriceInvoiceItem(invoice3.getId(), invoice1.getAccountId(), null, null, null, UUID.randomUUID().toString(),
UUID.randomUUID().toString(), clock.getUTCToday(), new BigDecimal("5"), Currency.USD);
final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem3 = new CreditBalanceAdjInvoiceItem(fixedItem3.getInvoiceId(), fixedItem3.getAccountId(),
fixedItem3.getStartDate(), fixedItem3.getAmount().negate(),
@@ -1728,7 +1746,6 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
invoiceUtil.verifyInvoice(invoice1.getId(), 0.00, 10.00, context);
}
-
@Test(groups = "slow")
public void testWithFailedPaymentAttempt() throws Exception {
final UUID accountId = account.getId();
@@ -1737,14 +1754,13 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
final UUID bundleId = UUID.randomUUID();
final UUID subscriptionId = UUID.randomUUID();
- final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice.getId(), accountId, bundleId, subscriptionId, "test product", "test plan", "test ZOO", clock.getUTCNow().plusMonths(-1).toLocalDate(), clock.getUTCNow().toLocalDate(),
+ final RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice.getId(), accountId, bundleId, subscriptionId, "test product", "test plan", "test ZOO", clock.getUTCNow().plusMonths(-1).toLocalDate(), clock.getUTCNow().toLocalDate(),
BigDecimal.TEN, BigDecimal.TEN, Currency.USD);
invoiceUtil.createInvoiceItem(item1, context);
final InvoiceModelDao retrievedInvoice = invoiceDao.getById(invoice.getId(), context);
assertEquals(retrievedInvoice.getInvoicePayments().size(), 0);
-
final UUID paymentId = UUID.randomUUID();
final DefaultInvoicePayment defaultInvoicePayment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice.getId(), clock.getUTCNow().plusDays(12), BigDecimal.TEN, Currency.USD, Currency.USD, "cookie", false);
invoiceDao.notifyOfPaymentCompletion(new InvoicePaymentModelDao(defaultInvoicePayment), context);
@@ -1761,8 +1777,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
assertEquals(retrievedInvoice2.getInvoicePayments().get(0).getSuccess(), Boolean.TRUE);
}
-
- private InvoiceItemModelDao createCredit(final UUID accountId, final LocalDate effectiveDate, final BigDecimal creditAmount, final boolean draft)throws InvoiceApiException {
+ private InvoiceItemModelDao createCredit(final UUID accountId, final LocalDate effectiveDate, final BigDecimal creditAmount, final boolean draft) throws InvoiceApiException {
return createCredit(accountId, null, effectiveDate, creditAmount, draft);
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
index e61343d..202cbef 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.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
@@ -317,11 +317,7 @@ public class TestInvoiceHelper {
}
public void createPayment(final InvoicePayment invoicePayment, final InternalCallContext internalCallContext) {
- try {
- invoicePaymentSqlDao.create(new InvoicePaymentModelDao(invoicePayment), internalCallContext);
- } catch (final EntityPersistenceException e) {
- Assert.fail(e.getMessage());
- }
+ invoicePaymentSqlDao.create(new InvoicePaymentModelDao(invoicePayment), internalCallContext);
}
public void verifyInvoice(final UUID invoiceId, final double balance, final double cbaAmount, final InternalTenantContext context) throws InvoiceApiException {
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodSqlDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodSqlDao.java
index 5512781..ca49110 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodSqlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodSqlDao.java
@@ -40,7 +40,7 @@ import org.skife.jdbi.v2.sqlobject.customizers.Define;
public interface PaymentMethodSqlDao extends EntitySqlDao<PaymentMethodModelDao, PaymentMethod> {
@SqlUpdate
- @Audited(ChangeType.UPDATE)
+ @Audited(ChangeType.DELETE)
void markPaymentMethodAsDeleted(@Bind("id") final String paymentMethodId,
@SmartBindBean final InternalCallContext context);
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorWithDB.java b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorWithDB.java
index e444b0a..73144b1 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorWithDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorWithDB.java
@@ -95,8 +95,7 @@ public class TestPaymentMethodProcessorWithDB extends PaymentTestSuiteWithEmbedd
Assert.assertEquals(history1.getTenantRecordId(), paymentMethodModelDao.getTenantRecordId());
Assert.assertEquals(history1.getExternalKey(), paymentMethodModelDao.getExternalKey());
Assert.assertTrue(history1.isActive());
- // Note: it looks like we don't consider this as a DELETE, probably because we can un-delete such payment methods?
- Assert.assertEquals(auditLogsWithHistory.get(1).getChangeType(), ChangeType.UPDATE);
+ Assert.assertEquals(auditLogsWithHistory.get(1).getChangeType(), ChangeType.DELETE);
Assert.assertEquals(history2.getAccountRecordId(), paymentMethodModelDao.getAccountRecordId());
Assert.assertEquals(history2.getTenantRecordId(), paymentMethodModelDao.getTenantRecordId());
Assert.assertEquals(history2.getExternalKey(), paymentMethodModelDao.getExternalKey());
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
index be4ddd1..01eaa91 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
@@ -38,7 +38,7 @@ import org.skife.jdbi.v2.sqlobject.SqlUpdate;
public interface SubscriptionEventSqlDao extends EntitySqlDao<SubscriptionEventModelDao, SubscriptionBaseEvent> {
@SqlUpdate
- @Audited(ChangeType.UPDATE)
+ @Audited(ChangeType.DELETE)
public void unactiveEvent(@Bind("id") String id,
@SmartBindBean final InternalCallContext context);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
index f1e5f39..806b386 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.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
@@ -35,7 +35,6 @@ import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.Entitlement;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
-import org.killbill.billing.entity.EntityPersistenceException;
import org.killbill.billing.subscription.SubscriptionTestSuiteWithEmbeddedDB;
import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.api.SubscriptionBillingApiException;
@@ -329,11 +328,7 @@ public class TestUserApiCancel extends SubscriptionTestSuiteWithEmbeddedDB {
final Handle handle = dbi.open();
final SubscriptionEventSqlDao sqlDao = handle.attach(SubscriptionEventSqlDao.class);
- try {
- sqlDao.create(newCancelEvent, internalCallContext);
- } catch (EntityPersistenceException e) {
- Assert.fail(e.getMessage());
- }
+ sqlDao.create(newCancelEvent, internalCallContext);
subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
// The extra cancel event is being ignored
diff --git a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java
index 9e9ed9d..9624246 100644
--- a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java
+++ b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 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,23 +22,17 @@ import java.util.Date;
import java.util.List;
import java.util.UUID;
-import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.util.entity.Entity;
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.SqlBatch;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
@KillBillSqlDaoStringTemplate
public interface RolledUpUsageSqlDao extends EntitySqlDao<RolledUpUsageModelDao, Entity> {
- @SqlBatch
- void create(@SmartBindBean Iterable<RolledUpUsageModelDao> usages,
- @SmartBindBean final InternalCallContext context);
-
@SqlQuery
Long recordsWithTrackingIdExist(@Bind("subscriptionId") final UUID subscriptionId,
@Bind("trackingId") final String trackingId,
diff --git a/util/src/main/java/org/killbill/billing/util/dao/AuditSqlDao.java b/util/src/main/java/org/killbill/billing/util/dao/AuditSqlDao.java
index e9cf5b8..9e06d81 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/AuditSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/AuditSqlDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 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
@@ -21,20 +21,20 @@ package org.killbill.billing.util.dao;
import java.util.Iterator;
import java.util.List;
-import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
-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.skife.jdbi.v2.sqlobject.customizers.Define;
-
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.commons.jdbi.statement.SmartFetchSize;
import org.killbill.billing.util.audit.dao.AuditLogModelDao;
import org.killbill.billing.util.cache.Cachable;
import org.killbill.billing.util.cache.Cachable.CacheType;
import org.killbill.billing.util.cache.CachableKey;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.statement.SmartFetchSize;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
+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.sqlobject.customizers.BatchChunkSize;
+import org.skife.jdbi.v2.sqlobject.customizers.Define;
/**
* Note 1: cache invalidation has to happen for audit logs (which is tricky in the multi-nodes scenario).
@@ -49,9 +49,10 @@ import org.killbill.billing.util.cache.CachableKey;
// Note: @RegisterMapper annotation won't work here as we build the SqlObject via EntitySqlDao (annotations won't be inherited for JDBI)
public interface AuditSqlDao {
- @SqlUpdate
- public void insertAuditFromTransaction(@SmartBindBean final EntityAudit audit,
- @SmartBindBean final InternalCallContext context);
+ @SqlBatch
+ @BatchChunkSize(1000) // Arbitrary value, just a safety mechanism in case of very large datasets
+ public void insertAuditsFromTransaction(@SmartBindBean final Iterable<EntityAudit> audits,
+ @SmartBindBean final InternalCallContext context);
@SqlQuery
@SmartFetchSize(shouldStream = true)
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntityDaoBase.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntityDaoBase.java
index a4684b3..d58c367 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntityDaoBase.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntityDaoBase.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 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
@@ -28,7 +28,6 @@ import java.util.UUID;
import org.killbill.billing.BillingExceptionBase;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.entity.EntityPersistenceException;
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.DefaultPagination;
import org.killbill.billing.util.entity.Entity;
@@ -37,6 +36,7 @@ import org.killbill.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.Orderi
import org.killbill.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.PaginationIteratorBuilder;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
public abstract class EntityDaoBase<M extends EntityModelDao<E>, E extends Entity, U extends BillingExceptionBase> implements EntityDao<M, E, U> {
@@ -112,11 +112,19 @@ public abstract class EntityDaoBase<M extends EntityModelDao<E>, E extends Entit
};
}
- protected <F extends EntityModelDao> F createAndRefresh(final EntitySqlDao transactional, final F entity, final InternalCallContext context) throws EntityPersistenceException {
+ protected <F extends EntityModelDao> F createAndRefresh(final EntitySqlDao transactional, final F entity, final InternalCallContext context) {
// We have overridden the jDBI return type in EntitySqlDaoWrapperInvocationHandler
return (F) transactional.create(entity, context);
}
+ protected <F extends EntityModelDao> void bulkCreate(final EntitySqlDao transactional, final Iterable<F> entities, final InternalCallContext context) {
+ if (Iterables.<F>isEmpty(entities)) {
+ return;
+ }
+
+ transactional.create(entities, context);
+ }
+
protected boolean checkEntityAlreadyExists(final EntitySqlDao<M, E> transactional, final M entity, final InternalCallContext context) {
return transactional.getRecordId(entity.getId().toString(), context) != null;
}
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDao.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDao.java
index a4ddb91..e97d5c3 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2011 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 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
@@ -23,7 +23,6 @@ import java.util.List;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.entity.EntityPersistenceException;
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.cache.Cachable;
import org.killbill.billing.util.cache.Cachable.CacheType;
@@ -31,12 +30,14 @@ import org.killbill.billing.util.cache.CachableKey;
import org.killbill.billing.util.dao.AuditSqlDao;
import org.killbill.billing.util.dao.HistorySqlDao;
import org.killbill.billing.util.entity.Entity;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
import org.killbill.commons.jdbi.statement.SmartFetchSize;
import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.BatchChunkSize;
import org.skife.jdbi.v2.sqlobject.customizers.Define;
import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
@@ -47,7 +48,13 @@ public interface EntitySqlDao<M extends EntityModelDao<E>, E extends Entity> ext
@SqlUpdate
@Audited(ChangeType.INSERT)
public Object create(@SmartBindBean final M entity,
- @SmartBindBean final InternalCallContext context) throws EntityPersistenceException;
+ @SmartBindBean final InternalCallContext context);
+
+ @SqlBatch
+ @BatchChunkSize(1000) // Arbitrary value, just a safety mechanism in case of very large datasets
+ @Audited(ChangeType.INSERT)
+ public void create(@SmartBindBean final Iterable<M> entity,
+ @SmartBindBean final InternalCallContext context);
@SqlQuery
public M getById(@Bind("id") final String id,
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 195d41d..b09eec7 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
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2012 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
@@ -31,6 +31,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -63,6 +64,7 @@ import org.skife.jdbi.v2.StatementContext;
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.unstable.BindIn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -319,8 +321,7 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
// Get the current state before deletion for the history tables
final Map<String, M> deletedEntities = new HashMap<String, M>();
- // Unfortunately, we cannot just look at DELETE as "markAsInactive" operations are often treated as UPDATE
- if (changeType == ChangeType.UPDATE || changeType == ChangeType.DELETE) {
+ if (changeType == ChangeType.DELETE) {
for (final String entityId : entityIds) {
deletedEntities.put(entityId, sqlDao.getById(entityId, context));
printSQLWarnings();
@@ -335,19 +336,15 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
}
});
- M m = null;
- for (final String entityId : entityIds) {
- m = updateHistoryAndAudit(entityId, deletedEntities.get(entityId), changeType, context);
+ if (entityIds.isEmpty() ) {
+ return obj;
}
// PERF: override the return value with the reHydrated entity to avoid an extra 'get' in the transaction,
// (see EntityDaoBase#createAndRefresh for an example, but it works for updates as well).
- if (entityIds.size() == 1) {
- return m;
- } else {
- // jDBI will return the number of rows modified otherwise
- return obj;
- }
+ final List<M> ms = updateHistoryAndAudit(entityIds, deletedEntities, changeType, context);
+ final boolean isBatchQuery = method.getAnnotation(SqlBatch.class) != null;
+ return isBatchQuery ? ms : Iterables.<M>getFirst(ms, null);
}
private Object executeJDBCCall(final Method method, final Object[] args) throws IllegalAccessException, InvocationTargetException {
@@ -399,37 +396,51 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
rawKey;
}
- private M updateHistoryAndAudit(final String entityId, @Nullable final M deletedEntity, final ChangeType changeType, final InternalCallContext context) throws Throwable {
- final Object reHydratedEntity = prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, getProfilingId("history/audit", null), new WithProfilingCallback<Object, Throwable>() {
+ private List<M> updateHistoryAndAudit(final Collection<String> entityIds,
+ final Map<String, M> deletedEntities,
+ final ChangeType changeType,
+ final InternalCallContext context) throws Throwable {
+ final Object reHydratedEntities = prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, getProfilingId("history/audit", null), new WithProfilingCallback<Object, Throwable>() {
@Override
- public M execute() throws Throwable {
- final M reHydratedEntity;
- if (changeType == ChangeType.DELETE) {
- reHydratedEntity = deletedEntity;
- } else {
- // See note above regarding "markAsInactive" operations
- reHydratedEntity = MoreObjects.firstNonNull(sqlDao.getById(entityId, context), deletedEntity);
- printSQLWarnings();
- }
- Preconditions.checkNotNull(reHydratedEntity, "reHydratedEntity cannot be null");
- final Long entityRecordId = reHydratedEntity.getRecordId();
- final TableName tableName = reHydratedEntity.getTableName();
-
- // Note: audit entries point to the history record id
- final Long historyRecordId;
- if (tableName.getHistoryTableName() != null) {
- historyRecordId = insertHistory(entityRecordId, reHydratedEntity, changeType, context);
- } else {
- historyRecordId = entityRecordId;
+ public List<M> execute() {
+ TableName tableName = null;
+ // We'll keep the ordering
+ final Map<M, Long> reHydratedEntityModelDaoAndHistoryRecordIds = new LinkedHashMap<M, Long>(entityIds.size());
+ for (final String entityId : entityIds) {
+ final M reHydratedEntity;
+ if (changeType == ChangeType.DELETE) {
+ reHydratedEntity = deletedEntities.get(entityId);
+ } else {
+ // TODO Could we avoid this query?
+ reHydratedEntity = sqlDao.getById(entityId, context);
+ printSQLWarnings();
+ }
+ Preconditions.checkNotNull(reHydratedEntity, "reHydratedEntity cannot be null");
+ final Long entityRecordId = reHydratedEntity.getRecordId();
+ if (tableName == null) {
+ tableName = reHydratedEntity.getTableName();
+ }
+
+ // Note: audit entries point to the history record id
+ final Long historyRecordId;
+ if (tableName.getHistoryTableName() != null) {
+ // TODO Could we do this in bulk too?
+ historyRecordId = insertHistory(entityRecordId, reHydratedEntity, changeType, context);
+ } else {
+ historyRecordId = entityRecordId;
+ }
+
+ reHydratedEntityModelDaoAndHistoryRecordIds.put(reHydratedEntity, historyRecordId);
}
- // Make sure to re-hydrate the object (especially needed for create calls)
- insertAudits(tableName, reHydratedEntity, entityRecordId, historyRecordId, changeType, context);
- return reHydratedEntity;
+ // Make sure to re-hydrate the objects first (especially needed for create calls)
+ insertAudits(tableName, reHydratedEntityModelDaoAndHistoryRecordIds, changeType, context);
+
+ return ImmutableList.<M>copyOf(reHydratedEntityModelDaoAndHistoryRecordIds.keySet());
}
});
//noinspection unchecked
- return (M) reHydratedEntity;
+ return (List<M>) reHydratedEntities;
}
private List<String> retrieveEntityIdsFromArguments(final Method method, final Object[] args) {
@@ -510,35 +521,55 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>,
return recordId;
}
- private void insertAudits(final TableName tableName, final M entityModelDao, final Long entityRecordId, final Long historyRecordId, final ChangeType changeType, final InternalCallContext contextMaybeWithoutAccountRecordId) {
+ private void insertAudits(final TableName tableName,
+ final Map<M, Long> entityModelDaoAndHistoryRecordIds,
+ final ChangeType changeType,
+ final InternalCallContext contextMaybeWithoutAccountRecordId) {
final TableName destinationTableName = MoreObjects.firstNonNull(tableName.getHistoryTableName(), tableName);
- final EntityAudit audit = new EntityAudit(destinationTableName, historyRecordId, changeType, contextMaybeWithoutAccountRecordId.getCreatedDate());
final InternalCallContext context;
- // Populate the account record id when creating the account record
if (TableName.ACCOUNT.equals(tableName) && ChangeType.INSERT.equals(changeType)) {
+ Preconditions.checkState(entityModelDaoAndHistoryRecordIds.size() == 1, "Bulk insert of accounts isn't supported");
+ final M entityModelDao = Iterables.<M>getFirst(entityModelDaoAndHistoryRecordIds.keySet(), null);
// AccountModelDao in practice
final TimeZoneAwareEntity accountModelDao = (TimeZoneAwareEntity) entityModelDao;
- context = internalCallContextFactory.createInternalCallContext(accountModelDao, entityRecordId, contextMaybeWithoutAccountRecordId);
+ context = internalCallContextFactory.createInternalCallContext(accountModelDao, entityModelDao.getRecordId(), contextMaybeWithoutAccountRecordId);
+ } else if (contextMaybeWithoutAccountRecordId.getAccountRecordId() == null) {
+ Preconditions.checkState(tableName == TableName.TENANT || tableName == TableName.TENANT_BROADCASTS || tableName == TableName.TENANT_KVS || tableName == TableName.TAG_DEFINITIONS || tableName == TableName.SERVICE_BRODCASTS || tableName == TableName.NODE_INFOS,
+ "accountRecordId should be set for tableName=%s and changeType=%s", tableName, changeType);
+ context = contextMaybeWithoutAccountRecordId;
+
} else {
context = contextMaybeWithoutAccountRecordId;
}
- sqlDao.insertAuditFromTransaction(audit, context);
+
+ final Collection<EntityAudit> audits = new LinkedList<EntityAudit>();
+ for (final M entityModelDao : entityModelDaoAndHistoryRecordIds.keySet()) {
+ final Long targetRecordId = entityModelDaoAndHistoryRecordIds.get(entityModelDao);
+ final EntityAudit audit = new EntityAudit(destinationTableName, targetRecordId, changeType, context.getCreatedDate());
+ audits.add(audit);
+ }
+
+ sqlDao.insertAuditsFromTransaction(audits, context);
printSQLWarnings();
// We need to invalidate the caches. There is a small window of doom here where caches will be stale.
- // TODO Knowledge on how the key is constructed is also in AuditSqlDao
- if (tableName.getHistoryTableName() != null) {
- final CacheController<String, List> cacheController = cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG_VIA_HISTORY);
- if (cacheController != null) {
- final String key = buildCacheKey(ImmutableMap.<Integer, Object>of(0, tableName.getHistoryTableName(), 1, tableName.getHistoryTableName(), 2, entityRecordId));
- cacheController.remove(key);
- }
- } else {
- final CacheController<String, List> cacheController = cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG);
- if (cacheController != null) {
- final String key = buildCacheKey(ImmutableMap.<Integer, Object>of(0, tableName, 1, entityRecordId));
- cacheController.remove(key);
+ for (final M entityModelDao : entityModelDaoAndHistoryRecordIds.keySet()) {
+ final Long entityRecordId = entityModelDao.getRecordId();
+
+ // TODO Knowledge on how the key is constructed is also in AuditSqlDao
+ if (tableName.getHistoryTableName() != null) {
+ final CacheController<String, List> cacheController = cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG_VIA_HISTORY);
+ if (cacheController != null) {
+ final String key = buildCacheKey(ImmutableMap.<Integer, Object>of(0, tableName.getHistoryTableName(), 1, tableName.getHistoryTableName(), 2, entityRecordId));
+ cacheController.remove(key);
+ }
+ } else {
+ final CacheController<String, List> cacheController = cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG);
+ if (cacheController != null) {
+ final String key = buildCacheKey(ImmutableMap.<Integer, Object>of(0, tableName, 1, entityRecordId));
+ cacheController.remove(key);
+ }
}
}
}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.java
index ad4187f..aa83bd7 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.java
@@ -43,7 +43,7 @@ public interface RolesPermissionsSqlDao extends Transactional<RolesPermissionsSq
public void create(@SmartBindBean final RolesPermissionsModelDao rolesPermissions);
@SqlUpdate
- @Audited(ChangeType.UPDATE)
+ @Audited(ChangeType.DELETE)
public void unactiveEvent(@Bind("recordId") final Long recordId,
@Bind("createdDate") final DateTime createdDate,
@Bind("createdBy") final String createdBy);
diff --git a/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
index 5b861e8..ab7f88f 100644
--- a/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
@@ -364,7 +364,7 @@ values (
>>
-insertAuditFromTransaction() ::= <<
+insertAuditsFromTransaction() ::= <<
insert into <auditTableName()> (
<auditTableFields("")>
)
diff --git a/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritance.java b/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritance.java
index c099f56..56caa8d 100644
--- a/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritance.java
+++ b/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritance.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2012 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:
*
@@ -139,33 +141,33 @@ public class TestStringTemplateInheritance extends UtilTestSuiteNoDB {
"\\)\r?\n" +
";");
- assertPattern(kombucha.getInstanceOf("insertAuditFromTransaction").render(), "insert into audit_log \\(\r?\n" +
- "id\r?\n" +
- ", table_name\r?\n" +
- ", target_record_id\r?\n" +
- ", change_type\r?\n" +
- ", created_by\r?\n" +
- ", reason_code\r?\n" +
- ", comments\r?\n" +
- ", user_token\r?\n" +
- ", created_date\r?\n" +
- ", account_record_id\r?\n" +
- ", tenant_record_id\r?\n" +
- "\\)\r?\n" +
- "values \\(\r?\n" +
- " :id\r?\n" +
- ", :tableName\r?\n" +
- ", :targetRecordId\r?\n" +
- ", :changeType\r?\n" +
- ", :createdBy\r?\n" +
- ", :reasonCode\r?\n" +
- ", :comments\r?\n" +
- ", :userToken\r?\n" +
- ", :createdDate\r?\n" +
- ", :accountRecordId\r?\n" +
- ", :tenantRecordId\r?\n" +
- "\\)\r?\n" +
- ";");
+ assertPattern(kombucha.getInstanceOf("insertAuditsFromTransaction").render(), "insert into audit_log \\(\r?\n" +
+ "id\r?\n" +
+ ", table_name\r?\n" +
+ ", target_record_id\r?\n" +
+ ", change_type\r?\n" +
+ ", created_by\r?\n" +
+ ", reason_code\r?\n" +
+ ", comments\r?\n" +
+ ", user_token\r?\n" +
+ ", created_date\r?\n" +
+ ", account_record_id\r?\n" +
+ ", tenant_record_id\r?\n" +
+ "\\)\r?\n" +
+ "values \\(\r?\n" +
+ " :id\r?\n" +
+ ", :tableName\r?\n" +
+ ", :targetRecordId\r?\n" +
+ ", :changeType\r?\n" +
+ ", :createdBy\r?\n" +
+ ", :reasonCode\r?\n" +
+ ", :comments\r?\n" +
+ ", :userToken\r?\n" +
+ ", :createdDate\r?\n" +
+ ", :accountRecordId\r?\n" +
+ ", :tenantRecordId\r?\n" +
+ "\\)\r?\n" +
+ ";");
}
private void assertPattern(final String actual, final String expected) {