killbill-memoizeit

analytics: listen for tag notification events Signed-off-by:

6/21/2012 11:42:41 PM

Details

diff --git a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
index caefaef..458e088 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
@@ -21,20 +21,34 @@ import com.google.inject.Inject;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountChangeEvent;
 import com.ning.billing.account.api.AccountCreationEvent;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementEvent;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.invoice.api.EmptyInvoiceEvent;
 import com.ning.billing.invoice.api.InvoiceCreationEvent;
 import com.ning.billing.payment.api.PaymentErrorEvent;
 import com.ning.billing.payment.api.PaymentInfoEvent;
+import com.ning.billing.util.tag.api.ControlTagCreationEvent;
+import com.ning.billing.util.tag.api.ControlTagDefinitionCreationEvent;
+import com.ning.billing.util.tag.api.ControlTagDefinitionDeletionEvent;
+import com.ning.billing.util.tag.api.ControlTagDeletionEvent;
+import com.ning.billing.util.tag.api.UserTagCreationEvent;
+import com.ning.billing.util.tag.api.UserTagDefinitionCreationEvent;
+import com.ning.billing.util.tag.api.UserTagDefinitionDeletionEvent;
+import com.ning.billing.util.tag.api.UserTagDeletionEvent;
 
 public class AnalyticsListener {
     private final BusinessSubscriptionTransitionRecorder bstRecorder;
     private final BusinessAccountRecorder bacRecorder;
+    private final BusinessTagRecorder tagRecorder;
 
     @Inject
-    public AnalyticsListener(final BusinessSubscriptionTransitionRecorder bstRecorder, final BusinessAccountRecorder bacRecorder) {
+    public AnalyticsListener(final BusinessSubscriptionTransitionRecorder bstRecorder,
+                             final BusinessAccountRecorder bacRecorder,
+                             final BusinessTagRecorder tagRecorder) {
         this.bstRecorder = bstRecorder;
         this.bacRecorder = bacRecorder;
+        this.tagRecorder = tagRecorder;
     }
 
     @Subscribe
@@ -81,11 +95,15 @@ public class AnalyticsListener {
     }
 
     @Subscribe
-    public void handleInvoice(final InvoiceCreationEvent event) {
+    public void handleInvoiceCreation(final InvoiceCreationEvent event) {
         bacRecorder.accountUpdated(event.getAccountId());
     }
 
     @Subscribe
+    public void handleNullInvoice(final EmptyInvoiceEvent event) {
+    }
+
+    @Subscribe
     public void handlePaymentInfo(final PaymentInfoEvent paymentInfo) {
         bacRecorder.accountUpdated(paymentInfo);
     }
@@ -94,4 +112,49 @@ public class AnalyticsListener {
     public void handlePaymentError(final PaymentErrorEvent paymentError) {
         // TODO - we can't tie the error back to an account yet
     }
+
+    @Subscribe
+    public void handleControlTagCreation(final ControlTagCreationEvent event) {
+        tagRecorder.tagAdded(event.getObjectType(), event.getObjectId(), event.getTagDefinition().getName());
+    }
+
+    @Subscribe
+    public void handleControlTagDeletion(final ControlTagDeletionEvent event) {
+        tagRecorder.tagRemoved(event.getObjectType(), event.getObjectId(), event.getTagDefinition().getName());
+    }
+
+    @Subscribe
+    public void handleUserTagCreation(final UserTagCreationEvent event) {
+        tagRecorder.tagAdded(event.getObjectType(), event.getObjectId(), event.getTagDefinition().getName());
+    }
+
+    @Subscribe
+    public void handleUserTagDeletion(final UserTagDeletionEvent event) {
+        tagRecorder.tagRemoved(event.getObjectType(), event.getObjectId(), event.getTagDefinition().getName());
+    }
+
+    @Subscribe
+    public void handleControlTagDefinitionCreation(final ControlTagDefinitionCreationEvent event) {
+        // Ignored for now
+    }
+
+    @Subscribe
+    public void handleControlTagDefinitionDeletion(final ControlTagDefinitionDeletionEvent event) {
+        // Ignored for now
+    }
+
+    @Subscribe
+    public void handleUserTagDefinitionCreation(final UserTagDefinitionCreationEvent event) {
+        // Ignored for now
+    }
+
+    @Subscribe
+    public void handleUserTagDefinitionDeletion(final UserTagDefinitionDeletionEvent event) {
+        // Ignored for now
+    }
+
+    @Subscribe
+    public void handleRepairEntitlement(final RepairEntitlementEvent event) {
+        // Ignored for now
+    }
 }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessTagRecorder.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessTagRecorder.java
new file mode 100644
index 0000000..7b8488d
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessTagRecorder.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2010-2012 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.analytics;
+
+import javax.inject.Inject;
+import java.util.UUID;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.analytics.dao.BusinessAccountTagSqlDao;
+import com.ning.billing.analytics.dao.BusinessInvoicePaymentTagSqlDao;
+import com.ning.billing.analytics.dao.BusinessInvoiceTagSqlDao;
+import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionTagSqlDao;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.util.dao.ObjectType;
+
+public class BusinessTagRecorder {
+    private static final Logger log = LoggerFactory.getLogger(BusinessTagRecorder.class);
+
+    private final BusinessAccountTagSqlDao accountTagSqlDao;
+    private final BusinessInvoiceTagSqlDao invoiceTagSqlDao;
+    private final BusinessInvoicePaymentTagSqlDao invoicePaymentTagSqlDao;
+    private final BusinessSubscriptionTransitionTagSqlDao subscriptionTransitionTagSqlDao;
+    private final AccountUserApi accountApi;
+    private final EntitlementUserApi entitlementUserApi;
+
+    @Inject
+    public BusinessTagRecorder(final BusinessAccountTagSqlDao accountTagSqlDao,
+                               final BusinessInvoicePaymentTagSqlDao invoicePaymentTagSqlDao,
+                               final BusinessInvoiceTagSqlDao invoiceTagSqlDao,
+                               final BusinessSubscriptionTransitionTagSqlDao subscriptionTransitionTagSqlDao,
+                               final AccountUserApi accountApi,
+                               final EntitlementUserApi entitlementUserApi) {
+        this.accountTagSqlDao = accountTagSqlDao;
+        this.invoicePaymentTagSqlDao = invoicePaymentTagSqlDao;
+        this.invoiceTagSqlDao = invoiceTagSqlDao;
+        this.subscriptionTransitionTagSqlDao = subscriptionTransitionTagSqlDao;
+        this.accountApi = accountApi;
+        this.entitlementUserApi = entitlementUserApi;
+    }
+
+    public void tagAdded(final ObjectType objectType, final UUID objectId, final String name) {
+        if (objectType.equals(ObjectType.ACCOUNT)) {
+            tagAddedForAccount(objectId, name);
+        } else if (objectType.equals(ObjectType.BUNDLE)) {
+            tagAddedForBundle(objectId, name);
+        } else if (objectType.equals(ObjectType.INVOICE)) {
+            tagAddedForInvoice(objectId, name);
+        } else if (objectType.equals(ObjectType.PAYMENT)) {
+            tagAddedForPayment(objectId, name);
+        } else {
+            log.info("Ignoring tag addition of {} for object id {} (type {})", new Object[]{name, objectId.toString(), objectType.toString()});
+        }
+    }
+
+    public void tagRemoved(final ObjectType objectType, final UUID objectId, final String name) {
+        if (objectType.equals(ObjectType.ACCOUNT)) {
+            tagRemovedForAccount(objectId, name);
+        } else if (objectType.equals(ObjectType.BUNDLE)) {
+            tagRemovedForBundle(objectId, name);
+        } else if (objectType.equals(ObjectType.INVOICE)) {
+            tagRemovedForInvoice(objectId, name);
+        } else if (objectType.equals(ObjectType.PAYMENT)) {
+            tagRemovedForPayment(objectId, name);
+        } else {
+            log.info("Ignoring tag removal of {} for object id {} (type {})", new Object[]{name, objectId.toString(), objectType.toString()});
+        }
+    }
+
+    private void tagAddedForAccount(final UUID objectId, final String name) {
+        final Account account;
+        try {
+            account = accountApi.getAccountById(objectId);
+        } catch (AccountApiException e) {
+            log.warn("Ignoring tag addition of {} for account id {} (account does not exist)", name, objectId.toString());
+            return;
+        }
+
+        final String accountKey = account.getExternalKey();
+        accountTagSqlDao.addTag(accountKey, name);
+    }
+
+    private void tagRemovedForAccount(final UUID objectId, final String name) {
+        final Account account;
+        try {
+            account = accountApi.getAccountById(objectId);
+        } catch (AccountApiException e) {
+            log.warn("Ignoring tag removal of {} for account id {} (account does not exist)", name, objectId.toString());
+            return;
+        }
+
+        final String accountKey = account.getExternalKey();
+        accountTagSqlDao.removeTag(accountKey, name);
+    }
+
+    private void tagAddedForBundle(final UUID objectId, final String name) {
+        final SubscriptionBundle bundle;
+        try {
+            bundle = entitlementUserApi.getBundleFromId(objectId);
+        } catch (EntitlementUserApiException e) {
+            log.warn("Ignoring tag addition of {} for bundle id {} (bundle does not exist)", name, objectId.toString());
+            return;
+        }
+
+        // TODO - this stores tags for bundles?
+        final String externalKey = bundle.getKey();
+        subscriptionTransitionTagSqlDao.addTag(externalKey, name);
+    }
+
+    private void tagRemovedForBundle(final UUID objectId, final String name) {
+        final SubscriptionBundle bundle;
+        try {
+            bundle = entitlementUserApi.getBundleFromId(objectId);
+        } catch (EntitlementUserApiException e) {
+            log.warn("Ignoring tag removal of {} for bundle id {} (bundle does not exist)", name, objectId.toString());
+            return;
+        }
+
+        // TODO - this stores tags for bundles?
+        final String externalKey = bundle.getKey();
+        subscriptionTransitionTagSqlDao.removeTag(externalKey, name);
+    }
+
+    private void tagAddedForInvoice(final UUID objectId, final String name) {
+        invoiceTagSqlDao.addTag(objectId.toString(), name);
+    }
+
+    private void tagRemovedForInvoice(final UUID objectId, final String name) {
+        invoiceTagSqlDao.removeTag(objectId.toString(), name);
+    }
+
+    private void tagAddedForPayment(final UUID objectId, final String name) {
+        invoicePaymentTagSqlDao.addTag(objectId.toString(), name);
+    }
+
+    private void tagRemovedForPayment(final UUID objectId, final String name) {
+        invoicePaymentTagSqlDao.removeTag(objectId.toString(), name);
+    }
+}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
index bb564e6..c59edfe 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
@@ -21,21 +21,44 @@ import com.google.inject.AbstractModule;
 import com.ning.billing.analytics.AnalyticsListener;
 import com.ning.billing.analytics.BusinessAccountRecorder;
 import com.ning.billing.analytics.BusinessSubscriptionTransitionRecorder;
+import com.ning.billing.analytics.BusinessTagRecorder;
 import com.ning.billing.analytics.api.AnalyticsService;
 import com.ning.billing.analytics.api.DefaultAnalyticsService;
 import com.ning.billing.analytics.dao.BusinessAccountSqlDao;
-import com.ning.billing.analytics.dao.BusinessAccountSqlDaoProvider;
+import com.ning.billing.analytics.dao.BusinessAccountTagSqlDao;
+import com.ning.billing.analytics.dao.BusinessInvoiceFieldSqlDao;
+import com.ning.billing.analytics.dao.BusinessInvoiceItemSqlDao;
+import com.ning.billing.analytics.dao.BusinessInvoicePaymentFieldSqlDao;
+import com.ning.billing.analytics.dao.BusinessInvoicePaymentSqlDao;
+import com.ning.billing.analytics.dao.BusinessInvoicePaymentTagSqlDao;
+import com.ning.billing.analytics.dao.BusinessInvoiceSqlDao;
+import com.ning.billing.analytics.dao.BusinessInvoiceTagSqlDao;
+import com.ning.billing.analytics.dao.BusinessOverdueStatusSqlDao;
+import com.ning.billing.analytics.dao.BusinessSqlProvider;
+import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionFieldSqlDao;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionSqlDao;
-import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionSqlDaoProvider;
+import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionTagSqlDao;
 
 public class AnalyticsModule extends AbstractModule {
     @Override
     protected void configure() {
-        bind(BusinessSubscriptionTransitionSqlDao.class).toProvider(BusinessSubscriptionTransitionSqlDaoProvider.class).asEagerSingleton();
-        bind(BusinessAccountSqlDao.class).toProvider(BusinessAccountSqlDaoProvider.class).asEagerSingleton();
+        bind(BusinessAccountSqlDao.class).toProvider(new BusinessSqlProvider<BusinessAccountSqlDao>(BusinessAccountSqlDao.class));
+        bind(BusinessAccountTagSqlDao.class).toProvider(new BusinessSqlProvider<BusinessAccountTagSqlDao>(BusinessAccountTagSqlDao.class));
+        bind(BusinessInvoiceFieldSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoiceFieldSqlDao>(BusinessInvoiceFieldSqlDao.class));
+        bind(BusinessInvoiceItemSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoiceItemSqlDao>(BusinessInvoiceItemSqlDao.class));
+        bind(BusinessInvoicePaymentFieldSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoicePaymentFieldSqlDao>(BusinessInvoicePaymentFieldSqlDao.class));
+        bind(BusinessInvoicePaymentSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoicePaymentSqlDao>(BusinessInvoicePaymentSqlDao.class));
+        bind(BusinessInvoicePaymentTagSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoicePaymentTagSqlDao>(BusinessInvoicePaymentTagSqlDao.class));
+        bind(BusinessInvoiceSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoiceSqlDao>(BusinessInvoiceSqlDao.class));
+        bind(BusinessInvoiceTagSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoiceTagSqlDao>(BusinessInvoiceTagSqlDao.class));
+        bind(BusinessOverdueStatusSqlDao.class).toProvider(new BusinessSqlProvider<BusinessOverdueStatusSqlDao>(BusinessOverdueStatusSqlDao.class));
+        bind(BusinessSubscriptionTransitionFieldSqlDao.class).toProvider(new BusinessSqlProvider<BusinessSubscriptionTransitionFieldSqlDao>(BusinessSubscriptionTransitionFieldSqlDao.class));
+        bind(BusinessSubscriptionTransitionSqlDao.class).toProvider(new BusinessSqlProvider<BusinessSubscriptionTransitionSqlDao>(BusinessSubscriptionTransitionSqlDao.class));
+        bind(BusinessSubscriptionTransitionTagSqlDao.class).toProvider(new BusinessSqlProvider<BusinessSubscriptionTransitionTagSqlDao>(BusinessSubscriptionTransitionTagSqlDao.class));
 
         bind(BusinessSubscriptionTransitionRecorder.class).asEagerSingleton();
         bind(BusinessAccountRecorder.class).asEagerSingleton();
+        bind(BusinessTagRecorder.class).asEagerSingleton();
         bind(AnalyticsListener.class).asEagerSingleton();
 
         bind(AnalyticsService.class).to(DefaultAnalyticsService.class).asEagerSingleton();
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
index 2d29f08..58fbc6d 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
@@ -74,7 +74,7 @@ public class TestAnalyticsListener extends AnalyticsTestSuite {
     @BeforeMethod(groups = "fast")
     public void setUp() throws Exception {
         final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(dao, catalogService, new MockEntitlementUserApi(bundleUUID, EXTERNAL_KEY), new MockAccountUserApi(ACCOUNT_KEY, CURRENCY));
-        listener = new AnalyticsListener(recorder, null);
+        listener = new AnalyticsListener(recorder, null, null);
     }
 
     @Test(groups = "fast")
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessTagRecorder.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessTagRecorder.java
new file mode 100644
index 0000000..2eb018b
--- /dev/null
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessTagRecorder.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2010-2012 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.analytics;
+
+import java.util.UUID;
+
+import org.mockito.Mockito;
+import org.skife.jdbi.v2.IDBI;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.account.api.user.DefaultAccountUserApi;
+import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.account.dao.AccountEmailDao;
+import com.ning.billing.account.dao.AuditedAccountDao;
+import com.ning.billing.account.dao.AuditedAccountEmailDao;
+import com.ning.billing.analytics.dao.BusinessAccountTagSqlDao;
+import com.ning.billing.analytics.dao.BusinessInvoicePaymentTagSqlDao;
+import com.ning.billing.analytics.dao.BusinessInvoiceTagSqlDao;
+import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionTagSqlDao;
+import com.ning.billing.catalog.DefaultCatalogService;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.io.VersionedCatalogLoader;
+import com.ning.billing.config.CatalogConfig;
+import com.ning.billing.entitlement.alignment.PlanAligner;
+import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionApiService;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.engine.addon.AddonUtils;
+import com.ning.billing.entitlement.engine.dao.AuditedEntitlementDao;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.util.bus.InMemoryBus;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.DefaultClock;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.notificationq.DefaultNotificationQueueService;
+
+public class TestBusinessTagRecorder extends TestWithEmbeddedDB {
+    private BusinessAccountTagSqlDao accountTagSqlDao;
+    private BusinessSubscriptionTransitionTagSqlDao subscriptionTransitionTagSqlDao;
+    private InMemoryBus eventBus;
+    private DefaultCallContextFactory callContextFactory;
+    private AccountUserApi accountUserApi;
+    private EntitlementUserApi entitlementUserApi;
+    private BusinessTagRecorder tagRecorder;
+
+    @BeforeMethod(groups = "slow")
+    public void setUp() throws Exception {
+        final IDBI dbi = helper.getDBI();
+        accountTagSqlDao = dbi.onDemand(BusinessAccountTagSqlDao.class);
+        final BusinessInvoiceTagSqlDao invoiceTagSqlDao = dbi.onDemand(BusinessInvoiceTagSqlDao.class);
+        final BusinessInvoicePaymentTagSqlDao invoicePaymentTagSqlDao = dbi.onDemand(BusinessInvoicePaymentTagSqlDao.class);
+        subscriptionTransitionTagSqlDao = dbi.onDemand(BusinessSubscriptionTransitionTagSqlDao.class);
+        eventBus = new InMemoryBus();
+        final AccountDao accountDao = new AuditedAccountDao(dbi, eventBus);
+        final AccountEmailDao accountEmailDao = new AuditedAccountEmailDao(dbi);
+        final DefaultClock clock = new DefaultClock();
+        callContextFactory = new DefaultCallContextFactory(clock);
+        accountUserApi = new DefaultAccountUserApi(callContextFactory, accountDao, accountEmailDao);
+        final CatalogService catalogService = new DefaultCatalogService(Mockito.mock(CatalogConfig.class), Mockito.mock(VersionedCatalogLoader.class));
+        final AddonUtils addonUtils = new AddonUtils(catalogService);
+        final DefaultNotificationQueueService notificationQueueService = new DefaultNotificationQueueService(dbi, clock);
+        final EntitlementDao entitlementDao = new AuditedEntitlementDao(dbi, clock, addonUtils, notificationQueueService, eventBus);
+        final PlanAligner planAligner = new PlanAligner(catalogService);
+        final DefaultSubscriptionApiService apiService = new DefaultSubscriptionApiService(clock, entitlementDao, catalogService, planAligner);
+        final DefaultSubscriptionFactory subscriptionFactory = new DefaultSubscriptionFactory(apiService, clock, catalogService);
+        entitlementUserApi = new DefaultEntitlementUserApi(clock, entitlementDao, catalogService,
+                                                           apiService, subscriptionFactory, addonUtils);
+        tagRecorder = new BusinessTagRecorder(accountTagSqlDao, invoicePaymentTagSqlDao, invoiceTagSqlDao, subscriptionTransitionTagSqlDao,
+                                              accountUserApi, entitlementUserApi);
+
+        eventBus.start();
+    }
+
+    @AfterMethod(groups = "slow")
+    public void tearDown() throws Exception {
+        eventBus.stop();
+    }
+
+    @Test(groups = "slow")
+    public void testAddAndRemoveTagsForAccount() throws Exception {
+        final String name = UUID.randomUUID().toString().substring(0, 20);
+        final CallContext callContext = callContextFactory.createCallContext(UUID.randomUUID().toString(), CallOrigin.TEST, UserType.TEST);
+        final String accountKey = UUID.randomUUID().toString();
+
+        final Account account = accountUserApi.createAccount(new MockAccount(UUID.randomUUID(), accountKey, Currency.MXN), callContext);
+        final UUID accountId = account.getId();
+
+        Assert.assertEquals(accountTagSqlDao.getTagsForAccount(accountKey).size(), 0);
+        tagRecorder.tagAdded(ObjectType.ACCOUNT, accountId, name);
+        Assert.assertEquals(accountTagSqlDao.getTagsForAccount(accountKey).size(), 1);
+        tagRecorder.tagRemoved(ObjectType.ACCOUNT, accountId, name);
+        Assert.assertEquals(accountTagSqlDao.getTagsForAccount(accountKey).size(), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testAddAndRemoveTagsForBundle() throws Exception {
+        final String name = UUID.randomUUID().toString().substring(0, 20);
+        final CallContext callContext = callContextFactory.createCallContext(UUID.randomUUID().toString(), CallOrigin.TEST, UserType.TEST);
+        final String externalKey = UUID.randomUUID().toString();
+
+        final Account account = accountUserApi.createAccount(new MockAccount(UUID.randomUUID(), UUID.randomUUID().toString(), Currency.MXN), callContext);
+        final SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), externalKey, callContext);
+        final UUID bundleId = bundle.getId();
+
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.getTagsForBusinessSubscriptionTransition(externalKey).size(), 0);
+        tagRecorder.tagAdded(ObjectType.BUNDLE, bundleId, name);
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.getTagsForBusinessSubscriptionTransition(externalKey).size(), 1);
+        tagRecorder.tagRemoved(ObjectType.BUNDLE, bundleId, name);
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.getTagsForBusinessSubscriptionTransition(externalKey).size(), 0);
+    }
+}