killbill-uncached

analytics: rebuild bin/bii on invoice event reception Due

7/3/2012 8:24:40 PM

Details

diff --git a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
index bcddd64..1a2585b 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
@@ -57,11 +57,13 @@ public class AnalyticsListener {
 
     @Subscribe
     public void handleEffectiveSubscriptionTransitionChange(final EffectiveSubscriptionEvent eventEffective) throws AccountApiException, EntitlementUserApiException {
+        // The event is used as a trigger to rebuild all transitions for this bundle
         bstRecorder.rebuildTransitionsForBundle(eventEffective.getBundleId());
     }
 
     @Subscribe
     public void handleRequestedSubscriptionTransitionChange(final RequestedSubscriptionEvent eventRequested) throws AccountApiException, EntitlementUserApiException {
+        // The event is used as a trigger to rebuild all transitions for this bundle
         bstRecorder.rebuildTransitionsForBundle(eventRequested.getBundleId());
     }
 
@@ -87,8 +89,8 @@ public class AnalyticsListener {
 
     @Subscribe
     public void handleInvoiceCreation(final InvoiceCreationEvent event) {
-        // TODO - follow same logic as entitlements to support repair
-        invoiceRecorder.invoiceCreated(event.getInvoiceId());
+        // The event is used as a trigger to rebuild all invoices and invoice items for this account
+        invoiceRecorder.rebuildInvoicesForAccount(event.getAccountId());
     }
 
     @Subscribe
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessInvoiceRecorder.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessInvoiceRecorder.java
index 21693f3..59912a3 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessInvoiceRecorder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessInvoiceRecorder.java
@@ -21,13 +21,18 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.analytics.dao.AnalyticsDao;
+import com.ning.billing.analytics.dao.BusinessAccountSqlDao;
+import com.ning.billing.analytics.dao.BusinessInvoiceItemSqlDao;
+import com.ning.billing.analytics.dao.BusinessInvoiceSqlDao;
+import com.ning.billing.analytics.model.BusinessAccount;
 import com.ning.billing.analytics.model.BusinessInvoice;
 import com.ning.billing.analytics.model.BusinessInvoiceItem;
 import com.ning.billing.catalog.api.Plan;
@@ -39,46 +44,73 @@ import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.util.clock.Clock;
 
 public class BusinessInvoiceRecorder {
     private static final Logger log = LoggerFactory.getLogger(BusinessInvoiceRecorder.class);
 
-    private final AnalyticsDao analyticsDao;
     private final AccountUserApi accountApi;
     private final EntitlementUserApi entitlementApi;
     private final InvoiceUserApi invoiceApi;
+    private final BusinessInvoiceSqlDao sqlDao;
+    private final Clock clock;
 
     @Inject
-    public BusinessInvoiceRecorder(final AnalyticsDao analyticsDao,
-                                   final AccountUserApi accountApi,
+    public BusinessInvoiceRecorder(final AccountUserApi accountApi,
                                    final EntitlementUserApi entitlementApi,
-                                   final InvoiceUserApi invoiceApi) {
-        this.analyticsDao = analyticsDao;
+                                   final InvoiceUserApi invoiceApi,
+                                   final BusinessInvoiceSqlDao sqlDao,
+                                   final Clock clock) {
         this.accountApi = accountApi;
         this.entitlementApi = entitlementApi;
         this.invoiceApi = invoiceApi;
+        this.sqlDao = sqlDao;
+        this.clock = clock;
     }
 
-    public void invoiceCreated(final UUID invoiceId) {
-        // Lookup the invoice object
-        final Invoice invoice = invoiceApi.getInvoice(invoiceId);
-        if (invoice == null) {
-            log.warn("Ignoring invoice creation for invoice id {} (invoice does not exist)", invoiceId.toString());
-            return;
-        }
-
+    public void rebuildInvoicesForAccount(final UUID accountId) {
         // Lookup the associated account
         final String accountKey;
         try {
-            final Account account = accountApi.getAccountById(invoice.getAccountId());
+            final Account account = accountApi.getAccountById(accountId);
             accountKey = account.getExternalKey();
         } catch (AccountApiException e) {
-            log.warn("Ignoring invoice creation for invoice id {} and account id {} (account does not exist)",
-                     invoice.getId().toString(),
-                     invoice.getAccountId().toString());
+            log.warn("Ignoring invoice update for account id {} (account does not exist)", accountId);
             return;
         }
 
+        sqlDao.inTransaction(new Transaction<Void, BusinessInvoiceSqlDao>() {
+            @Override
+            public Void inTransaction(final BusinessInvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
+                log.info("Started rebuilding transitions for account id {}", accountId);
+                deleteInvoicesAndInvoiceItemsForAccountInTransaction(transactional, accountKey);
+
+                for (final Invoice invoice : invoiceApi.getInvoicesByAccount(accountId)) {
+                    createInvoiceInTransaction(transactional, accountKey, invoice);
+                }
+
+                log.info("Finished rebuilding transitions for account id {}", accountId);
+                return null;
+            }
+        });
+    }
+
+    private void deleteInvoicesAndInvoiceItemsForAccountInTransaction(final BusinessInvoiceSqlDao transactional, final String accountKey) {
+        // We don't use on cascade delete here as we don't want the database layer to be generic - hence we have
+        // to delete the invoice items manually.
+        final List<BusinessInvoice> invoicesToDelete = transactional.getInvoicesForAccount(accountKey);
+        final BusinessInvoiceItemSqlDao invoiceItemSqlDao = transactional.become(BusinessInvoiceItemSqlDao.class);
+        for (final BusinessInvoice businessInvoice : invoicesToDelete) {
+            final List<BusinessInvoiceItem> invoiceItemsForInvoice = invoiceItemSqlDao.getInvoiceItemsForInvoice(businessInvoice.getInvoiceId().toString());
+            for (final BusinessInvoiceItem invoiceItemToDelete : invoiceItemsForInvoice) {
+                invoiceItemSqlDao.deleteInvoiceItem(invoiceItemToDelete.getItemId().toString());
+            }
+        }
+
+        transactional.deleteInvoicesForAccount(accountKey);
+    }
+
+    private void createInvoiceInTransaction(final BusinessInvoiceSqlDao transactional, final String accountKey, final Invoice invoice) {
         // Create the invoice
         final BusinessInvoice businessInvoice = new BusinessInvoice(accountKey, invoice);
 
@@ -91,8 +123,33 @@ public class BusinessInvoiceRecorder {
             }
         }
 
-        // Update the Analytics tables
-        analyticsDao.createInvoice(accountKey, businessInvoice, businessInvoiceItems);
+        createInvoiceInTransaction(transactional, accountKey, businessInvoice, businessInvoiceItems);
+    }
+
+    private void createInvoiceInTransaction(final BusinessInvoiceSqlDao transactional, final String accountKey,
+                                            final BusinessInvoice invoice, final Iterable<BusinessInvoiceItem> invoiceItems) {
+        // Create the invoice
+        log.info("Adding invoice {}", invoice);
+        transactional.createInvoice(invoice);
+
+        // Add associated invoice items
+        final BusinessInvoiceItemSqlDao invoiceItemSqlDao = transactional.become(BusinessInvoiceItemSqlDao.class);
+        for (final BusinessInvoiceItem invoiceItem : invoiceItems) {
+            log.info("Adding invoice item {}", invoiceItem);
+            invoiceItemSqlDao.createInvoiceItem(invoiceItem);
+        }
+
+        // Update BAC
+        final BusinessAccountSqlDao accountSqlDao = transactional.become(BusinessAccountSqlDao.class);
+        final BusinessAccount account = accountSqlDao.getAccount(accountKey);
+        if (account == null) {
+            throw new IllegalStateException("Account does not exist for key " + accountKey);
+        }
+        account.setBalance(account.getBalance().add(invoice.getBalance()));
+        account.setLastInvoiceDate(invoice.getInvoiceDate());
+        account.setTotalInvoiceBalance(account.getTotalInvoiceBalance().add(invoice.getBalance()));
+        account.setUpdatedDt(clock.getUTCNow());
+        accountSqlDao.saveAccount(account);
     }
 
     private BusinessInvoiceItem createBusinessInvoiceItem(final InvoiceItem invoiceItem) {
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsDao.java
index 8dcc6c8..2f0b6db 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsDao.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsDao.java
@@ -34,6 +34,4 @@ public interface AnalyticsDao {
     List<BusinessAccountTag> getTagsForAccount(final String accountKey);
 
     List<BusinessInvoiceItem> getInvoiceItemsForInvoice(final String invoiceId);
-
-    void createInvoice(final String accountKey, final BusinessInvoice invoice, final Iterable<BusinessInvoiceItem> invoiceItems);
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessInvoiceSqlDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessInvoiceSqlDao.java
index d3c8c6e..7348fe3 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessInvoiceSqlDao.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessInvoiceSqlDao.java
@@ -47,5 +47,8 @@ public interface BusinessInvoiceSqlDao extends Transactional<BusinessInvoiceSqlD
     int deleteInvoice(@Bind("invoice_id") final String invoiceId);
 
     @SqlUpdate
+    void deleteInvoicesForAccount(@Bind("account_key") final String accountKey);
+
+    @SqlUpdate
     void test();
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/DefaultAnalyticsDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/DefaultAnalyticsDao.java
index d5f8627..410ee6e 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/DefaultAnalyticsDao.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/DefaultAnalyticsDao.java
@@ -74,35 +74,4 @@ public class DefaultAnalyticsDao implements AnalyticsDao {
     public List<BusinessInvoiceItem> getInvoiceItemsForInvoice(final String invoiceId) {
         return invoiceItemSqlDao.getInvoiceItemsForInvoice(invoiceId);
     }
-
-    @Override
-    public void createInvoice(final String accountKey, final BusinessInvoice invoice, final Iterable<BusinessInvoiceItem> invoiceItems) {
-        invoiceSqlDao.inTransaction(new Transaction<Void, BusinessInvoiceSqlDao>() {
-            @Override
-            public Void inTransaction(final BusinessInvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
-                // Create the invoice
-                transactional.createInvoice(invoice);
-
-                // Add associated invoice items
-                final BusinessInvoiceItemSqlDao invoiceItemSqlDao = transactional.become(BusinessInvoiceItemSqlDao.class);
-                for (final BusinessInvoiceItem invoiceItem : invoiceItems) {
-                    invoiceItemSqlDao.createInvoiceItem(invoiceItem);
-                }
-
-                // Update BAC
-                final BusinessAccountSqlDao accountSqlDao = transactional.become(BusinessAccountSqlDao.class);
-                final BusinessAccount account = accountSqlDao.getAccount(accountKey);
-                if (account == null) {
-                    throw new IllegalStateException("Account does not exist for key " + accountKey);
-                }
-                account.setBalance(account.getBalance().add(invoice.getBalance()));
-                account.setLastInvoiceDate(invoice.getInvoiceDate());
-                account.setTotalInvoiceBalance(account.getTotalInvoiceBalance().add(invoice.getBalance()));
-                account.setUpdatedDt(new DateTime(DateTimeZone.UTC));
-                accountSqlDao.saveAccount(account);
-
-                return null;
-            }
-        });
-    }
 }
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessInvoiceSqlDao.sql.stg b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessInvoiceSqlDao.sql.stg
index 83d3ffc..ed8d685 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessInvoiceSqlDao.sql.stg
+++ b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessInvoiceSqlDao.sql.stg
@@ -85,6 +85,10 @@ deleteInvoice(invoice_id) ::= <<
 delete from bin where invoice_id = :invoice_id;
 >>
 
+deleteInvoicesForAccount(account_key) ::= <<
+delete from bin where account_key = :account_key;
+>>
+
 test() ::= <<
 select 1 from bin;
->>
\ No newline at end of file
+>>