killbill-memoizeit

Details

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 00af476..31b4556 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -903,16 +903,19 @@ public class InvoiceDispatcher {
             public FutureAccountNotificationsBuilder() {
             }
 
-            public void setNotificationListForTrigger(final Map<LocalDate, Set<UUID>> notificationListForTrigger) {
+            public FutureAccountNotificationsBuilder setNotificationListForTrigger(final Map<LocalDate, Set<UUID>> notificationListForTrigger) {
                 this.notificationListForTrigger = notificationListForTrigger;
+                return this;
             }
 
-            public void setNotificationListForDryRun(final Map<LocalDate, Set<UUID>> notificationListForDryRun) {
+            public FutureAccountNotificationsBuilder setNotificationListForDryRun(final Map<LocalDate, Set<UUID>> notificationListForDryRun) {
                 this.notificationListForDryRun = notificationListForDryRun;
+                return this;
             }
 
-            public void setRescheduled(final boolean rescheduled) {
+            public FutureAccountNotificationsBuilder setRescheduled(final boolean rescheduled) {
                 isRescheduled = rescheduled;
+                return this;
             }
 
             public Map<LocalDate, Set<UUID>> getNotificationListForTrigger() {
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDatePoster.java b/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDatePoster.java
new file mode 100644
index 0000000..b9bf095
--- /dev/null
+++ b/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDatePoster.java
@@ -0,0 +1,202 @@
+/*
+ * 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
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.notification;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
+import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.FutureAccountNotificationsBuilder;
+import org.killbill.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
+import org.killbill.billing.invoice.api.DefaultInvoiceService;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.subscription.api.SubscriptionBase;
+import org.killbill.notificationq.api.NotificationEventWithMetadata;
+import org.killbill.notificationq.api.NotificationQueue;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class TestNextBillingDatePoster extends InvoiceTestSuiteWithEmbeddedDB {
+
+    protected KillbillConfigSource getConfigSource() {
+        final ImmutableMap<String, String> extraProperties = ImmutableMap.<String, String>builder()
+                .put("org.killbill.invoice.dryRunNotificationSchedule", "48h")
+                .build();
+        return getConfigSource("/resource.properties", extraProperties);
+    }
+
+    @Test(groups = "slow")
+    public void testDryRunReInsertion() throws Exception {
+        final Account account = invoiceUtil.createAccount(callContext);
+        final Long accountRecordId = nonEntityDao.retrieveAccountRecordIdFromObject(account.getId(), ObjectType.ACCOUNT, null);
+
+        final DateTime now = clock.getUTCNow();
+        final LocalDate nowLocal = internalCallContext.toLocalDate(now);
+
+        final SubscriptionBase subscription = invoiceUtil.createSubscription();
+        final UUID subscriptionId = subscription.getId();
+
+        final FutureAccountNotifications futureAccountNotifications = createFutureAccountNotifications(subscriptionId, nowLocal);
+
+        // Add 3 seconds to make it more interesting
+        clock.addDeltaFromReality(3000);
+
+        invoiceDao.setFutureAccountNotificationsForEmptyInvoice(account.getId(), futureAccountNotifications, internalCallContext);
+
+
+        // Add 3 seconds to make it more interesting
+        clock.addDeltaFromReality(3000);
+        invoiceDao.setFutureAccountNotificationsForEmptyInvoice(account.getId(), futureAccountNotifications, internalCallContext);
+
+        final NotificationQueue nextBillingQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
+                                                                                                 DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
+        final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = nextBillingQueue.getFutureNotificationForSearchKeys(accountRecordId, internalCallContext.getTenantRecordId());
+        final ImmutableList<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotificationsList = ImmutableList.copyOf(futureNotifications);
+        Assert.assertEquals(futureNotificationsList.size(), 1);
+
+        // We expect only one notification for which effectiveDate matches our original effectiveDate (conversion DateTime -> LocalDate -> DateTime)
+        final NotificationEventWithMetadata<NextBillingDateNotificationKey> notification = futureNotificationsList.get(0);
+        Assert.assertEquals(notification.getEffectiveDate(), internalCallContext.toUTCDateTime(nowLocal));
+
+        final Iterable<UUID> uuidKeys = notification.getEvent().getUuidKeys();
+        Assert.assertFalse(Iterables.isEmpty(uuidKeys));
+        final List<UUID> uuidKeysList = ImmutableList.copyOf(uuidKeys);
+        Assert.assertEquals(uuidKeysList.size(), 1);
+        Assert.assertEquals(uuidKeysList.get(0), subscriptionId);
+    }
+
+    @Test(groups = "slow")
+    public void testDryRunUpdateWithNewSubscription() throws Exception {
+        final Account account = invoiceUtil.createAccount(callContext);
+        final Long accountRecordId = nonEntityDao.retrieveAccountRecordIdFromObject(account.getId(), ObjectType.ACCOUNT, null);
+
+        final DateTime now = clock.getUTCNow();
+        final LocalDate nowLocal = internalCallContext.toLocalDate(now);
+
+        final SubscriptionBase subscription1 = invoiceUtil.createSubscription();
+        final UUID subscriptionId1 = subscription1.getId();
+
+        final FutureAccountNotifications futureAccountNotifications1 = createFutureAccountNotifications(subscriptionId1, nowLocal);
+        invoiceDao.setFutureAccountNotificationsForEmptyInvoice(account.getId(), futureAccountNotifications1, internalCallContext);
+
+        final SubscriptionBase subscription2 = invoiceUtil.createSubscription();
+        final UUID subscriptionId2 = subscription2.getId();
+
+        final FutureAccountNotifications futureAccountNotifications2 = createFutureAccountNotifications(subscriptionId2, nowLocal);
+        invoiceDao.setFutureAccountNotificationsForEmptyInvoice(account.getId(), futureAccountNotifications2, internalCallContext);
+
+
+        final NotificationQueue nextBillingQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
+                                                                                                 DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
+
+        final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = nextBillingQueue.getFutureNotificationForSearchKeys(accountRecordId, internalCallContext.getTenantRecordId());
+        final ImmutableList<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotificationsList = ImmutableList.copyOf(futureNotifications);
+        Assert.assertEquals(futureNotificationsList.size(), 1);
+
+        // We expect only one notification but this time we should see a list with both subscriptionIds.
+        final NotificationEventWithMetadata<NextBillingDateNotificationKey> notification = futureNotificationsList.get(0);
+        Assert.assertEquals(notification.getEffectiveDate(), internalCallContext.toUTCDateTime(nowLocal));
+
+        final Iterable<UUID> uuidKeys = notification.getEvent().getUuidKeys();
+        Assert.assertFalse(Iterables.isEmpty(uuidKeys));
+        final List<UUID> uuidKeysList = ImmutableList.copyOf(uuidKeys);
+        Assert.assertEquals(uuidKeysList.size(), 2);
+        Assert.assertEquals(uuidKeysList.get(0), subscriptionId1);
+        Assert.assertEquals(uuidKeysList.get(1), subscriptionId2);
+    }
+
+
+    @Test(groups = "slow")
+    public void testDryRunWithSameSubscriptionLater() throws Exception {
+        final Account account = invoiceUtil.createAccount(callContext);
+        final Long accountRecordId = nonEntityDao.retrieveAccountRecordIdFromObject(account.getId(), ObjectType.ACCOUNT, null);
+
+        final DateTime now = clock.getUTCNow();
+        final LocalDate nowLocal = internalCallContext.toLocalDate(now);
+
+        final SubscriptionBase subscription = invoiceUtil.createSubscription();
+        final UUID subscriptionId = subscription.getId();
+
+        final FutureAccountNotifications futureAccountNotifications1 = createFutureAccountNotifications(subscriptionId, nowLocal);
+
+        // Add 3 seconds to make it more interesting
+        clock.addDeltaFromReality(3000);
+
+        invoiceDao.setFutureAccountNotificationsForEmptyInvoice(account.getId(), futureAccountNotifications1, internalCallContext);
+
+
+        clock.addDays(1);
+        final DateTime newNow = clock.getUTCNow();
+        final LocalDate newNowLocal = internalCallContext.toLocalDate(newNow);
+
+        final FutureAccountNotifications futureAccountNotifications2 = createFutureAccountNotifications(subscriptionId, newNowLocal);
+
+
+        invoiceDao.setFutureAccountNotificationsForEmptyInvoice(account.getId(), futureAccountNotifications2, internalCallContext);
+
+        final NotificationQueue nextBillingQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
+                                                                                                 DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
+        final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = nextBillingQueue.getFutureNotificationForSearchKeys(accountRecordId, internalCallContext.getTenantRecordId());
+        final ImmutableList<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotificationsList = ImmutableList.copyOf(futureNotifications);
+        // We expect only two notifications, one for each date
+        Assert.assertEquals(futureNotificationsList.size(), 2);
+
+        final NotificationEventWithMetadata<NextBillingDateNotificationKey> notification1 = futureNotificationsList.get(0);
+        Assert.assertEquals(notification1.getEffectiveDate(), internalCallContext.toUTCDateTime(nowLocal));
+
+        final Iterable<UUID> uuidKeys1 = notification1.getEvent().getUuidKeys();
+        Assert.assertFalse(Iterables.isEmpty(uuidKeys1));
+        final List<UUID> uuidKeysList1 = ImmutableList.copyOf(uuidKeys1);
+        Assert.assertEquals(uuidKeysList1.size(), 1);
+        Assert.assertEquals(uuidKeysList1.get(0), subscriptionId);
+
+
+        final NotificationEventWithMetadata<NextBillingDateNotificationKey> notification2 = futureNotificationsList.get(1);
+        Assert.assertEquals(notification2.getEffectiveDate(), internalCallContext.toUTCDateTime(newNowLocal));
+
+        final Iterable<UUID> uuidKeys2 = notification2.getEvent().getUuidKeys();
+        Assert.assertFalse(Iterables.isEmpty(uuidKeys2));
+        final List<UUID> uuidKeysList2 = ImmutableList.copyOf(uuidKeys2);
+        Assert.assertEquals(uuidKeysList2.size(), 1);
+        Assert.assertEquals(uuidKeysList2.get(0), subscriptionId);
+
+    }
+
+    private FutureAccountNotifications createFutureAccountNotifications(final UUID subscriptionId, final LocalDate notificationDate) {
+        final Map<LocalDate, Set<UUID>> notificationListForDryRun = new HashMap<LocalDate, Set<UUID>>();
+        notificationListForDryRun.put(notificationDate, ImmutableSet.<UUID>of(subscriptionId));
+
+        return new FutureAccountNotificationsBuilder()
+                .setNotificationListForDryRun(notificationListForDryRun)
+                .build();
+
+    }
+
+}

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index 0737bf3..2755c95 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.141.62</version>
+        <version>0.141.63</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.19.14-SNAPSHOT</version>
diff --git a/util/src/main/java/org/killbill/billing/util/dao/TableName.java b/util/src/main/java/org/killbill/billing/util/dao/TableName.java
index 041a38a..f1f51a0 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/TableName.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/TableName.java
@@ -48,8 +48,6 @@ public enum TableName {
     SERVICE_BRODCASTS("service_broadcasts", ObjectType.SERVICE_BROADCAST),
     SUBSCRIPTIONS("subscriptions", ObjectType.SUBSCRIPTION),
     SUBSCRIPTION_EVENTS("subscription_events", ObjectType.SUBSCRIPTION_EVENT),
-    REFUND_HISTORY("refund_history"),
-    REFUNDS("refunds", ObjectType.REFUND, REFUND_HISTORY),
     TAG_DEFINITION_HISTORY("tag_definition_history"),
     TAG_DEFINITIONS("tag_definitions", ObjectType.TAG_DEFINITION, TAG_DEFINITION_HISTORY),
     TAG_HISTORY("tag_history"),
diff --git a/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDefinitionDao.java b/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDefinitionDao.java
index 6a42f6f..43d6ff5 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDefinitionDao.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/dao/DefaultTagDefinitionDao.java
@@ -90,24 +90,28 @@ public class DefaultTagDefinitionDao extends EntityDaoBase<TagDefinitionModelDao
     }
 
     @Override
-    public TagDefinitionModelDao getByName(final String definitionName, final InternalTenantContext context) {
-        return transactionalSqlDao.execute(true, new EntitySqlDaoTransactionWrapper<TagDefinitionModelDao>() {
+    public TagDefinitionModelDao getByName(final String definitionName, final InternalTenantContext context) throws TagDefinitionApiException {
+        return transactionalSqlDao.execute(true, TagDefinitionApiException.class, new EntitySqlDaoTransactionWrapper<TagDefinitionModelDao>() {
             @Override
             public TagDefinitionModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
-                final TagDefinitionModelDao tagDefinitionModelDao = SystemTags.lookup(definitionName);
-                return tagDefinitionModelDao != null ? tagDefinitionModelDao : entitySqlDaoWrapperFactory.become(TagDefinitionSqlDao.class).getByName(definitionName, context);
+                final TagDefinitionModelDao systemTag = SystemTags.lookup(definitionName);
+                final TagDefinitionModelDao tag = systemTag != null ? systemTag : entitySqlDaoWrapperFactory.become(TagDefinitionSqlDao.class).getByName(definitionName, context);
+                if (tag == null) {
+                    throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_DOES_NOT_EXIST, definitionName);
+                }
+                return tag;
             }
         });
     }
 
     @Override
-    public TagDefinitionModelDao getById(final UUID definitionId, final InternalTenantContext context) {
-        return transactionalSqlDao.execute(true, new EntitySqlDaoTransactionWrapper<TagDefinitionModelDao>() {
+    public TagDefinitionModelDao getById(final UUID definitionId, final InternalTenantContext context) /* throws TagDefinitionApiException */ {
+        return transactionalSqlDao.execute(true, /*TagDefinitionApiException.class,*/ new EntitySqlDaoTransactionWrapper<TagDefinitionModelDao>() {
             @Override
             public TagDefinitionModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
                 final TagDefinitionModelDao systemTag = SystemTags.lookup(definitionId);
                 final TagDefinitionModelDao tag = systemTag != null ? systemTag : entitySqlDaoWrapperFactory.become(TagDefinitionSqlDao.class).getById(definitionId.toString(), context);
-                if(tag == null) {
+                if (tag == null) {
                     throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_DOES_NOT_EXIST, definitionId);
                 }
                 return tag;
diff --git a/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionDao.java b/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionDao.java
index e409c89..c056077 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionDao.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionDao.java
@@ -30,7 +30,7 @@ public interface TagDefinitionDao extends EntityDao<TagDefinitionModelDao, TagDe
 
     public List<TagDefinitionModelDao> getTagDefinitions(boolean includeSystemTags, InternalTenantContext context);
 
-    public TagDefinitionModelDao getByName(String definitionName, InternalTenantContext context);
+    public TagDefinitionModelDao getByName(String definitionName, InternalTenantContext context) throws TagDefinitionApiException;
 
     public List<TagDefinitionModelDao> getByIds(Collection<UUID> definitionIds, InternalTenantContext context);
 
diff --git a/util/src/test/java/org/killbill/billing/util/tag/dao/TestDefaultTagDefinitionDao.java b/util/src/test/java/org/killbill/billing/util/tag/dao/TestDefaultTagDefinitionDao.java
index 1e5d3cf..1a11dae 100644
--- a/util/src/test/java/org/killbill/billing/util/tag/dao/TestDefaultTagDefinitionDao.java
+++ b/util/src/test/java/org/killbill/billing/util/tag/dao/TestDefaultTagDefinitionDao.java
@@ -16,27 +16,25 @@
 
 package org.killbill.billing.util.tag.dao;
 
-import java.io.ObjectOutput;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
+import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
-import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import org.killbill.billing.api.TestApiListener;
 import org.killbill.billing.api.TestApiListener.NextEvent;
-import org.killbill.billing.util.UtilTestSuiteWithEmbeddedDB;
 import org.killbill.billing.events.BusInternalEvent;
 import org.killbill.billing.events.TagDefinitionInternalEvent;
+import org.killbill.billing.util.UtilTestSuiteWithEmbeddedDB;
+import org.killbill.billing.util.api.TagDefinitionApiException;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
 import com.google.common.eventbus.Subscribe;
 
 public class TestDefaultTagDefinitionDao extends UtilTestSuiteWithEmbeddedDB {
 
+
     @Test(groups = "slow")
     public void testCatchEventsOnCreateAndDelete() throws Exception {
         final String definitionName = UUID.randomUUID().toString().substring(0, 5);
@@ -70,7 +68,14 @@ public class TestDefaultTagDefinitionDao extends UtilTestSuiteWithEmbeddedDB {
         assertListenerStatus();
 
         // Make sure the tag definition is deleted
-        Assert.assertNull(tagDefinitionDao.getByName(definitionName, internalCallContext));
+        try {
+            tagDefinitionDao.getByName(definitionName, internalCallContext);
+            Assert.fail("Retrieving tag definition should fail");
+        } catch (final TagDefinitionApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.TAG_DEFINITION_DOES_NOT_EXIST.getCode());
+        }
+
+
 
         /*
         // Verify we caught an event on the bus
diff --git a/util/src/test/java/org/killbill/billing/util/tag/TestTagStore.java b/util/src/test/java/org/killbill/billing/util/tag/TestTagStore.java
index d9bd318..a21826f 100644
--- a/util/src/test/java/org/killbill/billing/util/tag/TestTagStore.java
+++ b/util/src/test/java/org/killbill/billing/util/tag/TestTagStore.java
@@ -19,6 +19,7 @@ package org.killbill.billing.util.tag;
 import java.util.List;
 import java.util.UUID;
 
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import org.killbill.billing.ObjectType;
@@ -93,8 +94,11 @@ public class TestTagStore extends UtilTestSuiteWithEmbeddedDB {
         tagDefinitionDao.deleteById(tagDefinition.getId(), internalCallContext);
         assertListenerStatus();
 
-        tagDefinition = tagDefinitionDao.getByName(definitionName, internalCallContext);
-        assertNull(tagDefinition);
+        try {
+            tagDefinitionDao.getByName(definitionName, internalCallContext);
+            Assert.fail("Call should fail");
+        } catch (TagDefinitionApiException expected) {
+        }
     }
 
     @Test(groups = "slow", expectedExceptions = TagDefinitionApiException.class)