killbill-uncached

Changes

account/pom.xml 13(+11 -2)

analytics/pom.xml 10(+6 -4)

invoice/pom.xml 10(+10 -0)

util/pom.xml 10(+10 -0)

Details

account/pom.xml 13(+11 -2)

diff --git a/account/pom.xml b/account/pom.xml
index 46078b3..b5c1e6f 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -8,7 +8,8 @@
     OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for 
     the specific language governing permissions and limitations ~ under the License. -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>com.ning.billing</groupId>
@@ -68,11 +69,19 @@
             <artifactId>slf4j-log4j12</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.antlr</groupId>
+            <artifactId>stringtemplate</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+       <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
             <scope>test</scope>
         </dependency>
-
     </dependencies>
     <build>
         <plugins>
diff --git a/account/src/main/java/com/ning/billing/account/dao/IAccountDaoSql.java b/account/src/main/java/com/ning/billing/account/dao/IAccountDaoSql.java
index c7068c1..1c032aa 100644
--- a/account/src/main/java/com/ning/billing/account/dao/IAccountDaoSql.java
+++ b/account/src/main/java/com/ning/billing/account/dao/IAccountDaoSql.java
@@ -30,26 +30,27 @@ import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
 import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.IAccount;
 
-
+@ExternalizedSqlViaStringTemplate3()
 public interface IAccountDaoSql extends Transactional<IAccountDaoSql>, CloseMe {
 
-    @SqlUpdate("insert into accounts (id, key_name) values (:id, :key_name)")
+    @SqlUpdate
     public void insertAccount(@Bind(binder = IAccountSqlBinder.class) IAccount account);
 
-    @SqlQuery("select id, key_name from accounts where key_name = :key_name")
+    @SqlQuery
     @Mapper(IAccountSqlMapper.class)
     public IAccount getAccountByKey(@Bind("key_name") String key);
 
-    @SqlQuery("select id, key_name from accounts where id = :id")
+    @SqlQuery
     @Mapper(IAccountSqlMapper.class)
     public IAccount getAccountFromId(@Bind("id") String id);
 
-    @SqlQuery("select id, key_name from accounts")
+    @SqlQuery
     @Mapper(IAccountSqlMapper.class)
     public List<IAccount> getAccounts();
 
diff --git a/account/src/main/resources/com/ning/billing/account/dao/IAccountDaoSql.sql.stg b/account/src/main/resources/com/ning/billing/account/dao/IAccountDaoSql.sql.stg
new file mode 100644
index 0000000..b15fc2d
--- /dev/null
+++ b/account/src/main/resources/com/ning/billing/account/dao/IAccountDaoSql.sql.stg
@@ -0,0 +1,40 @@
+group IAccountDaoSql;
+
+insertAccount() ::= <<
+    insert into accounts (
+      id
+      , key_name
+    ) values (
+      :id
+      , :key_name
+    );
+>> 
+
+getAccountByKey(key) ::= <<
+    select
+      id
+      , key_name
+    from accounts
+    where
+      key_name = :key_name
+    ;
+>>
+
+getAccountFromId(id) ::= <<
+    select
+      id
+      , key_name
+    from accounts
+    where
+      id = :id
+    ;
+>>
+
+
+getAccounts() ::= <<
+    select
+      id
+      , key_name
+    from accounts
+    ;
+>>
diff --git a/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java b/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java
index 20d082f..b47f8ad 100644
--- a/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java
+++ b/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java
@@ -16,28 +16,24 @@
 
 package com.ning.billing.account.dao;
 
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.assertFalse;
-
-import java.io.IOException;
-import java.net.URL;
-import java.util.List;
-
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.IAccount;
-import com.ning.billing.account.glue.AccountModule;
 import com.ning.billing.account.glue.AccountModuleMock;
 
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
 public class TestSimpleAccountDao {
 
 
@@ -76,17 +72,15 @@ public class TestSimpleAccountDao {
 
         IAccount r = dao.getAccountByKey("foo");
         assertNotNull(r);
-        assertEquals(r.getId(), a.getId());
         assertEquals(r.getKey(), a.getKey());
 
-        r = dao.getAccountFromId(a.getId());
+        r = dao.getAccountFromId(r.getId());
         assertNotNull(r);
-        assertEquals(r.getId(), a.getId());
         assertEquals(r.getKey(), a.getKey());
 
         List<IAccount> all = dao.getAccounts();
         assertNotNull(all);
-        assertEquals(all.size(), 1);
+        assertTrue(all.size() >= 1);
     }
 
 

analytics/pom.xml 10(+6 -4)

diff --git a/analytics/pom.xml b/analytics/pom.xml
index a6aa930..81f5197 100644
--- a/analytics/pom.xml
+++ b/analytics/pom.xml
@@ -63,14 +63,16 @@
             <scope>runtime</scope>
         </dependency>
         <dependency>
-            <groupId>org.jdbi</groupId>
-            <artifactId>jdbi</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
     </build>
diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/TestEventDao.java b/analytics/src/test/java/com/ning/billing/analytics/dao/TestEventDao.java
index 453f35f..f27db78 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/dao/TestEventDao.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/dao/TestEventDao.java
@@ -16,21 +16,11 @@
 
 package com.ning.billing.analytics.dao;
 
-import com.ning.billing.analytics.BusinessSubscription;
-import com.ning.billing.analytics.BusinessSubscriptionEvent;
-import com.ning.billing.analytics.BusinessSubscriptionTransition;
-import com.ning.billing.analytics.MockDuration;
-import com.ning.billing.analytics.MockPhase;
-import com.ning.billing.analytics.MockPlan;
-import com.ning.billing.analytics.MockProduct;
-import com.ning.billing.analytics.MockSubscription;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.catalog.api.IPlan;
-import com.ning.billing.catalog.api.IPlanPhase;
-import com.ning.billing.catalog.api.IProduct;
-import com.ning.billing.catalog.api.PhaseType;
-import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.analytics.*;
+import com.ning.billing.catalog.api.*;
+import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.entitlement.api.user.ISubscription;
+import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.skife.jdbi.v2.IDBI;
@@ -56,8 +46,10 @@ public class TestEventDao
     @BeforeClass(alwaysRun = true)
     public void startMysql() throws IOException, ClassNotFoundException, SQLException
     {
+        final String ddl = IOUtils.toString(EventDao.class.getResourceAsStream("/com/ning/billing/analytics/ddl.sql"));
+
         helper.startMysql();
-        helper.initDb();
+        helper.initDb(ddl);
 
         final IProduct product = new MockProduct("platinium", "subscription", ProductCategory.BASE);
         final IPlan plan = new MockPlan("platinum-monthly", product);
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 9173e3a..faca145 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -8,7 +8,8 @@
     OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for 
     the specific language governing permissions and limitations ~ under the License. -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>com.ning.billing</groupId>
@@ -92,6 +93,11 @@
             <groupId>org.codehaus.jackson</groupId>
             <artifactId>jackson-mapper-asl</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.antlr</groupId>
+            <artifactId>stringtemplate</artifactId>
+            <scope>runtime</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.java
index a151e74..737bf52 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.java
@@ -35,22 +35,23 @@ import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
 import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
 import com.ning.billing.entitlement.api.user.ISubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 
-
+@ExternalizedSqlViaStringTemplate3()
 public interface IBundleSqlDao extends Transactional<IEventSqlDao>, CloseMe, Transmogrifier {
 
-    @SqlUpdate("insert into bundles (id, start_dt, name, account_id) values (:id, :start_dt, :name, :account_id)")
+    @SqlUpdate
     public void insertBundle(@Bind(binder = SubscriptionBundleBinder.class) SubscriptionBundle bundle);
 
-    @SqlQuery("select id, start_dt, name, account_id from bundles where id = :id")
+    @SqlQuery
     @Mapper(ISubscriptionBundleSqlMapper.class)
     public ISubscriptionBundle getBundleFromId(@Bind("id") String id);
 
-    @SqlQuery("select id, start_dt, name, account_id from bundles where account_id = :account_id")
+    @SqlQuery
     @Mapper(ISubscriptionBundleSqlMapper.class)
     public List<ISubscriptionBundle> getBundleFromAccount(@Bind("account_id") String accountId);
 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IEventSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IEventSqlDao.java
index 5a2ea54..007d5b9 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IEventSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/IEventSqlDao.java
@@ -35,6 +35,7 @@ import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
 import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
 import com.ning.billing.entitlement.events.IEvent;
@@ -52,46 +53,39 @@ import com.ning.billing.entitlement.events.user.ApiEventUncancel;
 import com.ning.billing.entitlement.events.user.IUserEvent;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
 
+@ExternalizedSqlViaStringTemplate3()
 public interface IEventSqlDao extends Transactional<IEventSqlDao>, CloseMe, Transmogrifier  {
 
-    static final String EVENT_FIELDS = "event_id, event_type, user_type, created_dt, updated_dt, requested_dt, effective_dt, subscription_id, plan_name, phase_name, plist_name, current_version, is_active, processing_owner, processing_available_dt, processing_state";
-    static final String EVENT_VALUES = ":event_id, :event_type, :user_type, :created_dt, :updated_dt, :requested_dt, :effective_dt, :subscription_id, :plan_name, :phase_name, :plist_name, :current_version, :is_active, :processing_owner, :processing_available_dt, :processing_state";
-    static final String GET_READY_WHERE = "effective_dt <= :now and is_active = 1 and processing_state != 'PROCESSED' and (processing_owner IS NULL OR processing_available_dt <= :now)";
-    static final String CLAIM_WHERE = "event_id = :event_id and is_active = 1 and processing_state != 'PROCESSED' and (processing_owner IS NULL OR processing_available_dt <= :now)";
-    static final String EVENT_ORDER = " order by effective_dt asc, created_dt asc, requested_dt asc, id asc";
-
     //
     // APIs for event notifications
     //
-    @SqlQuery("select " + EVENT_FIELDS + " from events where " + GET_READY_WHERE + EVENT_ORDER + " limit :max")
+    @SqlQuery
     @Mapper(IEventSqlMapper.class)
     public List<IEvent> getReadyEvents(@Bind("now") Date now, @Bind("max") int max);
 
-    @SqlUpdate("update events set processing_owner = :owner, processing_available_dt = :next_available, processing_state = 'IN_PROCESSING' where " + CLAIM_WHERE)
+    @SqlUpdate
     public int claimEvent(@Bind("owner") String owner, @Bind("next_available") Date nextAvailable, @Bind("event_id") String eventId, @Bind("now") Date now);
 
-    @SqlUpdate("update events set processing_owner = NULL, processing_state = 'PROCESSED' where event_id = :event_id and processing_owner = :owner")
+    @SqlUpdate
     public void clearEvent(@Bind("event_id") String eventId, @Bind("owner") String owner);
 
-
-    @SqlUpdate("insert into events (" + EVENT_FIELDS + ") values (" + EVENT_VALUES + ")")
+    @SqlUpdate
     public void insertEvent(@Bind(binder = IEventSqlDaoBinder.class) IEvent evt);
 
-    @SqlUpdate("insert into claimed_events (sequence_id, owner_id, hostname, claimed_dt, event_id) values (:sequence_id, :owner_id, :hostname, :claimed_dt, :event_id)")
+    @SqlUpdate
     public void insertClaimedHistory(@Bind("sequence_id") int sequenceId, @Bind("owner_id") String ownerId, @Bind("hostname") String hostname, @Bind("claimed_dt") Date clainedDate, @Bind("event_id") String eventId);
 
-    @SqlUpdate("update events set is_active = 0, updated_dt = :now where event_id = :event_id")
+    @SqlUpdate
     public void unactiveEvent(@Bind("event_id")String eventId, @Bind("now") Date now);
 
-    @SqlUpdate("update events set is_active = 1, updated_dt = :now where event_id = :event_id")
+    @SqlUpdate
     public void reactiveEvent(@Bind("event_id")String eventId, @Bind("now") Date now);
 
-
-    @SqlQuery("select " + EVENT_FIELDS + " from events where subscription_id = :subscription_id and is_active = 1 and effective_dt > :now" + EVENT_ORDER)
+    @SqlQuery
     @Mapper(IEventSqlMapper.class)
     public List<IEvent> getFutureActiveEventForSubscription(@Bind("subscription_id") String subscriptionId, @Bind("now") Date now);
 
-    @SqlQuery("select " + EVENT_FIELDS + " from events where subscription_id = :subscription_id" + EVENT_ORDER)
+    @SqlQuery
     @Mapper(IEventSqlMapper.class)
     public List<IEvent> getEventsForSubscription(@Bind("subscription_id") String subscriptionId);
 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/ISubscriptionSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/ISubscriptionSqlDao.java
index 3182082..83ce0fe 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/ISubscriptionSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/ISubscriptionSqlDao.java
@@ -35,30 +35,28 @@ import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
 import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.ISubscription;
 import com.ning.billing.entitlement.api.user.Subscription;
 
+@ExternalizedSqlViaStringTemplate3()
 public interface ISubscriptionSqlDao extends Transactional<ISubscriptionSqlDao>, CloseMe, Transmogrifier {
 
-    final static String SUBSCRIPTION_FIELDS = " id, bundle_id, category, start_dt, bundle_start_dt, active_version, ctd_dt, ptd_dt ";
-    final static String SUBSCRIPTION_VALUES = " :id, :bundle_id, :category, :start_dt, :bundle_start_dt, :active_version, :ctd_dt, :ptd_dt ";
-
-
-    @SqlUpdate("insert into subscriptions (" + SUBSCRIPTION_FIELDS +") value (" + SUBSCRIPTION_VALUES + ")")
+    @SqlUpdate
     public void insertSubscription(@Bind(binder = ISubscriptionDaoBinder.class) Subscription sub);
 
-    @SqlQuery("select " + SUBSCRIPTION_FIELDS + " from subscriptions where id = :id")
+    @SqlQuery
     @Mapper(ISubscriptionDaoSqlMapper.class)
     public ISubscription getSubscriptionFromId(@Bind("id") String id);
 
-    @SqlQuery("select " + SUBSCRIPTION_FIELDS + "from subscriptions where bundle_id = :bundle_id")
+    @SqlQuery
     @Mapper(ISubscriptionDaoSqlMapper.class)
     public List<ISubscription> getSubscriptionsFromBundleId(@Bind("bundle_id") String bundleId);
 
-    @SqlUpdate("update subscriptions set active_version = :active_version, ctd_dt = :ctd_dt, ptd_dt = :ptd_dt")
+    @SqlUpdate
     public void updateSubscription(@Bind("active_version") long activeVersion, @Bind("ctd_dt") Date ctd, @Bind("ptd_dt") Date ptd);
 
     public static class ISubscriptionDaoBinder implements Binder<Bind, Subscription> {
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.sql.stg
new file mode 100644
index 0000000..22a85f8
--- /dev/null
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/IBundleSqlDao.sql.stg
@@ -0,0 +1,41 @@
+group IBundleSqlDao;
+
+insertBundle() ::= <<
+    insert into bundles (
+      id
+      , start_dt
+      , name
+      , account_id
+    ) values (
+      :id
+      , :start_dt
+      , :name
+      , :account_id
+    );
+>>
+
+
+getBundleFromId(id) ::= <<
+    select
+      id
+      , start_dt
+      , name
+      , account_id
+    from bundles
+    where
+      id = :id
+    ;
+>>
+
+
+getBundleFromAccount(account_id) ::= <<
+    select
+      id
+      , start_dt
+      , name
+      , account_id
+    from bundles
+    where
+      account_id = :account_id
+    ;
+>>
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/IEventSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/IEventSqlDao.sql.stg
new file mode 100644
index 0000000..ba05178
--- /dev/null
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/IEventSqlDao.sql.stg
@@ -0,0 +1,195 @@
+group IEventSqlDao;
+
+getReadyEvents(now, max) ::= <<
+    select
+      event_id
+      , event_type
+      , user_type
+      , created_dt
+      , updated_dt
+      , requested_dt
+      , effective_dt
+      , subscription_id
+      , plan_name
+      , phase_name
+      , plist_name
+      , current_version
+      , is_active
+      , processing_owner
+      , processing_available_dt
+      , processing_state
+    from events
+    where
+      effective_dt \<= :now
+      and is_active = 1
+      and processing_state != 'PROCESSED'
+      and (processing_owner IS NULL OR processing_available_dt \<= :now)
+    order by
+      effective_dt asc
+      , created_dt asc
+      , requested_dt asc
+      , id asc
+    limit :max
+    ;
+>>
+
+claimEvent(owner, next_available, event_id, now) ::= <<
+    update events
+    set
+      processing_owner = :owner
+      , processing_available_dt = :next_available
+      , processing_state = 'IN_PROCESSING'
+    where
+      event_id = :event_id
+      and is_active = 1
+      and processing_state != 'PROCESSED'
+      and (processing_owner IS NULL OR processing_available_dt \<= :now)
+    ;
+>>
+
+clearEvent(event_id, owner) ::= <<
+    update events
+    set
+      processing_owner = NULL
+      , processing_state = 'PROCESSED'
+    where
+      event_id = :event_id
+      and processing_owner = :owner
+    ;
+>>
+
+insertEvent() ::= <<
+    insert into events (
+      event_id
+      , event_type
+      , user_type
+      , created_dt
+      , updated_dt
+      , requested_dt
+      , effective_dt
+      , subscription_id
+      , plan_name
+      , phase_name
+      , plist_name
+      , current_version
+      , is_active
+      , processing_owner
+      , processing_available_dt
+      , processing_state
+    ) values (
+      :event_id
+      , :event_type
+      , :user_type
+      , :created_dt
+      , :updated_dt
+      , :requested_dt
+      , :effective_dt
+      , :subscription_id
+      , :plan_name
+      , :phase_name
+      , :plist_name
+      , :current_version
+      , :is_active
+      , :processing_owner
+      , :processing_available_dt
+      , :processing_state
+    );   
+>>
+
+insertClaimedHistory(sequence_id, owner_id, hostname, claimed_dt, event_id) ::= <<
+    insert into claimed_events (
+        sequence_id
+        , owner_id
+        , hostname
+        , claimed_dt
+        , event_id
+      ) values (
+        :sequence_id
+        , :owner_id
+        , :hostname
+        , :claimed_dt
+        , :event_id
+      );
+>>
+
+unactiveEvent(event_id, now) ::= <<
+    update events
+    set
+      is_active = 0
+      , updated_dt = :now
+    where
+      event_id = :event_id
+    ;
+>>
+
+reactiveEvent(event_id, now) ::= <<
+    update events
+    set
+      is_active = 1
+      , updated_dt = :now
+    where
+      event_id = :event_id
+    ;
+>>
+
+getFutureActiveEventForSubscription(subscription_id, now) ::= <<
+    select 
+      event_id
+      , event_type
+      , user_type
+      , created_dt
+      , updated_dt
+      , requested_dt
+      , effective_dt
+      , subscription_id
+      , plan_name
+      , phase_name
+      , plist_name
+      , current_version
+      , is_active
+      , processing_owner
+      , processing_available_dt
+      , processing_state    
+    from events
+    where
+      subscription_id = :subscription_id
+      and is_active = 1
+      and effective_dt > :now
+    order by
+      effective_dt asc
+      , created_dt asc
+      , requested_dt asc
+      , id asc
+    ;
+>> 
+
+getEventsForSubscription(subscription_id) ::= <<
+    select
+      event_id
+      , event_type
+      , user_type
+      , created_dt
+      , updated_dt
+      , requested_dt
+      , effective_dt
+      , subscription_id
+      , plan_name
+      , phase_name
+      , plist_name
+      , current_version
+      , is_active
+      , processing_owner
+      , processing_available_dt
+      , processing_state       
+    from events
+    where
+      subscription_id = :subscription_id
+    order by
+      effective_dt asc
+      , created_dt asc
+      , requested_dt asc
+      , id asc
+    ;      
+>>
+
+
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/ISubscriptionSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/ISubscriptionSqlDao.sql.stg
new file mode 100644
index 0000000..1be11b8
--- /dev/null
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/ISubscriptionSqlDao.sql.stg
@@ -0,0 +1,62 @@
+group ISubscriptionSqlDao;
+
+insertSubscription() ::= <<
+    insert into subscriptions (
+        id
+      , bundle_id
+      , category
+      , start_dt
+      , bundle_start_dt
+      , active_version
+      , ctd_dt
+      , ptd_dt
+    ) values (
+        :id
+      , :bundle_id
+      , :category
+      , :start_dt
+      , :bundle_start_dt
+      , :active_version
+      , :ctd_dt
+      , :ptd_dt 
+    );
+>>
+
+getSubscriptionFromId(id) ::= <<
+    select
+        id
+      , bundle_id
+      , category
+      , start_dt
+      , bundle_start_dt
+      , active_version
+      , ctd_dt
+      , ptd_dt    
+    from subscriptions
+    where id = :id
+    ;
+>>
+
+getSubscriptionsFromBundleId(bundle_id) ::= <<
+    select
+      id
+      , bundle_id
+      , category
+      , start_dt
+      , bundle_start_dt
+      , active_version
+      , ctd_dt
+      , ptd_dt    
+    from subscriptions
+    where bundle_id = :bundle_id
+    ;
+>>
+
+updateSubscription(active_version, ctd_dt, ptd_dt) ::= <<
+    update subscriptions
+    set
+      active_version = :active_version
+      , ctd_dt = :ctd_dt
+      , ptd_dt = :ptd_dt
+    ;
+>>
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiBase.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiBase.java
index a31140d..6842133 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiBase.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiBase.java
@@ -184,7 +184,7 @@ public abstract class TestUserApiBase {
         testListener.pushExpectedEvent(NextEvent.CREATE);
         Subscription subscription = (Subscription) entitlementApi.createSubscription(bundle.getId(), productName, term, planSet);
         assertNotNull(subscription);
-        assertTrue(testListener.isCompleted(3000));
+        assertTrue(testListener.isCompleted(5000));
         return subscription;
     }
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
index 5a5175a..30ace87 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
@@ -25,7 +25,7 @@ import com.ning.billing.entitlement.glue.EngineModuleSqlMock;
 
 public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
 
-    private final int MAX_STRESS_ITERATIONS = 30;
+    private final int MAX_STRESS_ITERATIONS = 10;
 
     @Override
     public Injector getInjector() {

invoice/pom.xml 10(+10 -0)

diff --git a/invoice/pom.xml b/invoice/pom.xml
index a8d9ad0..ea85b04 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -30,6 +30,16 @@
             <artifactId>testng</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jdbi</groupId>
+            <artifactId>jdbi</artifactId>
+            <version>2.27</version>
+        </dependency>
     </dependencies>
     <build>
     </build>
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
new file mode 100644
index 0000000..9abc12a
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
@@ -0,0 +1,47 @@
+/*
+ * 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.invoice.dao;
+
+import com.ning.billing.invoice.model.Invoice;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(InvoiceMapper.class)
+public interface InvoiceDao {
+    @SqlQuery
+    List<Invoice> getInvoicesByAccount(@Bind final String accountId);
+
+    @SqlQuery
+    Invoice getInvoice(@Bind final String invoiceId);
+
+    @SqlUpdate
+    void createInvoice(@BindBean final Invoice invoice);
+
+    @SqlUpdate
+    void addPayment(@Bind final String invoiceId, @Bind final BigDecimal paymentAmount);
+
+    @SqlQuery
+    int test();
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemDao.java
new file mode 100644
index 0000000..9de9f76
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemDao.java
@@ -0,0 +1,39 @@
+/*
+ * 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.invoice.dao;
+
+import com.ning.billing.invoice.model.InvoiceItem;
+import com.ning.billing.invoice.model.InvoiceItemList;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(InvoiceItemMapper.class)
+public interface InvoiceItemDao {
+    @SqlQuery
+    InvoiceItemList getInvoiceItemsByInvoice(@Bind final String invoiceId);
+
+    @SqlQuery
+    InvoiceItemList getInvoiceItemsByAccount(@Bind final String accountId);
+
+    @SqlUpdate
+    void createInvoiceItem(@BindBean final InvoiceItem invoiceItem);
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceMapper.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceMapper.java
new file mode 100644
index 0000000..335b3c5
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceMapper.java
@@ -0,0 +1,26 @@
+/*
+ * 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.invoice.dao;
+
+import com.ning.billing.invoice.model.Invoice;
+import org.skife.jdbi.v2.BeanMapper;
+
+public class InvoiceMapper extends BeanMapper<Invoice> {
+    public InvoiceMapper() {
+        super(Invoice.class);
+    }
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
index 29d4370..730c22b 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
@@ -27,21 +27,28 @@ import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.UUID;
 
 public class DefaultInvoiceGenerator implements IInvoiceGenerator {
     @Override
-    public InvoiceItemList generateInvoiceItems(final BillingEventSet events, final InvoiceItemList existingItems, final DateTime targetDate, final Currency targetCurrency) {
-        if (events == null) {return new InvoiceItemList();}
-        if (events.size() == 0) {return new InvoiceItemList();}
+    public Invoice generateInvoice(final UUID accountId, final BillingEventSet events, final InvoiceItemList existingItems, final DateTime targetDate, final Currency targetCurrency) {
+        if (events == null) {return new Invoice(accountId, targetCurrency);}
+        if (events.size() == 0) {return new Invoice(accountId, targetCurrency);}
 
-        InvoiceItemList currentItems = generateInvoiceItems(events, targetDate, targetCurrency);
-        InvoiceItemList itemsToPost = reconcileInvoiceItems(currentItems, existingItems);
+        Invoice invoice = new Invoice(accountId, targetCurrency);
+        InvoiceItemList currentItems = generateInvoiceItems(events, invoice.getInvoiceId(), targetDate, targetCurrency);
+        InvoiceItemList itemsToPost = reconcileInvoiceItems(invoice.getInvoiceId(), currentItems, existingItems);
+        invoice.add(itemsToPost);
 
-        return itemsToPost;
+        return invoice;
     }
 
-    private InvoiceItemList reconcileInvoiceItems(final InvoiceItemList currentInvoiceItems, final InvoiceItemList existingInvoiceItems) {
-        InvoiceItemList currentItems = (InvoiceItemList) currentInvoiceItems.clone();
+    private InvoiceItemList reconcileInvoiceItems(final UUID invoiceId, final InvoiceItemList currentInvoiceItems, final InvoiceItemList existingInvoiceItems) {
+        InvoiceItemList currentItems = new InvoiceItemList();
+        for (InvoiceItem item : currentInvoiceItems) {
+            currentItems.add(new InvoiceItem(item, invoiceId));
+        }
+
         InvoiceItemList existingItems = (InvoiceItemList) existingInvoiceItems.clone();
 
         Collections.sort(currentItems);
@@ -69,13 +76,13 @@ public class DefaultInvoiceGenerator implements IInvoiceGenerator {
 
         // add existing items that aren't covered by current items as credit items
         for (InvoiceItem existingItem : existingItems) {
-            currentItems.add(existingItem.asCredit());
+            currentItems.add(existingItem.asCredit(invoiceId));
         }
 
         return currentItems;
     }
 
-    private InvoiceItemList generateInvoiceItems(BillingEventSet events, DateTime targetDate, Currency targetCurrency) {
+    private InvoiceItemList generateInvoiceItems(BillingEventSet events, UUID invoiceId, DateTime targetDate, Currency targetCurrency) {
         InvoiceItemList items = new InvoiceItemList();
 
         // sort events; this relies on the sort order being by subscription id then start date
@@ -88,41 +95,41 @@ public class DefaultInvoiceGenerator implements IInvoiceGenerator {
             IBillingEvent nextEvent = events.get(i + 1);
 
             if (thisEvent.getSubscriptionId() == nextEvent.getSubscriptionId()) {
-                processEvents(thisEvent, nextEvent, items, targetDate, targetCurrency);
+                processEvents(invoiceId, thisEvent, nextEvent, items, targetDate, targetCurrency);
             } else {
-                processEvent(thisEvent, items, targetDate, targetCurrency);
+                processEvent(invoiceId, thisEvent, items, targetDate, targetCurrency);
             }
         }
 
         // process the last item in the event set
         if (events.size() > 0) {
-            processEvent(events.getLast(), items, targetDate, targetCurrency);
+            processEvent(invoiceId, events.getLast(), items, targetDate, targetCurrency);
         }
 
         return items;
     }
 
-    private void processEvent(IBillingEvent event, List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) {
+    private void processEvent(UUID invoiceId, IBillingEvent event, List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) {
         BigDecimal rate = event.getPrice(targetCurrency);
         BigDecimal invoiceItemAmount = calculateInvoiceItemAmount(event, targetDate, rate);
         IBillingMode billingMode = getBillingMode(event.getBillingMode());
         DateTime billThroughDate = billingMode.calculateEffectiveEndDate(event.getEffectiveDate(), targetDate, event.getBillCycleDay(), event.getBillingPeriod());
 
-        addInvoiceItem(items, event, billThroughDate, invoiceItemAmount, rate, targetCurrency);
+        addInvoiceItem(invoiceId, items, event, billThroughDate, invoiceItemAmount, rate, targetCurrency);
     }
 
-    private void processEvents(IBillingEvent firstEvent, IBillingEvent secondEvent, List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) {
+    private void processEvents(UUID invoiceId, IBillingEvent firstEvent, IBillingEvent secondEvent, List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) {
         BigDecimal rate = firstEvent.getPrice(targetCurrency);
         BigDecimal invoiceItemAmount = calculateInvoiceItemAmount(firstEvent, secondEvent, targetDate, rate);
         IBillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
         DateTime billThroughDate = billingMode.calculateEffectiveEndDate(firstEvent.getEffectiveDate(), secondEvent.getEffectiveDate(), targetDate, firstEvent.getBillCycleDay(), firstEvent.getBillingPeriod());
 
-        addInvoiceItem(items, firstEvent, billThroughDate, invoiceItemAmount, rate, targetCurrency);
+        addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, invoiceItemAmount, rate, targetCurrency);
     }
 
-    private void addInvoiceItem(List<InvoiceItem> items, IBillingEvent event, DateTime billThroughDate, BigDecimal amount, BigDecimal rate, Currency currency) {
+    private void addInvoiceItem(UUID invoiceId, List<InvoiceItem> items, IBillingEvent event, DateTime billThroughDate, BigDecimal amount, BigDecimal rate, Currency currency) {
         if (!(amount.compareTo(BigDecimal.ZERO) == 0)) {
-            InvoiceItem item = new InvoiceItem(event.getSubscriptionId(), event.getEffectiveDate(), billThroughDate, event.getDescription(), amount, rate, currency);
+            InvoiceItem item = new InvoiceItem(invoiceId, event.getSubscriptionId(), event.getEffectiveDate(), billThroughDate, event.getDescription(), amount, rate, currency);
             items.add(item);
         }
     }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/IInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/IInvoiceGenerator.java
index 468686b..09d5e47 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/IInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/IInvoiceGenerator.java
@@ -20,7 +20,9 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.BillingEventSet;
 import org.joda.time.DateTime;
 
+import java.util.UUID;
+
 // TODO: Jeff -- Determine what the consequence of account-level currency changes are on repair scenarios
 public interface IInvoiceGenerator {
-    public InvoiceItemList generateInvoiceItems(BillingEventSet events, InvoiceItemList items, DateTime targetDate, Currency targetCurrency);
+    public Invoice generateInvoice(UUID accountId, BillingEventSet events, InvoiceItemList items, DateTime targetDate, Currency targetCurrency);
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/Invoice.java b/invoice/src/main/java/com/ning/billing/invoice/model/Invoice.java
index 02ce760..8d67d53 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/Invoice.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/Invoice.java
@@ -17,21 +17,35 @@
 package com.ning.billing.invoice.model;
 
 import com.ning.billing.catalog.api.Currency;
+import org.joda.time.DateTime;
 
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.UUID;
 
 public class Invoice {
     private final InvoiceItemList items = new InvoiceItemList();
+    private final UUID invoiceId;
+    private UUID accountId;
+    private final DateTime invoiceDate;
     private Currency currency;
 
-    public Invoice() {}
+    public Invoice() {
+        this.invoiceId = UUID.randomUUID();
+        this.invoiceDate = new DateTime();
+    }
 
-    public Invoice(Currency currency) {
+    public Invoice(UUID accountId, Currency currency) {
+        this.invoiceId = UUID.randomUUID();
+        this.accountId = accountId;
+        this.invoiceDate = new DateTime();
         this.currency = currency;
     }
 
-    public Invoice(List<InvoiceItem> items, Currency currency) {
+    public Invoice(UUID accountId, List<InvoiceItem> items, Currency currency) {
+        this.invoiceId = UUID.randomUUID();
+        this.accountId = accountId;
+        this.invoiceDate = new DateTime();
         this.currency = currency;
         this.items.addAll(items);
     }
@@ -48,6 +62,22 @@ public class Invoice {
         return items;
     }
 
+    public int getNumberOfItems() {
+        return items.size();
+    }
+
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    public DateTime getInvoiceDate() {
+        return invoiceDate;
+    }
+
     public Currency getCurrency() {
         return currency;
     }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItem.java
index ae3ab53..7645575 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItem.java
@@ -23,6 +23,8 @@ import java.math.BigDecimal;
 import java.util.UUID;
 
 public class InvoiceItem implements Comparable<InvoiceItem> {
+    private final UUID invoiceItemId;
+    private final UUID invoiceId;
     private final UUID subscriptionId;
     private DateTime startDate;
     private DateTime endDate;
@@ -31,7 +33,9 @@ public class InvoiceItem implements Comparable<InvoiceItem> {
     private final BigDecimal rate;
     private final Currency currency;
 
-    public InvoiceItem(UUID subscriptionId, DateTime startDate, DateTime endDate, String description, BigDecimal amount, BigDecimal rate, Currency currency) {
+    public InvoiceItem(UUID invoiceId, UUID subscriptionId, DateTime startDate, DateTime endDate, String description, BigDecimal amount, BigDecimal rate, Currency currency) {
+        this.invoiceItemId = UUID.randomUUID();
+        this.invoiceId = invoiceId;
         this.subscriptionId = subscriptionId;
         this.startDate = startDate;
         this.endDate = endDate;
@@ -41,8 +45,20 @@ public class InvoiceItem implements Comparable<InvoiceItem> {
         this.currency = currency;
     }
 
-    public InvoiceItem asCredit() {
-        return new InvoiceItem(subscriptionId, startDate, endDate, description, amount.negate(), rate, currency);
+    public InvoiceItem(InvoiceItem that, UUID invoiceId) {
+        this.invoiceItemId = UUID.randomUUID();
+        this.invoiceId = invoiceId;
+        this.subscriptionId = that.subscriptionId;
+        this.startDate = that.startDate;
+        this.endDate = that.endDate;
+        this.description = that.description;
+        this.amount = that.amount;
+        this.rate = that.rate;
+        this.currency = that.currency;
+    }
+
+    public InvoiceItem asCredit(UUID invoiceId) {
+        return new InvoiceItem(invoiceId, subscriptionId, startDate, endDate, description, amount.negate(), rate, currency);
     }
 
     public UUID getSubscriptionId() {
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceDao.sql.stg
new file mode 100644
index 0000000..da2eade
--- /dev/null
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceDao.sql.stg
@@ -0,0 +1,24 @@
+group InvoiceDao;
+
+getInvoicesByAccount() ::= <<
+  select * from invoices where account_id = :accountId order by invoiceDate asc;
+>>
+
+getInvoice() ::= <<
+  select * from invoices where invoice_id = :invoiceId;
+>>
+
+createInvoice() ::= <<
+  INSERT INTO invoices(invoice_id, account_id, invoiceDate)
+  VALUES (:invoiceId, :accountId, :invoiceDate);
+>>
+
+addPayment() ::= <<
+  UPDATE invoices SET amount_paid = amount_paid + :paymentAmount
+  WHERE invoice_id = :invoiceId;
+>>
+
+test() ::= <<
+  SELECT DISTINCT 1 FROM invoices;
+>>
+;
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemDao.sql.stg
new file mode 100644
index 0000000..479b4cf
--- /dev/null
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemDao.sql.stg
@@ -0,0 +1,13 @@
+group InvoiceItemDao;
+
+getInvoiceItemsByInvoice() ::= <<
+>>
+
+getInvoiceItemsByAccount() ::= <<
+>>
+
+createInvoiceItem() ::= <<
+  INSERT INTO invoice_items(invoice_item_id, subscription_id, start_date, end_date, description, amount, rate, currency)
+  VALUES(:invoiceItemId, :invoiceId, :subscriptionId, :startDate, :endDate, :description, :amount, :rate, :currency)
+>>
+;
diff --git a/invoice/src/main/resources/ddl.sql b/invoice/src/main/resources/ddl.sql
new file mode 100644
index 0000000..1a851a0
--- /dev/null
+++ b/invoice/src/main/resources/ddl.sql
@@ -0,0 +1,34 @@
+DROP TABLE IF EXISTS invoice_items;
+CREATE TABLE invoice_items (
+  id int(11) unsigned NOT NULL AUTO_INCREMENT,
+  invoice_item_id char(36) NOT NULL,
+  invoice_id char(36) NOT NULL,
+  subscription_id char(36) NOT NULL,
+  start_date datetime NOT NULL,
+  end_date datetime NOT NULL,
+  description varchar(100) NOT NULL,
+  amount numeric(10,4) NOT NULL,
+  rate numeric(10,4) NOT NULL,
+  currency varchar(5) NOT NULL,
+  PRIMARY KEY(id)
+) ENGINE=innodb;
+
+CREATE INDEX invoice_items_subscription_id ON invoice_items(subscription_id ASC);
+
+DROP TABLE IF EXISTS invoices;
+CREATE TABLE invoices (
+  id int(11) unsigned NOT NULL AUTO_INCREMENT,
+  invoice_id char(36) NOT NULL,
+  account_id char(36) NOT NULL,
+  invoice_date datetime NOT NULL,
+  amount_paid numeric(10,4) NOT NULL DEFAULT 0,
+  amount_outstanding numeric(10,4) NOT NULL,
+  last_payment_attempt datetime DEFAULT NULL,
+  PRIMARY KEY(id)
+) ENGINE=innodb;
+
+CREATE INDEX invoices_account_id ON invoices(account_id ASC);
+CREATE INDEX invoices_invoice_id ON invoices(invoice_id ASC);
+
+
+
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
new file mode 100644
index 0000000..5447094
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
@@ -0,0 +1,65 @@
+/*
+ * 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.invoice.dao;
+
+import org.testng.annotations.Test;
+
+@Test(groups = {"invoicing", "invoicing-dao"})
+public class InvoiceDaoTests {
+//    private final MysqlTestingHelper helper = new MysqlTestingHelper();
+//    private InvoiceDao dao;
+//
+//    @BeforeClass(alwaysRun = true)
+//    private void setup() {
+//        final String ddl = IOUtils.toString(InvoiceDao.class.getResourceAsStream("/ddl.sql"));
+//
+//        helper.startMysql();
+//        helper.initDb();
+//
+//        final IDBI dbi = helper.getDBI();
+//        dao = dbi.onDemand(EventDao.class);
+//
+//        // Healthcheck test to make sure MySQL is setup properly
+//        try {
+//            dao.test();
+//        }
+//        catch (Throwable t) {
+//            Assert.fail(t.toString());
+//        }
+//    }
+//
+//    @Test
+//    public void testCreationAndRetrievalByAccount() {
+//        InvoiceDao dao = dbi.onDemand(InvoiceDao.class);
+//        UUID accountId = UUID.randomUUID();
+//        Invoice invoice = new Invoice(accountId, Currency.USD);
+//        DateTime invoiceDate = invoice.getInvoiceDate();
+//
+//        dao.createInvoice(invoice);
+//
+//        List<Invoice> invoices = dao.getInvoicesByAccount(accountId.toString());
+//        assertNotNull(invoices);
+//        assertEquals(invoices.size(), 1);
+//        Invoice thisInvoice = invoices.get(0);
+//        assertEquals(invoice.getAccountId(), accountId);
+//        assertTrue(thisInvoice.getInvoiceDate().equals(invoiceDate));
+//        assertEquals(thisInvoice.getCurrency(), Currency.USD);
+//        assertEquals(thisInvoice.getNumberOfItems(), 0);
+//        assertTrue(thisInvoice.getTotalAmount().compareTo(BigDecimal.ZERO) == 0);
+//    }
+
+}
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 4e7b756..01a533c 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
@@ -22,10 +22,7 @@ import com.ning.billing.entitlement.api.billing.BillingMode;
 import com.ning.billing.entitlement.api.billing.IBillingEvent;
 import com.ning.billing.invoice.api.BillingEvent;
 import com.ning.billing.invoice.api.BillingEventSet;
-import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
-import com.ning.billing.invoice.model.IInvoiceGenerator;
-import com.ning.billing.invoice.model.InvoiceItem;
-import com.ning.billing.invoice.model.InvoiceItemList;
+import com.ning.billing.invoice.model.*;
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
@@ -41,11 +38,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
     @Test
     public void testWithNullEventSetAndNullInvoiceSet() {
-        InvoiceItemList invoiceItems = generator.generateInvoiceItems(null, null, new DateTime(), Currency.USD);
+        UUID accountId = UUID.randomUUID();
+        Invoice invoice = generator.generateInvoice(accountId, null, null, new DateTime(), Currency.USD);
 
-        assertNotNull(invoiceItems);
-        assertEquals(invoiceItems.size(), 0);
-        assertEquals(invoiceItems.getTotalAmount(), ZERO);
+        assertNotNull(invoice);
+        assertEquals(invoice.getNumberOfItems(), 0);
+        assertEquals(invoice.getTotalAmount(), ZERO);
     }
 
     @Test
@@ -53,11 +51,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         BillingEventSet events = new BillingEventSet();
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
-        InvoiceItemList invoiceItems = generator.generateInvoiceItems(events, existingInvoiceItems, new DateTime(), Currency.USD);
+        UUID accountId = UUID.randomUUID();
+        Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, new DateTime(), Currency.USD);
 
-        assertNotNull(invoiceItems);
-        assertEquals(invoiceItems.size(), 0);
-        assertEquals(invoiceItems.getTotalAmount(), ZERO);
+        assertNotNull(invoice);
+        assertEquals(invoice.getNumberOfItems(), 0);
+        assertEquals(invoice.getTotalAmount(), ZERO);
     }
 
     @Test
@@ -77,11 +76,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
         
         DateTime targetDate = buildDateTime(2011, 10, 3);
-        InvoiceItemList invoiceItems = generator.generateInvoiceItems(events, existingInvoiceItems, targetDate, Currency.USD);
+        UUID accountId = UUID.randomUUID();
+        Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
 
-        assertNotNull(invoiceItems);
-        assertEquals(invoiceItems.size(), 1);
-        assertEquals(invoiceItems.getTotalAmount(), TWENTY);
+        assertNotNull(invoice);
+        assertEquals(invoice.getNumberOfItems(), 1);
+        assertEquals(invoice.getTotalAmount(), TWENTY);
     }
 
     @Test
@@ -102,15 +102,16 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
         
         DateTime targetDate = buildDateTime(2011, 10, 3);        
-        InvoiceItemList invoiceItems = generator.generateInvoiceItems(events, existingInvoiceItems, targetDate, Currency.USD);
+        UUID accountId = UUID.randomUUID();
+        Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
 
-        assertNotNull(invoiceItems);
-        assertEquals(invoiceItems.size(), 1);
+        assertNotNull(invoice);
+        assertEquals(invoice.getNumberOfItems(), 1);
 
         BigDecimal expectedNumberOfBillingCycles;
         expectedNumberOfBillingCycles = ONE.add(FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD));
         BigDecimal expectedAmount = expectedNumberOfBillingCycles.multiply(rate).setScale(NUMBER_OF_DECIMALS);
-        assertEquals(invoiceItems.getTotalAmount(), expectedAmount);
+        assertEquals(invoice.getTotalAmount(), expectedAmount);
     }
 
     @Test
@@ -131,11 +132,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
         DateTime targetDate = buildDateTime(2011, 10, 3);
-        InvoiceItemList invoiceItems = generator.generateInvoiceItems(events, existingInvoiceItems, targetDate, Currency.USD);
+        UUID accountId = UUID.randomUUID();
+        Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
 
-        assertNotNull(invoiceItems);
-        assertEquals(invoiceItems.size(), 2);
-        assertEquals(invoiceItems.getTotalAmount(), FIVE.multiply(TWO).add(TEN).setScale(NUMBER_OF_DECIMALS));
+        assertNotNull(invoice);
+        assertEquals(invoice.getNumberOfItems(), 2);
+        assertEquals(invoice.getTotalAmount(), FIVE.multiply(TWO).add(TEN).setScale(NUMBER_OF_DECIMALS));
     }
 
     @Test
@@ -157,10 +159,11 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
         DateTime targetDate = buildDateTime(2011, 12, 3);
-        InvoiceItemList invoiceItems = generator.generateInvoiceItems(events, existingInvoiceItems, targetDate, Currency.USD);
+        UUID accountId = UUID.randomUUID();
+        Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
 
-        assertNotNull(invoiceItems);
-        assertEquals(invoiceItems.size(), 2);
+        assertNotNull(invoice);
+        assertEquals(invoice.getNumberOfItems(), 2);
 
         BigDecimal numberOfCyclesEvent1;
         numberOfCyclesEvent1 = ONE.add(FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD));
@@ -172,7 +175,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         expectedValue = expectedValue.add(numberOfCyclesEvent2.multiply(TEN));
         expectedValue = expectedValue.setScale(NUMBER_OF_DECIMALS);
 
-        assertEquals(invoiceItems.getTotalAmount(), expectedValue);
+        assertEquals(invoice.getTotalAmount(), expectedValue);
     }
 
     @Test
@@ -200,11 +203,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
         DateTime targetDate = buildDateTime(2011, 12, 3);
-        InvoiceItemList invoiceItems = generator.generateInvoiceItems(events, existingInvoiceItems, targetDate, Currency.USD);
+        UUID accountId = UUID.randomUUID();
+        Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
 
-        assertNotNull(invoiceItems);
-        assertEquals(invoiceItems.size(), 3);
-        assertEquals(invoiceItems.getTotalAmount(), FIVE.add(TEN).add(TWO.multiply(THIRTY)).setScale(NUMBER_OF_DECIMALS));
+        assertNotNull(invoice);
+        assertEquals(invoice.getNumberOfItems(), 3);
+        assertEquals(invoice.getTotalAmount(), FIVE.add(TEN).add(TWO.multiply(THIRTY)).setScale(NUMBER_OF_DECIMALS));
     }
 
     @Test
@@ -222,16 +226,17 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         events.add(event1);
 
         InvoiceItemList existingInvoiceItems = new InvoiceItemList();
-        InvoiceItem invoiceItem = new InvoiceItem(subscriptionId, startDate, buildDateTime(2012, 1, 1), "",
+        InvoiceItem invoiceItem = new InvoiceItem(UUID.randomUUID(), subscriptionId, startDate, buildDateTime(2012, 1, 1), "",
                                                  rate.multiply(FOUR), rate, Currency.USD);
         existingInvoiceItems.add(invoiceItem);
 
         DateTime targetDate = buildDateTime(2011, 12, 3);
-        InvoiceItemList invoiceItems = generator.generateInvoiceItems(events, existingInvoiceItems, targetDate, Currency.USD);
+        UUID accountId = UUID.randomUUID();
+        Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
 
-        assertNotNull(invoiceItems);
-        assertEquals(invoiceItems.size(), 0);
-        assertEquals(invoiceItems.getTotalAmount(), ZERO);
+        assertNotNull(invoice);
+        assertEquals(invoice.getNumberOfItems(), 0);
+        assertEquals(invoice.getTotalAmount(), ZERO);
     }
 
     @Test
@@ -404,11 +409,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
     private void testInvoiceGeneration(BillingEventSet events, InvoiceItemList existingInvoiceItems, DateTime targetDate, int expectedNumberOfItems, BigDecimal expectedAmount) {
         Currency currency = Currency.USD;
-        InvoiceItemList invoiceItems = generator.generateInvoiceItems(events, existingInvoiceItems, targetDate, currency);
-        existingInvoiceItems.addAll(invoiceItems);
-        assertNotNull(invoiceItems);
-        assertEquals(invoiceItems.size(), expectedNumberOfItems);
-        assertEquals(invoiceItems.getTotalAmount(), expectedAmount);
+        UUID accountId = UUID.randomUUID();
+        Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, currency);
+        existingInvoiceItems.addAll(invoice.getItems());
+        assertNotNull(invoice);
+        assertEquals(invoice.getNumberOfItems(), expectedNumberOfItems);
+        assertEquals(invoice.getTotalAmount(), expectedAmount);
     }
 
     // TODO: Jeff C -- how do we ensure that an annual add-on is properly aligned *at the end* with the base plan?

util/pom.xml 10(+10 -0)

diff --git a/util/pom.xml b/util/pom.xml
index 1266ef7..e41c388 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -56,6 +56,16 @@
             <artifactId>testng</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>management</artifactId>
+            <version>5.0.11</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>