killbill-uncached

Changes

Details

diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsListener.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsListener.java
index 3a18bb0..5e2afc3 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsListener.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsListener.java
@@ -27,6 +27,7 @@ import com.ning.billing.beatrix.bus.api.ExtBusEvent;
 import com.ning.billing.commons.locker.GlobalLock;
 import com.ning.billing.commons.locker.GlobalLocker;
 import com.ning.billing.commons.locker.mysql.MySqlGlobalLocker;
+import com.ning.billing.osgi.bundles.analytics.dao.AllBusinessObjectsDao;
 import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountDao;
 import com.ning.billing.osgi.bundles.analytics.dao.BusinessFieldDao;
 import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceAndInvoicePaymentDao;
@@ -53,6 +54,7 @@ public class AnalyticsListener implements OSGIKillbillEventHandler {
     private final BusinessOverdueStatusDao bosDao;
     private final BusinessFieldDao bFieldDao;
     private final BusinessTagDao bTagDao;
+    private final AllBusinessObjectsDao allBusinessObjectsDao;
     private final GlobalLocker locker;
 
     public AnalyticsListener(final OSGIKillbillLogService logService,
@@ -66,7 +68,9 @@ public class AnalyticsListener implements OSGIKillbillEventHandler {
         this.bosDao = new BusinessOverdueStatusDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
         this.bFieldDao = new BusinessFieldDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
         this.bTagDao = new BusinessTagDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        this.allBusinessObjectsDao = new AllBusinessObjectsDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
 
+        // TODO Do we still need it?
         this.locker = new MySqlGlobalLocker(osgiKillbillDataSource.getDataSource());
     }
 
@@ -77,7 +81,9 @@ public class AnalyticsListener implements OSGIKillbillEventHandler {
         switch (killbillEvent.getEventType()) {
             case ACCOUNT_CREATION:
             case ACCOUNT_CHANGE:
-                handleAccountEvent(killbillEvent, callContext);
+                // Note: account information is denormalized across all tables, we pretty much
+                // have to refresh all objects
+                handleFullRefreshEvent(killbillEvent, callContext);
                 break;
             case SUBSCRIPTION_CREATION:
             case SUBSCRIPTION_CHANGE:
@@ -97,7 +103,9 @@ public class AnalyticsListener implements OSGIKillbillEventHandler {
                 break;
             case TAG_CREATION:
             case TAG_DELETION:
-                handleTagEvent(killbillEvent, callContext);
+                // Note: tags determine the report group. Since it is denormalized across all tables, we pretty much
+                // have to refresh all objects
+                handleFullRefreshEvent(killbillEvent, callContext);
                 break;
             case CUSTOM_FIELD_CREATION:
             case CUSTOM_FIELD_DELETION:
@@ -178,6 +186,16 @@ public class AnalyticsListener implements OSGIKillbillEventHandler {
         });
     }
 
+    private void handleFullRefreshEvent(final ExtBusEvent killbillEvent, final CallContext callContext) {
+        updateWithAccountLock(killbillEvent, new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                allBusinessObjectsDao.update(killbillEvent.getAccountId(), callContext);
+                return null;
+            }
+        });
+    }
+
     private static final class AnalyticsCallContext implements CallContext {
 
         private static final String USER_NAME = AnalyticsListener.class.getName();
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsRefreshException.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsRefreshException.java
index 5a54d5a..b3ab241 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsRefreshException.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsRefreshException.java
@@ -16,14 +16,12 @@
 
 package com.ning.billing.osgi.bundles.analytics;
 
-import com.ning.billing.BillingExceptionBase;
-
 public class AnalyticsRefreshException extends Exception {
 
     public AnalyticsRefreshException() {
     }
 
-    public AnalyticsRefreshException(final BillingExceptionBase e) {
+    public AnalyticsRefreshException(final Exception e) {
         super(e);
     }
 
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/BusinessField.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/BusinessField.java
index 8324189..8a11600 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/BusinessField.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/BusinessField.java
@@ -18,6 +18,7 @@ package com.ning.billing.osgi.bundles.analytics.api;
 
 import com.ning.billing.ObjectType;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountFieldModelDao;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleFieldModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessFieldModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceFieldModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoicePaymentFieldModelDao;
@@ -45,6 +46,8 @@ public class BusinessField extends BusinessEntityBase {
     public static BusinessField create(final BusinessFieldModelDao businessFieldModelDao) {
         if (businessFieldModelDao instanceof BusinessAccountFieldModelDao) {
             return new BusinessField(ObjectType.ACCOUNT, businessFieldModelDao);
+        } else if (businessFieldModelDao instanceof BusinessBundleFieldModelDao) {
+            return new BusinessField(ObjectType.BUNDLE, businessFieldModelDao);
         } else if (businessFieldModelDao instanceof BusinessInvoiceFieldModelDao) {
             return new BusinessField(ObjectType.INVOICE, businessFieldModelDao);
         } else if (businessFieldModelDao instanceof BusinessInvoicePaymentFieldModelDao) {
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/BusinessTag.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/BusinessTag.java
index 08d99f6..e65ed76 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/BusinessTag.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/BusinessTag.java
@@ -18,6 +18,7 @@ package com.ning.billing.osgi.bundles.analytics.api;
 
 import com.ning.billing.ObjectType;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountTagModelDao;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleTagModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoicePaymentTagModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceTagModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessTagModelDao;
@@ -43,6 +44,8 @@ public class BusinessTag extends BusinessEntityBase {
     public static BusinessTag create(final BusinessTagModelDao businessTagModelDao) {
         if (businessTagModelDao instanceof BusinessAccountTagModelDao) {
             return new BusinessTag(ObjectType.ACCOUNT, businessTagModelDao);
+        } else if (businessTagModelDao instanceof BusinessBundleTagModelDao) {
+            return new BusinessTag(ObjectType.BUNDLE, businessTagModelDao);
         } else if (businessTagModelDao instanceof BusinessInvoiceTagModelDao) {
             return new BusinessTag(ObjectType.INVOICE, businessTagModelDao);
         } else if (businessTagModelDao instanceof BusinessInvoicePaymentTagModelDao) {
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/user/AnalyticsUserApi.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/user/AnalyticsUserApi.java
index 1f14af8..b2ac8b4 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/user/AnalyticsUserApi.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/user/AnalyticsUserApi.java
@@ -19,9 +19,6 @@ package com.ning.billing.osgi.bundles.analytics.api.user;
 import java.util.Collection;
 import java.util.UUID;
 
-import org.osgi.service.log.LogService;
-
-import com.ning.billing.ObjectType;
 import com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException;
 import com.ning.billing.osgi.bundles.analytics.api.BusinessAccount;
 import com.ning.billing.osgi.bundles.analytics.api.BusinessField;
@@ -31,13 +28,8 @@ import com.ning.billing.osgi.bundles.analytics.api.BusinessOverdueStatus;
 import com.ning.billing.osgi.bundles.analytics.api.BusinessSnapshot;
 import com.ning.billing.osgi.bundles.analytics.api.BusinessSubscriptionTransition;
 import com.ning.billing.osgi.bundles.analytics.api.BusinessTag;
+import com.ning.billing.osgi.bundles.analytics.dao.AllBusinessObjectsDao;
 import com.ning.billing.osgi.bundles.analytics.dao.AnalyticsDao;
-import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountDao;
-import com.ning.billing.osgi.bundles.analytics.dao.BusinessFieldDao;
-import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceAndInvoicePaymentDao;
-import com.ning.billing.osgi.bundles.analytics.dao.BusinessOverdueStatusDao;
-import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionDao;
-import com.ning.billing.osgi.bundles.analytics.dao.BusinessTagDao;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.TenantContext;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
@@ -46,26 +38,14 @@ import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
 
 public class AnalyticsUserApi {
 
-    private final LogService logService;
     private final AnalyticsDao analyticsDao;
-    private final BusinessSubscriptionTransitionDao bstDao;
-    private final BusinessInvoiceAndInvoicePaymentDao binAndBipDao;
-    private final BusinessOverdueStatusDao bosDao;
-    private final BusinessFieldDao bFieldDao;
-    private final BusinessTagDao bTagDao;
+    private final AllBusinessObjectsDao allBusinessObjectsDao;
 
     public AnalyticsUserApi(final OSGIKillbillLogService logService,
                             final OSGIKillbillAPI osgiKillbillAPI,
                             final OSGIKillbillDataSource osgiKillbillDataSource) {
-        this.logService = logService;
-        this.analyticsDao = new AnalyticsDao(osgiKillbillAPI, osgiKillbillDataSource);
-
-        final BusinessAccountDao bacDao = new BusinessAccountDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
-        this.bstDao = new BusinessSubscriptionTransitionDao(logService, osgiKillbillAPI, osgiKillbillDataSource, bacDao);
-        this.binAndBipDao = new BusinessInvoiceAndInvoicePaymentDao(logService, osgiKillbillAPI, osgiKillbillDataSource, bacDao);
-        this.bosDao = new BusinessOverdueStatusDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
-        this.bFieldDao = new BusinessFieldDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
-        this.bTagDao = new BusinessTagDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        this.analyticsDao = new AnalyticsDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        this.allBusinessObjectsDao = new AllBusinessObjectsDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
     }
 
     public BusinessSnapshot getBusinessSnapshot(final UUID accountId, final TenantContext context) {
@@ -98,23 +78,7 @@ public class AnalyticsUserApi {
     }
 
     public void rebuildAnalyticsForAccount(final UUID accountId, final CallContext context) throws AnalyticsRefreshException {
-        logService.log(LogService.LOG_INFO, "Starting rebuild of Analytics for account " + accountId);
-
-        // Refresh invoices and payments. This will automatically trigger a refresh of account
-        binAndBipDao.update(accountId, context);
-
-        // Refresh BST
-        bstDao.update(accountId, context);
-
-        // Refresh tags
-        bTagDao.update(accountId, context);
-
-        // Refresh fields
-        bFieldDao.update(accountId, context);
-
-        // Refresh BOS (bundles only for now)
-        bosDao.update(accountId, ObjectType.BUNDLE, context);
-
-        logService.log(LogService.LOG_INFO, "Finished rebuild of Analytics for account " + accountId);
+        // TODO Should we take the account lock?
+        allBusinessObjectsDao.update(accountId, context);
     }
 }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/AllBusinessObjectsDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/AllBusinessObjectsDao.java
new file mode 100644
index 0000000..49f95d8
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/AllBusinessObjectsDao.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.osgi.bundles.analytics.dao;
+
+import java.util.UUID;
+
+import org.osgi.service.log.LogService;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
+
+public class AllBusinessObjectsDao {
+
+    private final LogService logService;
+    private final BusinessSubscriptionTransitionDao bstDao;
+    private final BusinessInvoiceAndInvoicePaymentDao binAndBipDao;
+    private final BusinessOverdueStatusDao bosDao;
+    private final BusinessFieldDao bFieldDao;
+    private final BusinessTagDao bTagDao;
+
+    public AllBusinessObjectsDao(final OSGIKillbillLogService logService,
+                                 final OSGIKillbillAPI osgiKillbillAPI,
+                                 final OSGIKillbillDataSource osgiKillbillDataSource) {
+        this.logService = logService;
+
+        final BusinessAccountDao bacDao = new BusinessAccountDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        this.bstDao = new BusinessSubscriptionTransitionDao(logService, osgiKillbillAPI, osgiKillbillDataSource, bacDao);
+        this.binAndBipDao = new BusinessInvoiceAndInvoicePaymentDao(logService, osgiKillbillAPI, osgiKillbillDataSource, bacDao);
+        this.bosDao = new BusinessOverdueStatusDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        this.bFieldDao = new BusinessFieldDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        this.bTagDao = new BusinessTagDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
+    }
+
+    // TODO: each refresh is done in a transaction - do we want to share a long running transaction across all refreshes?
+    public void update(final UUID accountId, final CallContext context) throws AnalyticsRefreshException {
+        logService.log(LogService.LOG_INFO, "Starting rebuild of Analytics for account " + accountId);
+
+        // Refresh invoices and payments. This will automatically trigger a refresh of account
+        binAndBipDao.update(accountId, context);
+
+        // Refresh BST
+        bstDao.update(accountId, context);
+
+        // Refresh tags
+        bTagDao.update(accountId, context);
+
+        // Refresh fields
+        bFieldDao.update(accountId, context);
+
+        // Refresh BOS (bundles only for now)
+        bosDao.update(accountId, ObjectType.BUNDLE, context);
+
+        logService.log(LogService.LOG_INFO, "Finished rebuild of Analytics for account " + accountId);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/AnalyticsDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/AnalyticsDao.java
index d22ca3f..f549805 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/AnalyticsDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/AnalyticsDao.java
@@ -44,6 +44,7 @@ import com.ning.billing.util.api.RecordIdApi;
 import com.ning.billing.util.callcontext.TenantContext;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
 
 import com.google.common.base.Function;
 import com.google.common.base.Objects;
@@ -54,9 +55,10 @@ public class AnalyticsDao extends BusinessAnalyticsDaoBase {
 
     private final OSGIKillbillAPI osgiKillbillAPI;
 
-    public AnalyticsDao(final OSGIKillbillAPI osgiKillbillAPI,
+    public AnalyticsDao(final OSGIKillbillLogService logService,
+                        final OSGIKillbillAPI osgiKillbillAPI,
                         final OSGIKillbillDataSource osgiKillbillDataSource) {
-        super(osgiKillbillDataSource);
+        super(logService, osgiKillbillDataSource);
         this.osgiKillbillAPI = osgiKillbillAPI;
     }
 
@@ -148,6 +150,7 @@ public class AnalyticsDao extends BusinessAnalyticsDaoBase {
 
         final List<BusinessFieldModelDao> businessFieldModelDaos = new LinkedList<BusinessFieldModelDao>();
         businessFieldModelDaos.addAll(sqlDao.getAccountFieldsByAccountRecordId(accountRecordId, tenantRecordId, context));
+        businessFieldModelDaos.addAll(sqlDao.getBundleFieldsByAccountRecordId(accountRecordId, tenantRecordId, context));
         businessFieldModelDaos.addAll(sqlDao.getInvoiceFieldsByAccountRecordId(accountRecordId, tenantRecordId, context));
         businessFieldModelDaos.addAll(sqlDao.getInvoicePaymentFieldsByAccountRecordId(accountRecordId, tenantRecordId, context));
 
@@ -165,6 +168,7 @@ public class AnalyticsDao extends BusinessAnalyticsDaoBase {
 
         final List<BusinessTagModelDao> businessTagModelDaos = new LinkedList<BusinessTagModelDao>();
         businessTagModelDaos.addAll(sqlDao.getAccountTagsByAccountRecordId(accountRecordId, tenantRecordId, context));
+        businessTagModelDaos.addAll(sqlDao.getBundleTagsByAccountRecordId(accountRecordId, tenantRecordId, context));
         businessTagModelDaos.addAll(sqlDao.getInvoiceTagsByAccountRecordId(accountRecordId, tenantRecordId, context));
         businessTagModelDaos.addAll(sqlDao.getInvoicePaymentTagsByAccountRecordId(accountRecordId, tenantRecordId, context));
 
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountDao.java
index e54a01c..de5eb53 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountDao.java
@@ -18,6 +18,7 @@ package com.ning.billing.osgi.bundles.analytics.dao;
 
 import java.util.UUID;
 
+import org.osgi.service.log.LogService;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 
@@ -36,11 +37,13 @@ public class BusinessAccountDao extends BusinessAnalyticsDaoBase {
     public BusinessAccountDao(final OSGIKillbillLogService logService,
                               final OSGIKillbillAPI osgiKillbillAPI,
                               final OSGIKillbillDataSource osgiKillbillDataSource) {
-        super(osgiKillbillDataSource);
+        super(logService, osgiKillbillDataSource);
         bacFactory = new BusinessAccountFactory(logService, osgiKillbillAPI);
     }
 
     public void update(final UUID accountId, final CallContext context) throws AnalyticsRefreshException {
+        logService.log(LogService.LOG_INFO, "Starting rebuild of Analytics account for account " + accountId);
+
         // Recompute the account record
         final BusinessAccountModelDao bac = bacFactory.createBusinessAccount(accountId, context);
 
@@ -51,6 +54,8 @@ public class BusinessAccountDao extends BusinessAnalyticsDaoBase {
                 return null;
             }
         });
+
+        logService.log(LogService.LOG_INFO, "Finished rebuild of Analytics account for account " + accountId);
     }
 
     // Note: computing the BusinessAccountModelDao object is fairly expensive, hence should be done outside of the transaction
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsDaoBase.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsDaoBase.java
index f676281..73faf7b 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsDaoBase.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsDaoBase.java
@@ -19,13 +19,16 @@ package com.ning.billing.osgi.bundles.analytics.dao;
 import org.skife.jdbi.v2.DBI;
 
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
 
 public class BusinessAnalyticsDaoBase {
 
+    protected final OSGIKillbillLogService logService;
     protected final BusinessAnalyticsSqlDao sqlDao;
 
-    public BusinessAnalyticsDaoBase(final OSGIKillbillDataSource osgiKillbillDataSource) {
+    public BusinessAnalyticsDaoBase(final OSGIKillbillLogService logService, final OSGIKillbillDataSource osgiKillbillDataSource) {
         final DBI dbi = BusinessDBIProvider.get(osgiKillbillDataSource.getDataSource());
         sqlDao = dbi.onDemand(BusinessAnalyticsSqlDao.class);
+        this.logService = logService;
     }
 }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsSqlDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsSqlDao.java
index 94725f4..76997fa 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsSqlDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsSqlDao.java
@@ -28,7 +28,9 @@ import com.ning.billing.commons.jdbi.binder.SmartBindBean;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountFieldModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountTagModelDao;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleFieldModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleSummaryModelDao;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleTagModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceAdjustmentModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceFieldModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceItemAdjustmentModelDao;
@@ -128,6 +130,11 @@ public interface BusinessAnalyticsSqlDao extends Transactional<BusinessAnalytics
                                                                                 final TenantContext tenantContext);
 
     @SqlQuery
+    public List<BusinessBundleFieldModelDao> getBundleFieldsByAccountRecordId(@Bind("accountRecordId") final Long accountRecordId,
+                                                                              @Bind("tenantRecordId") final Long tenantRecordId,
+                                                                              final TenantContext tenantContext);
+
+    @SqlQuery
     public List<BusinessInvoiceFieldModelDao> getInvoiceFieldsByAccountRecordId(@Bind("accountRecordId") final Long accountRecordId,
                                                                                 @Bind("tenantRecordId") final Long tenantRecordId,
                                                                                 final TenantContext tenantContext);
@@ -143,6 +150,11 @@ public interface BusinessAnalyticsSqlDao extends Transactional<BusinessAnalytics
                                                                             final TenantContext tenantContext);
 
     @SqlQuery
+    public List<BusinessBundleTagModelDao> getBundleTagsByAccountRecordId(@Bind("accountRecordId") final Long accountRecordId,
+                                                                          @Bind("tenantRecordId") final Long tenantRecordId,
+                                                                          final TenantContext tenantContext);
+
+    @SqlQuery
     public List<BusinessInvoiceTagModelDao> getInvoiceTagsByAccountRecordId(@Bind("accountRecordId") final Long accountRecordId,
                                                                             @Bind("tenantRecordId") final Long tenantRecordId,
                                                                             final TenantContext tenantContext);
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessBundleSummaryDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessBundleSummaryDao.java
index ff8a054..7a92f05 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessBundleSummaryDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessBundleSummaryDao.java
@@ -21,11 +21,12 @@ import java.util.Collection;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleSummaryModelDao;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
 
 public class BusinessBundleSummaryDao extends BusinessAnalyticsDaoBase {
 
-    public BusinessBundleSummaryDao(final OSGIKillbillDataSource osgiKillbillDataSource) {
-        super(osgiKillbillDataSource);
+    public BusinessBundleSummaryDao(final OSGIKillbillLogService logService, final OSGIKillbillDataSource osgiKillbillDataSource) {
+        super(logService, osgiKillbillDataSource);
     }
 
     public void updateInTransaction(final Collection<BusinessBundleSummaryModelDao> bbss,
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessDBIProvider.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessDBIProvider.java
index 7c8cf5d..80fb9ea 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessDBIProvider.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessDBIProvider.java
@@ -28,7 +28,9 @@ import com.ning.billing.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountFieldModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountTagModelDao;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleFieldModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleSummaryModelDao;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleTagModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceAdjustmentModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceFieldModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceItemAdjustmentModelDao;
@@ -71,6 +73,8 @@ public class BusinessDBIProvider {
         dbi.registerMapper(new LowerToCamelBeanMapperFactory(BusinessOverdueStatusModelDao.class));
         dbi.registerMapper(new LowerToCamelBeanMapperFactory(BusinessSubscriptionTransitionModelDao.class));
         dbi.registerMapper(new LowerToCamelBeanMapperFactory(BusinessBundleSummaryModelDao.class));
+        dbi.registerMapper(new LowerToCamelBeanMapperFactory(BusinessBundleFieldModelDao.class));
+        dbi.registerMapper(new LowerToCamelBeanMapperFactory(BusinessBundleTagModelDao.class));
 
         dbi.setStatementLocator(new AnalyticsStatementLocator());
 
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessFieldDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessFieldDao.java
index 3a962bd..8ab37ff 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessFieldDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessFieldDao.java
@@ -19,6 +19,7 @@ package com.ning.billing.osgi.bundles.analytics.dao;
 import java.util.Collection;
 import java.util.UUID;
 
+import org.osgi.service.log.LogService;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 
@@ -37,11 +38,13 @@ public class BusinessFieldDao extends BusinessAnalyticsDaoBase {
     public BusinessFieldDao(final OSGIKillbillLogService logService,
                             final OSGIKillbillAPI osgiKillbillAPI,
                             final OSGIKillbillDataSource osgiKillbillDataSource) {
-        super(osgiKillbillDataSource);
+        super(logService, osgiKillbillDataSource);
         bFieldFactory = new BusinessFieldFactory(logService, osgiKillbillAPI);
     }
 
     public void update(final UUID accountId, final CallContext context) throws AnalyticsRefreshException {
+        logService.log(LogService.LOG_INFO, "Starting rebuild of Analytics custom fields for account " + accountId);
+
         final Collection<BusinessFieldModelDao> fieldModelDaos = bFieldFactory.createBusinessFields(accountId, context);
 
         sqlDao.inTransaction(new Transaction<Void, BusinessAnalyticsSqlDao>() {
@@ -51,6 +54,8 @@ public class BusinessFieldDao extends BusinessAnalyticsDaoBase {
                 return null;
             }
         });
+
+        logService.log(LogService.LOG_INFO, "Finished rebuild of Analytics custom fields for account " + accountId);
     }
 
     private void updateInTransaction(final Collection<BusinessFieldModelDao> fieldModelDaos,
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceAndInvoicePaymentDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceAndInvoicePaymentDao.java
index a08bcd8..3f14461 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceAndInvoicePaymentDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceAndInvoicePaymentDao.java
@@ -21,6 +21,7 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.UUID;
 
+import org.osgi.service.log.LogService;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 
@@ -61,16 +62,18 @@ public class BusinessInvoiceAndInvoicePaymentDao extends BusinessAnalyticsDaoBas
                                                final OSGIKillbillAPI osgiKillbillAPI,
                                                final OSGIKillbillDataSource osgiKillbillDataSource,
                                                final BusinessAccountDao businessAccountDao) {
-        super(osgiKillbillDataSource);
+        super(logService, osgiKillbillDataSource);
         this.businessAccountDao = businessAccountDao;
-        this.businessInvoiceDao = new BusinessInvoiceDao(osgiKillbillDataSource);
-        this.businessInvoicePaymentDao = new BusinessInvoicePaymentDao(osgiKillbillDataSource);
+        this.businessInvoiceDao = new BusinessInvoiceDao(logService, osgiKillbillDataSource);
+        this.businessInvoicePaymentDao = new BusinessInvoicePaymentDao(logService, osgiKillbillDataSource);
         bacFactory = new BusinessAccountFactory(logService, osgiKillbillAPI);
         binFactory = new BusinessInvoiceFactory(logService, osgiKillbillAPI);
         bipFactory = new BusinessInvoicePaymentFactory(logService, osgiKillbillAPI);
     }
 
     public void update(final UUID accountId, final CallContext context) throws AnalyticsRefreshException {
+        logService.log(LogService.LOG_INFO, "Starting rebuild of Analytics invoices and payments for account " + accountId);
+
         // Recompute the account record
         final BusinessAccountModelDao bac = bacFactory.createBusinessAccount(accountId, context);
 
@@ -88,6 +91,8 @@ public class BusinessInvoiceAndInvoicePaymentDao extends BusinessAnalyticsDaoBas
                 return null;
             }
         });
+
+        logService.log(LogService.LOG_INFO, "Finished rebuild of Analytics invoices and payments for account " + accountId);
     }
 
     @VisibleForTesting
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceDao.java
index 3a5759f..8e3464c 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceDao.java
@@ -25,13 +25,14 @@ import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceItemBase
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceModelDao;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
 
 import com.google.common.collect.Multimap;
 
 public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
 
-    public BusinessInvoiceDao(final OSGIKillbillDataSource osgiKillbillDataSource) {
-        super(osgiKillbillDataSource);
+    public BusinessInvoiceDao(final OSGIKillbillLogService logService, final OSGIKillbillDataSource osgiKillbillDataSource) {
+        super(logService, osgiKillbillDataSource);
     }
 
     /**
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentDao.java
index a3b4276..933d843 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentDao.java
@@ -20,11 +20,12 @@ import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountModelDao
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoicePaymentBaseModelDao;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
 
 public class BusinessInvoicePaymentDao extends BusinessAnalyticsDaoBase {
 
-    public BusinessInvoicePaymentDao(final OSGIKillbillDataSource osgiKillbillDataSource) {
-        super(osgiKillbillDataSource);
+    public BusinessInvoicePaymentDao(final OSGIKillbillLogService logService, final OSGIKillbillDataSource osgiKillbillDataSource) {
+        super(logService, osgiKillbillDataSource);
     }
 
     /**
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusDao.java
index 8bc03f1..d1cc126 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusDao.java
@@ -40,7 +40,7 @@ public class BusinessOverdueStatusDao extends BusinessAnalyticsDaoBase {
     public BusinessOverdueStatusDao(final OSGIKillbillLogService logService,
                                     final OSGIKillbillAPI osgiKillbillAPI,
                                     final OSGIKillbillDataSource osgiKillbillDataSource) {
-        super(osgiKillbillDataSource);
+        super(logService, osgiKillbillDataSource);
         this.logService = logService;
         bosFactory = new BusinessOverdueStatusFactory(logService, osgiKillbillAPI);
     }
@@ -54,6 +54,8 @@ public class BusinessOverdueStatusDao extends BusinessAnalyticsDaoBase {
     }
 
     private void updateForBundle(final UUID accountId, final CallContext context) throws AnalyticsRefreshException {
+        logService.log(LogService.LOG_INFO, "Starting rebuild of Analytics overdue states for account " + accountId);
+
         final Collection<BusinessOverdueStatusModelDao> businessOverdueStatuses = bosFactory.createBusinessOverdueStatuses(accountId, context);
 
         sqlDao.inTransaction(new Transaction<Void, BusinessAnalyticsSqlDao>() {
@@ -63,6 +65,8 @@ public class BusinessOverdueStatusDao extends BusinessAnalyticsDaoBase {
                 return null;
             }
         });
+
+        logService.log(LogService.LOG_INFO, "Finished rebuild of Analytics overdue states for account " + accountId);
     }
 
     private void updateInTransaction(final Collection<BusinessOverdueStatusModelDao> businessOverdueStatuses,
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionDao.java
index 307acf5..fc77ed9 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionDao.java
@@ -19,6 +19,7 @@ package com.ning.billing.osgi.bundles.analytics.dao;
 import java.util.Collection;
 import java.util.UUID;
 
+import org.osgi.service.log.LogService;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 
@@ -46,15 +47,17 @@ public class BusinessSubscriptionTransitionDao extends BusinessAnalyticsDaoBase 
                                              final OSGIKillbillAPI osgiKillbillAPI,
                                              final OSGIKillbillDataSource osgiKillbillDataSource,
                                              final BusinessAccountDao businessAccountDao) {
-        super(osgiKillbillDataSource);
+        super(logService, osgiKillbillDataSource);
         this.businessAccountDao = businessAccountDao;
-        this.businessBundleSummaryDao = new BusinessBundleSummaryDao(osgiKillbillDataSource);
+        this.businessBundleSummaryDao = new BusinessBundleSummaryDao(logService, osgiKillbillDataSource);
         bacFactory = new BusinessAccountFactory(logService, osgiKillbillAPI);
         bbsFactory = new BusinessBundleSummaryFactory(logService, osgiKillbillAPI);
         bstFactory = new BusinessSubscriptionTransitionFactory(logService, osgiKillbillAPI);
     }
 
     public void update(final UUID accountId, final CallContext context) throws AnalyticsRefreshException {
+        logService.log(LogService.LOG_INFO, "Starting rebuild of Analytics subscriptions for account " + accountId);
+
         // Recompute the account record
         final BusinessAccountModelDao bac = bacFactory.createBusinessAccount(accountId, context);
 
@@ -77,6 +80,8 @@ public class BusinessSubscriptionTransitionDao extends BusinessAnalyticsDaoBase 
                 return null;
             }
         });
+
+        logService.log(LogService.LOG_INFO, "Finished rebuild of Analytics subscriptions for account " + accountId);
     }
 
     private void updateInTransaction(final BusinessAccountModelDao bac,
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessTagDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessTagDao.java
index 38f177e..0959676 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessTagDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessTagDao.java
@@ -19,6 +19,7 @@ package com.ning.billing.osgi.bundles.analytics.dao;
 import java.util.Collection;
 import java.util.UUID;
 
+import org.osgi.service.log.LogService;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 
@@ -37,11 +38,13 @@ public class BusinessTagDao extends BusinessAnalyticsDaoBase {
     public BusinessTagDao(final OSGIKillbillLogService logService,
                           final OSGIKillbillAPI osgiKillbillAPI,
                           final OSGIKillbillDataSource osgiKillbillDataSource) {
-        super(osgiKillbillDataSource);
+        super(logService, osgiKillbillDataSource);
         bTagFactory = new BusinessTagFactory(logService, osgiKillbillAPI);
     }
 
     public void update(final UUID accountId, final CallContext context) throws AnalyticsRefreshException {
+        logService.log(LogService.LOG_INFO, "Starting rebuild of Analytics tags for account " + accountId);
+
         final Collection<BusinessTagModelDao> tagModelDaos = bTagFactory.createBusinessTags(accountId, context);
 
         sqlDao.inTransaction(new Transaction<Void, BusinessAnalyticsSqlDao>() {
@@ -51,6 +54,8 @@ public class BusinessTagDao extends BusinessAnalyticsDaoBase {
                 return null;
             }
         });
+
+        logService.log(LogService.LOG_INFO, "Finished rebuild of Analytics tags for account " + accountId);
     }
 
     private void updateInTransaction(final Collection<BusinessTagModelDao> tagModelDaos,
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessFieldFactory.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessFieldFactory.java
index ce8adab..9a8c0de 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessFieldFactory.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessFieldFactory.java
@@ -48,6 +48,7 @@ public class BusinessFieldFactory extends BusinessFactoryBase {
         final Collection<CustomField> fields = getFieldsForAccount(account.getId(), context);
 
         final Collection<BusinessFieldModelDao> fieldModelDaos = new LinkedList<BusinessFieldModelDao>();
+        // We process custom fields sequentially: in practice, an account will be associated with a dozen fields at most
         for (final CustomField field : fields) {
             final Long customFieldRecordId = getFieldRecordId(field.getId(), context);
             final AuditLog creationAuditLog = getFieldCreationAuditLog(field.getId(), context);
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceFactory.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceFactory.java
index 5dfcc22..3174f6d 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceFactory.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceFactory.java
@@ -21,6 +21,12 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionService;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.Executors;
 
 import javax.annotation.Nullable;
 
@@ -59,9 +65,14 @@ import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils
 
 public class BusinessInvoiceFactory extends BusinessFactoryBase {
 
+    private static final int NB_THREADS = 20;
+
+    private final Executor executor;
+
     public BusinessInvoiceFactory(final OSGIKillbillLogService logService,
                                   final OSGIKillbillAPI osgiKillbillAPI) {
         super(logService, osgiKillbillAPI);
+        executor = Executors.newFixedThreadPool(NB_THREADS);
     }
 
     /**
@@ -93,26 +104,34 @@ public class BusinessInvoiceFactory extends BusinessFactoryBase {
         }
 
         // Create the business invoice items
+        // We build them in parallel as invoice items are directly proportional to subscriptions (@see BusinessSubscriptionTransitionFactory)
+        final CompletionService<BusinessInvoiceItemBaseModelDao> completionService = new ExecutorCompletionService<BusinessInvoiceItemBaseModelDao>(executor);
         final Multimap<UUID, BusinessInvoiceItemBaseModelDao> businessInvoiceItemsForInvoiceId = ArrayListMultimap.<UUID, BusinessInvoiceItemBaseModelDao>create();
         for (final InvoiceItem invoiceItem : allInvoiceItems.values()) {
-            final Invoice invoice = invoiceIdToInvoiceMappings.get(invoiceItem.getInvoiceId());
-            final Collection<InvoiceItem> otherInvoiceItems = Collections2.filter(allInvoiceItems.values(),
-                                                                                  new Predicate<InvoiceItem>() {
-                                                                                      @Override
-                                                                                      public boolean apply(final InvoiceItem input) {
-                                                                                          return input.getId() != null && !input.getId().equals(invoiceItem.getId());
-                                                                                      }
-                                                                                  });
-            final BusinessInvoiceItemBaseModelDao businessInvoiceItem = createBusinessInvoiceItem(account,
-                                                                                                  invoice,
-                                                                                                  invoiceItem,
-                                                                                                  otherInvoiceItems,
-                                                                                                  accountRecordId,
-                                                                                                  tenantRecordId,
-                                                                                                  reportGroup,
-                                                                                                  context);
-            if (businessInvoiceItem != null) {
-                businessInvoiceItemsForInvoiceId.get(invoice.getId()).add(businessInvoiceItem);
+            completionService.submit(new Callable<BusinessInvoiceItemBaseModelDao>() {
+                @Override
+                public BusinessInvoiceItemBaseModelDao call() throws Exception {
+                    return createBusinessInvoiceItem(invoiceItem,
+                                                     allInvoiceItems,
+                                                     invoiceIdToInvoiceMappings,
+                                                     account,
+                                                     accountRecordId,
+                                                     tenantRecordId,
+                                                     reportGroup,
+                                                     context);
+                }
+            });
+        }
+        for (int i = 0; i < allInvoiceItems.values().size(); ++i) {
+            try {
+                final BusinessInvoiceItemBaseModelDao businessInvoiceItemModelDao = completionService.take().get();
+                if (businessInvoiceItemModelDao != null) {
+                    businessInvoiceItemsForInvoiceId.get(businessInvoiceItemModelDao.getInvoiceId()).add(businessInvoiceItemModelDao);
+                }
+            } catch (InterruptedException e) {
+                throw new AnalyticsRefreshException(e);
+            } catch (ExecutionException e) {
+                throw new AnalyticsRefreshException(e);
             }
         }
 
@@ -136,6 +155,32 @@ public class BusinessInvoiceFactory extends BusinessFactoryBase {
         return businessRecords;
     }
 
+    private BusinessInvoiceItemBaseModelDao createBusinessInvoiceItem(final InvoiceItem invoiceItem,
+                                                                      final Multimap<UUID, InvoiceItem> allInvoiceItems,
+                                                                      final Map<UUID, Invoice> invoiceIdToInvoiceMappings,
+                                                                      final Account account,
+                                                                      final Long accountRecordId,
+                                                                      final Long tenantRecordId,
+                                                                      final ReportGroup reportGroup,
+                                                                      final CallContext context) throws AnalyticsRefreshException {
+        final Invoice invoice = invoiceIdToInvoiceMappings.get(invoiceItem.getInvoiceId());
+        final Collection<InvoiceItem> otherInvoiceItems = Collections2.filter(allInvoiceItems.values(),
+                                                                              new Predicate<InvoiceItem>() {
+                                                                                  @Override
+                                                                                  public boolean apply(final InvoiceItem input) {
+                                                                                      return input.getId() != null && !input.getId().equals(invoiceItem.getId());
+                                                                                  }
+                                                                              });
+        return createBusinessInvoiceItem(account,
+                                         invoice,
+                                         invoiceItem,
+                                         otherInvoiceItems,
+                                         accountRecordId,
+                                         tenantRecordId,
+                                         reportGroup,
+                                         context);
+    }
+
     private BusinessInvoiceModelDao createBusinessInvoice(final Account account,
                                                           final Invoice invoice,
                                                           final Long accountRecordId,
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessSubscriptionTransitionFactory.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessSubscriptionTransitionFactory.java
index 7cbc5f4..dfc2750 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessSubscriptionTransitionFactory.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessSubscriptionTransitionFactory.java
@@ -17,9 +17,16 @@
 package com.ning.billing.osgi.bundles.analytics.dao.factory;
 
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionService;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.Executors;
 
 import javax.annotation.Nullable;
 
@@ -39,45 +46,97 @@ import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
 
 public class BusinessSubscriptionTransitionFactory extends BusinessFactoryBase {
 
+    private static final int NB_THREADS = 20;
+
+    private final Executor executor;
+
     public BusinessSubscriptionTransitionFactory(final OSGIKillbillLogService logService,
                                                  final OSGIKillbillAPI osgiKillbillAPI) {
         super(logService, osgiKillbillAPI);
+        executor = Executors.newFixedThreadPool(NB_THREADS);
     }
 
     public Collection<BusinessSubscriptionTransitionModelDao> createBusinessSubscriptionTransitions(final UUID accountId,
                                                                                                     final Long accountRecordId,
                                                                                                     final Long tenantRecordId,
                                                                                                     final CallContext context) throws AnalyticsRefreshException {
+        // We build bsts for each subscription in parallel as large accounts may have 50,000+ bundles
+        // We don't care about the overall ordering but we do care about ordering for
+        // a given subscription (we'd like the generated record ids to be sequential).
+        final CompletionService<Collection<BusinessSubscriptionTransitionModelDao>> completionService = new ExecutorCompletionService<Collection<BusinessSubscriptionTransitionModelDao>>(executor);
+
         final Account account = getAccount(accountId, context);
         final ReportGroup reportGroup = getReportGroup(account.getId(), context);
 
-        final Collection<BusinessSubscriptionTransitionModelDao> bsts = new LinkedList<BusinessSubscriptionTransitionModelDao>();
-
+        int nbSubscriptions = 0;
         final List<SubscriptionBundle> bundles = getSubscriptionBundlesForAccount(account.getId(), context);
         for (final SubscriptionBundle bundle : bundles) {
             final Collection<Subscription> subscriptions = getSubscriptionsForBundle(bundle.getId(), context);
+            nbSubscriptions += subscriptions.size();
+
             for (final Subscription subscription : subscriptions) {
-                final List<SubscriptionTransition> transitions = subscription.getAllTransitions();
-
-                BusinessSubscription prevNextSubscription = null;
-
-                // Ordered for us by entitlement
-                for (final SubscriptionTransition transition : transitions) {
-                    final BusinessSubscription nextSubscription = getBusinessSubscriptionFromTransition(account, transition);
-                    final BusinessSubscriptionTransitionModelDao bst = createBusinessSubscriptionTransition(account,
-                                                                                                            accountRecordId,
-                                                                                                            bundle,
-                                                                                                            transition,
-                                                                                                            prevNextSubscription,
-                                                                                                            nextSubscription,
-                                                                                                            tenantRecordId,
-                                                                                                            reportGroup,
-                                                                                                            context);
-                    if (bst != null) {
-                        bsts.add(bst);
-                        prevNextSubscription = nextSubscription;
+                completionService.submit(new Callable<Collection<BusinessSubscriptionTransitionModelDao>>() {
+                    @Override
+                    public Collection<BusinessSubscriptionTransitionModelDao> call() throws Exception {
+                        return buildTransitionsForSubscription(account, bundle, subscription, accountRecordId, tenantRecordId, reportGroup, context);
                     }
-                }
+                });
+            }
+        }
+
+        final Collection<BusinessSubscriptionTransitionModelDao> bsts = new LinkedList<BusinessSubscriptionTransitionModelDao>();
+        for (int i = 0; i < nbSubscriptions; ++i) {
+            try {
+                bsts.addAll(completionService.take().get());
+            } catch (InterruptedException e) {
+                throw new AnalyticsRefreshException(e);
+            } catch (ExecutionException e) {
+                throw new AnalyticsRefreshException(e);
+            }
+        }
+        return bsts;
+    }
+
+    private Collection<BusinessSubscriptionTransitionModelDao> buildTransitionsForSubscription(final Account account,
+                                                                                               final SubscriptionBundle bundle,
+                                                                                               final Subscription subscription,
+                                                                                               final Long accountRecordId,
+                                                                                               final Long tenantRecordId,
+                                                                                               @Nullable final ReportGroup reportGroup,
+                                                                                               final CallContext context) throws AnalyticsRefreshException {
+        final Collection<BusinessSubscriptionTransitionModelDao> bsts = new LinkedList<BusinessSubscriptionTransitionModelDao>();
+
+        final List<SubscriptionTransition> transitions = subscription.getAllTransitions();
+
+        BusinessSubscription prevNextSubscription = null;
+
+        // Ordered for us by entitlement
+        for (final SubscriptionTransition transition : transitions) {
+            final BusinessSubscription nextSubscription = getBusinessSubscriptionFromTransition(account, transition);
+            final BusinessSubscriptionTransitionModelDao bst = createBusinessSubscriptionTransition(account,
+                                                                                                    accountRecordId,
+                                                                                                    bundle,
+                                                                                                    transition,
+                                                                                                    prevNextSubscription,
+                                                                                                    nextSubscription,
+                                                                                                    tenantRecordId,
+                                                                                                    reportGroup,
+                                                                                                    context);
+            if (bst != null) {
+                bsts.add(bst);
+                prevNextSubscription = nextSubscription;
+            }
+        }
+
+        // We can now fix the next end date (the last next_end date will be set by the catalog by using the phase name)
+        final Iterator<BusinessSubscriptionTransitionModelDao> bstIterator = bsts.iterator();
+        if (bstIterator.hasNext()) {
+            BusinessSubscriptionTransitionModelDao prevBst = bstIterator.next();
+
+            while (bstIterator.hasNext()) {
+                final BusinessSubscriptionTransitionModelDao nextBst = bstIterator.next();
+                prevBst.setNextEndDate(nextBst.getNextStartDate());
+                prevBst = nextBst;
             }
         }
 
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessTagFactory.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessTagFactory.java
index 1df645d..a365962 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessTagFactory.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessTagFactory.java
@@ -49,6 +49,7 @@ public class BusinessTagFactory extends BusinessFactoryBase {
         final Collection<Tag> tags = getTagsForAccount(account.getId(), context);
 
         final Collection<BusinessTagModelDao> tagModelDaos = new LinkedList<BusinessTagModelDao>();
+        // We process tags sequentially: in practice, an account will be associated with a dozen tags at most
         for (final Tag tag : tags) {
             final Long tagRecordId = getTagRecordId(tag.getId(), context);
             final TagDefinition tagDefinition = getTagDefinition(tag.getTagDefinitionId(), context);
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessBundleFieldModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessBundleFieldModelDao.java
new file mode 100644
index 0000000..25ff33b
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessBundleFieldModelDao.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.osgi.bundles.analytics.dao.model;
+
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.util.audit.AuditLog;
+import com.ning.billing.util.customfield.CustomField;
+
+public class BusinessBundleFieldModelDao extends BusinessFieldModelDao {
+
+    private UUID bundleId;
+
+    public BusinessBundleFieldModelDao() { /* When reading from the database */ }
+
+    public BusinessBundleFieldModelDao(final Account account,
+                                       final Long accountRecordId,
+                                       final CustomField customField,
+                                       final Long customFieldRecordId,
+                                       @Nullable final AuditLog creationAuditLog,
+                                       final Long tenantRecordId,
+                                       @Nullable final ReportGroup reportGroup) {
+        super(account,
+              accountRecordId,
+              customField,
+              customFieldRecordId,
+              creationAuditLog,
+              tenantRecordId,
+              reportGroup);
+        this.bundleId = customField.getObjectId();
+    }
+
+    @Override
+    public String getTableName() {
+        return BUNDLE_FIELDS_TABLE_NAME;
+    }
+
+    public UUID getBundleId() {
+        return bundleId;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessBundleFieldModelDao");
+        sb.append("{bundleId=").append(bundleId);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+
+        final BusinessBundleFieldModelDao that = (BusinessBundleFieldModelDao) o;
+
+        if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessBundleTagModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessBundleTagModelDao.java
new file mode 100644
index 0000000..9f96b9a
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessBundleTagModelDao.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.osgi.bundles.analytics.dao.model;
+
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.util.audit.AuditLog;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+
+public class BusinessBundleTagModelDao extends BusinessTagModelDao {
+
+    private UUID bundleId;
+
+    public BusinessBundleTagModelDao() { /* When reading from the database */ }
+
+    public BusinessBundleTagModelDao(final Account account,
+                                     final Long accountRecordId,
+                                     final Tag tag,
+                                     final Long tagRecordId,
+                                     final TagDefinition tagDefinition,
+                                     @Nullable final AuditLog creationAuditLog,
+                                     final Long tenantRecordId,
+                                     @Nullable final ReportGroup reportGroup) {
+        super(account,
+              accountRecordId,
+              tag,
+              tagRecordId,
+              tagDefinition,
+              creationAuditLog,
+              tenantRecordId,
+              reportGroup);
+        this.bundleId = tag.getObjectId();
+    }
+
+    @Override
+    public String getTableName() {
+        return BUNDLE_TAGS_TABLE_NAME;
+    }
+
+    public UUID getBundleId() {
+        return bundleId;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessBundleTagModelDao");
+        sb.append("{bundleId=").append(bundleId);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+
+        final BusinessBundleTagModelDao that = (BusinessBundleTagModelDao) o;
+
+        if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessFieldModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessFieldModelDao.java
index 9e71e90..582040e 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessFieldModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessFieldModelDao.java
@@ -30,10 +30,11 @@ import com.ning.billing.util.customfield.CustomField;
 public abstract class BusinessFieldModelDao extends BusinessModelDaoBase {
 
     protected static final String ACCOUNT_FIELDS_TABLE_NAME = "bac_fields";
+    protected static final String BUNDLE_FIELDS_TABLE_NAME = "bbu_fields";
     protected static final String INVOICE_FIELDS_TABLE_NAME = "bin_fields";
     protected static final String INVOICE_PAYMENT_FIELDS_TABLE_NAME = "bip_fields";
 
-    public static final String[] ALL_FIELDS_TABLE_NAMES = new String[]{ACCOUNT_FIELDS_TABLE_NAME, INVOICE_FIELDS_TABLE_NAME, INVOICE_PAYMENT_FIELDS_TABLE_NAME};
+    public static final String[] ALL_FIELDS_TABLE_NAMES = new String[]{ACCOUNT_FIELDS_TABLE_NAME, BUNDLE_FIELDS_TABLE_NAME, INVOICE_FIELDS_TABLE_NAME, INVOICE_PAYMENT_FIELDS_TABLE_NAME};
 
     private Long customFieldRecordId;
     private String name;
@@ -54,6 +55,14 @@ public abstract class BusinessFieldModelDao extends BusinessModelDaoBase {
                                                     creationAuditLog,
                                                     tenantRecordId,
                                                     reportGroup);
+        } else if (ObjectType.BUNDLE.equals(customField.getObjectType())) {
+            return new BusinessBundleFieldModelDao(account,
+                                                   accountRecordId,
+                                                   customField,
+                                                   customFieldRecordId,
+                                                   creationAuditLog,
+                                                   tenantRecordId,
+                                                   reportGroup);
         } else if (ObjectType.INVOICE_PAYMENT.equals(customField.getObjectType())) {
             return new BusinessInvoiceFieldModelDao(account,
                                                     accountRecordId,
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessSubscriptionTransitionModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessSubscriptionTransitionModelDao.java
index f12cc58..cadfec5 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessSubscriptionTransitionModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessSubscriptionTransitionModelDao.java
@@ -192,6 +192,10 @@ public class BusinessSubscriptionTransitionModelDao extends BusinessModelDaoBase
         return SUBSCRIPTION_TABLE_NAME;
     }
 
+    public void setNextEndDate(final DateTime nextEndDate) {
+        this.nextEndDate = nextEndDate;
+    }
+
     public Long getSubscriptionEventRecordId() {
         return subscriptionEventRecordId;
     }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessTagModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessTagModelDao.java
index 8a4868d..600fb30 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessTagModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessTagModelDao.java
@@ -31,10 +31,11 @@ import com.ning.billing.util.tag.TagDefinition;
 public abstract class BusinessTagModelDao extends BusinessModelDaoBase {
 
     protected static final String ACCOUNT_TAGS_TABLE_NAME = "bac_tags";
+    protected static final String BUNDLE_TAGS_TABLE_NAME = "bbu_tags";
     protected static final String INVOICE_PAYMENT_TAGS_TABLE_NAME = "bip_tags";
     protected static final String INVOICE_TAGS_TABLE_NAME = "bin_tags";
 
-    public static final String[] ALL_TAGS_TABLE_NAMES = new String[]{ACCOUNT_TAGS_TABLE_NAME, INVOICE_PAYMENT_TAGS_TABLE_NAME, INVOICE_TAGS_TABLE_NAME};
+    public static final String[] ALL_TAGS_TABLE_NAMES = new String[]{ACCOUNT_TAGS_TABLE_NAME, BUNDLE_TAGS_TABLE_NAME, INVOICE_PAYMENT_TAGS_TABLE_NAME, INVOICE_TAGS_TABLE_NAME};
 
     private Long tagRecordId;
     private String name;
@@ -56,6 +57,15 @@ public abstract class BusinessTagModelDao extends BusinessModelDaoBase {
                                                   creationAuditLog,
                                                   tenantRecordId,
                                                   reportGroup);
+        } else if (ObjectType.BUNDLE.equals(tag.getObjectType())) {
+            return new BusinessBundleTagModelDao(account,
+                                                 accountRecordId,
+                                                 tag,
+                                                 tagRecordId,
+                                                 tagDefinition,
+                                                 creationAuditLog,
+                                                 tenantRecordId,
+                                                 reportGroup);
         } else if (ObjectType.INVOICE_PAYMENT.equals(tag.getObjectType())) {
             return new BusinessInvoicePaymentTagModelDao(account,
                                                          accountRecordId,
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsSqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsSqlDao.sql.stg
index 5207470..92c2d40 100644
--- a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsSqlDao.sql.stg
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsSqlDao.sql.stg
@@ -1014,6 +1014,38 @@ insert into bac_tags (
 );
 >>
 
+createBbuTags() ::= <<
+insert into bbu_tags (
+  tag_record_id
+, bundle_id
+, name
+, created_date
+, created_by
+, created_reason_code
+, created_comments
+, account_id
+, account_name
+, account_external_key
+, account_record_id
+, tenant_record_id
+, report_group
+) values (
+  :tagRecordId
+, :bundleId
+, :name
+, :createdDate
+, :createdBy
+, :createdReasonCode
+, :createdComments
+, :accountId
+, :accountName
+, :accountExternalKey
+, :accountRecordId
+, :tenantRecordId
+, :reportGroup
+);
+>>
+
 createBinTags() ::= <<
 insert into bin_tags (
   tag_record_id
@@ -1110,6 +1142,40 @@ insert into bac_fields (
 );
 >>
 
+createBbuFields() ::= <<
+insert into bbu_fields (
+  custom_field_record_id
+, bundle_id
+, name
+, value
+, created_date
+, created_by
+, created_reason_code
+, created_comments
+, account_id
+, account_name
+, account_external_key
+, account_record_id
+, tenant_record_id
+, report_group
+) values (
+  :customFieldRecordId
+, :bundleId
+, :name
+, :value
+, :createdDate
+, :createdBy
+, :createdReasonCode
+, :createdComments
+, :accountId
+, :accountName
+, :accountExternalKey
+, :accountRecordId
+, :tenantRecordId
+, :reportGroup
+);
+>>
+
 createBinFields() ::= <<
 insert into bin_fields (
   custom_field_record_id
@@ -1260,6 +1326,11 @@ getAccountFieldsByAccountRecordId() ::= <<
 ;
 >>
 
+getBundleFieldsByAccountRecordId() ::= <<
+<SELECT_STAR_FROM_TABLE("bbu_fields")>
+;
+>>
+
 getInvoiceFieldsByAccountRecordId() ::= <<
 <SELECT_STAR_FROM_TABLE("bin_fields")>
 ;
@@ -1275,6 +1346,11 @@ getAccountTagsByAccountRecordId() ::= <<
 ;
 >>
 
+getBundleTagsByAccountRecordId() ::= <<
+<SELECT_STAR_FROM_TABLE("bbu_tags")>
+;
+>>
+
 getInvoiceTagsByAccountRecordId() ::= <<
 <SELECT_STAR_FROM_TABLE("bin_tags")>
 ;
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/ddl.sql b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/ddl.sql
index 80273fa..456f17a 100644
--- a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/ddl.sql
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/ddl.sql
@@ -616,6 +616,28 @@ create index bac_tags_account_id on bac_tags(account_id);
 create index bac_tags_account_record_id on bac_tags(account_record_id);
 create index bac_tags_tenant_account_record_id on bac_tags(tenant_record_id, account_record_id);
 
+drop table if exists bbu_tags;
+create table bbu_tags (
+  record_id int(11) unsigned not null auto_increment
+, tag_record_id int(11) unsigned default null
+, bundle_id char(36) default null
+, name varchar(50) default null
+, created_date datetime default null
+, created_by varchar(50) default null
+, created_reason_code varchar(255) default null
+, created_comments varchar(255) default null
+, account_id char(36) default null
+, account_name varchar(100) default null
+, account_external_key varchar(50) default null
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, report_group enum('default', 'test', 'partner') not null
+, primary key(record_id)
+);
+create index bbu_tags_account_id on bbu_tags(account_id);
+create index bbu_tags_account_record_id on bbu_tags(account_record_id);
+create index bbu_tags_tenant_account_record_id on bbu_tags(tenant_record_id, account_record_id);
+
 drop table if exists bin_tags;
 create table bin_tags (
   record_id int(11) unsigned not null auto_increment
@@ -682,6 +704,29 @@ create index bac_fields_account_id on bac_fields(account_id);
 create index bac_fields_account_record_id on bac_fields(account_record_id);
 create index bac_fields_tenant_account_record_id on bac_fields(tenant_record_id, account_record_id);
 
+drop table if exists bbu_fields;
+create table bbu_fields (
+  record_id int(11) unsigned not null auto_increment
+, custom_field_record_id int(11) unsigned default null
+, bundle_id char(36) default null
+, name varchar(50) default null
+, value varchar(255) default null
+, created_date datetime default null
+, created_by varchar(50) default null
+, created_reason_code varchar(255) default null
+, created_comments varchar(255) default null
+, account_id char(36) default null
+, account_name varchar(100) default null
+, account_external_key varchar(50) default null
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, report_group enum('default', 'test', 'partner') not null
+, primary key(record_id)
+);
+create index bbu_fields_account_id on bbu_fields(account_id);
+create index bbu_fields_account_record_id on bbu_fields(account_record_id);
+create index bbu_fields_tenant_account_record_id on bbu_fields(tenant_record_id, account_record_id);
+
 drop table if exists bin_fields;
 create table bin_fields (
   record_id int(11) unsigned not null auto_increment
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessField.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessField.java
index faeb548..0479a43 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessField.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessField.java
@@ -22,6 +22,7 @@ import org.testng.annotations.Test;
 import com.ning.billing.ObjectType;
 import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountFieldModelDao;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleFieldModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceFieldModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoicePaymentFieldModelDao;
 
@@ -42,6 +43,20 @@ public class TestBusinessField extends AnalyticsTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
+    public void testConstructorBundle() throws Exception {
+        final BusinessBundleFieldModelDao businessBundleFieldModelDao = new BusinessBundleFieldModelDao(account,
+                                                                                                        accountRecordId,
+                                                                                                        customField,
+                                                                                                        fieldRecordId,
+                                                                                                        auditLog,
+                                                                                                        tenantRecordId,
+                                                                                                        reportGroup);
+        final BusinessField businessField = BusinessField.create(businessBundleFieldModelDao);
+        verifyBusinessField(businessField);
+        Assert.assertEquals(businessField.getObjectType(), ObjectType.BUNDLE);
+    }
+
+    @Test(groups = "fast")
     public void testConstructorInvoice() throws Exception {
         final BusinessInvoiceFieldModelDao businessInvoiceFieldModelDao = new BusinessInvoiceFieldModelDao(account,
                                                                                                            accountRecordId,
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessTag.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessTag.java
index 5a629c3..3518043 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessTag.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessTag.java
@@ -22,6 +22,7 @@ import org.testng.annotations.Test;
 import com.ning.billing.ObjectType;
 import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountTagModelDao;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleTagModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoicePaymentTagModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceTagModelDao;
 
@@ -43,6 +44,21 @@ public class TestBusinessTag extends AnalyticsTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
+    public void testConstructorBundle() throws Exception {
+        final BusinessBundleTagModelDao businessBundleTagModelDao = new BusinessBundleTagModelDao(account,
+                                                                                                  accountRecordId,
+                                                                                                  tag,
+                                                                                                  tagRecordId,
+                                                                                                  tagDefinition,
+                                                                                                  auditLog,
+                                                                                                  tenantRecordId,
+                                                                                                  reportGroup);
+        final BusinessTag businessTag = BusinessTag.create(businessBundleTagModelDao);
+        verifyBusinessTag(businessTag);
+        Assert.assertEquals(businessTag.getObjectType(), ObjectType.BUNDLE);
+    }
+
+    @Test(groups = "fast")
     public void testConstructorInvoice() throws Exception {
         final BusinessInvoiceTagModelDao businessInvoiceTagModelDao = new BusinessInvoiceTagModelDao(account,
                                                                                                      accountRecordId,
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestAnalyticsDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestAnalyticsDao.java
index 63efa2e..4a12e5f 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestAnalyticsDao.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestAnalyticsDao.java
@@ -29,7 +29,7 @@ public class TestAnalyticsDao extends AnalyticsTestSuiteWithEmbeddedDB {
 
     @Test(groups = "slow")
     public void testDao() throws Exception {
-        final AnalyticsDao analyticsDao = new AnalyticsDao(killbillAPI, killbillDataSource);
+        final AnalyticsDao analyticsDao = new AnalyticsDao(logService, killbillAPI, killbillDataSource);
         Assert.assertNull(analyticsDao.getAccountById(account.getId(), callContext));
 
         final BusinessAccountModelDao accountModelDao = new BusinessAccountModelDao(account,
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessAnalyticsSqlDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessAnalyticsSqlDao.java
index 5d2e8e3..ae9e9e7 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessAnalyticsSqlDao.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessAnalyticsSqlDao.java
@@ -31,7 +31,9 @@ import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteWithEmbeddedDB;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountFieldModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountTagModelDao;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleFieldModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleSummaryModelDao;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleTagModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessFieldModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceFieldModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceItemBaseModelDao;
@@ -105,6 +107,28 @@ public class TestBusinessAnalyticsSqlDao extends AnalyticsTestSuiteWithEmbeddedD
     }
 
     @Test(groups = "slow")
+    public void testSqlDaoForBundleField() throws Exception {
+        final BusinessFieldModelDao businessFieldModelDao = new BusinessBundleFieldModelDao(account,
+                                                                                            accountRecordId,
+                                                                                            customField,
+                                                                                            fieldRecordId,
+                                                                                            auditLog,
+                                                                                            tenantRecordId,
+                                                                                            reportGroup);
+        // Check the record doesn't exist yet
+        Assert.assertEquals(analyticsSqlDao.getBundleFieldsByAccountRecordId(accountRecordId, tenantRecordId, callContext).size(), 0);
+
+        // Create and check we can retrieve it
+        analyticsSqlDao.create(businessFieldModelDao.getTableName(), businessFieldModelDao, callContext);
+        Assert.assertEquals(analyticsSqlDao.getBundleFieldsByAccountRecordId(accountRecordId, tenantRecordId, callContext).size(), 1);
+        Assert.assertEquals(analyticsSqlDao.getBundleFieldsByAccountRecordId(accountRecordId, tenantRecordId, callContext).get(0), businessFieldModelDao);
+
+        // Delete and verify it doesn't exist anymore
+        analyticsSqlDao.deleteByAccountRecordId(businessFieldModelDao.getTableName(), accountRecordId, tenantRecordId, callContext);
+        Assert.assertEquals(analyticsSqlDao.getBundleFieldsByAccountRecordId(accountRecordId, tenantRecordId, callContext).size(), 0);
+    }
+
+    @Test(groups = "slow")
     public void testSqlDaoForInvoiceField() throws Exception {
         final BusinessFieldModelDao businessFieldModelDao = new BusinessInvoiceFieldModelDao(account,
                                                                                              accountRecordId,
@@ -377,6 +401,29 @@ public class TestBusinessAnalyticsSqlDao extends AnalyticsTestSuiteWithEmbeddedD
     }
 
     @Test(groups = "slow")
+    public void testSqlDaoForBundleTag() throws Exception {
+        final BusinessTagModelDao businessTagModelDao = new BusinessBundleTagModelDao(account,
+                                                                                      accountRecordId,
+                                                                                      tag,
+                                                                                      tagRecordId,
+                                                                                      tagDefinition,
+                                                                                      auditLog,
+                                                                                      tenantRecordId,
+                                                                                      reportGroup);
+        // Check the record doesn't exist yet
+        Assert.assertEquals(analyticsSqlDao.getBundleTagsByAccountRecordId(accountRecordId, tenantRecordId, callContext).size(), 0);
+
+        // Create and check we can retrieve it
+        analyticsSqlDao.create(businessTagModelDao.getTableName(), businessTagModelDao, callContext);
+        Assert.assertEquals(analyticsSqlDao.getBundleTagsByAccountRecordId(accountRecordId, tenantRecordId, callContext).size(), 1);
+        Assert.assertEquals(analyticsSqlDao.getBundleTagsByAccountRecordId(accountRecordId, tenantRecordId, callContext).get(0), businessTagModelDao);
+
+        // Delete and verify it doesn't exist anymore
+        analyticsSqlDao.deleteByAccountRecordId(businessTagModelDao.getTableName(), accountRecordId, tenantRecordId, callContext);
+        Assert.assertEquals(analyticsSqlDao.getBundleTagsByAccountRecordId(accountRecordId, tenantRecordId, callContext).size(), 0);
+    }
+
+    @Test(groups = "slow")
     public void testSqlDaoForInvoiceTag() throws Exception {
         final BusinessTagModelDao businessTagModelDao = new BusinessInvoiceTagModelDao(account,
                                                                                        accountRecordId,