killbill-aplcache

Changes

entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java 147(+0 -147)

entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java 148(+0 -148)

invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java 130(+0 -130)

invoice/src/test/java/com/ning/billing/invoice/notification/BrainDeadSubscription.java 146(+0 -146)

Details

diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
index f476a59..69abfb4 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
@@ -24,6 +24,8 @@ import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.util.customfield.CustomField;
+
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 
@@ -154,4 +156,34 @@ public class MockSubscription implements Subscription
             throws EntitlementUserApiException {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public String getFieldValue(String fieldName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setFieldValue(String fieldName, String fieldValue) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<CustomField> getFieldList() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void addFields(List<CustomField> fields) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clearFields() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getObjectName() {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java b/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
index eafe210..a9ce2c7 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
@@ -21,6 +21,7 @@ import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.util.customfield.CustomizableEntity;
 
 import org.joda.time.DateTime;
 
@@ -28,7 +29,7 @@ import java.util.List;
 import java.util.UUID;
 
 
-public interface Subscription {
+public interface Subscription extends CustomizableEntity {
 
     public void cancel(DateTime requestedDate, boolean eot)
     throws EntitlementUserApiException;
@@ -47,8 +48,6 @@ public interface Subscription {
         CANCELLED
     }
 
-    public UUID getId();
-
     public UUID getBundleId();
 
     public SubscriptionState getState();
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
index eb1f26d..72cc142 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
@@ -278,6 +278,10 @@ public class SubscriptionApiService {
         }
     }
 
+    public void commitCustomFields(SubscriptionData subscription) {
+        dao.saveCustomFields(subscription);
+    }
+
     private void validateRequestedDate(SubscriptionData subscription, DateTime now, DateTime requestedDate)
         throws EntitlementUserApiException {
 
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 178c4d5..2efe1c5 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
@@ -32,6 +32,9 @@ import com.ning.billing.entitlement.events.user.ApiEvent;
 import com.ning.billing.entitlement.events.user.ApiEventType;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.customfield.CustomizableEntityBase;
+
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -43,7 +46,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
 
-public class SubscriptionData implements Subscription {
+public class SubscriptionData extends CustomizableEntityBase implements Subscription {
 
     private final static Logger log = LoggerFactory.getLogger(SubscriptionData.class);
 
@@ -52,7 +55,6 @@ public class SubscriptionData implements Subscription {
     //
     // Final subscription fields
     //
-    private final UUID id;
     private final UUID bundleId;
     private final DateTime startDate;
     private final DateTime bundleStartDate;
@@ -78,10 +80,9 @@ public class SubscriptionData implements Subscription {
     }
 
     public SubscriptionData(SubscriptionBuilder builder, SubscriptionApiService apiService, Clock clock) {
-        super();
+        super(builder.getId());
         this.apiService = apiService;
         this.clock = clock;
-        this.id = builder.getId();
         this.bundleId = builder.getBundleId();
         this.startDate = builder.getStartDate();
         this.bundleStartDate = builder.getBundleStartDate();
@@ -92,8 +93,27 @@ public class SubscriptionData implements Subscription {
     }
 
     @Override
-    public UUID getId() {
-        return id;
+    public String getObjectName() {
+        return "Subscription";
+    }
+
+
+    @Override
+    public void setFieldValue(String fieldName, String fieldValue) {
+        super.setFieldValue(fieldName, fieldValue);
+        apiService.commitCustomFields(this);
+    }
+
+    @Override
+    public void addFields(List<CustomField> fields) {
+        super.addFields(fields);
+        apiService.commitCustomFields(this);
+    }
+
+    @Override
+    public void clearFields() {
+        super.clearFields();
+        apiService.commitCustomFields(this);
     }
 
     @Override
@@ -444,4 +464,5 @@ public class SubscriptionData implements Subscription {
             previousPriceList = nextPriceList;
         }
     }
+
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
index d7d6b94..fba1b3d 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
@@ -21,12 +21,16 @@ import java.util.UUID;
 
 import org.joda.time.DateTime;
 
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.dao.AccountSqlDao;
 import com.ning.billing.entitlement.api.migration.AccountMigrationData;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.customfield.dao.FieldStoreDao;
 
 public interface EntitlementDao {
 
@@ -79,4 +83,7 @@ public interface EntitlementDao {
 
     public void undoMigration(UUID accountId);
 
-	}
+    // Custom Fields
+    public void saveCustomFields(SubscriptionData subscription);
+}
+
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
index 15654d8..e34d48c 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
@@ -36,6 +36,8 @@ import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.dao.AccountSqlDao;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.catalog.api.ProductCategory;
@@ -59,6 +61,8 @@ import com.ning.billing.entitlement.events.user.ApiEventChange;
 import com.ning.billing.entitlement.events.user.ApiEventType;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.customfield.dao.FieldStoreDao;
 import com.ning.billing.util.notificationq.NotificationKey;
 import com.ning.billing.util.notificationq.NotificationQueue;
 import com.ning.billing.util.notificationq.NotificationQueueService;
@@ -166,12 +170,25 @@ public class EntitlementSqlDao implements EntitlementDao {
     }
 
     @Override
-    public void updateSubscription(SubscriptionData subscription) {
-        Date ctd = (subscription.getChargedThroughDate() != null)  ? subscription.getChargedThroughDate().toDate() : null;
-        Date ptd = (subscription.getPaidThroughDate() != null)  ? subscription.getPaidThroughDate().toDate() : null;
-        subscriptionsDao.updateSubscription(subscription.getId().toString(), subscription.getActiveVersion(), ctd, ptd);
+    public void updateSubscription(final SubscriptionData subscription) {
+
+        final Date ctd = (subscription.getChargedThroughDate() != null)  ? subscription.getChargedThroughDate().toDate() : null;
+        final Date ptd = (subscription.getPaidThroughDate() != null)  ? subscription.getPaidThroughDate().toDate() : null;
+
+
+        subscriptionsDao.inTransaction(new Transaction<Void, SubscriptionSqlDao>() {
+
+            @Override
+            public Void inTransaction(SubscriptionSqlDao transactionalDao,
+                    TransactionStatus status) throws Exception {
+                transactionalDao.updateSubscription(subscription.getId().toString(), subscription.getActiveVersion(), ctd, ptd);
+                return null;
+            }
+        });
     }
 
+
+
     @Override
     public void createNextPhaseEvent(final UUID subscriptionId, final EntitlementEvent nextPhase) {
         eventsDao.inTransaction(new Transaction<Void, EventSqlDao>() {
@@ -391,17 +408,20 @@ public class EntitlementSqlDao implements EntitlementDao {
         }
     }
 
-    public Subscription getBaseSubscription(final UUID bundleId, boolean rebuildSubscription) {
-        List<Subscription> subscriptions = subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString());
-        for (Subscription cur : subscriptions) {
-            if (((SubscriptionData)cur).getCategory() == ProductCategory.BASE) {
-                return  rebuildSubscription ? buildSubscription(cur) : cur;
-            }
+    private void updateCustomFieldsFromTransaction(SubscriptionSqlDao transactionalDao, final SubscriptionData subscription) {
+
+        String SubscriptionId = subscription.getId().toString();
+        String objectType = subscription.getObjectName();
+
+        FieldStoreDao fieldStoreDao = transactionalDao.become(FieldStoreDao.class);
+        fieldStoreDao.clear(SubscriptionId, objectType);
+
+        List<CustomField> fieldList = subscription.getFieldList();
+        if (fieldList != null) {
+            fieldStoreDao.batchSaveFromTransaction(SubscriptionId, objectType, fieldList);
         }
-        return null;
     }
 
-
     private Subscription buildSubscription(Subscription input) {
         if (input == null) {
             return null;
@@ -491,6 +511,7 @@ public class EntitlementSqlDao implements EntitlementDao {
             default:
                 break;
             }
+            loadCustomFields(reloaded);
             result.add(reloaded);
         }
         return result;
@@ -536,6 +557,16 @@ public class EntitlementSqlDao implements EntitlementDao {
     }
 
 
+    public Subscription getBaseSubscription(final UUID bundleId, boolean rebuildSubscription) {
+        List<Subscription> subscriptions = subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString());
+        for (Subscription cur : subscriptions) {
+            if (((SubscriptionData)cur).getCategory() == ProductCategory.BASE) {
+                return  rebuildSubscription ? buildSubscription(cur) : cur;
+            }
+        }
+        return null;
+    }
+
     @Override
     public void undoMigration(final UUID accountId) {
 
@@ -575,4 +606,25 @@ public class EntitlementSqlDao implements EntitlementDao {
         }
     }
 
+    @Override
+    public void saveCustomFields(final SubscriptionData subscription) {
+        subscriptionsDao.inTransaction(new Transaction<Void, SubscriptionSqlDao>() {
+            @Override
+            public Void inTransaction(SubscriptionSqlDao transactionalDao,
+                    TransactionStatus status) throws Exception {
+                updateCustomFieldsFromTransaction(transactionalDao, subscription);
+                return null;
+            }
+        });
+    }
+
+
+    private void loadCustomFields(final Subscription subscription) {
+        FieldStoreDao fieldStoreDao = subscriptionsDao.become(FieldStoreDao.class);
+        List<CustomField> fields = fieldStoreDao.load(subscription.getId().toString(), subscription.getObjectName());
+        subscription.clearFields();
+        if (fields != null) {
+            subscription.addFields(fields);
+        }
+    }
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java
index 100d184..fc3362a 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java
@@ -40,6 +40,8 @@ import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 
 public class TestDefaultBillingEvent {
 	public static final UUID ID_ZERO = new UUID(0L,0L);
@@ -48,88 +50,88 @@ public class TestDefaultBillingEvent {
 
 	@Test(groups={"fast"})
 	public void testEventOrderingSubscription() {
-	
+
 		BillingEvent event0 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-31T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
 		BillingEvent event1 = createEvent(subscription(ID_ONE), new DateTime("2012-01-31T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
 		BillingEvent event2 = createEvent(subscription(ID_TWO), new DateTime("2012-01-31T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
-		
+
 		SortedSet<BillingEvent> set = new TreeSet<BillingEvent>();
 		set.add(event2);
 		set.add(event1);
 		set.add(event0);
-		
+
 		Iterator<BillingEvent> it = set.iterator();
-		
+
 		Assert.assertEquals(event0, it.next());
 		Assert.assertEquals(event1, it.next());
 		Assert.assertEquals(event2, it.next());
 	}
-	
+
 	@Test(groups={"fast"})
 	public void testEventOrderingDate() {
-	
+
 		BillingEvent event0 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
 		BillingEvent event1 = createEvent(subscription(ID_ZERO), new DateTime("2012-02-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
 		BillingEvent event2 = createEvent(subscription(ID_ZERO), new DateTime("2012-03-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
-		
+
 		SortedSet<BillingEvent> set = new TreeSet<BillingEvent>();
 		set.add(event2);
 		set.add(event1);
 		set.add(event0);
-		
+
 		Iterator<BillingEvent> it = set.iterator();
-		
+
 		Assert.assertEquals(event0, it.next());
 		Assert.assertEquals(event1, it.next());
 		Assert.assertEquals(event2, it.next());
 	}
-	
+
 	@Test(groups={"fast"})
 	public void testEventOrderingType() {
-	
+
 		BillingEvent event0 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
 		BillingEvent event1 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CHANGE);
 		BillingEvent event2 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CANCEL);
-		
+
 		SortedSet<BillingEvent> set = new TreeSet<BillingEvent>();
 		set.add(event2);
 		set.add(event1);
 		set.add(event0);
-		
+
 		Iterator<BillingEvent> it = set.iterator();
-		
+
 		Assert.assertEquals(event0, it.next());
 		Assert.assertEquals(event1, it.next());
 		Assert.assertEquals(event2, it.next());
 	}
-	
+
 	@Test(groups={"fast"})
 	public void testEventOrderingMix() {
-	
+
 		BillingEvent event0 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
 		BillingEvent event1 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-02T00:02:04.000Z"), SubscriptionTransitionType.CHANGE);
 		BillingEvent event2 = createEvent(subscription(ID_ONE), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CANCEL);
-		
+
 		SortedSet<BillingEvent> set = new TreeSet<BillingEvent>();
 		set.add(event2);
 		set.add(event1);
 		set.add(event0);
-		
+
 		Iterator<BillingEvent> it = set.iterator();
-		
+
 		Assert.assertEquals(event0, it.next());
 		Assert.assertEquals(event1, it.next());
 		Assert.assertEquals(event2, it.next());
 	}
 
-	
+
 	private BillingEvent createEvent(Subscription sub, DateTime effectiveDate, SubscriptionTransitionType type) {
 		InternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(BigDecimal.ZERO, Currency.USD));
 		int billCycleDay = 1;
 
 		Plan shotgun = new MockPlan();
 		PlanPhase shotgunMonthly = createMockMonthlyPlanPhase(null, BigDecimal.ZERO, PhaseType.TRIAL);
-		
+
 		return new DefaultBillingEvent(sub , effectiveDate,
 				shotgun, shotgunMonthly,
 				zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, billCycleDay,
@@ -142,13 +144,11 @@ public class TestDefaultBillingEvent {
 				new MockInternationalPrice(new DefaultPrice(fixedRate, Currency.USD)),
 				BillingPeriod.MONTHLY, phaseType);
 	}
-	
+
 	private Subscription subscription(final UUID id) {
-		return new BrainDeadSubscription() {
-			public UUID getId() {
-				return id;
-			}
-		};
+	    Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+	    ((ZombieControl) subscription).addResult("getId", id);
+	    return subscription;
 	}
 
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
index 55d383a..21ed6fa 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
@@ -72,7 +72,7 @@ public class TestDefaultEntitlementBillingApi {
 	private ArrayList<SubscriptionBundle> bundles;
 	private ArrayList<Subscription> subscriptions;
 	private ArrayList<SubscriptionTransition> transitions;
-	private BrainDeadMockEntitlementDao dao;
+	private EntitlementDao dao;
 
 	private Clock clock;
 	private SubscriptionData subscription;
@@ -112,54 +112,21 @@ public class TestDefaultEntitlementBillingApi {
 
 		subscriptions.add(subscription);
 
-		dao = new BrainDeadMockEntitlementDao() {
-			@Override
-            public List<SubscriptionBundle> getSubscriptionBundleForAccount(
-					UUID accountId) {
-				return bundles;
-
-			}
-
-			@Override
-            public List<Subscription> getSubscriptions(UUID bundleId) {
-				return subscriptions;
-			}
-
-			@Override
-            public Subscription getSubscriptionFromId(UUID subscriptionId) {
-				return subscription;
-
-			}
-
-            @Override
-            public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
-                throw new UnsupportedOperationException();
-            }
-
-            @Override
-			public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
-				return bundle;
-			}
-		};
+        dao = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementDao.class);
+        ((ZombieControl) dao).addResult("getSubscriptionBundleForAccount", bundles);
+        ((ZombieControl) dao).addResult("getSubscriptions", subscriptions);
+        ((ZombieControl) dao).addResult("getSubscriptionFromId", subscription);
+        ((ZombieControl) dao).addResult("getSubscriptionBundleFromId", bundle);
 
         assertTrue(true);
 	}
 
     @Test(enabled=true, groups="fast")
 	public void testBillingEventsEmpty() {
-		EntitlementDao dao = new BrainDeadMockEntitlementDao() {
-			@Override
-            public List<SubscriptionBundle> getSubscriptionBundleForAccount(
-					UUID accountId) {
-				return new ArrayList<SubscriptionBundle>();
-			}
 
-            @Override
-            public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
-                throw new UnsupportedOperationException();
-            }
+        dao = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementDao.class);
+        ((ZombieControl) dao).addResult("getSubscriptionBundleForAccount", new ArrayList<SubscriptionBundle>());
 
-        };
 		AccountUserApi accountApi = new BrainDeadAccountUserApi() ;
 		DefaultEntitlementBillingApi api = new DefaultEntitlementBillingApi(dao,accountApi,catalogService);
 		SortedSet<BillingEvent> events = api.getBillingEventsForAccount(new UUID(0L,0L));
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java
new file mode 100644
index 0000000..d888c14
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2010-2011 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.entitlement.api.user;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.entitlement.api.TestApiBase;
+import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
+import com.ning.billing.entitlement.glue.MockEngineModuleSql;
+import com.ning.billing.util.customfield.CustomField;
+
+
+public class TestUserCustomFieldsSql extends TestApiBase {
+
+    @Override
+    protected Injector getInjector() {
+        return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
+    }
+
+
+
+    @Test(enabled=false, groups={"slow"})
+    public void stress() {
+        cleanupTest();
+        for (int i = 0; i < 20; i++) {
+            setupTest();
+            testOverwriteCustomFields();
+            cleanupTest();
+
+            setupTest();
+            testBasicCustomFields();
+            cleanupTest();
+        }
+    }
+
+    @Test(enabled=true, groups={"slow"})
+    public void testOverwriteCustomFields() {
+        log.info("Starting testCreateWithRequestedDate");
+        try {
+
+            DateTime init = clock.getUTCNow();
+            DateTime requestedDate = init.minusYears(1);
+
+            String productName = "Shotgun";
+            BillingPeriod term = BillingPeriod.MONTHLY;
+            String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.CREATE);
+            SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
+                    getProductSpecifier(productName, planSetName, term, null), requestedDate);
+            assertNotNull(subscription);
+
+            assertEquals(subscription.getFieldValue("nonExistent"), null);
+
+            subscription.setFieldValue("field1", "value1");
+            assertEquals(subscription.getFieldValue("field1"), "value1");
+            List<CustomField> allFields = subscription.getFieldList();
+            assertEquals(allFields.size(), 1);
+
+            subscription.setFieldValue("field1", "valueNew1");
+            assertEquals(subscription.getFieldValue("field1"), "valueNew1");
+            allFields = subscription.getFieldList();
+            assertEquals(allFields.size(), 1);
+
+            subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
+            assertEquals(subscription.getFieldValue("field1"), "valueNew1");
+            allFields = subscription.getFieldList();
+            assertEquals(allFields.size(), 1);
+
+            subscription.setFieldValue("field1", "valueSuperNew1");
+            assertEquals(subscription.getFieldValue("field1"), "valueSuperNew1");
+            allFields = subscription.getFieldList();
+            assertEquals(allFields.size(), 1);
+
+            subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
+            assertEquals(subscription.getFieldValue("field1"), "valueSuperNew1");
+            allFields = subscription.getFieldList();
+            assertEquals(allFields.size(), 1);
+
+            /*
+             * BROKEN
+            subscription.setFieldValue("field1", null);
+            subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
+            assertEquals(subscription.getFieldValue("field1"), null);
+            allFields = subscription.getFieldList();
+            assertEquals(allFields.size(), 1);
+             */
+        } catch (EntitlementUserApiException e) {
+            log.error("Unexpected exception",e);
+            Assert.fail(e.getMessage());
+        }
+    }
+
+    @Test(enabled=true, groups={"slow"})
+    public void testBasicCustomFields() {
+        log.info("Starting testCreateWithRequestedDate");
+        try {
+
+            DateTime init = clock.getUTCNow();
+            DateTime requestedDate = init.minusYears(1);
+
+            String productName = "Shotgun";
+            BillingPeriod term = BillingPeriod.MONTHLY;
+            String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.CREATE);
+            SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
+                    getProductSpecifier(productName, planSetName, term, null), requestedDate);
+            assertNotNull(subscription);
+
+
+            subscription.setFieldValue("field1", "value1");
+            assertEquals(subscription.getFieldValue("field1"), "value1");
+            List<CustomField> allFields = subscription.getFieldList();
+            assertEquals(allFields.size(), 1);
+
+            subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
+            assertEquals(subscription.getFieldValue("field1"), "value1");
+            assertEquals(allFields.size(), 1);
+
+            subscription.clearFields();
+
+            subscription.setFieldValue("field2", "value2");
+            subscription.setFieldValue("field3", "value3");
+            assertEquals(subscription.getFieldValue("field2"), "value2");
+            assertEquals(subscription.getFieldValue("field3"), "value3");
+            allFields = subscription.getFieldList();
+            assertEquals(allFields.size(), 2);
+
+            subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
+            assertEquals(subscription.getFieldValue("field2"), "value2");
+            assertEquals(subscription.getFieldValue("field3"), "value3");
+            allFields = subscription.getFieldList();
+            assertEquals(allFields.size(), 2);
+
+        } catch (EntitlementUserApiException e) {
+            log.error("Unexpected exception",e);
+            Assert.fail(e.getMessage());
+        }
+    }
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
index fac7d62..085afb9 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
@@ -23,6 +23,8 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.TreeSet;
 import java.util.UUID;
+
+import org.apache.commons.lang.NotImplementedException;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.slf4j.Logger;
@@ -447,4 +449,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
         }
     }
 
+    @Override
+    public void saveCustomFields(SubscriptionData subscription) {
+        throw new NotImplementedException();
+    }
+
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
index 4f936fd..b46247a 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
@@ -39,6 +39,8 @@ import com.ning.billing.invoice.model.DefaultInvoicePayment;
 import com.ning.billing.invoice.model.InvoiceGenerator;
 import com.ning.billing.invoice.model.InvoiceItemList;
 import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.DefaultClock;
 import org.joda.time.DateTime;
@@ -508,7 +510,9 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         MockPlanPhase phase1 = new MockPlanPhase(recurringPrice, null, BillingPeriod.MONTHLY, PhaseType.TRIAL);
         MockPlan plan1 = new MockPlan(phase1);
 
-        Subscription subscription = new MockSubscription();
+        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+
         DateTime effectiveDate1 = new DateTime(2011, 2, 1, 0, 0, 0, 0);
         BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan1, phase1, null,
                 recurringPrice, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
@@ -556,7 +560,8 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         MockPlanPhase phase = new MockPlanPhase(recurringPrice, null);
         MockPlan plan = new MockPlan(phase);
 
-        Subscription subscription = new MockSubscription();
+        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
         DateTime effectiveDate = buildDateTime(2011, 1, 1);
 
         BillingEvent event = new DefaultBillingEvent(subscription, effectiveDate, plan, phase, null,
@@ -586,7 +591,9 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         MockPlanPhase phase2 = new MockPlanPhase(recurringPrice, null);
 
         MockPlan plan = new MockPlan();
-        Subscription subscription = new MockSubscription();
+
+        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
         DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
 
         BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan, phase1, fixedPrice,
@@ -643,7 +650,9 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         MockPlanPhase phase2 = new MockPlanPhase(recurringPrice, null);
 
         MockPlan plan = new MockPlan();
-        Subscription subscription = new MockSubscription();
+
+        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
         DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
 
         BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan, phase1, fixedPrice,
diff --git a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
index f6c1063..fdc5076 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
@@ -57,6 +57,8 @@ import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.invoice.InvoiceListener;
 import com.ning.billing.invoice.dao.DefaultInvoiceDao;
 import com.ning.billing.lifecycle.KillbillService.ServiceException;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.bus.InMemoryBus;
 import com.ning.billing.util.clock.Clock;
@@ -102,135 +104,6 @@ public class TestNextBillingDateNotifier {
 
 	}
 
-	private class MockEntitlementDao implements EntitlementDao {
-
-		@Override
-		public List<SubscriptionBundle> getSubscriptionBundleForAccount(
-				UUID accountId) {
-			throw new UnsupportedOperationException();
-		}
-
-		@Override
-		public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
-			throw new UnsupportedOperationException();
-		}
-
-		@Override
-		public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
-			throw new UnsupportedOperationException();
-		}
-
-		@Override
-		public SubscriptionBundle createSubscriptionBundle(
-				SubscriptionBundleData bundle) {
-            throw new UnsupportedOperationException();
-
-		}
-
-		@Override
-		public Subscription getSubscriptionFromId(UUID subscriptionId) {
-			return new BrainDeadSubscription();
-
-		}
-
-		@Override
-		public UUID getAccountIdFromSubscriptionId(UUID subscriptionId) {
-			throw new UnsupportedOperationException();
-
-		}
-
-		@Override
-		public Subscription getBaseSubscription(UUID bundleId) {
-			throw new UnsupportedOperationException();
-
-		}
-
-		@Override
-		public List<Subscription> getSubscriptions(UUID bundleId) {
-			throw new UnsupportedOperationException();
-
-		}
-
-		@Override
-		public List<Subscription> getSubscriptionsForKey(String bundleKey) {
-			throw new UnsupportedOperationException();
-
-		}
-
-		@Override
-		public void updateSubscription(SubscriptionData subscription) {
-			throw new UnsupportedOperationException();
-		}
-
-		@Override
-		public void createNextPhaseEvent(UUID subscriptionId,
-				EntitlementEvent nextPhase) {
-			throw new UnsupportedOperationException();
-		}
-
-		@Override
-		public EntitlementEvent getEventById(UUID eventId) {
-			throw new UnsupportedOperationException();
-
-		}
-
-		@Override
-		public List<EntitlementEvent> getEventsForSubscription(
-				UUID subscriptionId) {
-			throw new UnsupportedOperationException();
-
-		}
-
-		@Override
-		public List<EntitlementEvent> getPendingEventsForSubscription(
-				UUID subscriptionId) {
-			throw new UnsupportedOperationException();
-
-		}
-
-		@Override
-		public void createSubscription(SubscriptionData subscription,
-				List<EntitlementEvent> initialEvents) {
-			throw new UnsupportedOperationException();
-		}
-
-		@Override
-		public void cancelSubscription(UUID subscriptionId,
-				EntitlementEvent cancelEvent) {
-			throw new UnsupportedOperationException();
-
-		}
-
-		@Override
-		public void uncancelSubscription(UUID subscriptionId,
-				List<EntitlementEvent> uncancelEvents) {
-			throw new UnsupportedOperationException();
-
-		}
-
-		@Override
-		public void changePlan(UUID subscriptionId,
-				List<EntitlementEvent> changeEvents) {
-			throw new UnsupportedOperationException();
-		}
-
-		@Override
-		public void migrate(UUID acountId, AccountMigrationData data) {
-			throw new UnsupportedOperationException();
-		}
-
-		@Override
-		public void undoMigration(UUID accountId) {
-			throw new UnsupportedOperationException();
-		}
-
-        @Override
-        public void recreateSubscription(UUID subscriptionId,
-                List<EntitlementEvent> recreateEvents) {
-            throw new UnsupportedOperationException();
-        }
-
-	}
 
 	@BeforeClass(groups={"setup"})
 	public void setup() throws ServiceException, IOException, ClassNotFoundException, SQLException {
@@ -260,7 +133,13 @@ public class TestNextBillingDateNotifier {
         eventBus = g.getInstance(Bus.class);
         helper = g.getInstance(MysqlTestingHelper.class);
         notificationQueueService = g.getInstance(NotificationQueueService.class);
-        notifier = new DefaultNextBillingDateNotifier(notificationQueueService,g.getInstance(InvoiceConfig.class), new MockEntitlementDao(), listener);
+
+
+        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+        EntitlementDao entitlementDao = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementDao.class);
+        ((ZombieControl) entitlementDao).addResult("getSubscriptionFromId", subscription);
+
+        notifier = new DefaultNextBillingDateNotifier(notificationQueueService,g.getInstance(InvoiceConfig.class), entitlementDao, listener);
         startMysql();
 	}
 
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
index 136eeb5..e633755 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
@@ -35,13 +35,14 @@ import com.ning.billing.entitlement.api.user.SubscriptionTransition.Subscription
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.dao.MockSubscription;
 import com.ning.billing.invoice.model.BillingEventSet;
 import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
 import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
 import com.ning.billing.invoice.model.InvoiceGenerator;
 import com.ning.billing.invoice.model.InvoiceItemList;
 import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.DefaultClock;
 
@@ -452,7 +453,9 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     @Test
     public void testFixedPriceLifeCycle() throws InvoiceApiException {
         UUID accountId = UUID.randomUUID();
-        Subscription subscription = new MockSubscription();
+        Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+        ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+
         Plan plan = new MockPlan("plan 1");
         MockInternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(ZERO, Currency.USD));
         MockInternationalPrice cheapPrice = new MockInternationalPrice(new DefaultPrice(ONE, Currency.USD));
diff --git a/util/src/test/java/com/ning/billing/mock/BrainDeadProxyFactory.java b/util/src/test/java/com/ning/billing/mock/BrainDeadProxyFactory.java
index 31dcd30..2998d07 100644
--- a/util/src/test/java/com/ning/billing/mock/BrainDeadProxyFactory.java
+++ b/util/src/test/java/com/ning/billing/mock/BrainDeadProxyFactory.java
@@ -26,48 +26,48 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public class BrainDeadProxyFactory {
-	private static final Logger log = LoggerFactory.getLogger(BrainDeadProxyFactory.class);
+    private static final Logger log = LoggerFactory.getLogger(BrainDeadProxyFactory.class);
 
-	public static interface ZombieControl {
-		
-		public ZombieControl addResult(String method, Object result);
-		
-		public ZombieControl clearResults();
-		
-	}
+    public static interface ZombieControl {
 
-	@SuppressWarnings("unchecked")
-	public static <T> T createBrainDeadProxyFor(final Class<T> clazz) {
-		return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
+        public ZombieControl addResult(String method, Object result);
+
+        public ZombieControl clearResults();
+
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T> T createBrainDeadProxyFor(final Class<T> clazz) {
+        return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
                 new Class[] { clazz , ZombieControl.class},
                 new InvocationHandler() {
-					private Map<String,Object> results = new HashMap<String,Object>();
-			
-					@Override
-					public Object invoke(Object proxy, Method method, Object[] args)
-							throws Throwable {
-						
-						if(method.getDeclaringClass().equals(ZombieControl.class)) {
-							if(method.getName().equals("addResult")) {
-								results.put((String) args[0], args[1]);
-								return proxy;
-							} else if(method.getName().equals("clearResults")) {
-								results.clear();
-								return proxy;
-							}
+            private final Map<String,Object> results = new HashMap<String,Object>();
+
+            @Override
+            public Object invoke(Object proxy, Method method, Object[] args)
+                throws Throwable {
+
+                if (method.getDeclaringClass().equals(ZombieControl.class)) {
+                    if(method.getName().equals("addResult")) {
+                        results.put((String) args[0], args[1]);
+                        return proxy;
+                    } else if(method.getName().equals("clearResults")) {
+                        results.clear();
+                        return proxy;
+                    }
+
+                } else {
 
-						} else {
-							
-							Object result = results.get(method.getName());
-							if (result != null) {
-								return result;
-							} else {
-								log.error(String.format("No result for Method: '%s' on Class '%s'",method.getName(), method.getDeclaringClass().getName()));
-								throw new UnsupportedOperationException();
-							}
-						}
-						return (Void) null;
-					}
-				});
-	}
+                    Object result = results.get(method.getName());
+                    if (result != null) {
+                        return result;
+                    } else {
+                        log.error(String.format("No result for Method: '%s' on Class '%s'",method.getName(), method.getDeclaringClass().getName()));
+                        throw new UnsupportedOperationException();
+                    }
+                }
+                return null;
+            }
+        });
+    }
 }