killbill-memoizeit

analytics: rework parts of the DAO * Simplify invoices handling

10/8/2012 11:17:34 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 184ea75..d196729 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
@@ -50,7 +50,7 @@ public class AnalyticsListener {
 
     private final BusinessSubscriptionTransitionRecorder bstRecorder;
     private final BusinessAccountRecorder bacRecorder;
-    private final BusinessInvoiceRecorder invoiceRecorder;
+    private final BusinessInvoiceDao invoiceDao;
     private final BusinessOverdueStatusRecorder bosRecorder;
     private final BusinessInvoicePaymentRecorder bipRecorder;
     private final BusinessTagRecorder tagRecorder;
@@ -59,14 +59,14 @@ public class AnalyticsListener {
     @Inject
     public AnalyticsListener(final BusinessSubscriptionTransitionRecorder bstRecorder,
                              final BusinessAccountRecorder bacRecorder,
-                             final BusinessInvoiceRecorder invoiceRecorder,
+                             final BusinessInvoiceDao invoiceDao,
                              final BusinessOverdueStatusRecorder bosRecorder,
                              final BusinessInvoicePaymentRecorder bipRecorder,
                              final BusinessTagRecorder tagRecorder,
                              final InternalCallContextFactory internalCallContextFactory) {
         this.bstRecorder = bstRecorder;
         this.bacRecorder = bacRecorder;
-        this.invoiceRecorder = invoiceRecorder;
+        this.invoiceDao = invoiceDao;
         this.bosRecorder = bosRecorder;
         this.bipRecorder = bipRecorder;
         this.tagRecorder = tagRecorder;
@@ -108,7 +108,7 @@ public class AnalyticsListener {
     @Subscribe
     public void handleInvoiceCreation(final InvoiceCreationEvent event) {
         // The event is used as a trigger to rebuild all invoices and invoice items for this account
-        invoiceRecorder.rebuildInvoicesForAccount(event.getAccountId(), createCallContext(event));
+        invoiceDao.rebuildInvoicesForAccount(event.getAccountId(), createCallContext(event));
     }
 
     @Subscribe
@@ -119,7 +119,7 @@ public class AnalyticsListener {
     @Subscribe
     public void handleInvoiceAdjustment(final InvoiceAdjustmentEvent event) {
         // The event is used as a trigger to rebuild all invoices and invoice items for this account
-        invoiceRecorder.rebuildInvoicesForAccount(event.getAccountId(), createCallContext(event));
+        invoiceDao.rebuildInvoicesForAccount(event.getAccountId(), createCallContext(event));
     }
 
     @Subscribe
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessInvoicePaymentRecorder.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessInvoicePaymentRecorder.java
index 42ec7d9..0f551d7 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessInvoicePaymentRecorder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessInvoicePaymentRecorder.java
@@ -28,11 +28,12 @@ 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.BusinessAccountSqlDao;
 import com.ning.billing.analytics.dao.BusinessInvoicePaymentSqlDao;
 import com.ning.billing.analytics.dao.BusinessInvoiceSqlDao;
 import com.ning.billing.analytics.model.BusinessInvoicePayment;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoicePayment;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
 import com.ning.billing.payment.api.Payment;
@@ -42,29 +43,33 @@ import com.ning.billing.payment.api.PaymentMethod;
 import com.ning.billing.payment.api.PaymentMethodPlugin;
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.svcapi.account.AccountInternalApi;
+import com.ning.billing.util.svcapi.invoice.InvoiceInternalApi;
 
 public class BusinessInvoicePaymentRecorder {
 
     private static final Logger log = LoggerFactory.getLogger(BusinessInvoicePaymentRecorder.class);
 
     private final BusinessInvoicePaymentSqlDao invoicePaymentSqlDao;
-    private final AccountUserApi accountApi;
+    private final AccountInternalApi accountApi;
     private final InvoicePaymentApi invoicePaymentApi;
+    private final InvoiceInternalApi invoiceApi;
     private final PaymentApi paymentApi;
     private final Clock clock;
-    private final BusinessInvoiceRecorder invoiceRecorder;
+    private final BusinessInvoiceDao invoiceDao;
     private final BusinessAccountRecorder accountRecorder;
 
     @Inject
-    public BusinessInvoicePaymentRecorder(final BusinessInvoicePaymentSqlDao invoicePaymentSqlDao, final AccountUserApi accountApi,
-                                          final InvoicePaymentApi invoicePaymentApi, final PaymentApi paymentApi, final Clock clock,
-                                          final BusinessInvoiceRecorder invoiceRecorder, final BusinessAccountRecorder accountRecorder) {
+    public BusinessInvoicePaymentRecorder(final BusinessInvoicePaymentSqlDao invoicePaymentSqlDao, final AccountInternalApi accountApi,
+                                          final InvoicePaymentApi invoicePaymentApi, final InvoiceInternalApi invoiceApi, final PaymentApi paymentApi,
+                                          final Clock clock, final BusinessInvoiceDao invoiceDao, final BusinessAccountRecorder accountRecorder) {
         this.invoicePaymentSqlDao = invoicePaymentSqlDao;
         this.accountApi = accountApi;
         this.invoicePaymentApi = invoicePaymentApi;
+        this.invoiceApi = invoiceApi;
         this.paymentApi = paymentApi;
         this.clock = clock;
-        this.invoiceRecorder = invoiceRecorder;
+        this.invoiceDao = invoiceDao;
         this.accountRecorder = accountRecorder;
     }
 
@@ -77,7 +82,7 @@ public class BusinessInvoicePaymentRecorder {
 
         final Account account;
         try {
-            account = accountApi.getAccountById(accountId, context.toCallContext());
+            account = accountApi.getAccountById(accountId, context);
         } catch (AccountApiException e) {
             log.warn("Ignoring payment {}: account {} does not exist", paymentId, accountId);
             return;
@@ -91,7 +96,6 @@ public class BusinessInvoicePaymentRecorder {
             return;
         }
 
-        final InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePaymentForAttempt(paymentId, context.toCallContext());
         final PaymentMethod paymentMethod;
         try {
             paymentMethod = paymentApi.getPaymentMethod(account, payment.getPaymentMethodId(), true, context.toCallContext());
@@ -100,10 +104,20 @@ public class BusinessInvoicePaymentRecorder {
             return;
         }
 
-        createPayment(account, invoicePayment, payment, paymentMethod, extFirstPaymentRefId, extSecondPaymentRefId, message, context);
+        final InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePaymentForAttempt(paymentId, context.toCallContext());
+        Invoice invoice = null;
+        if (invoicePayment != null) {
+            try {
+                invoice = invoiceApi.getInvoiceById(invoicePayment.getInvoiceId(), context);
+            } catch (InvoiceApiException e) {
+                log.warn("Unable to find invoice {} for payment {}", invoicePayment.getInvoiceId(), paymentId);
+            }
+        }
+
+        createPayment(account, invoice, invoicePayment, payment, paymentMethod, extFirstPaymentRefId, extSecondPaymentRefId, message, context);
     }
 
-    private void createPayment(final Account account, @Nullable final InvoicePayment invoicePayment, final Payment payment,
+    private void createPayment(final Account account, @Nullable final Invoice invoice, @Nullable final InvoicePayment invoicePayment, final Payment payment,
                                final PaymentMethod paymentMethod, final String extFirstPaymentRefId, final String extSecondPaymentRefId,
                                final String message, final InternalCallContext context) {
         final PaymentMethodPlugin pluginDetail = paymentMethod.getPluginDetail();
@@ -111,50 +125,54 @@ public class BusinessInvoicePaymentRecorder {
         final String cardType = PaymentMethodUtils.getCardType(pluginDetail);
         final String paymentMethodString = PaymentMethodUtils.getPaymentMethodType(pluginDetail);
 
+        // invoicePayment may be null on payment failures
+        final String invoicePaymentType;
+        final UUID linkedInvoicePaymentId;
+        if (invoicePayment != null) {
+            invoicePaymentType = invoicePayment.getType().toString();
+            linkedInvoicePaymentId = invoicePayment.getLinkedInvoicePaymentId();
+        } else {
+            invoicePaymentType = null;
+            linkedInvoicePaymentId = null;
+        }
+
+        final BusinessInvoicePayment businessInvoicePayment = new BusinessInvoicePayment(
+                account.getExternalKey(),
+                payment.getAmount(),
+                extFirstPaymentRefId,
+                extSecondPaymentRefId,
+                cardCountry,
+                cardType,
+                clock.getUTCNow(),
+                payment.getCurrency(),
+                payment.getEffectiveDate(),
+                payment.getInvoiceId(),
+                message,
+                payment.getId(),
+                paymentMethodString,
+                "Electronic",
+                paymentMethod.getPluginName(),
+                payment.getPaymentStatus().toString(),
+                payment.getAmount(),
+                clock.getUTCNow(),
+                invoicePaymentType,
+                linkedInvoicePaymentId);
+
+        // Make sure to limit the scope of the transaction to avoid InnoDB deadlocks
         invoicePaymentSqlDao.inTransaction(new Transaction<Void, BusinessInvoicePaymentSqlDao>() {
             @Override
             public Void inTransaction(final BusinessInvoicePaymentSqlDao transactional, final TransactionStatus status) throws Exception {
                 // Delete the existing payment if it exists - this is to make the call idempotent
                 transactional.deleteInvoicePayment(payment.getId().toString(), context);
 
-                // invoicePayment may be null on payment failures
-                final String invoicePaymentType;
-                final UUID linkedInvoicePaymentId;
-                if (invoicePayment != null) {
-                    invoicePaymentType = invoicePayment.getType().toString();
-                    linkedInvoicePaymentId = invoicePayment.getLinkedInvoicePaymentId();
-                } else {
-                    invoicePaymentType = null;
-                    linkedInvoicePaymentId = null;
-                }
-
                 // Create the bip record
-                final BusinessInvoicePayment businessInvoicePayment = new BusinessInvoicePayment(
-                        account.getExternalKey(),
-                        payment.getAmount(),
-                        extFirstPaymentRefId,
-                        extSecondPaymentRefId,
-                        cardCountry,
-                        cardType,
-                        clock.getUTCNow(),
-                        payment.getCurrency(),
-                        payment.getEffectiveDate(),
-                        payment.getInvoiceId(),
-                        message,
-                        payment.getId(),
-                        paymentMethodString,
-                        "Electronic",
-                        paymentMethod.getPluginName(),
-                        payment.getPaymentStatus().toString(),
-                        payment.getAmount(),
-                        clock.getUTCNow(),
-                        invoicePaymentType,
-                        linkedInvoicePaymentId);
                 transactional.createInvoicePayment(businessInvoicePayment, context);
 
-                // Update bin to get the latest invoice(s) balance(s)
-                final BusinessInvoiceSqlDao invoiceSqlDao = transactional.become(BusinessInvoiceSqlDao.class);
-                invoiceRecorder.rebuildInvoicesForAccountInTransaction(account.getId(), invoiceSqlDao, context);
+                if (invoice != null) {
+                    // Update bin to get the latest invoice balance
+                    final BusinessInvoiceSqlDao invoiceSqlDao = transactional.become(BusinessInvoiceSqlDao.class);
+                    invoiceDao.rebuildInvoiceInTransaction(account.getExternalKey(), invoice, invoiceSqlDao, context);
+                }
 
                 // Update bac to get the latest account balance, total invoice balance, etc.
                 final BusinessAccountSqlDao accountSqlDao = transactional.become(BusinessAccountSqlDao.class);
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessInvoiceItemSqlDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessInvoiceItemSqlDao.java
index 1ff220b..c464094 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessInvoiceItemSqlDao.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessInvoiceItemSqlDao.java
@@ -56,5 +56,9 @@ public interface BusinessInvoiceItemSqlDao extends Transactional<BusinessInvoice
                           @InternalTenantContextBinder final InternalCallContext context);
 
     @SqlUpdate
+    void deleteInvoiceItemsForAccount(@Bind("account_id") final String accountId,
+                                      @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
     void test(@InternalTenantContextBinder final InternalTenantContext context);
 }
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessInvoiceItemSqlDao.sql.stg b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessInvoiceItemSqlDao.sql.stg
index 8fe8ad5..485fa28 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessInvoiceItemSqlDao.sql.stg
+++ b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessInvoiceItemSqlDao.sql.stg
@@ -1,7 +1,7 @@
 group BusinessInvoiceItem;
 
-CHECK_TENANT() ::= "tenant_record_id = :tenantRecordId"
-AND_CHECK_TENANT() ::= "AND <CHECK_TENANT()>"
+CHECK_TENANT(prefix) ::= "<prefix>tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT(prefix) ::= "AND <CHECK_TENANT(prefix)>"
 
 getInvoiceItem(item_id) ::= <<
 select
@@ -132,6 +132,10 @@ deleteInvoiceItem(item_id) ::= <<
 delete from bii where item_id = :item_id <AND_CHECK_TENANT()>;
 >>
 
+deleteInvoiceItemsForAccount(account_id) ::= <<
+delete bii.* from bii left join bin using(invoice_id) where bin.account_id = :account_id <AND_CHECK_TENANT("bii.")> <AND_CHECK_TENANT("bin.")>;
+>>
+
 test() ::= <<
 select 1 from bii where <CHECK_TENANT()>;
 >>
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessInvoiceRecorder.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessInvoiceRecorder.java
index fce78b9..25d5e01 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessInvoiceRecorder.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessInvoiceRecorder.java
@@ -24,29 +24,29 @@ import org.mockito.Mockito;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
-import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.analytics.dao.BusinessInvoiceSqlDao;
 import com.ning.billing.analytics.model.BusinessInvoiceItem;
 import com.ning.billing.catalog.MockCatalog;
 import com.ning.billing.catalog.MockCatalogService;
 import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceItemType;
-import com.ning.billing.invoice.api.InvoiceUserApi;
-import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.svcapi.account.AccountInternalApi;
+import com.ning.billing.util.svcapi.entitlement.EntitlementInternalApi;
+import com.ning.billing.util.svcapi.invoice.InvoiceInternalApi;
 
 public class TestBusinessInvoiceRecorder extends AnalyticsTestSuite {
 
-    private final AccountUserApi accountApi = Mockito.mock(AccountUserApi.class);
-    private final EntitlementUserApi entitlementApi = Mockito.mock(EntitlementUserApi.class);
-    private final InvoiceUserApi invoiceApi = Mockito.mock(InvoiceUserApi.class);
+    private final AccountInternalApi accountApi = Mockito.mock(AccountInternalApi.class);
+    private final EntitlementInternalApi entitlementApi = Mockito.mock(EntitlementInternalApi.class);
+    private final InvoiceInternalApi invoiceApi = Mockito.mock(InvoiceInternalApi.class);
+    private final BusinessAccountRecorder bacDao = Mockito.mock(BusinessAccountRecorder.class);
     private final BusinessInvoiceSqlDao sqlDao = Mockito.mock(BusinessInvoiceSqlDao.class);
-    private final Clock clock = Mockito.mock(Clock.class);
 
     @Test(groups = "fast")
     public void testShouldBeAbleToHandleNullFieldsInInvoiceItem() throws Exception {
-        final BusinessInvoiceRecorder recorder = new BusinessInvoiceRecorder(accountApi, entitlementApi, invoiceApi, sqlDao, new MockCatalogService(new MockCatalog()), clock);
+        final BusinessInvoiceDao dao = new BusinessInvoiceDao(accountApi, entitlementApi, invoiceApi, bacDao,
+                                                              sqlDao, new MockCatalogService(new MockCatalog()));
 
         final InvoiceItem invoiceItem = Mockito.mock(InvoiceItem.class);
         Mockito.when(invoiceItem.getAmount()).thenReturn(BigDecimal.TEN);
@@ -59,7 +59,7 @@ public class TestBusinessInvoiceRecorder extends AnalyticsTestSuite {
         Mockito.when(invoiceItem.getStartDate()).thenReturn(new LocalDate(1985, 9, 10));
         Mockito.when(invoiceItem.getInvoiceItemType()).thenReturn(InvoiceItemType.CREDIT_ADJ);
 
-        final BusinessInvoiceItem bii = recorder.createBusinessInvoiceItem(invoiceItem, internalCallContext);
+        final BusinessInvoiceItem bii = dao.createBusinessInvoiceItem(invoiceItem, internalCallContext);
         Assert.assertNotNull(bii);
         Assert.assertEquals(bii.getAmount(), invoiceItem.getAmount());
         Assert.assertEquals(bii.getCurrency(), invoiceItem.getCurrency());
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
index d687736..70833fc 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
@@ -13,6 +13,7 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package com.ning.billing.invoice.api.svcs;
 
 import java.util.Collection;
@@ -23,6 +24,7 @@ import javax.inject.Inject;
 import org.joda.time.LocalDate;
 
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.util.callcontext.InternalTenantContext;
 import com.ning.billing.util.svcapi.invoice.InvoiceInternalApi;
@@ -33,12 +35,21 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
 
     @Inject
     public DefaultInvoiceInternalApi(final InvoiceDao dao) {
-        this.dao  = dao;
+        this.dao = dao;
+    }
+
+    @Override
+    public Invoice getInvoiceById(final UUID invoiceId, final InternalTenantContext context) throws InvoiceApiException {
+        return dao.getById(invoiceId, context);
     }
 
     @Override
-    public Collection<Invoice> getUnpaidInvoicesByAccountId(UUID accountId,
-            LocalDate upToDate, InternalTenantContext context) {
+    public Collection<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final LocalDate upToDate, final InternalTenantContext context) {
         return dao.getUnpaidInvoicesByAccountId(accountId, upToDate, context);
     }
+
+    @Override
+    public Collection<Invoice> getInvoicesByAccountId(final UUID accountId, final InternalTenantContext context) {
+        return dao.getInvoicesByAccount(accountId, context);
+    }
 }
diff --git a/util/src/main/java/com/ning/billing/util/svcapi/invoice/InvoiceInternalApi.java b/util/src/main/java/com/ning/billing/util/svcapi/invoice/InvoiceInternalApi.java
index 92242f6..7248272 100644
--- a/util/src/main/java/com/ning/billing/util/svcapi/invoice/InvoiceInternalApi.java
+++ b/util/src/main/java/com/ning/billing/util/svcapi/invoice/InvoiceInternalApi.java
@@ -13,6 +13,7 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package com.ning.billing.util.svcapi.invoice;
 
 import java.util.Collection;
@@ -21,10 +22,14 @@ import java.util.UUID;
 import org.joda.time.LocalDate;
 
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.util.callcontext.InternalTenantContext;
 
 public interface InvoiceInternalApi {
 
+    public Invoice getInvoiceById(UUID invoiceId, InternalTenantContext context) throws InvoiceApiException;
+
     public Collection<Invoice> getUnpaidInvoicesByAccountId(UUID accountId, LocalDate upToDate, InternalTenantContext context);
 
+    public Collection<Invoice> getInvoicesByAccountId(UUID accountId, InternalTenantContext context);
 }