killbill-memoizeit

Merge remote-tracking branch 'origin/analytics-as-osgi-plugin-support'

4/5/2013 4:22:28 PM

Changes

Details

diff --git a/account/src/test/java/com/ning/billing/account/dao/TestAccountDao.java b/account/src/test/java/com/ning/billing/account/dao/TestAccountDao.java
index 0140d40..e92996d 100644
--- a/account/src/test/java/com/ning/billing/account/dao/TestAccountDao.java
+++ b/account/src/test/java/com/ning/billing/account/dao/TestAccountDao.java
@@ -111,7 +111,7 @@ public class TestAccountDao extends AccountTestSuiteWithEmbeddedDB {
         final CustomField field = new StringCustomField(fieldName, fieldValue, ObjectType.ACCOUNT, accountId, internalCallContext.getCreatedDate());
         customFieldDao.create(new CustomFieldModelDao(field), internalCallContext);
 
-        final List<CustomFieldModelDao> customFieldMap = customFieldDao.getCustomFields(accountId, ObjectType.ACCOUNT, internalCallContext);
+        final List<CustomFieldModelDao> customFieldMap = customFieldDao.getCustomFieldsForObject(accountId, ObjectType.ACCOUNT, internalCallContext);
         Assert.assertEquals(customFieldMap.size(), 1);
 
         final CustomFieldModelDao customField = customFieldMap.get(0);
diff --git a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
index ace0c73..9a0b572 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
@@ -27,7 +27,6 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
 import com.ning.billing.account.dao.AccountModelDao;
 import com.ning.billing.analytics.AnalyticsTestSuiteWithEmbeddedDB;
@@ -153,6 +152,10 @@ public class TestAnalyticsService extends AnalyticsTestSuiteWithEmbeddedDB {
                 null,
                 null,
                 null,
+                null,
+                null,
+                null,
+                null,
                 Subscription.SubscriptionState.ACTIVE,
                 plan,
                 phase,
diff --git a/api/src/main/java/com/ning/billing/beatrix/bus/api/ExtBusEventType.java b/api/src/main/java/com/ning/billing/beatrix/bus/api/ExtBusEventType.java
index 940e34b..ac4fe08 100644
--- a/api/src/main/java/com/ning/billing/beatrix/bus/api/ExtBusEventType.java
+++ b/api/src/main/java/com/ning/billing/beatrix/bus/api/ExtBusEventType.java
@@ -16,8 +16,9 @@
 package com.ning.billing.beatrix.bus.api;
 
 /**
- * The enum {@code ExtBusEventType} rerpesents the user visible bus event types.
+ * The enum {@code ExtBusEventType} represents the user visible bus event types.
  */
+
 public enum ExtBusEventType {
     ACCOUNT_CREATION,
     ACCOUNT_CHANGE,
@@ -26,6 +27,11 @@ public enum ExtBusEventType {
     SUBSCRIPTION_CANCEL,
     OVERDUE_CHANGE,
     INVOICE_CREATION,
+    INVOICE_ADJUSTMENT,
     PAYMENT_SUCCESS,
-    PAYMENT_FAILED
+    PAYMENT_FAILED,
+    TAG_CREATION,
+    TAG_DELETION,
+    CUSTOM_FIELD_CREATION,
+    CUSTOM_FIELD_DELETION
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
index c8a8bca..76ff799 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
@@ -35,12 +35,20 @@ public interface SubscriptionTransition {
 
     public SubscriptionState getNextState();
 
+    public UUID getPreviousEventId();
+
+    public DateTime getPreviousEventCreatedDate();
+
     public Plan getPreviousPlan();
 
     public Plan getNextPlan();
 
     public PlanPhase getPreviousPhase();
 
+    public UUID getNextEventId();
+
+    public DateTime getNextEventCreatedDate();
+
     public PlanPhase getNextPhase();
 
     public PriceList getPreviousPriceList();
diff --git a/api/src/main/java/com/ning/billing/util/api/CustomFieldUserApi.java b/api/src/main/java/com/ning/billing/util/api/CustomFieldUserApi.java
index 12f278b..25905e8 100644
--- a/api/src/main/java/com/ning/billing/util/api/CustomFieldUserApi.java
+++ b/api/src/main/java/com/ning/billing/util/api/CustomFieldUserApi.java
@@ -25,10 +25,43 @@ import com.ning.billing.ObjectType;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.TenantContext;
 import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.Tag;
 
 public interface CustomFieldUserApi {
 
-    List<CustomField> getCustomFields(UUID objectId, ObjectType objectType, TenantContext context);
-
+    /**
+     *
+     * @param fields the list of fields to add
+     * @param context the call context
+     * @throws CustomFieldApiException
+     */
     void addCustomFields(List<CustomField> fields, CallContext context) throws CustomFieldApiException;
+
+    /**
+     *
+     * @param objectId the object id
+     * @param objectType the object type
+     * @param context the call context
+     * @return the list of custom fields associated with that object
+     */
+    List<CustomField> getCustomFieldsForObject(UUID objectId, ObjectType objectType, TenantContext context);
+
+
+    /**
+     *
+     * @param accountId the account id
+     * @param objectType the object type
+     * @param context the call context
+     * @return  the list of custom fields associated with that account for the specified type
+     */
+    List<CustomField> getCustomFieldsForAccountType(UUID accountId, ObjectType objectType, TenantContext context);
+
+
+    /**
+     *
+     * @param accountId the account id
+     * @param context the call context
+     * @return  the list of custom fields associated with that account
+     */
+    List<CustomField> getCustomFieldsForAccount(UUID accountId, TenantContext context);
 }
diff --git a/api/src/main/java/com/ning/billing/util/api/RecordIdApi.java b/api/src/main/java/com/ning/billing/util/api/RecordIdApi.java
index 376056d..d2fb17f 100644
--- a/api/src/main/java/com/ning/billing/util/api/RecordIdApi.java
+++ b/api/src/main/java/com/ning/billing/util/api/RecordIdApi.java
@@ -19,6 +19,7 @@ package com.ning.billing.util.api;
 import java.util.UUID;
 
 import com.ning.billing.ObjectType;
+import com.ning.billing.util.callcontext.TenantContext;
 
 public interface RecordIdApi {
 
@@ -26,9 +27,11 @@ public interface RecordIdApi {
      *
      * This can be used by external plugins to keep the mapping between UUID  and recordId
      *
+     *
      * @param objectId the uuid of the object
      * @param objectType the object type
+     * @param tenantContext the context associated with the call
      * @return the record id associated with that object
      */
-    Long getRecordId(UUID objectId, ObjectType objectType);
+    Long getRecordId(UUID objectId, ObjectType objectType, final TenantContext tenantContext);
 }
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/extbus/BeatrixListener.java b/beatrix/src/main/java/com/ning/billing/beatrix/extbus/BeatrixListener.java
index 2f87e4a..4ffc2bb 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/extbus/BeatrixListener.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/extbus/BeatrixListener.java
@@ -35,7 +35,16 @@ import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.events.AccountChangeInternalEvent;
 import com.ning.billing.util.events.AccountCreationInternalEvent;
 import com.ning.billing.util.events.BusInternalEvent;
+import com.ning.billing.util.events.ControlTagCreationInternalEvent;
+import com.ning.billing.util.events.ControlTagDeletionInternalEvent;
+import com.ning.billing.util.events.CustomFieldCreationEvent;
+import com.ning.billing.util.events.CustomFieldDeletionEvent;
+import com.ning.billing.util.events.InvoiceAdjustmentInternalEvent;
+import com.ning.billing.util.events.InvoiceCreationInternalEvent;
+import com.ning.billing.util.events.InvoiceInternalEvent;
 import com.ning.billing.util.events.OverdueChangeInternalEvent;
+import com.ning.billing.util.events.PaymentErrorInternalEvent;
+import com.ning.billing.util.events.PaymentInfoInternalEvent;
 import com.ning.billing.util.events.SubscriptionInternalEvent;
 import com.ning.billing.util.svcsapi.bus.InternalBus.EventBusException;
 
@@ -59,24 +68,6 @@ public class BeatrixListener {
 
     @Subscribe
     public void handleAllInternalKillbillEvents(final BusInternalEvent event) {
-        switch(event.getBusEventType()) {
-            case ACCOUNT_CREATE:
-                break;
-            case ACCOUNT_CHANGE:
-                break;
-            case SUBSCRIPTION_TRANSITION:
-                break;
-            case INVOICE_CREATION:
-                break;
-            case PAYMENT_INFO:
-                break;
-            case PAYMENT_ERROR:
-                break;
-            case OVERDUE_CHANGE:
-                break;
-            default:
-                // Ignore for now.
-        }
         final ExtBusEventEntry externalEvent = computeExtBusEventEntryFromBusInternalEvent(event);
         try {
 
@@ -89,6 +80,7 @@ public class BeatrixListener {
         }
     }
 
+
     private ExtBusEventEntry computeExtBusEventEntryFromBusInternalEvent(final BusInternalEvent event) {
 
         ObjectType objectType  = null;
@@ -122,14 +114,36 @@ public class BeatrixListener {
             } else if (realEventST.getTransitionType() == SubscriptionTransitionType.CHANGE) {
                 eventBusType = ExtBusEventType.SUBSCRIPTION_CHANGE;
             }
-
             break;
+
         case INVOICE_CREATION:
+            InvoiceCreationInternalEvent realEventInv = (InvoiceCreationInternalEvent) event;
+            objectType = ObjectType.INVOICE;
+            objectId = realEventInv.getInvoiceId();
+            eventBusType = ExtBusEventType.INVOICE_CREATION;
+            break;
+
+        case INVOICE_ADJUSTMENT:
+            InvoiceAdjustmentInternalEvent realEventInvAdj = (InvoiceAdjustmentInternalEvent) event;
+            objectType = ObjectType.INVOICE;
+            objectId = realEventInvAdj.getInvoiceId();
+            eventBusType = ExtBusEventType.INVOICE_ADJUSTMENT;
             break;
+
         case PAYMENT_INFO:
+            PaymentInfoInternalEvent realEventPay = (PaymentInfoInternalEvent) event;
+            objectType = ObjectType.PAYMENT;
+            objectId = realEventPay.getPaymentId();
+            eventBusType = ExtBusEventType.PAYMENT_SUCCESS;
             break;
+
         case PAYMENT_ERROR:
+            PaymentErrorInternalEvent realEventPayErr = (PaymentErrorInternalEvent) event;
+            objectType = ObjectType.PAYMENT;
+            objectId = realEventPayErr.getPaymentId();
+            eventBusType = ExtBusEventType.PAYMENT_FAILED;
             break;
+
         case OVERDUE_CHANGE:
             OverdueChangeInternalEvent realEventOC = (OverdueChangeInternalEvent) event;
             // TODO When Killbil supports more than overdue for bundle, this will break...
@@ -137,6 +151,37 @@ public class BeatrixListener {
             objectId = realEventOC.getOverdueObjectId();
             eventBusType = ExtBusEventType.OVERDUE_CHANGE;
             break;
+
+       case USER_TAG_CREATION:
+       case CONTROL_TAG_CREATION:
+           ControlTagCreationInternalEvent realTagEventCr = (ControlTagCreationInternalEvent) event;
+           objectType = ObjectType.TAG;
+           objectId = realTagEventCr.getTagId();
+           eventBusType = ExtBusEventType.TAG_CREATION;
+            break;
+
+       case USER_TAG_DELETION:
+       case CONTROL_TAG_DELETION:
+           ControlTagDeletionInternalEvent realTagEventDel = (ControlTagDeletionInternalEvent) event;
+           objectType = ObjectType.TAG;
+           objectId = realTagEventDel.getTagId();
+           eventBusType = ExtBusEventType.TAG_DELETION;
+           break;
+
+       case CUSTOM_FIELD_CREATION:
+           CustomFieldCreationEvent realCustomEveventCr = (CustomFieldCreationEvent) event;
+           objectType = ObjectType.CUSTOM_FIELD;
+           objectId = realCustomEveventCr.getCustomFieldId();
+           eventBusType = ExtBusEventType.CUSTOM_FIELD_CREATION;
+           break;
+
+       case CUSTOM_FIELD_DELETION:
+           CustomFieldDeletionEvent realCustomEveventDel = (CustomFieldDeletionEvent) event;
+           objectType = ObjectType.CUSTOM_FIELD;
+           objectId = realCustomEveventDel.getCustomFieldId();
+           eventBusType = ExtBusEventType.CUSTOM_FIELD_DELETION;
+           break;
+
         default:
         }
         return eventBusType != null ?
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/BeatrixIntegrationModule.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/BeatrixIntegrationModule.java
index 776b127..66f3169 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/BeatrixIntegrationModule.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/BeatrixIntegrationModule.java
@@ -67,6 +67,7 @@ import com.ning.billing.util.glue.ExportModule;
 import com.ning.billing.util.glue.GlobalLockerModule;
 import com.ning.billing.util.glue.NonEntityDaoModule;
 import com.ning.billing.util.glue.NotificationQueueModule;
+import com.ning.billing.util.glue.RecordIdModule;
 import com.ning.billing.util.glue.TagStoreModule;
 import com.ning.billing.util.svcsapi.bus.BusService;
 
@@ -122,6 +123,7 @@ public class BeatrixIntegrationModule extends AbstractModule {
         install(new ExportModule());
         install(new DefaultOSGIModule(configSource));
         install(new NonEntityDaoModule());
+        install(new RecordIdModule());
 
         bind(AccountChecker.class).asEagerSingleton();
         bind(EntitlementChecker.class).asEagerSingleton();
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestCustomFieldApi.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestCustomFieldApi.java
new file mode 100644
index 0000000..8ee396a
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestCustomFieldApi.java
@@ -0,0 +1,147 @@
+/*
+ * 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.beatrix.integration;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.util.api.CustomFieldApiException;
+import com.ning.billing.util.api.CustomFieldUserApi;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.customfield.StringCustomField;
+
+import com.google.inject.Inject;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+public class TestCustomFieldApi extends TestIntegrationBase {
+
+
+    private Account account;
+
+    @Inject
+    private CustomFieldUserApi customFieldApi;
+
+    @Override
+    @BeforeMethod(groups = {"slow"})
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+        account = createAccountWithNonOsgiPaymentMethod(getAccountData(25));
+        assertNotNull(account);
+    }
+
+    @Test(groups = "slow")
+    public void testCustomFieldForAccount() throws CustomFieldApiException {
+        addCustomField("name1", "value1", account.getId(), ObjectType.ACCOUNT, clock.getUTCNow());
+        addCustomField("name2", "value2", account.getId(), ObjectType.ACCOUNT, clock.getUTCNow());
+
+        List<CustomField> fields = customFieldApi.getCustomFieldsForAccount(account.getId(), callContext);
+        Assert.assertEquals(fields.size(), 2);
+
+        fields = customFieldApi.getCustomFieldsForAccountType(account.getId(), ObjectType.ACCOUNT, callContext);
+        Assert.assertEquals(fields.size(), 2);
+
+        fields = customFieldApi.getCustomFieldsForObject(account.getId(), ObjectType.ACCOUNT, callContext);
+        Assert.assertEquals(fields.size(), 2);
+    }
+
+
+    @Test(groups = "slow")
+    public void testCustomFieldForInvoice() throws CustomFieldApiException, EntitlementUserApiException {
+
+        //
+        // Create necessary logic to end up with an Invoice object on that account.
+        //
+        final SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever", callContext);
+
+        final String productName = "Shotgun";
+        final BillingPeriod term = BillingPeriod.ANNUAL;
+        final String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.INVOICE);
+        final PlanPhaseSpecifier bpPlanPhaseSpecifier = new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null);
+        final SubscriptionData bpSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+                                                                                                                       bpPlanPhaseSpecifier,
+                                                                                                                       null,
+                                                                                                                       callContext));
+        assertNotNull(bpSubscription);
+        assertTrue(busHandler.isCompleted(DELAY));
+        assertListenerStatus();
+
+        final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        Assert.assertEquals(invoices.size(), 1);
+
+        final Invoice invoice = invoices.get(0);
+        Assert.assertEquals(invoice.getAccountId(), account.getId());
+
+
+        addCustomField("name1", "value1", invoice.getId(), ObjectType.INVOICE, clock.getUTCNow());
+        addCustomField("name2", "value2", invoice.getId(), ObjectType.INVOICE, clock.getUTCNow());
+
+        List<CustomField> fields = customFieldApi.getCustomFieldsForAccount(invoice.getId(), callContext);
+        Assert.assertEquals(fields.size(), 2);
+
+        fields = customFieldApi.getCustomFieldsForAccountType(invoice.getId(), ObjectType.INVOICE, callContext);
+        Assert.assertEquals(fields.size(), 2);
+
+        fields = customFieldApi.getCustomFieldsForObject(invoice.getId(), ObjectType.INVOICE, callContext);
+        Assert.assertEquals(fields.size(), 2);
+
+        //
+        // Add custom field on account and retry
+        //
+        addCustomField("foo", "bar", account.getId(), ObjectType.ACCOUNT, clock.getUTCNow());
+
+        fields = customFieldApi.getCustomFieldsForAccount(invoice.getId(), callContext);
+        Assert.assertEquals(fields.size(), 3);
+
+        fields = customFieldApi.getCustomFieldsForAccountType(invoice.getId(), ObjectType.INVOICE, callContext);
+        Assert.assertEquals(fields.size(), 2);
+
+        fields = customFieldApi.getCustomFieldsForObject(invoice.getId(), ObjectType.INVOICE, callContext);
+        Assert.assertEquals(fields.size(), 2);
+    }
+
+    private void addCustomField(String name, String value, UUID objectId, ObjectType type, DateTime createdDate) throws CustomFieldApiException {
+        CustomField f = new StringCustomField(name, value, type, objectId, clock.getUTCNow());
+        busHandler.pushExpectedEvents(NextEvent.CUSTOM_FIELD);
+        List<CustomField> fields = new ArrayList<CustomField>();
+        fields.add(f);
+        customFieldApi.addCustomFields(fields, callContext);
+        assertTrue(busHandler.isCompleted(DELAY));
+
+    }
+
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
index 47aa41e..5841a98 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
@@ -28,7 +28,6 @@ import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.ning.billing.ErrorCode;
 import com.ning.billing.catalog.api.ActionPolicy;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Catalog;
@@ -517,19 +516,24 @@ public class SubscriptionData extends EntityBase implements Subscription {
 
         this.events = inputEvents;
 
+        UUID nextUserToken = null;
+
+        UUID nextEventId = null;
+        DateTime nextCreatedDate = null;
         SubscriptionState nextState = null;
         String nextPlanName = null;
         String nextPhaseName = null;
         String nextPriceListName = null;
-        UUID nextUserToken = null;
 
+        UUID prevEventId = null;
+        DateTime prevCreatedDate = null;
         SubscriptionState previousState = null;
         PriceList previousPriceList = null;
-
-        transitions = new LinkedList<SubscriptionTransition>();
         Plan previousPlan = null;
         PlanPhase previousPhase = null;
 
+        transitions = new LinkedList<SubscriptionTransition>();
+
         for (final EntitlementEvent cur : inputEvents) {
 
             if (!cur.isActive() || cur.getActiveVersion() < activeVersion) {
@@ -540,6 +544,9 @@ public class SubscriptionData extends EntityBase implements Subscription {
 
             boolean isFromDisk = true;
 
+            nextEventId = cur.getId();
+            nextCreatedDate = cur.getCreatedDate();
+
             switch (cur.getType()) {
 
             case PHASE:
@@ -558,6 +565,8 @@ public class SubscriptionData extends EntityBase implements Subscription {
                 case MIGRATE_ENTITLEMENT:
                 case CREATE:
                 case RE_CREATE:
+                    prevEventId = null;
+                    prevCreatedDate = null;
                     previousState = null;
                     previousPlan = null;
                     previousPhase = null;
@@ -605,8 +614,11 @@ public class SubscriptionData extends EntityBase implements Subscription {
             final SubscriptionTransitionData transition = new SubscriptionTransitionData(
                     cur.getId(), id, bundleId, cur.getType(), apiEventType,
                     cur.getRequestedDate(), cur.getEffectiveDate(),
+                    prevEventId, prevCreatedDate,
                     previousState, previousPlan, previousPhase,
-                    previousPriceList, nextState, nextPlan, nextPhase,
+                    previousPriceList,
+                    nextEventId, nextCreatedDate,
+                    nextState, nextPlan, nextPhase,
                     nextPriceList, cur.getTotalOrdering(), nextUserToken,
                     isFromDisk);
 
@@ -616,8 +628,9 @@ public class SubscriptionData extends EntityBase implements Subscription {
             previousPlan = nextPlan;
             previousPhase = nextPhase;
             previousPriceList = nextPriceList;
+            prevEventId = nextEventId;
+            prevCreatedDate = nextCreatedDate;
+
         }
     }
-
-
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
index 7e6db65..2cadced 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
@@ -40,8 +40,12 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
     private final DateTime effectiveTransitionTime;
     private final SubscriptionState previousState;
     private final PriceList previousPriceList;
+    private final UUID previousEventId;
+    private final DateTime previousEventCreatedDate;
     private final Plan previousPlan;
     private final PlanPhase previousPhase;
+    private final UUID nextEventId;
+    private final DateTime nextEventCreatedDate;
     private final SubscriptionState nextState;
     private final PriceList nextPriceList;
     private final Plan nextPlan;
@@ -57,10 +61,14 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
                                       final ApiEventType apiEventType,
                                       final DateTime requestedTransitionTime,
                                       final DateTime effectiveTransitionTime,
+                                      final UUID previousEventId,
+                                      final DateTime previousEventCreatedDate,
                                       final SubscriptionState previousState,
                                       final Plan previousPlan,
                                       final PlanPhase previousPhase,
                                       final PriceList previousPriceList,
+                                      final UUID nextEventId,
+                                      final DateTime nextEventCreatedDate,
                                       final SubscriptionState nextState,
                                       final Plan nextPlan,
                                       final PlanPhase nextPhase,
@@ -84,12 +92,16 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
         this.nextPriceList = nextPriceList;
         this.nextPhase = nextPhase;
         this.totalOrdering = totalOrdering;
+        this.previousEventId = previousEventId;
+        this.previousEventCreatedDate = previousEventCreatedDate;
+        this.nextEventId = nextEventId;
+        this.nextEventCreatedDate = nextEventCreatedDate;
         this.isFromDisk = isFromDisk;
         this.userToken = userToken;
         this.remainingEventsForUserOperation = 0;
     }
 
-    public SubscriptionTransitionData(final SubscriptionTransitionData input, final int remainingEventsForUserOperation) {
+    public SubscriptionTransitionData(final SubscriptionTransitionData input, int remainingEventsForUserOperation) {
         super();
         this.eventId = input.getId();
         this.subscriptionId = input.getSubscriptionId();
@@ -98,10 +110,14 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
         this.apiEventType = input.getApiEventType();
         this.requestedTransitionTime = input.getRequestedTransitionTime();
         this.effectiveTransitionTime = input.getEffectiveTransitionTime();
+        this.previousEventId = input.getPreviousEventId();
+        this.previousEventCreatedDate = input.getPreviousEventCreatedDate();
         this.previousState = input.getPreviousState();
         this.previousPriceList = input.getPreviousPriceList();
         this.previousPlan = input.getPreviousPlan();
         this.previousPhase = input.getPreviousPhase();
+        this.nextEventId = input.getNextEventId();
+        this.nextEventCreatedDate = input.getNextEventCreatedDate();
         this.nextState = input.getNextState();
         this.nextPlan = input.getNextPlan();
         this.nextPriceList = input.getNextPriceList();
@@ -142,6 +158,16 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
     }
 
     @Override
+    public UUID getNextEventId() {
+        return nextEventId;
+    }
+
+    @Override
+    public DateTime getNextEventCreatedDate() {
+        return nextEventCreatedDate;
+    }
+
+    @Override
     public Plan getNextPlan() {
         return nextPlan;
     }
@@ -157,6 +183,16 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
     }
 
     @Override
+    public UUID getPreviousEventId() {
+        return previousEventId;
+    }
+
+    @Override
+    public DateTime getPreviousEventCreatedDate() {
+        return previousEventCreatedDate;
+    }
+
+    @Override
     public PriceList getPreviousPriceList() {
         return previousPriceList;
     }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBaseBuilder.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBaseBuilder.java
index 87d8533..4321cd1 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBaseBuilder.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBaseBuilder.java
@@ -46,7 +46,7 @@ public class EventBaseBuilder<T extends EventBaseBuilder<T>> {
         this.requestedDate = copy.requestedDate;
         this.effectiveDate = copy.effectiveDate;
         this.processedDate = copy.processedDate;
-
+        this.createdDate = copy.getCreatedDate();
         this.activeVersion = copy.activeVersion;
         this.isActive = copy.isActive;
         this.totalOrdering = copy.totalOrdering;
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
index da23035..d3c1929 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
@@ -433,6 +433,7 @@ public class TestUserApiChangePlan extends EntitlementTestSuiteWithEmbeddedDB {
             PlanPhase trialPhase = subscription.getCurrentPhase();
             assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
 
+
             // MOVE 2 DAYS AHEAD
             Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(2));
             clock.addDeltaFromReality(it.toDurationMillis());
@@ -468,8 +469,8 @@ public class TestUserApiChangePlan extends EntitlementTestSuiteWithEmbeddedDB {
 
             final DateTime expectedNextPhaseDate = subscription.getStartDate().plusDays(30).plusMonths(6);
             final SubscriptionTransition nextPhase = subscription.getPendingTransition();
-            final DateTime nextPhaseEffectiveDate = nextPhase.getEffectiveTransitionTime();
 
+            final DateTime nextPhaseEffectiveDate = nextPhase.getEffectiveTransitionTime();
             assertEquals(nextPhaseEffectiveDate, expectedNextPhaseDate);
 
             assertListenerStatus();
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
index 22a09cc..ef3e65f 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
@@ -62,15 +62,33 @@ public class TestUserApiCreate extends EntitlementTestSuiteWithEmbeddedDB {
                                                                                                        testUtil.getProductSpecifier(productName, planSetName, term, null), requestedDate, callContext);
             assertNotNull(subscription);
 
+            //
+            // In addition to Alignment phase we also test SubscriptionTransition eventIds and created dates.
+            // Keep tracks of row events to compare with ids and created dates returned by SubscriptionTransition later.
+            //
+            final List<EntitlementEvent> events = subscription.getEvents();
+            Assert.assertEquals(events.size(), 2);
+
+            final EntitlementEvent trialEvent = events.get(0);
+            final EntitlementEvent phaseEvent = events.get(1);
+
+
             assertEquals(subscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
             //assertEquals(subscription.getAccount(), account.getId());
             assertEquals(subscription.getBundleId(), bundle.getId());
             assertEquals(subscription.getStartDate(), requestedDate);
 
             assertTrue(testListener.isCompleted(5000));
-
             assertListenerStatus();
 
+            final SubscriptionTransition transition = subscription.getPreviousTransition();
+
+            assertEquals(transition.getPreviousEventId(), trialEvent.getId());
+            assertEquals(transition.getNextEventId(), phaseEvent.getId());
+
+            assertEquals(transition.getPreviousEventCreatedDate().compareTo(trialEvent.getCreatedDate()), 0);
+            assertEquals(transition.getNextEventCreatedDate().compareTo(phaseEvent.getCreatedDate()), 0);
+
         } catch (EntitlementUserApiException e) {
             log.error("Unexpected exception", e);
             Assert.fail(e.getMessage());
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxRsResourceBase.java
index 5899f83..40092a2 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -153,7 +153,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
     }
 
     protected Response getCustomFields(final UUID id, final TenantContext context) {
-        final List<CustomField> fields = customFieldUserApi.getCustomFields(id, getObjectType(), context);
+        final List<CustomField> fields = customFieldUserApi.getCustomFieldsForObject(id, getObjectType(), context);
 
         final List<CustomFieldJson> result = new LinkedList<CustomFieldJson>();
         for (final CustomField cur : fields) {
diff --git a/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
index e6ff204..7690c01 100644
--- a/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
+++ b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
@@ -60,6 +60,7 @@ import com.ning.billing.util.glue.ExportModule;
 import com.ning.billing.util.glue.GlobalLockerModule;
 import com.ning.billing.util.glue.NonEntityDaoModule;
 import com.ning.billing.util.glue.NotificationQueueModule;
+import com.ning.billing.util.glue.RecordIdModule;
 import com.ning.billing.util.glue.TagStoreModule;
 
 import com.google.inject.AbstractModule;
@@ -135,6 +136,7 @@ public class KillbillServerModule extends AbstractModule {
         install(new NonEntityDaoModule());
         install(new DefaultOSGIModule(configSource));
         install(new UsageModule(configSource));
+        install(new RecordIdModule());
 
         installClock();
     }
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
index 421caef..b2882cf 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -70,6 +70,7 @@ import com.ning.billing.util.glue.CustomFieldModule;
 import com.ning.billing.util.glue.ExportModule;
 import com.ning.billing.util.glue.NonEntityDaoModule;
 import com.ning.billing.util.glue.NotificationQueueModule;
+import com.ning.billing.util.glue.RecordIdModule;
 import com.ning.billing.util.glue.TagStoreModule;
 import com.ning.http.client.AsyncHttpClient;
 import com.ning.http.client.AsyncHttpClientConfig;
@@ -206,6 +207,7 @@ public class TestJaxrsBase extends KillbillClient {
             install(new ExportModule());
             install(new DefaultOSGIModule(configSource));
             install(new UsageModule(configSource));
+            install(new RecordIdModule());
             installClock();
         }
     }
diff --git a/util/src/main/java/com/ning/billing/util/customfield/api/DefaultCustomFieldCreationEvent.java b/util/src/main/java/com/ning/billing/util/customfield/api/DefaultCustomFieldCreationEvent.java
new file mode 100644
index 0000000..fe085d6
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/customfield/api/DefaultCustomFieldCreationEvent.java
@@ -0,0 +1,100 @@
+/*
+ * 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.util.customfield.api;
+
+import java.util.UUID;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.util.events.CustomFieldCreationEvent;
+import com.ning.billing.util.events.DefaultBusInternalEvent;
+import com.ning.billing.util.tag.TagDefinition;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class DefaultCustomFieldCreationEvent extends DefaultBusInternalEvent implements CustomFieldCreationEvent {
+
+    private final UUID customFieldId;
+    private final UUID objectId;
+    private final ObjectType objectType;
+
+    @JsonCreator
+    public DefaultCustomFieldCreationEvent(@JsonProperty("customFieldId") final UUID customFieldId,
+                                       @JsonProperty("objectId") final UUID objectId,
+                                       @JsonProperty("objectType") final ObjectType objectType,
+                                       @JsonProperty("userToken") final UUID userToken,
+                                       @JsonProperty("accountRecordId") final Long accountRecordId,
+                                       @JsonProperty("tenantRecordId") final Long tenantRecordId) {
+        super(userToken, accountRecordId, tenantRecordId);
+        this.customFieldId = customFieldId;
+        this.objectId = objectId;
+        this.objectType = objectType;
+    }
+
+    @Override
+    public UUID getCustomFieldId() {
+        return customFieldId;
+    }
+
+    @Override
+    public UUID getObjectId() {
+        return objectId;
+    }
+
+    @Override
+    public ObjectType getObjectType() {
+        return objectType;
+    }
+
+    @JsonIgnore
+    @Override
+    public BusInternalEventType getBusEventType() {
+        return BusInternalEventType.CUSTOM_FIELD_CREATION;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultCustomFieldCreationEvent)) {
+            return false;
+        }
+
+        final DefaultCustomFieldCreationEvent that = (DefaultCustomFieldCreationEvent) o;
+
+        if (customFieldId != null ? !customFieldId.equals(that.customFieldId) : that.customFieldId != null) {
+            return false;
+        }
+        if (objectId != null ? !objectId.equals(that.objectId) : that.objectId != null) {
+            return false;
+        }
+        if (objectType != that.objectType) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = customFieldId != null ? customFieldId.hashCode() : 0;
+        result = 31 * result + (objectId != null ? objectId.hashCode() : 0);
+        result = 31 * result + (objectType != null ? objectType.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/customfield/api/DefaultCustomFieldDeletionEvent.java b/util/src/main/java/com/ning/billing/util/customfield/api/DefaultCustomFieldDeletionEvent.java
new file mode 100644
index 0000000..b17adb4
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/customfield/api/DefaultCustomFieldDeletionEvent.java
@@ -0,0 +1,100 @@
+/*
+ * 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.util.customfield.api;
+
+import java.util.UUID;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.util.events.CustomFieldDeletionEvent;
+import com.ning.billing.util.events.DefaultBusInternalEvent;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class DefaultCustomFieldDeletionEvent extends DefaultBusInternalEvent implements CustomFieldDeletionEvent {
+
+    private final UUID customFieldId;
+    private final UUID objectId;
+    private final ObjectType objectType;
+
+    @JsonCreator
+    public DefaultCustomFieldDeletionEvent(@JsonProperty("customFieldId") final UUID customFieldId,
+                                           @JsonProperty("objectId") final UUID objectId,
+                                           @JsonProperty("objectType") final ObjectType objectType,
+                                           @JsonProperty("userToken") final UUID userToken,
+                                           @JsonProperty("accountRecordId") final Long accountRecordId,
+                                           @JsonProperty("tenantRecordId") final Long tenantRecordId) {
+        super(userToken, accountRecordId, tenantRecordId);
+        this.customFieldId = customFieldId;
+        this.objectId = objectId;
+        this.objectType = objectType;
+    }
+
+    @Override
+    public UUID getCustomFieldId() {
+        return customFieldId;
+    }
+
+    @Override
+    public UUID getObjectId() {
+        return objectId;
+    }
+
+    @Override
+    public ObjectType getObjectType() {
+        return objectType;
+    }
+
+    @JsonIgnore
+    @Override
+    public BusInternalEventType getBusEventType() {
+        return BusInternalEventType.CUSTOM_FIELD_DELETION;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultCustomFieldDeletionEvent)) {
+            return false;
+        }
+
+        final DefaultCustomFieldDeletionEvent that = (DefaultCustomFieldDeletionEvent) o;
+
+        if (customFieldId != null ? !customFieldId.equals(that.customFieldId) : that.customFieldId != null) {
+            return false;
+        }
+        if (objectId != null ? !objectId.equals(that.objectId) : that.objectId != null) {
+            return false;
+        }
+        if (objectType != that.objectType) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = customFieldId != null ? customFieldId.hashCode() : 0;
+        result = 31 * result + (objectId != null ? objectId.hashCode() : 0);
+        result = 31 * result + (objectType != null ? objectType.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/customfield/api/DefaultCustomFieldUserApi.java b/util/src/main/java/com/ning/billing/util/customfield/api/DefaultCustomFieldUserApi.java
index b1f0a38..e0e543d 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/api/DefaultCustomFieldUserApi.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/api/DefaultCustomFieldUserApi.java
@@ -47,21 +47,35 @@ public class DefaultCustomFieldUserApi implements CustomFieldUserApi {
     }
 
     @Override
-    public List<CustomField> getCustomFields(final UUID objectId, final ObjectType objectType, final TenantContext context) {
-        return ImmutableList.<CustomField>copyOf(Collections2.transform(customFieldDao.getCustomFields(objectId, objectType, internalCallContextFactory.createInternalTenantContext(context)),
-                                                                        new Function<CustomFieldModelDao, CustomField>() {
-                                                                            @Override
-                                                                            public CustomField apply(final CustomFieldModelDao input) {
-                                                                                return new StringCustomField(input);
-                                                                            }
-                                                                        }));
-    }
-
-    @Override
     public void addCustomFields(final List<CustomField> fields, final CallContext context) throws CustomFieldApiException {
         // TODO make it transactional
         for (final CustomField cur : fields) {
             customFieldDao.create(new CustomFieldModelDao(cur), internalCallContextFactory.createInternalCallContext(cur.getObjectId(), cur.getObjectType(), context));
         }
     }
+
+    @Override
+    public List<CustomField> getCustomFieldsForObject(final UUID objectId, final ObjectType objectType, final TenantContext context) {
+        return withCustomFieldsTransform(customFieldDao.getCustomFieldsForObject(objectId, objectType, internalCallContextFactory.createInternalTenantContext(context)));
+    }
+
+    @Override
+    public List<CustomField> getCustomFieldsForAccountType(final UUID accountId, final ObjectType objectType, final TenantContext context) {
+        return withCustomFieldsTransform(customFieldDao.getCustomFieldsForAccountType(objectType, internalCallContextFactory.createInternalTenantContext(accountId, context)));
+    }
+
+    @Override
+    public List<CustomField> getCustomFieldsForAccount(final UUID accountId, final TenantContext context) {
+        return withCustomFieldsTransform(customFieldDao.getCustomFieldsForAccount(internalCallContextFactory.createInternalTenantContext(accountId, context)));
+    }
+
+    private List<CustomField> withCustomFieldsTransform(List<CustomFieldModelDao> input) {
+        return ImmutableList.<CustomField>copyOf(Collections2.transform(input, new Function<CustomFieldModelDao, CustomField>() {
+            @Override
+            public CustomField apply(final CustomFieldModelDao input) {
+                return new StringCustomField(input);
+            }
+        }));
+    }
+
 }
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldDao.java
index ead8b57..6b9e9e0 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldDao.java
@@ -22,10 +22,16 @@ import java.util.UUID;
 import com.ning.billing.ObjectType;
 import com.ning.billing.util.api.CustomFieldApiException;
 import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.TenantContext;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.entity.dao.EntityDao;
 
 public interface CustomFieldDao extends EntityDao<CustomFieldModelDao, CustomField, CustomFieldApiException> {
 
-    public List<CustomFieldModelDao> getCustomFields(final UUID objectId, final ObjectType objectType, final InternalTenantContext context);
+    public List<CustomFieldModelDao> getCustomFieldsForObject(final UUID objectId, final ObjectType objectType, final InternalTenantContext context);
+
+    public List<CustomFieldModelDao> getCustomFieldsForAccountType(final ObjectType objectType, final InternalTenantContext context);
+
+    public List<CustomFieldModelDao> getCustomFieldsForAccount(final InternalTenantContext context);
+
 }
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/DefaultCustomFieldDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/DefaultCustomFieldDao.java
index 7f19fb0..61f6f03 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/DefaultCustomFieldDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/DefaultCustomFieldDao.java
@@ -19,34 +19,53 @@ package com.ning.billing.util.customfield.dao;
 import java.util.List;
 import java.util.UUID;
 
+import javax.annotation.Nullable;
+
 import org.skife.jdbi.v2.IDBI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import com.ning.billing.BillingExceptionBase;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.ObjectType;
 import com.ning.billing.util.api.CustomFieldApiException;
+import com.ning.billing.util.audit.ChangeType;
 import com.ning.billing.util.cache.CacheControllerDispatcher;
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalTenantContext;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.customfield.api.DefaultCustomFieldCreationEvent;
+import com.ning.billing.util.customfield.api.DefaultCustomFieldDeletionEvent;
 import com.ning.billing.util.dao.NonEntityDao;
 import com.ning.billing.util.entity.dao.EntityDaoBase;
 import com.ning.billing.util.entity.dao.EntitySqlDao;
 import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
 import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
 import com.ning.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
+import com.ning.billing.util.events.BusInternalEvent;
+import com.ning.billing.util.svcsapi.bus.InternalBus;
+import com.ning.billing.util.tag.dao.TagModelDao;
 
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
 import com.google.inject.Inject;
 
 public class DefaultCustomFieldDao extends EntityDaoBase<CustomFieldModelDao, CustomField, CustomFieldApiException> implements CustomFieldDao {
 
+    private final static Logger log = LoggerFactory.getLogger(DefaultCustomFieldDao.class);
+
+    private final InternalBus bus;
+
     @Inject
-    public DefaultCustomFieldDao(final IDBI dbi, final Clock clock, final CacheControllerDispatcher controllerDispatcher, final NonEntityDao nonEntityDao) {
+    public DefaultCustomFieldDao(final IDBI dbi, final Clock clock, final CacheControllerDispatcher controllerDispatcher, final NonEntityDao nonEntityDao, final InternalBus bus) {
         super(new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, controllerDispatcher, nonEntityDao), CustomFieldSqlDao.class);
+        this.bus = bus;
     }
 
     @Override
-    public List<CustomFieldModelDao> getCustomFields(final UUID objectId, final ObjectType objectType, final InternalTenantContext context) {
+    public List<CustomFieldModelDao> getCustomFieldsForObject(final UUID objectId, final ObjectType objectType, final InternalTenantContext context) {
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<CustomFieldModelDao>>() {
             @Override
             public List<CustomFieldModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
@@ -56,7 +75,56 @@ public class DefaultCustomFieldDao extends EntityDaoBase<CustomFieldModelDao, Cu
     }
 
     @Override
+    public List<CustomFieldModelDao> getCustomFieldsForAccountType(final ObjectType objectType, final InternalTenantContext context) {
+        final List<CustomFieldModelDao> allFields = getCustomFieldsForAccount(context);
+
+        return ImmutableList.<CustomFieldModelDao>copyOf(Collections2.filter(allFields, new Predicate<CustomFieldModelDao>() {
+            @Override
+            public boolean apply(@Nullable final CustomFieldModelDao input) {
+                return input.getObjectType() == objectType;
+            }
+        }));
+    }
+
+    @Override
+    public List<CustomFieldModelDao> getCustomFieldsForAccount(final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<CustomFieldModelDao>>() {
+            @Override
+            public List<CustomFieldModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(CustomFieldSqlDao.class).getByAccountRecordId(context);
+            }
+        });
+    }
+
+    @Override
     protected CustomFieldApiException generateAlreadyExistsException(final CustomFieldModelDao entity, final InternalCallContext context) {
         return new CustomFieldApiException(ErrorCode.CUSTOM_FIELD_ALREADY_EXISTS, entity.getId());
     }
+
+    @Override
+    protected void postBusEventFromTransaction(final CustomFieldModelDao customField, final CustomFieldModelDao savedCustomField, final ChangeType changeType,
+                                               final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalCallContext context)
+            throws BillingExceptionBase {
+
+        BusInternalEvent customFieldEvent = null;
+        switch(changeType) {
+            case INSERT:
+                customFieldEvent = new DefaultCustomFieldCreationEvent(customField.getId(), customField.getObjectId(), customField.getObjectType(), context.getUserToken(), context.getAccountRecordId(), context.getTenantRecordId());
+                break;
+            case DELETE:
+                customFieldEvent = new DefaultCustomFieldDeletionEvent(customField.getId(), customField.getObjectId(), customField.getObjectType(), context.getUserToken(), context.getAccountRecordId(), context.getTenantRecordId());
+
+                break;
+            default:
+                return;
+        }
+
+        try {
+            bus.postFromTransaction(customFieldEvent, entitySqlDaoWrapperFactory, context);
+        } catch (InternalBus.EventBusException e) {
+            log.warn("Failed to post tag event for custom field " + customField.getId().toString(), e);
+        }
+
+    }
+
 }
diff --git a/util/src/main/java/com/ning/billing/util/events/BusInternalEvent.java b/util/src/main/java/com/ning/billing/util/events/BusInternalEvent.java
index c4b2d4a..0b64bf4 100644
--- a/util/src/main/java/com/ning/billing/util/events/BusInternalEvent.java
+++ b/util/src/main/java/com/ning/billing/util/events/BusInternalEvent.java
@@ -38,7 +38,9 @@ public interface BusInternalEvent {
         CONTROL_TAGDEFINITION_DELETION,
         USER_TAGDEFINITION_CREATION,
         USER_TAGDEFINITION_DELETION,
-        OVERDUE_CHANGE
+        OVERDUE_CHANGE,
+        CUSTOM_FIELD_CREATION,
+        CUSTOM_FIELD_DELETION,
     }
 
     public BusInternalEventType getBusEventType();
diff --git a/util/src/main/java/com/ning/billing/util/events/CustomFieldCreationEvent.java b/util/src/main/java/com/ning/billing/util/events/CustomFieldCreationEvent.java
new file mode 100644
index 0000000..4699c1b
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/events/CustomFieldCreationEvent.java
@@ -0,0 +1,20 @@
+/*
+ * 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.util.events;
+
+public interface CustomFieldCreationEvent extends CustomFieldEvent {
+}
diff --git a/util/src/main/java/com/ning/billing/util/events/CustomFieldDeletionEvent.java b/util/src/main/java/com/ning/billing/util/events/CustomFieldDeletionEvent.java
new file mode 100644
index 0000000..a72b615
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/events/CustomFieldDeletionEvent.java
@@ -0,0 +1,24 @@
+/*
+ * 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.util.events;
+
+import java.util.UUID;
+
+import com.ning.billing.ObjectType;
+
+public interface CustomFieldDeletionEvent  extends CustomFieldEvent {
+}
diff --git a/util/src/main/java/com/ning/billing/util/events/CustomFieldEvent.java b/util/src/main/java/com/ning/billing/util/events/CustomFieldEvent.java
new file mode 100644
index 0000000..d981a3c
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/events/CustomFieldEvent.java
@@ -0,0 +1,30 @@
+/*
+ * 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.util.events;
+
+import java.util.UUID;
+
+import com.ning.billing.ObjectType;
+
+public interface CustomFieldEvent extends BusInternalEvent {
+
+    UUID getCustomFieldId();
+
+    UUID getObjectId();
+
+    ObjectType getObjectType();
+}
diff --git a/util/src/main/java/com/ning/billing/util/glue/RecordIdModule.java b/util/src/main/java/com/ning/billing/util/glue/RecordIdModule.java
new file mode 100644
index 0000000..2d1d019
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/glue/RecordIdModule.java
@@ -0,0 +1,31 @@
+/*
+ * 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.util.glue;
+
+import com.ning.billing.util.api.RecordIdApi;
+import com.ning.billing.util.recordid.DefaultRecordIdApi;
+
+import com.google.inject.AbstractModule;
+
+public class RecordIdModule extends AbstractModule {
+
+
+    @Override
+    protected void configure() {
+        bind(RecordIdApi.class).to(DefaultRecordIdApi.class).asEagerSingleton();
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/api/TestApiListener.java b/util/src/test/java/com/ning/billing/api/TestApiListener.java
index c18bceb..d09806b 100644
--- a/util/src/test/java/com/ning/billing/api/TestApiListener.java
+++ b/util/src/test/java/com/ning/billing/api/TestApiListener.java
@@ -26,6 +26,7 @@ import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.ning.billing.util.events.CustomFieldEvent;
 import com.ning.billing.util.events.EffectiveSubscriptionInternalEvent;
 import com.ning.billing.util.events.InvoiceAdjustmentInternalEvent;
 import com.ning.billing.util.events.InvoiceCreationInternalEvent;
@@ -76,7 +77,8 @@ public class TestApiListener {
         PAYMENT_ERROR,
         REPAIR_BUNDLE,
         TAG,
-        TAG_DEFINITION
+        TAG_DEFINITION,
+        CUSTOM_FIELD
     }
 
     public void setNonExpectedMode() {
@@ -145,6 +147,14 @@ public class TestApiListener {
     }
 
     @Subscribe
+    public synchronized void processCustomFieldEvent(final CustomFieldEvent event) {
+        log.info(String.format("Got CustomFieldEvent event %s", event.toString()));
+        assertEqualsNicely(NextEvent.CUSTOM_FIELD);
+        notifyIfStackEmpty();
+    }
+
+
+    @Subscribe
     public synchronized void processTagDefinitonEvent(final TagDefinitionInternalEvent event) {
         log.info(String.format("Got TagDefinitionInternalEvent event %s", event.toString()));
         assertEqualsNicely(NextEvent.TAG_DEFINITION);
diff --git a/util/src/test/java/com/ning/billing/util/customfield/api/TestDefaultCustomFieldCreationEvent.java b/util/src/test/java/com/ning/billing/util/customfield/api/TestDefaultCustomFieldCreationEvent.java
new file mode 100644
index 0000000..78421e3
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/customfield/api/TestDefaultCustomFieldCreationEvent.java
@@ -0,0 +1,65 @@
+/*
+ * 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.util.customfield.api;
+
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.util.events.BusInternalEvent.BusInternalEventType;
+import com.ning.billing.util.jackson.ObjectMapper;
+
+public class TestDefaultCustomFieldCreationEvent {
+
+
+    @Test(groups = "fast")
+    public void testPojo() throws Exception {
+        final UUID customFieldId = UUID.randomUUID();
+        final UUID objectId = UUID.randomUUID();
+        final ObjectType objectType = ObjectType.ACCOUNT_EMAIL;
+        final UUID userToken = UUID.randomUUID();
+
+        final DefaultCustomFieldCreationEvent event = new DefaultCustomFieldCreationEvent(customFieldId, objectId, objectType, userToken, 1L, 1L);
+        Assert.assertEquals(event.getBusEventType(), BusInternalEventType.CUSTOM_FIELD_CREATION);
+
+        Assert.assertEquals(event.getObjectId(), objectId);
+        Assert.assertEquals(event.getObjectType(), objectType);
+        Assert.assertEquals(event.getUserToken(), userToken);
+
+        Assert.assertEquals(event, event);
+        Assert.assertEquals(event, new DefaultCustomFieldCreationEvent(customFieldId, objectId, objectType, userToken, 1L, 1L));
+    }
+
+    @Test(groups = "fast")
+    public void testSerialization() throws Exception {
+
+        final ObjectMapper objectMapper = new ObjectMapper();
+
+        final UUID customFieldId = UUID.randomUUID();
+        final UUID objectId = UUID.randomUUID();
+        final ObjectType objectType = ObjectType.ACCOUNT_EMAIL;
+        final UUID userToken = UUID.randomUUID();
+
+        final DefaultCustomFieldCreationEvent event = new DefaultCustomFieldCreationEvent(customFieldId, objectId, objectType, userToken, 1L, 1L);
+
+        final String json = objectMapper.writeValueAsString(event);
+        final DefaultCustomFieldCreationEvent fromJson = objectMapper.readValue(json, DefaultCustomFieldCreationEvent.class);
+        Assert.assertEquals(fromJson, event);
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/util/customfield/api/TestDefaultCustomFieldDeletionEvent.java b/util/src/test/java/com/ning/billing/util/customfield/api/TestDefaultCustomFieldDeletionEvent.java
new file mode 100644
index 0000000..e0c5b28
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/customfield/api/TestDefaultCustomFieldDeletionEvent.java
@@ -0,0 +1,65 @@
+/*
+ * 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.util.customfield.api;
+
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.util.events.BusInternalEvent.BusInternalEventType;
+import com.ning.billing.util.jackson.ObjectMapper;
+
+public class TestDefaultCustomFieldDeletionEvent {
+
+
+    @Test(groups = "fast")
+    public void testPojo() throws Exception {
+        final UUID customFieldId = UUID.randomUUID();
+        final UUID objectId = UUID.randomUUID();
+        final ObjectType objectType = ObjectType.ACCOUNT_EMAIL;
+        final UUID userToken = UUID.randomUUID();
+
+        final DefaultCustomFieldDeletionEvent event = new DefaultCustomFieldDeletionEvent(customFieldId, objectId, objectType, userToken, 1L, 1L);
+        Assert.assertEquals(event.getBusEventType(), BusInternalEventType.CUSTOM_FIELD_DELETION);
+
+        Assert.assertEquals(event.getObjectId(), objectId);
+        Assert.assertEquals(event.getObjectType(), objectType);
+        Assert.assertEquals(event.getUserToken(), userToken);
+
+        Assert.assertEquals(event, event);
+        Assert.assertEquals(event, new DefaultCustomFieldDeletionEvent(customFieldId, objectId, objectType, userToken, 1L, 1L));
+    }
+
+    @Test(groups = "fast")
+    public void testSerialization() throws Exception {
+
+        final ObjectMapper objectMapper = new ObjectMapper();
+
+        final UUID customFieldId = UUID.randomUUID();
+        final UUID objectId = UUID.randomUUID();
+        final ObjectType objectType = ObjectType.ACCOUNT_EMAIL;
+        final UUID userToken = UUID.randomUUID();
+
+        final DefaultCustomFieldDeletionEvent event = new DefaultCustomFieldDeletionEvent(customFieldId, objectId, objectType, userToken, 1L, 1L);
+
+        final String json = objectMapper.writeValueAsString(event);
+        final DefaultCustomFieldDeletionEvent fromJson = objectMapper.readValue(json, DefaultCustomFieldDeletionEvent.class);
+        Assert.assertEquals(fromJson, event);
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java b/util/src/test/java/com/ning/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java
index c5522ad..ac85be2 100644
--- a/util/src/test/java/com/ning/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java
+++ b/util/src/test/java/com/ning/billing/util/customfield/api/TestDefaultCustomFieldUserApi.java
@@ -56,7 +56,7 @@ public class TestDefaultCustomFieldUserApi extends UtilTestSuiteWithEmbeddedDB {
         customFieldUserApi.addCustomFields(ImmutableList.<CustomField>of(customField), callContext);
 
         // Verify the field was saved
-        final List<CustomField> customFields = customFieldUserApi.getCustomFields(accountId, ObjectType.ACCOUNT, callContext);
+        final List<CustomField> customFields = customFieldUserApi.getCustomFieldsForObject(accountId, ObjectType.ACCOUNT, callContext);
         Assert.assertEquals(customFields.size(), 1);
         Assert.assertEquals(customFields.get(0), customField);
         // Verify the account_record_id was populated
diff --git a/util/src/test/java/com/ning/billing/util/customfield/dao/MockCustomFieldDao.java b/util/src/test/java/com/ning/billing/util/customfield/dao/MockCustomFieldDao.java
index 354da71..2694248 100644
--- a/util/src/test/java/com/ning/billing/util/customfield/dao/MockCustomFieldDao.java
+++ b/util/src/test/java/com/ning/billing/util/customfield/dao/MockCustomFieldDao.java
@@ -29,7 +29,7 @@ import com.ning.billing.util.entity.dao.MockEntityDaoBase;
 public class MockCustomFieldDao extends MockEntityDaoBase<CustomFieldModelDao, CustomField, CustomFieldApiException> implements CustomFieldDao {
 
     @Override
-    public List<CustomFieldModelDao> getCustomFields(final UUID objectId, final ObjectType objectType, final InternalTenantContext context) {
+    public List<CustomFieldModelDao> getCustomFieldsForObject(final UUID objectId, final ObjectType objectType, final InternalTenantContext context) {
         final List<CustomFieldModelDao> result = new ArrayList<CustomFieldModelDao>();
         final List<CustomFieldModelDao> all = get(context);
         for (final CustomFieldModelDao cur : all) {
@@ -39,4 +39,14 @@ public class MockCustomFieldDao extends MockEntityDaoBase<CustomFieldModelDao, C
         }
         return result;
     }
+
+    @Override
+    public List<CustomFieldModelDao> getCustomFieldsForAccountType(final ObjectType objectType, final InternalTenantContext context) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<CustomFieldModelDao> getCustomFieldsForAccount(final InternalTenantContext context) {
+        throw new UnsupportedOperationException();
+    }
 }