killbill-memoizeit
Changes
.circleci/config.yml 30(+1 -29)
invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java 35(+22 -13)
invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java 28(+17 -11)
NEWS 8(+7 -1)
overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java 19(+14 -5)
overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java 1(+1 -0)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 3(+2 -1)
subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg 13(+4 -9)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java 4(+3 -1)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java 8(+5 -3)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java 4(+3 -1)
util/src/main/resources/trimTenant.sql 75(+75 -0)
Details
.circleci/config.yml 30(+1 -29)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index ca7b9fc..ccde348 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -174,46 +174,18 @@ workflows:
version: 2
build-and-test:
jobs:
- - build:
- filters:
- branches:
- only:
- - master
- - work-for-release-0.19.x
- - circle-ci-experiment
+ - build
- test-h2:
requires:
- build
- filters:
- branches:
- only:
- - master
- - circle-ci-experiment
- test-mysql:
requires:
- build
- filters:
- branches:
- only:
- - master
- - work-for-release-0.19.x
- - circle-ci-experiment
- test-postgresql:
requires:
- build
- filters:
- branches:
- only:
- - master
- - circle-ci-experiment
- integration-tests:
requires:
- test-h2
- test-mysql
- test-postgresql
- filters:
- branches:
- only:
- - master
- - work-for-release-0.19.x
- - circle-ci-experiment
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
index 808e0c5..c812bfe 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -352,10 +352,11 @@ public class InvoiceDispatcher {
}
}
} else /* Dry run use cases */ {
-
final NotificationQueue notificationQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
- final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = notificationQueue.getFutureNotificationForSearchKeys(context.getAccountRecordId(), context.getTenantRecordId());
+ final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotificationsIterable = notificationQueue.getFutureNotificationForSearchKeys(context.getAccountRecordId(), context.getTenantRecordId());
+ // Copy the results as retrieving the iterator will issue a query each time. This also makes sure the underlying JDBC connection is closed.
+ final List<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = ImmutableList.<NotificationEventWithMetadata<NextBillingDateNotificationKey>>copyOf(futureNotificationsIterable);
final Map<UUID, DateTime> nextScheduledSubscriptionsEventMap = getNextTransitionsForSubscriptions(billingEvents);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
index fb269c5..6cfcb83 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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.notification;
import java.io.IOException;
+import java.util.Iterator;
import java.util.UUID;
import org.joda.time.DateTime;
@@ -84,19 +85,27 @@ public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = nextBillingQueue.getFutureNotificationFromTransactionForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId(), entitySqlDaoWrapperFactory.getHandle().getConnection());
NotificationEventWithMetadata<NextBillingDateNotificationKey> existingNotificationForEffectiveDate = null;
- for (final NotificationEventWithMetadata<NextBillingDateNotificationKey> input : futureNotifications) {
- final boolean isEventDryRunForNotifications = input.getEvent().isDryRunForInvoiceNotification() != null ?
- input.getEvent().isDryRunForInvoiceNotification() : false;
-
- final LocalDate notificationEffectiveLocaleDate = internalCallContext.toLocalDate(futureNotificationTime);
- final LocalDate eventEffectiveLocaleDate = internalCallContext.toLocalDate(input.getEffectiveDate());
-
- if (notificationEffectiveLocaleDate.compareTo(eventEffectiveLocaleDate) == 0 &&
- ((isDryRunForInvoiceNotification && isEventDryRunForNotifications) ||
- (!isDryRunForInvoiceNotification && !isEventDryRunForNotifications))) {
- existingNotificationForEffectiveDate = input;
+ final Iterator<NotificationEventWithMetadata<NextBillingDateNotificationKey>> iterator = futureNotifications.iterator();
+ try {
+ while (iterator.hasNext()) {
+ final NotificationEventWithMetadata<NextBillingDateNotificationKey> input = iterator.next();
+ final boolean isEventDryRunForNotifications = input.getEvent().isDryRunForInvoiceNotification() != null ?
+ input.getEvent().isDryRunForInvoiceNotification() : false;
+
+ final LocalDate notificationEffectiveLocaleDate = internalCallContext.toLocalDate(futureNotificationTime);
+ final LocalDate eventEffectiveLocaleDate = internalCallContext.toLocalDate(input.getEffectiveDate());
+
+ if (notificationEffectiveLocaleDate.compareTo(eventEffectiveLocaleDate) == 0 &&
+ ((isDryRunForInvoiceNotification && isEventDryRunForNotifications) ||
+ (!isDryRunForInvoiceNotification && !isEventDryRunForNotifications))) {
+ existingNotificationForEffectiveDate = input;
+ }
}
+ } finally {
// Go through all results to close the connection
+ while (iterator.hasNext()) {
+ iterator.next();
+ }
}
if (existingNotificationForEffectiveDate == null) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java
index 4a5d7e9..b8c7d76 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -18,6 +18,7 @@
package org.killbill.billing.invoice.notification;
import java.io.IOException;
+import java.util.Iterator;
import java.util.UUID;
import org.joda.time.DateTime;
@@ -58,17 +59,22 @@ public class ParentInvoiceCommitmentPoster {
final Iterable<NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey>> futureNotifications = commitInvoiceQueue.getFutureNotificationFromTransactionForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId(), entitySqlDaoWrapperFactory.getHandle().getConnection());
boolean existingFutureNotificationWithSameDateAndInvoiceId = false;
- for (final NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey> input : futureNotifications) {
-
-
-
- final LocalDate notificationEffectiveLocaleDate = internalCallContext.toLocalDate(futureNotificationTime);
- final LocalDate eventEffectiveLocaleDate = internalCallContext.toLocalDate(input.getEffectiveDate());
-
- if (notificationEffectiveLocaleDate.compareTo(eventEffectiveLocaleDate) == 0 && input.getEvent().getUuidKey().equals(invoiceId)) {
- existingFutureNotificationWithSameDateAndInvoiceId = true;
+ final Iterator<NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey>> iterator = futureNotifications.iterator();
+ try {
+ while (iterator.hasNext()) {
+ final NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey> input = iterator.next();
+ final LocalDate notificationEffectiveLocaleDate = internalCallContext.toLocalDate(futureNotificationTime);
+ final LocalDate eventEffectiveLocaleDate = internalCallContext.toLocalDate(input.getEffectiveDate());
+
+ if (notificationEffectiveLocaleDate.compareTo(eventEffectiveLocaleDate) == 0 && input.getEvent().getUuidKey().equals(invoiceId)) {
+ existingFutureNotificationWithSameDateAndInvoiceId = true;
+ }
}
+ } finally {
// Go through all results to close the connection
+ while (iterator.hasNext()) {
+ iterator.next();
+ }
}
if (!existingFutureNotificationWithSameDateAndInvoiceId) {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
index a73a9b9..5e01d3c 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -18,12 +18,15 @@
package org.killbill.billing.jaxrs.resources;
+import java.util.Iterator;
+
import javax.inject.Inject;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
+import javax.ws.rs.HEAD;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
@@ -253,12 +256,21 @@ public class TestResource extends JaxRsResourceBase {
private boolean areAllNotificationsProcessed(final Long tenantRecordId) {
int nbNotifications = 0;
- for (final NotificationQueue notificationQueue : notificationQueueService.getNotificationQueues()) {
- for (final NotificationEventWithMetadata<NotificationEvent> notificationEvent : notificationQueue.getFutureOrInProcessingNotificationForSearchKey2(null, tenantRecordId)) {
- if (!notificationEvent.getEffectiveDate().isAfter(clock.getUTCNow())) {
- nbNotifications += 1;
+ final Iterator<NotificationQueue> iterator = notificationQueueService.getNotificationQueues().iterator();
+ try {
+ while (iterator.hasNext()) {
+ final NotificationQueue notificationQueue = iterator.next();
+ for (final NotificationEventWithMetadata<NotificationEvent> notificationEvent : notificationQueue.getFutureOrInProcessingNotificationForSearchKey2(null, tenantRecordId)) {
+ if (!notificationEvent.getEffectiveDate().isAfter(clock.getUTCNow())) {
+ nbNotifications += 1;
+ }
}
}
+ } finally {
+ // Go through all results to close the connection
+ while (iterator.hasNext()) {
+ iterator.next();
+ }
}
if (nbNotifications != 0) {
log.info("TestResource: {} queue(s) with more notification(s) to process", nbNotifications);
@@ -268,6 +280,7 @@ public class TestResource extends JaxRsResourceBase {
private boolean areAllBusEventsProcessed(final Long tenantRecordId) {
final Iterable<BusEventWithMetadata<BusEvent>> availableBusEventForSearchKey2 = persistentBus.getAvailableOrInProcessingBusEventsForSearchKey2(null, tenantRecordId);
+ // This will go through all results to close the connection
final int nbBusEvents = Iterables.size(availableBusEventForSearchKey2);
if (nbBusEvents != 0) {
log.info("TestResource: at least {} more bus event(s) to process", nbBusEvents);
NEWS 8(+7 -1)
diff --git a/NEWS b/NEWS
index 4827589..1be25b9 100644
--- a/NEWS
+++ b/NEWS
@@ -3,7 +3,13 @@
Fix connection leak (#558)
Fix limitation where catalog plan name cannot end with an number (#842)
Fix missing Invoice Notification when we have future billing events (#846)
- Rreduce log level of InvoiceItemGeneratorLogger (#851)
+ Reduce log level of InvoiceItemGeneratorLogger (#851)
+
+0.18.17
+ Relax sanity checks for STANDALONE subscriptions #840
+ Fix JDBC connection leak in pagination API #853
+ Fix limitation where catalog plan name cannot end with an number #842
+ Reduce log level of InvoiceItemGeneratorLogger #851
0.18.16
See https://github.com/killbill/killbill/releases/tag/killbill-0.18.16
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java b/overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java
index d06c167..fc14f47 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -18,6 +18,7 @@
package org.killbill.billing.overdue.notification;
+import java.util.Iterator;
import java.util.UUID;
import org.joda.time.DateTime;
@@ -93,10 +94,18 @@ public abstract class DefaultOverduePosterBase implements OverduePoster {
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final Iterable<NotificationEventWithMetadata<T>> futureNotifications = getFutureNotificationsForAccountInTransaction(entitySqlDaoWrapperFactory, checkOverdueQueue,
clazz, context);
- for (final NotificationEventWithMetadata<T> notification : futureNotifications) {
- checkOverdueQueue.removeNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), notification.getRecordId());
+ final Iterator<NotificationEventWithMetadata<T>> iterator = futureNotifications.iterator();
+ try {
+ while (iterator.hasNext()) {
+ final NotificationEventWithMetadata<T> notification = iterator.next();
+ checkOverdueQueue.removeNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), notification.getRecordId());
+ }
+ } finally {
+ // Go through all results to close the connection
+ while (iterator.hasNext()) {
+ iterator.next();
+ }
}
-
return null;
}
});
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckPoster.java b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckPoster.java
index 25d7eff..3fc227a 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckPoster.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckPoster.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -18,6 +18,8 @@
package org.killbill.billing.overdue.notification;
+import java.util.Iterator;
+
import org.joda.time.DateTime;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
@@ -48,23 +50,32 @@ public class OverdueCheckPoster extends DefaultOverduePosterBase {
boolean shouldInsertNewNotification = true;
int minIndexToDeleteFrom = 0;
int index = 0;
- for (final NotificationEventWithMetadata<T> cur : futureNotifications) {
- // Results are ordered by effective date asc
- if (index == 0) {
- if (cur.getEffectiveDate().isBefore(futureNotificationTime)) {
- // We don't have to insert a new one. For sanity, delete any other future notification
- minIndexToDeleteFrom = 1;
- shouldInsertNewNotification = false;
- } else {
- // We win - we are before any other already recorded. Delete all others.
- minIndexToDeleteFrom = 0;
+ final Iterator<NotificationEventWithMetadata<T>> iterator = futureNotifications.iterator();
+ try {
+ while (iterator.hasNext()) {
+ final NotificationEventWithMetadata<T> cur = iterator.next();
+ // Results are ordered by effective date asc
+ if (index == 0) {
+ if (cur.getEffectiveDate().isBefore(futureNotificationTime)) {
+ // We don't have to insert a new one. For sanity, delete any other future notification
+ minIndexToDeleteFrom = 1;
+ shouldInsertNewNotification = false;
+ } else {
+ // We win - we are before any other already recorded. Delete all others.
+ minIndexToDeleteFrom = 0;
+ }
}
- }
- if (minIndexToDeleteFrom <= index) {
- overdueQueue.removeNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), cur.getRecordId());
+ if (minIndexToDeleteFrom <= index) {
+ overdueQueue.removeNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), cur.getRecordId());
+ }
+ index++;
+ }
+ } finally {
+ // Go through all results to close the connection
+ while (iterator.hasNext()) {
+ iterator.next();
}
- index++;
}
return shouldInsertNewNotification;
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java b/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java
index 1a7b3bd..ca991a2 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java
@@ -92,6 +92,7 @@ public class TestDefaultOverdueCheckPoster extends OverdueTestSuiteWithEmbeddedD
return entitySqlDaoTransactionalJdbiWrapper.execute(new EntitySqlDaoTransactionWrapper<List<NotificationEventWithMetadata<OverdueCheckNotificationKey>>>() {
@Override
public List<NotificationEventWithMetadata<OverdueCheckNotificationKey>> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ // This will go through all results to close the connection
return ImmutableList.<NotificationEventWithMetadata<OverdueCheckNotificationKey>>copyOf(((OverdueCheckPoster) checkPoster).getFutureNotificationsForAccountInTransaction(entitySqlDaoWrapperFactory, overdueQueue, OverdueCheckNotificationKey.class, internalCallContext));
}
});
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
index 4bd018a..ecaa6d8 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,6 +24,7 @@ import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -411,11 +412,19 @@ public class PaymentProcessor extends ProcessorBase {
final Iterable<NotificationEventWithMetadata<NotificationEvent>> notificationEventWithMetadatas =
retryQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
- for (final NotificationEventWithMetadata<NotificationEvent> notificationEvent : notificationEventWithMetadatas) {
- if (((PaymentRetryNotificationKey) notificationEvent.getEvent()).getAttemptId().equals(lastPaymentAttemptId)) {
- retryQueue.removeNotification(notificationEvent.getRecordId());
+ final Iterator<NotificationEventWithMetadata<NotificationEvent>> iterator = notificationEventWithMetadatas.iterator();
+ try {
+ while (iterator.hasNext()) {
+ final NotificationEventWithMetadata<NotificationEvent> notificationEvent = iterator.next();
+ if (((PaymentRetryNotificationKey) notificationEvent.getEvent()).getAttemptId().equals(lastPaymentAttemptId)) {
+ retryQueue.removeNotification(notificationEvent.getRecordId());
+ }
}
+ } finally {
// Go through all results to close the connection
+ while (iterator.hasNext()) {
+ iterator.next();
+ }
}
} catch (final NoSuchNotificationQueue noSuchNotificationQueue) {
log.error("ERROR Loading Notification Queue - " + noSuchNotificationQueue.getMessage());
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
index 8e0f0fe..27a8bde 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,7 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
import org.killbill.billing.payment.api.Payment;
@@ -94,7 +95,8 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
assertEquals(retrievedAttempts.get(0).getPluginName(), pluginName);
}
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testPaymentAndTransactions() {
final UUID paymentMethodId = UUID.randomUUID();
final UUID accountId = UUID.randomUUID();
@@ -293,7 +295,8 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
assertEquals(deletedPaymentMethod.getPluginName(), pluginName);
}
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testPendingTransactions() {
final UUID paymentMethodId = UUID.randomUUID();
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
index d09c923..086918e 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -20,6 +20,7 @@ package org.killbill.billing.payment;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -28,6 +29,7 @@ import java.util.concurrent.TimeUnit;
import org.joda.time.LocalDate;
import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.api.TestApiListener;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.callcontext.InternalCallContext;
@@ -414,7 +416,8 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
}
// The test will check that when a PENDING entry stays PENDING, we go through all our retries and eventually give up (no infinite loop of retries)
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testPendingEntriesThatDontMove() throws Exception {
final BigDecimal requestedAmount = BigDecimal.TEN;
@@ -447,13 +450,13 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
// Verify there is a notification to retry updating the value
assertEquals(getPendingNotificationCnt(internalCallContext), 1);
- clock.addDeltaFromReality(cur.getMillis() + 1);
+ clock.addDeltaFromReality(cur.getMillis() + 1000);
assertNotificationsCompleted(internalCallContext, 5);
// We add a sleep here to make sure the notification gets processed. Note that calling assertNotificationsCompleted alone would not work
// because there is a point in time where the notification queue is empty (showing notification was processed), but the processing of the notification
// will itself enter a new notification, and so the synchronization is difficult without writing *too much code*.
- Thread.sleep(1000);
+ Thread.sleep(1500);
assertNotificationsCompleted(internalCallContext, 5);
final Payment updatedPayment = paymentApi.getPayment(payment.getId(), false, false, ImmutableList.<PluginProperty>of(), callContext);
@@ -510,11 +513,19 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
@Override
public Boolean call() throws Exception {
boolean completed = true;
- for (final NotificationEventWithMetadata<NotificationEvent> notificationEvent : notificationQueueService.getNotificationQueue(DefaultPaymentService.SERVICE_NAME, Janitor.QUEUE_NAME).getFutureOrInProcessingNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId())) {
- if (!notificationEvent.getEffectiveDate().isAfter(clock.getUTCNow())) {
- completed = false;
+ final Iterator<NotificationEventWithMetadata<NotificationEvent>> iterator = notificationQueueService.getNotificationQueue(DefaultPaymentService.SERVICE_NAME, Janitor.QUEUE_NAME).getFutureOrInProcessingNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId()).iterator();
+ try {
+ while (iterator.hasNext()) {
+ final NotificationEventWithMetadata<NotificationEvent> notificationEvent = iterator.next();
+ if (!notificationEvent.getEffectiveDate().isAfter(clock.getUTCNow())) {
+ completed = false;
+ }
}
+ } finally {
// Go through all results to close the connection
+ while (iterator.hasNext()) {
+ iterator.next();
+ }
}
return completed;
}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java
index 4b32eba..5c53689 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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 javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.client.KillBillClientException;
import org.killbill.billing.client.model.TenantKey;
import org.killbill.billing.jaxrs.json.NotificationJson;
@@ -152,7 +153,8 @@ public class TestPushNotification extends TestJaxrsBase {
return callback;
}
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testPushNotificationRetries() throws Exception {
final String callback = registerTenantForCallback();
@@ -205,7 +207,8 @@ public class TestPushNotification extends TestJaxrsBase {
unregisterTenantForCallback(callback);
}
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testPushNotificationRetriesMaxAttemptNumber() throws Exception {
final String callback = registerTenantForCallback();
@@ -231,16 +234,16 @@ public class TestPushNotification extends TestJaxrsBase {
resetCallbackStatusProperties();
- // move clock 15 minutes and get 1st retry
- clock.addDeltaFromReality(900000);
+ // move clock 15 minutes (+10s for flakiness) and get 1st retry
+ clock.addDeltaFromReality(910000);
assertAllCallbacksCompleted();
Assert.assertTrue(callbackCompletedWithError);
resetCallbackStatusProperties();
- // move clock an hour and get 2nd retry
- clock.addDeltaFromReality(3600000);
+ // move clock an hour (+10s for flakiness) and get 2nd retry
+ clock.addDeltaFromReality(3610000);
assertAllCallbacksCompleted();
Assert.assertTrue(callbackCompletedWithError);
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 3af29b0..c56df6e 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
@@ -295,7 +295,8 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
@Override
public SubscriptionBaseBundle inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- final List<SubscriptionBundleModelDao> existingBundles = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getBundlesForLikeKey(bundle.getExternalKey(), context);
+ final List<SubscriptionBundleModelDao> existingBundles = bundle.getExternalKey() == null ? ImmutableList.<SubscriptionBundleModelDao>of()
+ : entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getBundlesForLikeKey(bundle.getExternalKey(), context);
final SubscriptionBaseBundle unusedBundle = findExistingUnusedBundleForExternalKeyAndAccount(existingBundles, entitySqlDaoWrapperFactory);
if (unusedBundle != null) {
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 369469b..ecfeb0f 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
@@ -49,15 +49,10 @@ where id = :id
renameBundleExternalKey(prefix) ::= <<
-update bundles b
-join (select
- record_id
- , external_key
- from
- bundles
- where external_key = :externalKey <AND_CHECK_TENANT("")>) t
-on b.record_id = t.record_id
-set b.external_key = concat('kb', '<prefix>', '-', t.record_id, ':', t.external_key)
+update bundles
+set external_key = concat('kb', '<prefix>', '-', record_id, ':', external_key)
+where external_key = :externalKey
+<AND_CHECK_TENANT("")>
;
>>
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 4ed3a8f..8174045 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
@@ -24,6 +24,7 @@ import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.LocalDate;
import org.killbill.billing.ErrorCode;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
@@ -205,7 +206,8 @@ public class TestUserApiCancel extends SubscriptionTestSuiteWithEmbeddedDB {
// Similar test to testCancelSubscriptionEOTWithChargeThroughDate except we uncancel and check things
// are as they used to be and we can move forward without hitting cancellation
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testUncancel() throws SubscriptionBillingApiException, SubscriptionBaseApiException {
final String prod = "Shotgun";
final BillingPeriod term = BillingPeriod.MONTHLY;
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
index 143041e..ba1229a 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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.api.user;
+import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;
@@ -34,7 +37,6 @@ import org.killbill.billing.subscription.DefaultSubscriptionTestInitializer;
import org.killbill.billing.subscription.SubscriptionTestSuiteWithEmbeddedDB;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.subscription.events.phase.PhaseEvent;
-import org.mariadb.jdbc.internal.util.dao.QueryException;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -82,7 +84,7 @@ public class TestUserApiCreate extends SubscriptionTestSuiteWithEmbeddedDB {
subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), DefaultSubscriptionTestInitializer.DEFAULT_BUNDLE_KEY, false, internalCallContext);
Assert.fail("createBundleForAccount should fail because key already exists");
} catch (final RuntimeException e) {
- assertTrue(e.getCause() instanceof SQLIntegrityConstraintViolationException);
+ assertTrue(e.getCause() instanceof SQLException && (e.getCause() instanceof SQLIntegrityConstraintViolationException || "23505".compareTo(((SQLException) e.getCause()).getSQLState()) == 0));
}
final SubscriptionBaseBundle newBundle = subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), DefaultSubscriptionTestInitializer.DEFAULT_BUNDLE_KEY, true, internalCallContext);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
index 04f0ab5..79bb057 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
@@ -25,6 +25,7 @@ import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.killbill.billing.ErrorCode;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
@@ -106,7 +107,8 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
}
}
- @Test(groups = "fast")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "fast", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testChangeSubscriptionNonActive() throws SubscriptionBaseApiException {
final SubscriptionBase subscription = testUtil.createSubscription(bundle, "Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME);
util/src/main/resources/trimTenant.sql 75(+75 -0)
diff --git a/util/src/main/resources/trimTenant.sql b/util/src/main/resources/trimTenant.sql
new file mode 100644
index 0000000..d0bfb0c
--- /dev/null
+++ b/util/src/main/resources/trimTenant.sql
@@ -0,0 +1,75 @@
+drop procedure if exists trimTenant;
+DELIMITER //
+CREATE PROCEDURE trimTenant(p_api_key varchar(36))
+BEGIN
+
+ DECLARE v_tenant_record_id bigint /*! unsigned */;
+
+ select record_id from tenants WHERE api_key = p_api_key into v_tenant_record_id;
+
+ DELETE FROM analytics_account_fields WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_account_tags WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_account_transitions WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_accounts WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_bundle_fields WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_bundle_tags WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_bundles WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_invoice_adjustments WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_invoice_credits WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_invoice_fields WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_invoice_item_adjustments WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_invoice_items WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_invoice_payment_fields WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_invoice_tags WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_invoices WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_notifications WHERE search_key2 = v_tenant_record_id;
+ DELETE FROM analytics_notifications_history WHERE search_key2 = v_tenant_record_id;
+ DELETE FROM analytics_payment_auths WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_captures WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_chargebacks WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_credits WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_fields WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_method_fields WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_purchases WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_refunds WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_tags WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_voids WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_subscription_transitions WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_transaction_fields WHERE tenant_record_id = v_tenant_record_id;
+
+ DELETE FROM account_email_history WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM account_emails WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM account_history WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM accounts WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM audit_log WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM blocking_states WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM bundles WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM bus_events WHERE search_key2 = v_tenant_record_id;
+ DELETE FROM bus_events_history WHERE search_key2 = v_tenant_record_id;
+ DELETE FROM bus_ext_events WHERE search_key2 = v_tenant_record_id;
+ DELETE FROM bus_ext_events_history WHERE search_key2 = v_tenant_record_id;
+ DELETE FROM custom_field_history WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM custom_fields WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM invoice_items WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM invoice_parent_children WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM invoice_payments WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM invoices WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM notifications WHERE search_key2 = v_tenant_record_id;
+ DELETE FROM notifications_history WHERE search_key2 = v_tenant_record_id;
+ DELETE FROM payment_attempt_history WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM payment_attempts WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM payment_history WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM payment_method_history WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM payment_methods WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM payment_transaction_history WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM payment_transactions WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM payments WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM rolled_up_usage WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM subscription_events WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM subscriptions WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM tag_history WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM tags WHERE tenant_record_id = v_tenant_record_id;
+
+ END;
+//
+DELIMITER ;
diff --git a/util/src/test/java/org/killbill/billing/DBTestingHelper.java b/util/src/test/java/org/killbill/billing/DBTestingHelper.java
index 2808866..d940406 100644
--- a/util/src/test/java/org/killbill/billing/DBTestingHelper.java
+++ b/util/src/test/java/org/killbill/billing/DBTestingHelper.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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,18 +19,27 @@
package org.killbill.billing;
import java.io.IOException;
+import java.io.PrintWriter;
import java.net.URL;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.SQLNonTransientConnectionException;
import java.util.Enumeration;
-import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.sql.DataSource;
import org.killbill.billing.platform.test.PlatformDBTestingHelper;
import org.killbill.billing.util.glue.IDBISetup;
import org.killbill.billing.util.io.IOUtils;
import org.killbill.commons.embeddeddb.EmbeddedDB;
+import org.killbill.commons.jdbi.guice.DBIProvider;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.ResultSetMapperFactory;
import org.skife.jdbi.v2.tweak.ResultSetMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.common.base.MoreObjects;
@@ -38,7 +47,7 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
private static DBTestingHelper dbTestingHelper = null;
- private AtomicBoolean initialized;
+ private DBI dbi;
public static synchronized DBTestingHelper get() {
if (dbTestingHelper == null) {
@@ -49,18 +58,18 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
private DBTestingHelper() {
super();
- initialized = new AtomicBoolean(false);
}
@Override
- public IDBI getDBI() {
- final DBI dbi = (DBI) super.getDBI();
- // Register KB specific mappers
- if (initialized.compareAndSet(false, true)) {
+ public synchronized IDBI getDBI() {
+ if (dbi == null) {
+ final RetryableDataSource retryableDataSource = new RetryableDataSource(getDataSource());
+ dbi = (DBI) new DBIProvider(null, retryableDataSource, null).get();
+
+ // Register KB specific mappers
for (final ResultSetMapperFactory resultSetMapperFactory : IDBISetup.mapperFactoriesToRegister()) {
dbi.registerMapper(resultSetMapperFactory);
}
-
for (final ResultSetMapper resultSetMapper : IDBISetup.mappersToRegister()) {
dbi.registerMapper(resultSetMapper);
}
@@ -202,4 +211,73 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
}
}
}
+
+ // DataSource which will retry recreating a connection once in case of a connection exception.
+ // This is useful for transient network errors in tests when using a separate database (e.g. Docker container),
+ // as we don't use a connection pool.
+ private static final class RetryableDataSource implements DataSource {
+
+ private static final Logger logger = LoggerFactory.getLogger(RetryableDataSource.class);
+
+ private final DataSource delegate;
+
+ private RetryableDataSource(final DataSource delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ try {
+ return delegate.getConnection();
+ } catch (final SQLNonTransientConnectionException e) {
+ logger.warn("Unable to retrieve connection, attempting to retry", e);
+ return delegate.getConnection();
+ }
+ }
+
+ @Override
+ public Connection getConnection(final String username, final String password) throws SQLException {
+ try {
+ return delegate.getConnection(username, password);
+ } catch (final SQLNonTransientConnectionException e) {
+ logger.warn("Unable to retrieve connection, attempting to retry", e);
+ return delegate.getConnection(username, password);
+ }
+ }
+
+ @Override
+ public <T> T unwrap(final Class<T> iface) throws SQLException {
+ return delegate.unwrap(iface);
+ }
+
+ @Override
+ public boolean isWrapperFor(final Class<?> iface) throws SQLException {
+ return delegate.isWrapperFor(iface);
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws SQLException {
+ return delegate.getLogWriter();
+ }
+
+ @Override
+ public void setLogWriter(final PrintWriter out) throws SQLException {
+ delegate.setLogWriter(out);
+ }
+
+ @Override
+ public void setLoginTimeout(final int seconds) throws SQLException {
+ delegate.setLoginTimeout(seconds);
+ }
+
+ @Override
+ public int getLoginTimeout() throws SQLException {
+ return delegate.getLoginTimeout();
+ }
+
+ //@Override
+ public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
+ throw new SQLFeatureNotSupportedException("javax.sql.DataSource.getParentLogger() is not currently supported by " + this.getClass().getName());
+ }
+ }
}