killbill-memoizeit

Details

diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
index fa9ec41..b9938a5 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
@@ -288,7 +288,7 @@ public class TestConsumableInArrear extends TestIntegrationBase {
 
         final List<UsageRecord> bp1StoneRecords2 = new ArrayList();
         bp1StoneRecords2.add(new UsageRecord(new LocalDate(2012, 4, 23), 10L));
-        // Outside of range for this period -> It's tracking ID spreads across 2 invoices
+        // Outside of range for this period -> Its tracking ID spreads across 2 invoices
         bp1StoneRecords2.add(new UsageRecord(new LocalDate(2012, 5, 1), 101L));
         final SubscriptionUsageRecord bp1UsageRecord2 = new SubscriptionUsageRecord(bp1.getId(), "bp1-tracking-2", ImmutableList.of(new UnitUsageRecord("stones", bp1StoneRecords2)));
         recordUsageData(bp1UsageRecord2, callContext);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java
index fa567d6..cd6e1cd 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java
@@ -88,6 +88,7 @@ public class InvoiceWithMetadata {
     }
 
     public static class TrackingRecordId {
+
         private final String trackingId;
         private final UUID invoiceId;
         private final UUID subscriptionId;
@@ -122,6 +123,25 @@ public class InvoiceWithMetadata {
             return unitType;
         }
 
+
+        //
+        // Two records are similar if they were issued from the same usage record {subscriptionId, trackingId, unitType, recordDate}
+        // regardless on which 'invoice' they got attached to.
+        //
+        public boolean isSimilarRecord(final Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof TrackingRecordId)) {
+                return false;
+            }
+            final TrackingRecordId that = (TrackingRecordId) o;
+            return Objects.equal(trackingId, that.trackingId) &&
+                   Objects.equal(subscriptionId, that.subscriptionId) &&
+                   Objects.equal(unitType, that.unitType) &&
+                   Objects.equal(recordDate, that.recordDate);
+        }
+
         @Override
         public boolean equals(final Object o) {
             if (this == o) {
@@ -131,13 +151,8 @@ public class InvoiceWithMetadata {
                 return false;
             }
             final TrackingRecordId that = (TrackingRecordId) o;
-            // !!! Exclude invoiceId on purpose.
-            //
-            // The Set methods (Sets.difference) is used to exclude usage record already invoiced (on a specified invoiceId),
-            // by comparing 2 TrackingIds items with different invoiceId
-            //
             return Objects.equal(trackingId, that.trackingId) &&
-                   //Objects.equal(invoiceId, that.invoiceId) &&
+                   Objects.equal(invoiceId, that.invoiceId) &&
                    Objects.equal(subscriptionId, that.subscriptionId) &&
                    Objects.equal(unitType, that.unitType) &&
                    Objects.equal(recordDate, that.recordDate);
@@ -145,8 +160,7 @@ public class InvoiceWithMetadata {
 
         @Override
         public int hashCode() {
-            // !!! Exclude invoiceId on purpose - see comment above
-            return Objects.hashCode(trackingId, subscriptionId, unitType, recordDate);
+            return Objects.hashCode(trackingId, invoiceId, subscriptionId, unitType, recordDate);
         }
     }
 
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 12296d5..271b15c 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -206,7 +206,7 @@ public class InvoiceDispatcher {
 
             final ImmutableAccountData account = accountApi.getImmutableAccountDataById(accountId, context);
 
-            commitInvoiceAndSetFutureNotifications(account, null, ImmutableSet.of(), notificationsBuilder.build(), context);
+            commitInvoiceAndSetFutureNotifications(account, notificationsBuilder.build(), context);
 
         } catch (final SubscriptionBaseApiException e) {
             log.warn("Failed handling SubscriptionBase change.",
@@ -539,7 +539,7 @@ public class InvoiceDispatcher {
                 log.warn("Ignoring rescheduleDate='{}', delayed scheduling is unsupported in dry-run", rescheduleDate);
             } else {
                 final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(rescheduleDate, billingEvents, internalCallContext);
-                commitInvoiceAndSetFutureNotifications(account, null, ImmutableSet.of(), futureAccountNotifications, internalCallContext);
+                commitInvoiceAndSetFutureNotifications(account, futureAccountNotifications, internalCallContext);
             }
             return null;
         }
@@ -562,7 +562,7 @@ public class InvoiceDispatcher {
                 final BusInternalEvent event = new DefaultNullInvoiceEvent(accountId, clock.getUTCToday(),
                                                                            internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId(), internalCallContext.getUserToken());
 
-                commitInvoiceAndSetFutureNotifications(account, null, ImmutableSet.of(), futureAccountNotifications, internalCallContext);
+                commitInvoiceAndSetFutureNotifications(account, futureAccountNotifications, internalCallContext);
                 postEvent(event);
             }
             return null;
@@ -626,7 +626,7 @@ public class InvoiceDispatcher {
         } finally {
             // Make sure we always set future notifications in case of errors
             if (!isDryRun && !success) {
-                commitInvoiceAndSetFutureNotifications(account, null, ImmutableSet.of(), futureAccountNotifications, internalCallContext);
+                commitInvoiceAndSetFutureNotifications(account, futureAccountNotifications, internalCallContext);
             }
 
             if (isDryRun || success) {
@@ -797,6 +797,14 @@ public class InvoiceDispatcher {
         log.info(tmp.toString());
     }
 
+
+    private void commitInvoiceAndSetFutureNotifications(final ImmutableAccountData account,
+                                                        final FutureAccountNotifications futureAccountNotifications,
+                                                        final InternalCallContext context) {
+        commitInvoiceAndSetFutureNotifications(account, null, ImmutableSet.of(), futureAccountNotifications, context);
+    }
+
+
     private void commitInvoiceAndSetFutureNotifications(final ImmutableAccountData account,
                                                         @Nullable final InvoiceModelDao invoiceModelDao,
                                                         final Set<InvoiceTrackingModelDao> trackingIds,
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java
index 47ea11f..8bf1ca5 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java
@@ -55,6 +55,7 @@ import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
@@ -200,7 +201,20 @@ public abstract class ContiguousIntervalUsageInArrear {
 
         final Set<TrackingRecordId> existingTrackingIds = extractTrackingIds(allExistingTrackingIds);
 
-        final Set<TrackingRecordId> newTrackingIds = Sets.difference(allTrackingIds, existingTrackingIds);
+
+        final Set<TrackingRecordId> newTrackingIds = Sets.filter(allTrackingIds, new Predicate<TrackingRecordId>() {
+            @Override
+            public boolean apply(final TrackingRecordId allRecord) {
+
+                return ! Iterables.any(existingTrackingIds, new Predicate<TrackingRecordId>() {
+                    @Override
+                    public boolean apply(final TrackingRecordId existingRecord) {
+                        return existingRecord.isSimilarRecord(allRecord);
+                    }
+                });
+            }
+        });
+
 
         // Each RolledUpUsage 'ru' is for a specific time period and across all units
         for (final RolledUpUsage ru : allUsage) {