killbill-memoizeit
Changes
account/pom.xml 2(+1 -1)
analytics/pom.xml 2(+1 -1)
analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java 6(+5 -1)
analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java 6(+3 -3)
api/pom.xml 2(+1 -1)
api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java 22(+9 -13)
beatrix/pom.xml 14(+12 -2)
beatrix/src/test/resources/catalogSample.xml 641(+641 -0)
beatrix/src/test/resources/log4j.xml 4(+4 -0)
catalog/pom.xml 2(+1 -1)
entitlement/pom.xml 8(+7 -1)
entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultBillingEvent.java 94(+67 -27)
entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java 108(+68 -40)
entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java 18(+17 -1)
entitlement/src/main/java/com/ning/billing/entitlement/api/test/DefaultEntitlementTestApi.java 6(+5 -1)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java 2(+0 -2)
entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java 62(+44 -18)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java 71(+36 -35)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java 144(+144 -0)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java 154(+154 -0)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java 44(+27 -17)
entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java 5(+3 -2)
entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java 13(+7 -6)
entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java 9(+5 -4)
entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java 11(+6 -5)
entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java 113(+60 -53)
entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java 15(+7 -8)
invoice/pom.xml 17(+15 -2)
invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java 35(+17 -18)
invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java 144(+144 -0)
invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java 164(+164 -0)
invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java 412(+271 -141)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java 2(+1 -1)
invoice/src/test/resources/log4j.xml 36(+36 -0)
payment/pom.xml 2(+1 -1)
pom.xml 6(+3 -3)
util/pom.xml 7(+6 -1)
util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java 1(+0 -1)
Details
account/pom.xml 2(+1 -1)
diff --git a/account/pom.xml b/account/pom.xml
index 0146205..141e6cb 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.3-SNAPSHOT</version>
+ <version>0.1.5-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-account</artifactId>
diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java b/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
index fd97eec..8e5c0b3 100644
--- a/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
@@ -19,16 +19,17 @@ package com.ning.billing.account.api;
import java.util.List;
import java.util.UUID;
+import com.google.inject.Inject;
+import com.ning.billing.util.clock.Clock;
import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.util.customfield.CustomizableEntityBase;
import com.ning.billing.util.tag.DefaultTagStore;
import com.ning.billing.util.tag.DescriptiveTag;
import com.ning.billing.util.tag.Tag;
import com.ning.billing.util.tag.TagDefinition;
-
+import org.joda.time.DateTimeZone;
+
public class DefaultAccount extends CustomizableEntityBase implements Account {
//public final static String OBJECT_TYPE = "Account";
diff --git a/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java b/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
index f501267..1e00dba 100644
--- a/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
@@ -22,7 +22,6 @@ import java.util.UUID;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.Transaction;
import org.skife.jdbi.v2.TransactionStatus;
-
import com.google.inject.Inject;
import com.ning.billing.ErrorCode;
import com.ning.billing.account.api.Account;
@@ -33,23 +32,23 @@ import com.ning.billing.account.api.user.DefaultAccountChangeNotification;
import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.customfield.dao.FieldStoreDao;
-import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.bus.Bus;
import com.ning.billing.util.tag.Tag;
import com.ning.billing.util.tag.dao.TagStoreSqlDao;
public class DefaultAccountDao implements AccountDao {
- private final AccountSqlDao accountDao;
- private final EventBus eventBus;
+ private final AccountSqlDao accountSqlDao;
+ private final Bus eventBus;
@Inject
- public DefaultAccountDao(final IDBI dbi, final EventBus eventBus) {
+ public DefaultAccountDao(IDBI dbi, Bus eventBus) {
this.eventBus = eventBus;
- this.accountDao = dbi.onDemand(AccountSqlDao.class);
+ this.accountSqlDao = dbi.onDemand(AccountSqlDao.class);
}
@Override
public Account getAccountByKey(final String key) {
- return accountDao.inTransaction(new Transaction<Account, AccountSqlDao>() {
+ return accountSqlDao.inTransaction(new Transaction<Account, AccountSqlDao>() {
@Override
public Account inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws Exception {
Account account = accountSqlDao.getAccountByKey(key);
@@ -67,46 +66,41 @@ public class DefaultAccountDao implements AccountDao {
if (externalKey == null) {
throw new AccountApiException(ErrorCode.ACCOUNT_CANNOT_MAP_NULL_KEY, "");
}
- return accountDao.getIdFromKey(externalKey);
+ return accountSqlDao.getIdFromKey(externalKey);
}
@Override
public Account getById(final String id) {
- return accountDao.inTransaction(new Transaction<Account, AccountSqlDao>() {
- @Override
- public Account inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws Exception {
- Account account = accountSqlDao.getById(id);
- if (account != null) {
- setCustomFieldsFromWithinTransaction(account, accountSqlDao);
- setTagsFromWithinTransaction(account, accountSqlDao);
- }
- return account;
- }
- });
+ Account account = accountSqlDao.getById(id);
+ if (account != null) {
+ setCustomFieldsFromWithinTransaction(account, accountSqlDao);
+ setTagsFromWithinTransaction(account, accountSqlDao);
+ }
+ return account;
}
@Override
public List<Account> get() {
- return accountDao.get();
+ return accountSqlDao.get();
}
@Override
public void create(final Account account) throws AccountApiException {
final String key = account.getExternalKey();
try {
- accountDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
+
+ accountSqlDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
@Override
- public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws AccountApiException, EventBus.EventBusException {
- Account currentAccount = accountSqlDao.getAccountByKey(key);
+ public Void inTransaction(final AccountSqlDao transactionalDao, final TransactionStatus status) throws AccountApiException, Bus.EventBusException {
+ Account currentAccount = transactionalDao.getAccountByKey(key);
if (currentAccount != null) {
throw new AccountApiException(ErrorCode.ACCOUNT_ALREADY_EXISTS, key);
}
- accountSqlDao.create(account);
-
- saveTagsFromWithinTransaction(account, accountSqlDao, true);
- saveCustomFieldsFromWithinTransaction(account, accountSqlDao, true);
+ transactionalDao.create(account);
+ saveTagsFromWithinTransaction(account, transactionalDao, true);
+ saveCustomFieldsFromWithinTransaction(account, transactionalDao, true);
AccountCreationNotification creationEvent = new DefaultAccountCreationEvent(account);
eventBus.post(creationEvent);
return null;
@@ -124,9 +118,9 @@ public class DefaultAccountDao implements AccountDao {
@Override
public void update(final Account account) throws AccountApiException {
try {
- accountDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
+ accountSqlDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
@Override
- public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws AccountApiException, EventBus.EventBusException {
+ public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws AccountApiException, Bus.EventBusException {
String accountId = account.getId().toString();
Account currentAccount = accountSqlDao.getById(accountId);
if (currentAccount == null) {
@@ -162,9 +156,9 @@ public class DefaultAccountDao implements AccountDao {
@Override
public void deleteByKey(final String externalKey) throws AccountApiException {
try {
- accountDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
+ accountSqlDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
@Override
- public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws AccountApiException, EventBus.EventBusException {
+ public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws AccountApiException, Bus.EventBusException {
accountSqlDao.deleteByKey(externalKey);
@@ -182,7 +176,7 @@ public class DefaultAccountDao implements AccountDao {
@Override
public void test() {
- accountDao.test();
+ accountSqlDao.test();
}
private void setCustomFieldsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao) {
@@ -216,7 +210,7 @@ public class DefaultAccountDao implements AccountDao {
List<Tag> tagList = account.getTagList();
if (tagList != null) {
- tagStoreDao.save(accountId, objectType, tagList);
+ tagStoreDao.batchSaveFromTransaction(accountId, objectType, tagList);
}
}
@@ -231,7 +225,7 @@ public class DefaultAccountDao implements AccountDao {
List<CustomField> fieldList = account.getFieldList();
if (fieldList != null) {
- fieldStoreDao.save(accountId, objectType, fieldList);
+ fieldStoreDao.batchSaveFromTransaction(accountId, objectType, fieldList);
}
}
diff --git a/account/src/main/java/com/ning/billing/account/glue/AccountModule.java b/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
index 23087e9..f6e51f4 100644
--- a/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
+++ b/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
@@ -25,7 +25,6 @@ import com.ning.billing.account.api.DefaultAccountService;
import com.ning.billing.account.api.user.DefaultAccountUserApi;
import com.ning.billing.account.dao.AccountDao;
import com.ning.billing.account.dao.DefaultAccountDao;
-import com.ning.billing.util.glue.ClockModule;
public class AccountModule extends AbstractModule {
@@ -46,17 +45,11 @@ public class AccountModule extends AbstractModule {
bind(AccountService.class).to(DefaultAccountService.class).asEagerSingleton();
}
- protected void installTestModules() {
- install(new ClockModule());
- }
-
-
@Override
protected void configure() {
installConfig();
installAccountDao();
installAccountService();
installAccountUserApi();
- installTestModules();
}
}
diff --git a/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java b/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
index 0a1c642..f4f530e 100644
--- a/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
+++ b/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
@@ -29,8 +29,8 @@ import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
import com.ning.billing.account.glue.AccountModuleWithEmbeddedDb;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-import com.ning.billing.util.eventbus.EventBusService;
+import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.bus.BusService;
public abstract class AccountDaoTestBase {
protected AccountModuleWithEmbeddedDb module;
@@ -55,8 +55,8 @@ public abstract class AccountDaoTestBase {
accountDao = injector.getInstance(AccountDao.class);
accountDao.test();
- EventBusService busService = injector.getInstance(EventBusService.class);
- ((DefaultEventBusService) busService).startBus();
+ BusService busService = injector.getInstance(BusService.class);
+ ((DefaultBusService) busService).startBus();
}
catch (Throwable t) {
fail(t.toString());
diff --git a/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
index d80a05e..b351709 100644
--- a/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
+++ b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
@@ -28,15 +28,15 @@ import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.AccountChangeNotification;
import com.ning.billing.account.api.user.DefaultAccountChangeNotification;
import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
public class MockAccountDao implements AccountDao {
- private final EventBus eventBus;
+ private final Bus eventBus;
private final Map<String, Account> accounts = new ConcurrentHashMap<String, Account>();
@Inject
- public MockAccountDao(EventBus eventBus) {
+ public MockAccountDao(Bus eventBus) {
this.eventBus = eventBus;
}
diff --git a/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithEmbeddedDb.java b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithEmbeddedDb.java
index e8850dd..1a894a7 100644
--- a/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithEmbeddedDb.java
+++ b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithEmbeddedDb.java
@@ -17,7 +17,8 @@
package com.ning.billing.account.glue;
import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.util.glue.EventBusModule;
+import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.glue.BusModule;
import org.skife.jdbi.v2.IDBI;
import java.io.IOException;
@@ -41,6 +42,7 @@ public class AccountModuleWithEmbeddedDb extends AccountModule {
protected void configure() {
bind(IDBI.class).toInstance(helper.getDBI());
super.configure();
- install(new EventBusModule());
+ install(new BusModule());
+ install(new MockClockModule());
}
}
diff --git a/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java
index 5c7b638..1c65c3c 100644
--- a/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java
+++ b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java
@@ -27,8 +27,10 @@ public class AccountModuleWithMocks extends AccountModule {
bind(AccountDao.class).to(MockAccountDao.class);
}
+
@Override
- protected void installTestModules() {
+ protected void configure() {
+ super.configure();
install(new MockClockModule());
}
}
analytics/pom.xml 2(+1 -1)
diff --git a/analytics/pom.xml b/analytics/pom.xml
index 4cd7332..18bd687 100644
--- a/analytics/pom.xml
+++ b/analytics/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.3-SNAPSHOT</version>
+ <version>0.1.5-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-analytics</artifactId>
diff --git a/analytics/src/main/java/com/ning/billing/analytics/api/AnalyticsService.java b/analytics/src/main/java/com/ning/billing/analytics/api/AnalyticsService.java
index a08e3ab..4adb21e 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/api/AnalyticsService.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/api/AnalyticsService.java
@@ -19,7 +19,7 @@ package com.ning.billing.analytics.api;
import com.google.inject.Inject;
import com.ning.billing.analytics.AnalyticsListener;
import com.ning.billing.lifecycle.LifecycleHandlerType;
-import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.bus.Bus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -30,10 +30,10 @@ public class AnalyticsService implements IAnalyticsService
private static final String ANALYTICS_SERVICE = "analytics-service";
private final AnalyticsListener listener;
- private final EventBus eventBus;
+ private final Bus eventBus;
@Inject
- public AnalyticsService(final AnalyticsListener listener, final EventBus eventBus)
+ public AnalyticsService(final AnalyticsListener listener, final Bus eventBus)
{
this.listener = listener;
this.eventBus = eventBus;
@@ -51,7 +51,7 @@ public class AnalyticsService implements IAnalyticsService
try {
eventBus.register(listener);
}
- catch (EventBus.EventBusException e) {
+ catch (Bus.EventBusException e) {
log.error("Unable to register to the EventBus!", e);
}
}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java
index aaf75a1..7557045 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java
@@ -213,7 +213,7 @@ public class BusinessAccount
if (key != null ? !key.equals(that.key) : that.key != null) {
return false;
}
- if (lastInvoiceDate != null ? !lastInvoiceDate.equals(that.lastInvoiceDate) : that.lastInvoiceDate != null) {
+ if (lastInvoiceDate != null ? lastInvoiceDate.compareTo(that.lastInvoiceDate) != 0 : that.lastInvoiceDate != null) {
return false;
}
if (lastPaymentStatus != null ? !lastPaymentStatus.equals(that.lastPaymentStatus) : that.lastPaymentStatus != null) {
@@ -228,7 +228,7 @@ public class BusinessAccount
if (totalInvoiceBalance != null ? !(Rounder.round(totalInvoiceBalance) == Rounder.round(that.totalInvoiceBalance)) : that.totalInvoiceBalance != null) {
return false;
}
- if (updatedDt != null ? !updatedDt.equals(that.updatedDt) : that.updatedDt != null) {
+ if (updatedDt != null ? updatedDt.compareTo(that.updatedDt) != 0 : that.updatedDt != null) {
return false;
}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
index baacad8..71e3402 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
@@ -17,7 +17,14 @@
package com.ning.billing.analytics;
import com.ning.billing.analytics.utils.Rounder;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.catalog.api.TimeUnit;
import com.ning.billing.entitlement.api.user.Subscription;
import org.joda.time.DateTime;
import org.slf4j.Logger;
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDaoProvider.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDaoProvider.java
index 0a66205..a2d6b2e 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDaoProvider.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDaoProvider.java
@@ -16,16 +16,16 @@
package com.ning.billing.analytics.dao;
+import org.skife.jdbi.v2.IDBI;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import org.skife.jdbi.v2.DBI;
public class BusinessAccountDaoProvider implements Provider<BusinessAccountDao>
{
- private final DBI dbi;
+ private final IDBI dbi;
@Inject
- public BusinessAccountDaoProvider(final DBI dbi)
+ public BusinessAccountDaoProvider(final IDBI dbi)
{
this.dbi = dbi;
}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
index 9db1b75..374f296 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
@@ -23,7 +23,11 @@ import org.skife.jdbi.v2.sqlobject.Binder;
import org.skife.jdbi.v2.sqlobject.BinderFactory;
import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
-import java.lang.annotation.*;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.sql.Types;
@BindingAnnotation(BusinessSubscriptionTransitionBinder.BstBinderFactory.class)
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java
index 86b5665..0890f84 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java
@@ -16,16 +16,16 @@
package com.ning.billing.analytics.dao;
+import org.skife.jdbi.v2.IDBI;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import org.skife.jdbi.v2.DBI;
public class BusinessSubscriptionTransitionDaoProvider implements Provider<BusinessSubscriptionTransitionDao>
{
- private final DBI dbi;
+ private final IDBI dbi;
@Inject
- public BusinessSubscriptionTransitionDaoProvider(final DBI dbi)
+ public BusinessSubscriptionTransitionDaoProvider(final IDBI dbi)
{
this.dbi = dbi;
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
index 48663fa..25496b5 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
@@ -16,16 +16,16 @@
package com.ning.billing.analytics;
-import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.IDBI;
-
import com.ning.billing.account.glue.AccountModule;
import com.ning.billing.analytics.setup.AnalyticsModule;
import com.ning.billing.catalog.glue.CatalogModule;
import com.ning.billing.dbi.MysqlTestingHelper;
import com.ning.billing.entitlement.glue.EntitlementModule;
+
+import com.ning.billing.util.glue.BusModule;
+
import com.ning.billing.util.glue.ClockModule;
-import com.ning.billing.util.glue.EventBusModule;
import com.ning.billing.util.glue.NotificationQueueModule;
import com.ning.billing.util.glue.TagStoreModule;
@@ -39,7 +39,7 @@ public class AnalyticsTestModule extends AnalyticsModule
// Need to configure a few more things for the EventBus
install(new AccountModule());
install(new CatalogModule());
- install(new EventBusModule());
+ install(new BusModule());
install(new EntitlementModule());
install(new ClockModule());
install(new TagStoreModule());
@@ -48,8 +48,7 @@ public class AnalyticsTestModule extends AnalyticsModule
// Install the Dao layer
final MysqlTestingHelper helper = new MysqlTestingHelper();
bind(MysqlTestingHelper.class).toInstance(helper);
- final DBI dbi = helper.getDBI();
+ final IDBI dbi = helper.getDBI();
bind(IDBI.class).toInstance(dbi);
- bind(DBI.class).toInstance(dbi);
}
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
index 621243f..395d694 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
@@ -47,7 +47,7 @@ import com.ning.billing.entitlement.api.user.SubscriptionTransition;
import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
import com.ning.billing.entitlement.events.EntitlementEvent;
import com.ning.billing.entitlement.events.user.ApiEventType;
-import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.bus.Bus;
import com.ning.billing.util.tag.DescriptiveTag;
import com.ning.billing.util.tag.DefaultTagDefinition;
import com.ning.billing.util.tag.Tag;
@@ -90,7 +90,7 @@ public class TestAnalyticsService
private AnalyticsService service;
@Inject
- private EventBus bus;
+ private Bus bus;
@Inject
private BusinessSubscriptionTransitionDao subscriptionDao;
diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java b/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
index 368b8c1..c17cdd4 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
@@ -16,9 +16,21 @@
package com.ning.billing.analytics.dao;
-import com.ning.billing.analytics.*;
+import com.ning.billing.analytics.BusinessAccount;
+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.utils.Rounder;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.dbi.MysqlTestingHelper;
import com.ning.billing.entitlement.api.user.Subscription;
import org.apache.commons.io.IOUtils;
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
index 2012995..f10ff2f 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
@@ -18,6 +18,8 @@ package com.ning.billing.analytics;
import com.ning.billing.catalog.api.Duration;
import com.ning.billing.catalog.api.TimeUnit;
+import org.apache.commons.lang.NotImplementedException;
+import org.joda.time.DateTime;
public class MockDuration
{
@@ -36,6 +38,11 @@ public class MockDuration
{
return 1;
}
+
+ @Override
+ public DateTime addToDateTime(DateTime dateTime) {
+ throw new NotImplementedException();
+ }
};
}
@@ -54,6 +61,11 @@ public class MockDuration
{
return 1;
}
+
+ @Override
+ public DateTime addToDateTime(DateTime dateTime) {
+ throw new NotImplementedException();
+ }
};
}
@@ -72,6 +84,11 @@ public class MockDuration
{
return 1;
}
+
+ @Override
+ public DateTime addToDateTime(DateTime dateTime) {
+ throw new NotImplementedException();
+ }
};
}
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java b/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
index 3f11c0e..663ed78 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
@@ -16,10 +16,16 @@
package com.ning.billing.analytics;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Price;
import java.math.BigDecimal;
-import java.util.Date;
public class MockPhase implements PlanPhase
{
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
index a1fdc99..ec6d9ae 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
@@ -16,13 +16,8 @@
package com.ning.billing.analytics;
-import java.util.UUID;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.testng.Assert;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
+import java.util.UUID;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.PhaseType;
@@ -36,6 +31,13 @@ import com.ning.billing.entitlement.events.EntitlementEvent;
import com.ning.billing.entitlement.events.user.ApiEventType;
import com.ning.billing.util.clock.ClockMock;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+
public class TestAnalyticsListener
{
private static final String KEY = "1234";
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java
index 7696723..fcf7405 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java
@@ -41,7 +41,7 @@ public class TestBusinessAccount
Assert.assertEquals(account, account);
Assert.assertTrue(account.equals(account));
- final BusinessAccount otherAccount = new BusinessAccount("pierre", BigDecimal.ONE, Collections.singletonList("batch15"), new DateTime(), BigDecimal.TEN, "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa", "");
+ final BusinessAccount otherAccount = new BusinessAccount("pierre cardin", BigDecimal.ONE, Collections.singletonList("batch15"), new DateTime(), BigDecimal.TEN, "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa", "");
Assert.assertFalse(account.equals(otherAccount));
}
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
index 6c38854..55c73ad 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
@@ -16,7 +16,12 @@
package com.ning.billing.analytics;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.Subscription;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
@@ -94,7 +99,7 @@ public class TestBusinessSubscription
Assert.assertEquals(subscription, subscription);
Assert.assertTrue(subscription.equals(subscription));
- final Subscription otherIsubscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
- Assert.assertTrue(!subscription.equals(new BusinessSubscription(otherIsubscription, USD)));
+ final Subscription otherSubscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
+ Assert.assertTrue(!subscription.equals(new BusinessSubscription(otherSubscription, USD)));
}
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
index 5ea1950..a3ca56c 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
@@ -16,7 +16,11 @@
package com.ning.billing.analytics;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.Subscription;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
index 592d20d..a8a954c 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
@@ -16,7 +16,11 @@
package com.ning.billing.analytics;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.Subscription;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
api/pom.xml 2(+1 -1)
diff --git a/api/pom.xml b/api/pom.xml
index 89f3a43..afbca6a 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.3-SNAPSHOT</version>
+ <version>0.1.5-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-api</artifactId>
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java b/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java
index 9e5f254..4102447 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java
@@ -16,12 +16,12 @@
package com.ning.billing.account.api;
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
import java.util.List;
import java.util.UUID;
-public interface AccountChangeNotification extends EventBusNotification {
+public interface AccountChangeNotification extends BusEvent {
public UUID getAccountId();
public List<ChangedField> getChangedFields();
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountCreationNotification.java b/api/src/main/java/com/ning/billing/account/api/AccountCreationNotification.java
index bc2c065..b30d290 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountCreationNotification.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountCreationNotification.java
@@ -16,11 +16,11 @@
package com.ning.billing.account.api;
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
import java.util.UUID;
-public interface AccountCreationNotification extends EventBusNotification {
+public interface AccountCreationNotification extends BusEvent {
public UUID getId();
public AccountData getData();
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java b/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
index fcdbcd9..e208c83 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
@@ -19,7 +19,6 @@ package com.ning.billing.account.api;
import java.util.List;
import java.util.UUID;
import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.eventbus.EventBus;
import com.ning.billing.util.tag.Tag;
public interface AccountUserApi {
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Duration.java b/api/src/main/java/com/ning/billing/catalog/api/Duration.java
index f9fe583..9025367 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Duration.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Duration.java
@@ -16,10 +16,13 @@
package com.ning.billing.catalog.api;
+import org.joda.time.DateTime;
+
public interface Duration {
public abstract TimeUnit getUnit();
public abstract int getNumber();
+ public DateTime addToDateTime(DateTime dateTime);
}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/config/InvoiceConfig.java b/api/src/main/java/com/ning/billing/config/InvoiceConfig.java
new file mode 100644
index 0000000..a2b4270
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/config/InvoiceConfig.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.config;
+
+import org.skife.config.Config;
+import org.skife.config.Default;
+
+public interface InvoiceConfig {
+
+ @Config("killbill.invoice.dao.claim.time")
+ @Default("60000")
+ public long getDaoClaimTimeMs();
+
+ @Config("killbill.invoice.dao.ready.max")
+ @Default("10")
+ public int getDaoMaxReadyEvents();
+
+ @Config("killbill.invoice.engine.notifications.sleep")
+ @Default("500")
+ public long getNotificationSleepTimeMs();
+
+ @Config("killbill.invoice.engine.events.off")
+ @Default("false")
+ public boolean isEventProcessingOff();
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
index d3425bb..2628b36 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
@@ -16,17 +16,14 @@
package com.ning.billing.entitlement.api.billing;
-import java.math.BigDecimal;
-
import org.joda.time.DateTime;
import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.catalog.api.CatalogApiException;
-import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.InternationalPrice;
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;
public interface BillingEvent extends Comparable<BillingEvent> {
@@ -93,19 +90,8 @@ public interface BillingEvent extends Comparable<BillingEvent> {
*/
public InternationalPrice getRecurringPrice();
- /**
- * Syntactic sugar to wrap currency access call
- *
- * @param currency
- * @return price value
- */
- public BigDecimal getRecurringPrice(Currency currency) throws CatalogApiException ;
-
- /**
- * Syntactic sugar to wrap currency access call
- *
- * @param currency
- * @return price value
- */
- public BigDecimal getFixedPrice(Currency currency) throws CatalogApiException ;
+ /**
+ * @return the transition type of the underlying subscription event that triggered this
+ */
+ public SubscriptionTransitionType getTransitionType();
}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApi.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApi.java
index 2b07182..6dc3e94 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApi.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApi.java
@@ -16,13 +16,12 @@
package com.ning.billing.entitlement.api.billing;
-import java.util.List;
import java.util.SortedSet;
import java.util.UUID;
import org.joda.time.DateTime;
-
-import com.ning.billing.account.api.Account;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
public interface EntitlementBillingApi {
@@ -35,7 +34,10 @@ public interface EntitlementBillingApi {
*/
public SortedSet<BillingEvent> getBillingEventsForAccount(UUID accountId);
+ public UUID getAccountIdFromSubscriptionId(UUID subscriptionId);
public void setChargedThroughDate(UUID subscriptionId, DateTime ctd);
+ public void setChargedThroughDateFromTransaction(Transmogrifier transactionalDao, UUID subscriptionId, DateTime ctd);
+
}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java
index 9c22915..4c20f7c 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java
@@ -16,25 +16,21 @@
package com.ning.billing.entitlement.api.billing;
-public class EntitlementBillingApiException extends Exception {
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+public class EntitlementBillingApiException extends BillingExceptionBase {
private static final long serialVersionUID = 127392038L;
- public EntitlementBillingApiException() {
- super();
+ public EntitlementBillingApiException(Throwable cause, int code, final String msg) {
+ super(cause, code, msg);
}
- public EntitlementBillingApiException(String msg, Throwable arg1) {
- super(msg, arg1);
+ public EntitlementBillingApiException(Throwable cause, ErrorCode code, final Object... args) {
+ super(cause, code, args);
}
- public EntitlementBillingApiException(String msg) {
- super(msg);
+ public EntitlementBillingApiException(ErrorCode code, final Object... args) {
+ super(code, args);
}
-
- public EntitlementBillingApiException(Throwable msg) {
- super(msg);
- }
-
-
}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
index 26ce81f..f4c971d 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
@@ -19,12 +19,12 @@ package com.ning.billing.entitlement.api.user;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
import org.joda.time.DateTime;
import java.util.UUID;
-public interface SubscriptionTransition extends EventBusNotification {
+public interface SubscriptionTransition extends BusEvent {
public enum SubscriptionTransitionType {
MIGRATE_ENTITLEMENT,
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index d1f5ed9..4e1ffaa 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -43,7 +43,10 @@ public enum ErrorCode {
ENT_CANCEL_BAD_STATE(1031, "Subscription %s is in state %s"),
/* Un-cancellation */
ENT_UNCANCEL_BAD_STATE(1070, "Subscription %s was not in a cancelled state"),
-
+ /* Fetch */
+ ENT_GET_NO_BUNDLE_FOR_SUBSCRIPTION(1080, "Could not find a bundle for subscription %s"),
+ ENT_GET_INVALID_BUNDLE_ID(1081, "Could not find a bundle matching id %s"),
+ ENT_INVALID_SUBSCRIPTION_ID(1082, "Unknown subscription %s"),
/*
*
* Range 2000 : CATALOG
@@ -66,7 +69,7 @@ public enum ErrorCode {
CAT_NO_PRICE_FOR_CURRENCY(2010, "This price does not have a value for the currency '%s'."),
/* Price value explicitly set to NULL meaning there is no price available in that currency */
- CAT_PRICE_VALUE_NULL_FOR_CURRENCY(2011, "The value for the currency '%s' is NULL. This plan cannot be bought in this currnency."),
+ CAT_PRICE_VALUE_NULL_FOR_CURRENCY(2011, "The value for the currency '%s' is NULL. This plan cannot be bought in this currency."),
CAT_NULL_PRICE_LIST_NAME(2012,"Price list name was null"),
CAT_PRICE_LIST_NOT_FOUND(2013, "Could not find a pricelist with name '%s'"),
/*
@@ -116,8 +119,16 @@ public enum ErrorCode {
TAG_DEFINITION_CONFLICTS_WITH_CONTROL_TAG(3900, "The tag definition name conflicts with a reserved name (name %s)"),
TAG_DEFINITION_ALREADY_EXISTS(3901, "The tag definition name already exists (name: %s)"),
TAG_DEFINITION_DOES_NOT_EXIST(3902, "The tag definition name does not exist (name: %s)"),
- TAG_DEFINITION_IN_USE(3903, "The tag definition name is currently in use (name: %s)")
-
+ TAG_DEFINITION_IN_USE(3903, "The tag definition name is currently in use (name: %s)"),
+
+ /*
+ *
+ * Range 4000: INVOICE
+ *
+ */
+ INVOICE_ACCOUNT_ID_INVALID(4001, "No account could be retrieved for id %s"),
+ INVOICE_INVALID_TRANSITION(4002, "Transition did not contain a subscription id."),
+ INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID(4003, "No account id was retrieved for subscription id %s")
;
private int code;
diff --git a/api/src/main/java/com/ning/billing/invoice/api/Invoice.java b/api/src/main/java/com/ning/billing/invoice/api/Invoice.java
index f69b934..0d13363 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/Invoice.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/Invoice.java
@@ -25,14 +25,22 @@ import java.util.List;
import java.util.UUID;
public interface Invoice extends Entity {
- boolean add(InvoiceItem item);
+ boolean addInvoiceItem(InvoiceItem item);
- boolean add(List<InvoiceItem> items);
+ boolean addInvoiceItems(List<InvoiceItem> items);
- List<InvoiceItem> getItems();
+ List<InvoiceItem> getInvoiceItems();
int getNumberOfItems();
+ boolean addPayment(InvoicePayment payment);
+
+ boolean addPayments(List<InvoicePayment> payments);
+
+ List<InvoicePayment> getPayments();
+
+ int getNumberOfPayments();
+
UUID getAccountId();
DateTime getInvoiceDate();
@@ -47,7 +55,7 @@ public interface Invoice extends Entity {
BigDecimal getTotalAmount();
- BigDecimal getAmountOutstanding();
+ BigDecimal getBalance();
boolean isDueForPayment(DateTime targetDate, int numberOfDays);
}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceApiException.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceApiException.java
new file mode 100644
index 0000000..a9275e8
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceApiException.java
@@ -0,0 +1,34 @@
+/*
+ * 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.api;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+
+public class InvoiceApiException extends BillingExceptionBase {
+ public InvoiceApiException(Throwable cause, int code, final String msg) {
+ super(cause, code, msg);
+ }
+
+ public InvoiceApiException(Throwable cause, ErrorCode code, final Object... args) {
+ super(cause, code, args);
+ }
+
+ public InvoiceApiException(ErrorCode code, final Object... args) {
+ super(code, args);
+ }
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java
index 5ecd311..478a1fb 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java
@@ -22,9 +22,9 @@ import java.util.UUID;
import org.joda.time.DateTime;
import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
-public interface InvoiceCreationNotification extends EventBusNotification {
+public interface InvoiceCreationNotification extends BusEvent {
public UUID getInvoiceId();
public UUID getAccountId();
public BigDecimal getAmountOwed();
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
index 44c46a7..8cadd9a 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
@@ -28,15 +28,19 @@ public interface InvoiceItem extends Entity, Comparable<InvoiceItem> {
UUID getSubscriptionId();
+ String getPlanName();
+
+ String getPhaseName();
+
DateTime getStartDate();
DateTime getEndDate();
- String getDescription();
+ BigDecimal getRecurringAmount();
- BigDecimal getAmount();
+ BigDecimal getRecurringRate();
- BigDecimal getRate();
+ BigDecimal getFixedAmount();
Currency getCurrency();
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.java b/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.java
new file mode 100644
index 0000000..b947762
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.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.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+import org.joda.time.DateTime;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.entity.Entity;
+
+public interface InvoicePayment {
+ UUID getPaymentAttemptId();
+
+ UUID getInvoiceId();
+
+ DateTime getPaymentAttemptDate();
+
+ BigDecimal getAmount();
+
+ Currency getCurrency();
+
+ DateTime getCreatedDate();
+
+ DateTime getUpdatedDate();
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
index aaa89a5..641afa6 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
@@ -23,7 +23,6 @@ import java.util.UUID;
import org.joda.time.DateTime;
import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.payment.api.InvoicePayment;
public interface InvoicePaymentApi {
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
index d826d5c..363e483 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
@@ -16,23 +16,27 @@
package com.ning.billing.invoice.api;
+import org.joda.time.DateTime;
+
import java.math.BigDecimal;
+import java.util.Collection;
import java.util.List;
import java.util.UUID;
-import org.joda.time.DateTime;
-
-import com.ning.billing.payment.api.InvoicePayment;
-
public interface InvoiceUserApi {
public List<UUID> getInvoicesForPayment(DateTime targetDate, int numberOfDays);
public List<Invoice> getInvoicesByAccount(UUID accountId);
+ public List<Invoice> getInvoicesByAccount(UUID accountId, DateTime fromDate);
+
+ public BigDecimal getAccountBalance(UUID accountId);
+
+ public List<InvoiceItem> getInvoiceItemsByAccount(UUID accountId);
+
public Invoice getInvoice(UUID invoiceId);
public void notifyOfPaymentAttempt(InvoicePayment invoicePayment);
- public BigDecimal getAccountBalance(UUID accountId);
-
+ public Collection<Invoice> getUnpaidInvoicesByAccountId(UUID accountId, DateTime upToDate);
}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/test/InvoiceTestApi.java b/api/src/main/java/com/ning/billing/invoice/api/test/InvoiceTestApi.java
new file mode 100644
index 0000000..9edcd8c
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/test/InvoiceTestApi.java
@@ -0,0 +1,23 @@
+/*
+ * 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.api.test;
+
+import com.ning.billing.invoice.api.Invoice;
+
+public interface InvoiceTestApi {
+ public void create(Invoice invoice);
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
index 189e87c..8ecae71 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
@@ -49,4 +49,8 @@ public interface PaymentApi {
PaymentAttempt getPaymentAttemptForPaymentId(String id);
+ List<PaymentInfo> getPaymentInfo(List<String> invoiceIds);
+
+ PaymentAttempt getPaymentAttemptForInvoiceId(String invoiceId);
+
}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java b/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
index d708be3..fcccf9b 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
@@ -99,7 +99,7 @@ public class PaymentAttempt {
}
public PaymentAttempt(UUID paymentAttemptId, Invoice invoice) {
- this(paymentAttemptId, invoice.getId(), invoice.getAccountId(), invoice.getAmountOutstanding(), invoice.getCurrency(), invoice.getInvoiceDate(), null, null, null, null);
+ this(paymentAttemptId, invoice.getId(), invoice.getAccountId(), invoice.getBalance(), invoice.getCurrency(), invoice.getInvoiceDate(), null, null, null, null);
}
public DateTime getInvoiceDate() {
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentError.java b/api/src/main/java/com/ning/billing/payment/api/PaymentError.java
index 45e8555..f1474c8 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentError.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentError.java
@@ -18,10 +18,10 @@ package com.ning.billing.payment.api;
import org.codehaus.jackson.annotate.JsonTypeInfo;
import org.codehaus.jackson.annotate.JsonTypeInfo.Id;
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
@JsonTypeInfo(use = Id.NAME, property = "error")
-public class PaymentError implements EventBusNotification {
+public class PaymentError implements BusEvent {
private final String type;
private final String message;
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java b/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
index d67f93c..943c5f7 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
@@ -24,9 +24,9 @@ import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import com.google.common.base.Objects;
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
-public class PaymentInfo implements EventBusNotification {
+public class PaymentInfo implements BusEvent {
private final String paymentId;
private final BigDecimal amount;
private final BigDecimal refundAmount;
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentService.java b/api/src/main/java/com/ning/billing/payment/api/PaymentService.java
index 988a00a..ede2506 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentService.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentService.java
@@ -23,4 +23,5 @@ public interface PaymentService extends KillbillService {
String getName();
PaymentApi getPaymentApi();
+
}
beatrix/pom.xml 14(+12 -2)
diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 9e2fdba..ded6f0f 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.3-SNAPSHOT</version>
+ <version>0.1.5-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-beatrix</artifactId>
@@ -28,7 +28,11 @@
<groupId>com.ning.billing</groupId>
<artifactId>killbill-entitlement</artifactId>
</dependency>
- <dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-invoice</artifactId>
+ </dependency>
+ <dependency>
<groupId>com.ning.billing</groupId>
<artifactId>killbill-catalog</artifactId>
</dependency>
@@ -78,6 +82,12 @@
<artifactId>commons-io</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>
<plugins>
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java b/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java
index 7a672c1..02da6c9 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java
@@ -17,13 +17,14 @@
package com.ning.billing.beatrix.glue;
import com.google.inject.AbstractModule;
+import com.ning.billing.beatrix.lifecycle.DefaultLifecycle;
import com.ning.billing.beatrix.lifecycle.Lifecycle;
public class BeatrixModule extends AbstractModule {
@Override
protected void configure() {
- bind(Lifecycle.class).asEagerSingleton();
+ bind(Lifecycle.class).to(DefaultLifecycle.class).asEagerSingleton();
}
}
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/DefaultLifecycle.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/DefaultLifecycle.java
new file mode 100644
index 0000000..8a07355
--- /dev/null
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/DefaultLifecycle.java
@@ -0,0 +1,177 @@
+/*
+ * 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.beatrix.lifecycle;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.ning.billing.lifecycle.KillbillService;
+import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel.Sequence;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+
+public class DefaultLifecycle implements Lifecycle {
+
+ private final static Logger log = LoggerFactory.getLogger(DefaultLifecycle.class);
+ private final SetMultimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> handlersByLevel;
+
+ private final ServiceFinder serviceFinder;
+
+ protected final Injector injector;
+
+ @Inject
+ public DefaultLifecycle(Injector injector) {
+
+ this.serviceFinder = new ServiceFinder(DefaultLifecycle.class.getClassLoader());
+ this.handlersByLevel = Multimaps.newSetMultimap(new ConcurrentHashMap<LifecycleLevel, Collection<LifecycleHandler<? extends KillbillService>>>(),
+
+ new Supplier<Set<LifecycleHandler<? extends KillbillService>>>() {
+ @Override
+ public Set<LifecycleHandler<? extends KillbillService>> get() {
+ return new CopyOnWriteArraySet<LifecycleHandler<? extends KillbillService>>();
+ }
+ });
+ this.injector = injector;
+
+ init();
+ }
+
+
+ @Override
+ public void fireStartupSequencePriorEventRegistration() {
+ fireSequence(Sequence.STARTUP_PRE_EVENT_REGISTRATION);
+ }
+
+ @Override
+ public void fireStartupSequencePostEventRegistration() {
+ fireSequence(Sequence.STARTUP_POST_EVENT_REGISTRATION);
+ }
+
+ @Override
+ public void fireShutdownSequencePriorEventUnRegistration() {
+ fireSequence(Sequence.SHUTDOWN_PRE_EVENT_UNREGISTRATION);
+ }
+
+ @Override
+ public void fireShutdownSequencePostEventUnRegistration() {
+ fireSequence(Sequence.SHUTDOWN_POST_EVENT_UNREGISTRATION);
+ }
+
+ protected Set<? extends KillbillService> findServices() {
+
+ Set<KillbillService> result = new HashSet<KillbillService>();
+ Set<Class<? extends KillbillService>> services = serviceFinder.getServices();
+ for (Class<? extends KillbillService> cur : services) {
+ log.debug("Found service {}", cur.getName());
+ try {
+ KillbillService instance = injector.getInstance(cur);
+ log.debug("got instance {}", instance.getName());
+ result.add(instance);
+ } catch (Exception e) {
+ logWarn("Failed to inject " + cur.getName(), e);
+ }
+
+ }
+ return result;
+ }
+
+ private void init() {
+ Set<? extends KillbillService> services = findServices();
+ Iterator<? extends KillbillService> it = services.iterator();
+ while (it.hasNext()) {
+ handlersByLevel.putAll(findAllHandlers(it.next()));
+ }
+ }
+
+ private void fireSequence(Sequence seq) {
+ List<LifecycleLevel> levels = LifecycleLevel.getLevelsForSequence(seq);
+ for (LifecycleLevel cur : levels) {
+ doFireStage(cur);
+ }
+ }
+
+ private void doFireStage(LifecycleLevel level) {
+ log.info("Killbill lifecycle firing stage {}", level);
+ Set<LifecycleHandler<? extends KillbillService>> handlers = handlersByLevel.get(level);
+ for (LifecycleHandler<? extends KillbillService> cur : handlers) {
+
+ try {
+ Method method = cur.getMethod();
+ KillbillService target = cur.getTarget();
+ log.info("Killbill lifecycle calling handler {} for service {}", cur.getMethod().getName(), target.getName());
+ method.invoke(target);
+ } catch (Exception e) {
+ logWarn("Killbill lifecycle failed to invoke lifecycle handler", e);
+ }
+ }
+
+ }
+
+
+ // Used to disable valid injection failure from unit tests
+ protected void logWarn(String msg, Exception e) {
+ log.warn(msg, e);
+ }
+
+ private Multimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> findAllHandlers(KillbillService service) {
+ Multimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> methodsInService = HashMultimap.create();
+ Class<? extends KillbillService> clazz = service.getClass();
+ for (Method method : clazz.getMethods()) {
+ LifecycleHandlerType annotation = method.getAnnotation(LifecycleHandlerType.class);
+ if (annotation != null) {
+ LifecycleLevel level = annotation.value();
+ LifecycleHandler<? extends KillbillService> handler = new LifecycleHandler<KillbillService>(service, method);
+ methodsInService.put(level, handler);
+ }
+ }
+ return methodsInService;
+ }
+
+ private final class LifecycleHandler<T> {
+ private final T target;
+ private final Method method;
+
+ public LifecycleHandler(T target, Method method) {
+ this.target = target;
+ this.method = method;
+ }
+
+ public T getTarget() {
+ return target;
+ }
+
+ public Method getMethod() {
+ return method;
+ }
+ }
+}
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java
index c0edf2c..8192a65 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java
@@ -16,156 +16,14 @@
package com.ning.billing.beatrix.lifecycle;
-import com.google.common.base.Supplier;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Multimaps;
-import com.google.common.collect.SetMultimap;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import com.ning.billing.lifecycle.KillbillService;
-import com.ning.billing.lifecycle.LifecycleHandlerType;
-import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
-import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel.Sequence;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import java.lang.reflect.Method;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArraySet;
+public interface Lifecycle {
+ public void fireStartupSequencePriorEventRegistration();
-public class Lifecycle {
+ public void fireStartupSequencePostEventRegistration();
- private final static Logger log = LoggerFactory.getLogger(Lifecycle.class);
- private final SetMultimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> handlersByLevel;
+ public void fireShutdownSequencePriorEventUnRegistration();
- private final ServiceFinder serviceFinder;
-
- private final Injector injector;
-
- @Inject
- public Lifecycle(Injector injector) {
-
- this.serviceFinder = new ServiceFinder(Lifecycle.class.getClassLoader());
- this.handlersByLevel = Multimaps.newSetMultimap(new ConcurrentHashMap<LifecycleLevel, Collection<LifecycleHandler<? extends KillbillService>>>(),
-
- new Supplier<Set<LifecycleHandler<? extends KillbillService>>>() {
- @Override
- public Set<LifecycleHandler<? extends KillbillService>> get() {
- return new CopyOnWriteArraySet<LifecycleHandler<? extends KillbillService>>();
- }
- });
- this.injector = injector;
-
- init();
- }
-
- public void init() {
- Set<? extends KillbillService> services = findServices();
- Iterator<? extends KillbillService> it = services.iterator();
- while (it.hasNext()) {
- handlersByLevel.putAll(findAllHandlers(it.next()));
- }
- }
-
-
- public void fireStartupSequencePriorEventRegistration() {
- fireSequence(Sequence.STARTUP_PRE_EVENT_REGISTRATION);
- }
-
- public void fireStartupSequencePostEventRegistration() {
- fireSequence(Sequence.STARTUP_POST_EVENT_REGISTRATION);
- }
-
- public void fireShutdownSequencePriorEventUnRegistration() {
- fireSequence(Sequence.SHUTDOWN_PRE_EVENT_UNREGISTRATION);
- }
-
- public void fireShutdownSequencePostEventUnRegistration() {
- fireSequence(Sequence.SHUTDOWN_POST_EVENT_UNREGISTRATION);
- }
-
- private void fireSequence(Sequence seq) {
- List<LifecycleLevel> levels = LifecycleLevel.getLevelsForSequence(seq);
- for (LifecycleLevel cur : levels) {
- doFireStage(cur);
- }
- }
-
- private void doFireStage(LifecycleLevel level) {
- log.info("Killbill lifecycle firing stage {}", level);
- Set<LifecycleHandler<? extends KillbillService>> handlers = handlersByLevel.get(level);
- for (LifecycleHandler<? extends KillbillService> cur : handlers) {
-
- try {
- Method method = cur.getMethod();
- KillbillService target = cur.getTarget();
- log.info("Killbill lifecycle calling handler {} for service {}", cur.getMethod().getName(), target.getName());
- method.invoke(target);
- } catch (Exception e) {
- logWarn("Killbill lifecycle failed to invoke lifecycle handler", e);
- }
- }
-
- }
-
-
- private Set<? extends KillbillService> findServices() {
-
- Set<KillbillService> result = new HashSet<KillbillService>();
- Set<Class<? extends KillbillService>> services = serviceFinder.getServices();
- for (Class<? extends KillbillService> cur : services) {
- log.debug("Found service {}", cur.getName());
- try {
- KillbillService instance = injector.getInstance(cur);
- log.debug("got instance {}", instance.getName());
- result.add(instance);
- } catch (Exception e) {
- logWarn("Failed to inject " + cur.getName(), e);
- }
-
- }
- return result;
- }
-
-
- // Used to disable valid injection failure from unit tests
- protected void logWarn(String msg, Exception e) {
- log.warn(msg, e);
- }
-
- public Multimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> findAllHandlers(KillbillService service) {
- Multimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> methodsInService = HashMultimap.create();
- Class<? extends KillbillService> clazz = service.getClass();
- for (Method method : clazz.getMethods()) {
- LifecycleHandlerType annotation = method.getAnnotation(LifecycleHandlerType.class);
- if (annotation != null) {
- LifecycleLevel level = annotation.value();
- LifecycleHandler<? extends KillbillService> handler = new LifecycleHandler<KillbillService>(service, method);
- methodsInService.put(level, handler);
- }
- }
- return methodsInService;
- }
-
-
- private final class LifecycleHandler<T> {
- private final T target;
- private final Method method;
-
- public LifecycleHandler(T target, Method method) {
- this.target = target;
- this.method = method;
- }
-
- public T getTarget() {
- return target;
- }
-
- public Method getMethod() {
- return method;
- }
- }
+ public void fireShutdownSequencePostEventUnRegistration();
}
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
index a4a4ce2..83115de 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
@@ -16,7 +16,6 @@
package com.ning.billing.beatrix.lifecycle;
-
import com.ning.billing.lifecycle.KillbillService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -25,7 +24,13 @@ import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
import java.util.jar.JarFile;
public class ServiceFinder {
@@ -81,8 +86,6 @@ public class ServiceFinder {
}
for (int h = 0; h < classPaths.length; h++) {
-
-
Enumeration<?> files = null;
JarFile module = null;
File classPath = new File( (URL.class).isInstance(classPaths[h]) ?
@@ -93,7 +96,7 @@ public class ServiceFinder {
List<String> dirListing = new ArrayList<String>();
recursivelyListDir(dirListing, classPath, new StringBuffer() );
- files = Collections.enumeration( dirListing );
+ files = Collections.enumeration(dirListing);
} else if (classPath.getName().endsWith(".jar")) {
log.debug("JAR : " + classPath);
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/MockModule.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/MockModule.java
new file mode 100644
index 0000000..28294f9
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/MockModule.java
@@ -0,0 +1,104 @@
+/*
+ * 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.beatrix.integration.inv_ent;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Set;
+
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.ning.billing.account.glue.AccountModule;
+import com.ning.billing.beatrix.lifecycle.DefaultLifecycle;
+import com.ning.billing.beatrix.lifecycle.Lifecycle;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.glue.CatalogModule;
+import com.ning.billing.dbi.DBIProvider;
+import com.ning.billing.dbi.DbiConfig;
+import com.ning.billing.entitlement.api.EntitlementService;
+import com.ning.billing.entitlement.glue.EntitlementModule;
+import com.ning.billing.invoice.api.InvoiceService;
+import com.ning.billing.invoice.glue.InvoiceModule;
+import com.ning.billing.lifecycle.KillbillService;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
+import com.ning.billing.util.glue.NotificationQueueModule;
+
+
+public class MockModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+
+ loadSystemPropertiesFromClasspath("/resource.properties");
+
+ bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+ bind(ClockMock.class).asEagerSingleton();
+ bind(Lifecycle.class).to(SubsetDefaultLifecycle.class).asEagerSingleton();
+ bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+ final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
+ bind(DbiConfig.class).toInstance(config);
+ install(new BusModule());
+ install(new NotificationQueueModule());
+ install(new AccountModule());
+ install(new CatalogModule());
+ install(new EntitlementModule());
+ install(new InvoiceModule());
+ install(new GlobalLockerModule());
+ }
+
+
+ private static void loadSystemPropertiesFromClasspath(final String resource) {
+ final URL url = TestBasic.class.getResource(resource);
+ assertNotNull(url);
+ try {
+ System.getProperties().load( url.openStream() );
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private final static class SubsetDefaultLifecycle extends DefaultLifecycle {
+
+
+ @Inject
+ public SubsetDefaultLifecycle(Injector injector) {
+ super(injector);
+ }
+
+ @Override
+ protected Set<? extends KillbillService> findServices() {
+ ImmutableSet<? extends KillbillService> services = new ImmutableSet.Builder<KillbillService>()
+ .add(injector.getInstance(BusService.class))
+ .add(injector.getInstance(CatalogService.class))
+ .add(injector.getInstance(EntitlementService.class))
+ .add(injector.getInstance(InvoiceService.class))
+ .build();
+ return services;
+ }
+ }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java
new file mode 100644
index 0000000..1f2adce
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java
@@ -0,0 +1,426 @@
+/*
+ * 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.beatrix.integration.inv_ent;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.UUID;
+
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.Interval;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.TransactionCallback;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.AfterSuite;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.AccountService;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.beatrix.integration.inv_ent.TestBusHandler.NextEvent;
+import com.ning.billing.beatrix.lifecycle.Lifecycle;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.EntitlementService;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.invoice.api.InvoiceService;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.bus.BusService;
+
+@Guice(modules = {MockModule.class})
+public class TestBasic {
+
+ private static final Logger log = LoggerFactory.getLogger(TestBasic.class);
+ private static long AT_LEAST_ONE_MONTH_MS = 31L * 24L * 3600L * 1000L;
+
+ @Inject IDBI dbi;
+
+ @Inject
+ private ClockMock clock;
+
+ @Inject
+ private Lifecycle lifecycle;
+
+ @Inject
+ private BusService busService;
+
+ @Inject
+ private EntitlementService entitlementService;
+
+ @Inject
+ private InvoiceService invoiceService;
+
+ @Inject
+ private AccountService accountService;
+
+ private EntitlementUserApi entitlementUserApi;
+
+ private InvoiceUserApi invoiceUserApi;
+
+ private AccountUserApi accountUserApi;
+
+ private TestBusHandler busHandler;
+
+
+
+ @BeforeSuite(alwaysRun = true)
+ public void setup() throws Exception{
+
+ /**
+ * Initialize lifecyle for subset of services
+ */
+ busHandler = new TestBusHandler();
+ lifecycle.fireStartupSequencePriorEventRegistration();
+ busService.getBus().register(busHandler);
+ lifecycle.fireStartupSequencePostEventRegistration();
+
+ /**
+ * Retrieve APIs
+ */
+ entitlementUserApi = entitlementService.getUserApi();
+ invoiceUserApi = invoiceService.getUserApi();
+ accountUserApi = accountService.getAccountUserApi();
+ }
+
+ @AfterSuite(alwaysRun = true)
+ public void tearDown() throws Exception {
+ lifecycle.fireShutdownSequencePriorEventUnRegistration();
+ busService.getBus().unregister(busHandler);
+ lifecycle.fireShutdownSequencePostEventUnRegistration();
+ }
+
+
+ @BeforeMethod(alwaysRun = true)
+ public void setupTest() {
+
+ log.warn("\n");
+ log.warn("RESET TEST FRAMEWORK\n\n");
+ busHandler.reset();
+ clock.resetDeltaFromReality();
+ cleanupData();
+ }
+
+ @AfterMethod(alwaysRun = true)
+ public void cleanupTest() {
+ log.warn("DONE WITH TEST\n");
+ }
+
+ private void cleanupData() {
+ dbi.inTransaction(new TransactionCallback<Void>() {
+ @Override
+ public Void inTransaction(Handle h, TransactionStatus status)
+ throws Exception {
+ h.execute("truncate table accounts");
+ h.execute("truncate table events");
+ h.execute("truncate table subscriptions");
+ h.execute("truncate table bundles");
+ h.execute("truncate table notifications");
+ h.execute("truncate table claimed_notifications");
+ h.execute("truncate table invoices");
+ h.execute("truncate table invoice_items");
+ h.execute("truncate table tag_definitions");
+ h.execute("truncate table tags");
+ return null;
+ }
+ });
+ }
+
+ private DateTime checkAndGetCTD(UUID subscriptionId) {
+
+ SubscriptionData subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscriptionId);
+ DateTime ctd = subscription.getChargedThroughDate();
+ assertNotNull(ctd);
+ log.info("Checking CTD: " + ctd.toString() + "; clock is " + clock.getUTCNow().toString());
+ assertTrue(clock.getUTCNow().isBefore(ctd));
+ return ctd;
+ }
+
+ @Test(groups = "fast", enabled = false)
+ public void testBasePlanCompleteWithBillingDayInPast() throws Exception {
+ testBasePlanComplete(clock.getUTCNow().minusDays(1).getDayOfMonth());
+ }
+
+ @Test(groups = "fast", enabled = false)
+ public void testBasePlanCompleteWithBillingDayPresent() throws Exception {
+ testBasePlanComplete(clock.getUTCNow().getDayOfMonth());
+ }
+
+ @Test(groups = "fast", enabled = false)
+ public void testBasePlanCompleteWithBillingDayInFuture() throws Exception {
+ testBasePlanComplete(clock.getUTCNow().plusDays(1).getDayOfMonth());
+ }
+
+ private void testBasePlanComplete(int billingDay) throws Exception {
+ long DELAY = 5000;
+
+ Account account = accountUserApi.createAccount(getAccountData(billingDay), null, null);
+ assertNotNull(account);
+
+ SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever");
+
+ String productName = "Shotgun";
+ BillingPeriod term = BillingPeriod.MONTHLY;
+ String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ //
+ // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+ //
+ busHandler.pushExpectedEvent(NextEvent.CREATE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ SubscriptionData subscription = (SubscriptionData) entitlementUserApi.createSubscription(bundle.getId(),
+ new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null);
+ assertNotNull(subscription);
+ assertTrue(busHandler.isCompleted(DELAY));
+ log.info("testSimple passed first busHandler checkpoint.");
+
+ //
+ // VERIFY CTD HAS BEEN SET
+ //
+
+ checkAndGetCTD(subscription.getId());
+
+ //
+ // CHANGE PLAN IMMEDIATELY AND EXPECT BOTH EVENTS: NextEvent.CHANGE NextEvent.INVOICE
+ //
+ busHandler.pushExpectedEvent(NextEvent.CHANGE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+
+ BillingPeriod newTerm = BillingPeriod.MONTHLY;
+ String newPlanSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+ String newProductName = "Assault-Rifle";
+ subscription.changePlan(newProductName, newTerm, newPlanSetName, clock.getUTCNow());
+
+ assertTrue(busHandler.isCompleted(DELAY));
+ log.info("testSimple passed second busHandler checkpoint.");
+
+ //
+ // VERIFY AGAIN CTD HAS BEEN SET
+ //
+ DateTime ctd = checkAndGetCTD(subscription.getId());
+
+ //
+ // MOVE TIME TO AFTER TRIAL AND EXPECT BOTH EVENTS : NextEvent.PHASE NextEvent.INVOICE
+ //
+ busHandler.pushExpectedEvent(NextEvent.PHASE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ clock.setDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ //
+ // CHANGE PLAN EOT AND EXPECT NOTHING
+ //
+ newTerm = BillingPeriod.MONTHLY;
+ newPlanSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+ newProductName = "Pistol";
+ subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+ subscription.changePlan(newProductName, newTerm, newPlanSetName, clock.getUTCNow());
+ log.info("testSimple has passed third busHandler checkpoint (no events)");
+
+ //
+ // MOVE TIME AFTER CTD AND EXPECT BOTH EVENTS : NextEvent.CHANGE NextEvent.INVOICE
+ //
+ busHandler.pushExpectedEvent(NextEvent.CHANGE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ //clock.addDeltaFromReality(ctd.getMillis() - clock.getUTCNow().getMillis());
+ clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
+ assertTrue(busHandler.isCompleted(DELAY));
+ log.info("testSimple passed fourth busHandler checkpoint.");
+
+ //
+ // MOVE TIME AFTER NEXT BILL CYCLE DAY AND EXPECT EVENT : NextEvent.INVOICE
+ //
+ int maxCycles = 3;
+ DateTime lastCtd = null;
+ do {
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
+ assertTrue(busHandler.isCompleted(DELAY));
+ lastCtd = checkAndGetCTD(subscription.getId());
+ } while (maxCycles-- > 0);
+
+ //
+ // FINALLY CANCEL SUBSCRIPTION EOT
+ //
+ subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+ subscription.cancel(clock.getUTCNow(), false);
+
+ // MOVE AFTER CANCEL DATE AND EXPECT EVENT : NextEvent.CANCEL
+ busHandler.pushExpectedEvent(NextEvent.CANCEL);
+ Interval it = new Interval(clock.getUTCNow(), lastCtd);
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ //
+ // CHECK AGAIN THERE IS NO MORE INVOICES GENERATED
+ //
+ busHandler.reset();
+ clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
+ assertTrue(busHandler.isCompleted(DELAY));
+
+
+ subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+ lastCtd = subscription.getChargedThroughDate();
+ assertNotNull(lastCtd);
+ log.info("Checking CTD: " + lastCtd.toString() + "; clock is " + clock.getUTCNow().toString());
+ assertTrue(lastCtd.isBefore(clock.getUTCNow()));
+ }
+
+ @Test(enabled=false)
+ public void testHappyPath() throws AccountApiException, EntitlementUserApiException {
+ long DELAY = 5000 * 10;
+
+ Account account = accountUserApi.createAccount(getAccountData(3), null, null);
+ assertNotNull(account);
+
+ SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever");
+
+ String productName = "Shotgun";
+ BillingPeriod term = BillingPeriod.MONTHLY;
+ String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ busHandler.pushExpectedEvent(NextEvent.CREATE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ SubscriptionData subscription = (SubscriptionData) entitlementUserApi.createSubscription(bundle.getId(),
+ new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null);
+ assertNotNull(subscription);
+
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ busHandler.pushExpectedEvent(NextEvent.CHANGE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ BillingPeriod newTerm = BillingPeriod.MONTHLY;
+ String newPlanSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+ String newProductName = "Assault-Rifle";
+ subscription.changePlan(newProductName, newTerm, newPlanSetName, clock.getUTCNow());
+
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ busHandler.pushExpectedEvent(NextEvent.PHASE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ clock.setDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ }
+
+
+ protected AccountData getAccountData(final int billingDay) {
+ AccountData accountData = new AccountData() {
+ @Override
+ public String getName() {
+ return "firstName lastName";
+ }
+ @Override
+ public int getFirstNameLength() {
+ return "firstName".length();
+ }
+ @Override
+ public String getEmail() {
+ return "accountName@yahoo.com";
+ }
+ @Override
+ public String getPhone() {
+ return "4152876341";
+ }
+ @Override
+ public String getExternalKey() {
+ return "k123456";
+ }
+ @Override
+ public int getBillCycleDay() {
+ return billingDay;
+ }
+ @Override
+ public Currency getCurrency() {
+ return Currency.USD;
+ }
+ @Override
+ public String getPaymentProviderName() {
+ return "Paypal";
+ }
+
+ @Override
+ public DateTimeZone getTimeZone() {
+ return null;
+ }
+
+ @Override
+ public String getLocale() {
+ return null;
+ }
+
+ @Override
+ public String getAddress1() {
+ return null;
+ }
+
+ @Override
+ public String getAddress2() {
+ return null;
+ }
+
+ @Override
+ public String getCompanyName() {
+ return null;
+ }
+
+ @Override
+ public String getCity() {
+ return null;
+ }
+
+ @Override
+ public String getStateOrProvince() {
+ return null;
+ }
+
+ @Override
+ public String getPostalCode() {
+ return null;
+ }
+
+ @Override
+ public String getCountry() {
+ return null;
+ }
+ };
+ return accountData;
+ }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBusHandler.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBusHandler.java
new file mode 100644
index 0000000..2cfd46d
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBusHandler.java
@@ -0,0 +1,169 @@
+/*
+ * 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.beatrix.integration.inv_ent;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Joiner;
+import com.google.common.eventbus.Subscribe;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.invoice.api.InvoiceCreationNotification;
+
+public class TestBusHandler {
+
+ protected static final Logger log = LoggerFactory.getLogger(TestBusHandler.class);
+
+ private final List<NextEvent> nextExpectedEvent;
+
+ private volatile boolean completed;
+
+ public TestBusHandler() {
+ nextExpectedEvent = new Stack<NextEvent>();
+ this.completed = false;
+ }
+
+ public enum NextEvent {
+ MIGRATE_ENTITLEMENT,
+ CREATE,
+ CHANGE,
+ CANCEL,
+ UNCANCEL,
+ PAUSE,
+ RESUME,
+ PHASE,
+ INVOICE
+ }
+
+ @Subscribe
+ public void handleEntitlementEvents(SubscriptionTransition event) {
+ log.info(String.format("TestBusHandler Got subscription event %s", event.toString()));
+ switch (event.getTransitionType()) {
+ case MIGRATE_ENTITLEMENT:
+ assertEqualsNicely(NextEvent.MIGRATE_ENTITLEMENT);
+ notifyIfStackEmpty();
+ break;
+ case CREATE:
+ assertEqualsNicely(NextEvent.CREATE);
+ notifyIfStackEmpty();
+
+ break;
+ case CANCEL:
+ assertEqualsNicely(NextEvent.CANCEL);
+ notifyIfStackEmpty();
+
+ break;
+ case CHANGE:
+ assertEqualsNicely(NextEvent.CHANGE);
+ notifyIfStackEmpty();
+
+ break;
+ case PAUSE:
+ assertEqualsNicely(NextEvent.PAUSE);
+ notifyIfStackEmpty();
+
+ break;
+ case RESUME:
+ assertEqualsNicely(NextEvent.RESUME);
+ notifyIfStackEmpty();
+
+ break;
+ case UNCANCEL:
+ assertEqualsNicely(NextEvent.UNCANCEL);
+ notifyIfStackEmpty();
+ break;
+ case PHASE:
+ assertEqualsNicely(NextEvent.PHASE);
+ notifyIfStackEmpty();
+ break;
+ default:
+ throw new RuntimeException("Unexpected event type " + event.getRequestedTransitionTime());
+ }
+ }
+
+ @Subscribe
+ public void handleInvoiceEvents(InvoiceCreationNotification event) {
+ log.info(String.format("TestBusHandler Got Invoice event %s", event.toString()));
+ assertEqualsNicely(NextEvent.INVOICE);
+ notifyIfStackEmpty();
+
+ }
+
+ public void reset() {
+ nextExpectedEvent.clear();
+ completed = true;
+ }
+
+ public void pushExpectedEvent(NextEvent next) {
+ synchronized (this) {
+ nextExpectedEvent.add(next);
+ completed = false;
+ }
+ }
+
+ public boolean isCompleted(long timeout) {
+ synchronized (this) {
+ if (completed) {
+ return completed;
+ }
+ try {
+ wait(timeout);
+ } catch (Exception ignore) {
+ }
+ }
+ if (!completed) {
+ Joiner joiner = Joiner.on(" ");
+ log.error("TestBusHandler did not complete in " + timeout + " ms, remaining events are " + joiner.join(nextExpectedEvent));
+ }
+ return completed;
+ }
+
+ private void notifyIfStackEmpty() {
+ log.debug("TestBusHandler notifyIfStackEmpty ENTER");
+ synchronized (this) {
+ if (nextExpectedEvent.isEmpty()) {
+ log.debug("notifyIfStackEmpty EMPTY");
+ completed = true;
+ notify();
+ }
+ }
+ log.debug("TestBusHandler notifyIfStackEmpty EXIT");
+ }
+
+ private void assertEqualsNicely(NextEvent received) {
+
+ boolean foundIt = false;
+ Iterator<NextEvent> it = nextExpectedEvent.iterator();
+ while (it.hasNext()) {
+ NextEvent ev = it.next();
+ if (ev == received) {
+ it.remove();
+ foundIt = true;
+ break;
+ }
+ }
+ if (!foundIt) {
+ Joiner joiner = Joiner.on(" ");
+ log.error("TestBusHandler Received event " + received + "; expected " + joiner.join(nextExpectedEvent));
+ // System.exit(1);
+ }
+ }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java b/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java
index 69e6a0d..9bf7c10 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java
@@ -16,7 +16,11 @@
package com.ning.billing.beatrix.lifecycle;
-import com.google.inject.*;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
import com.ning.billing.lifecycle.KillbillService;
import com.ning.billing.lifecycle.LifecycleHandlerType;
import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
@@ -34,7 +38,7 @@ public class TestLifecycle {
private Service1 s1;
private Service2 s2;
- private Lifecycle lifecycle;
+ private DefaultLifecycle lifecycle;
public static class ServiceBase {
@@ -122,7 +126,7 @@ public class TestLifecycle {
final Injector g = Guice.createInjector(Stage.DEVELOPMENT, new TestLifecycleModule());
s1 = g.getInstance(Service1.class);
s2 = g.getInstance(Service2.class);
- lifecycle = g.getInstance(Lifecycle.class);
+ lifecycle = g.getInstance(DefaultLifecycle.class);
}
@Test(enabled=true, groups={"fast"})
@@ -148,7 +152,7 @@ public class TestLifecycle {
Assert.assertEquals(s1.getCount() + s2.getCount(), 1);
}
- public static class LifecycleNoWarn extends Lifecycle {
+ public static class LifecycleNoWarn extends DefaultLifecycle {
@Inject
public LifecycleNoWarn(Injector injector) {
@@ -163,7 +167,7 @@ public class TestLifecycle {
@Override
protected void configure() {
- bind(Lifecycle.class).to(LifecycleNoWarn.class).asEagerSingleton();
+ bind(DefaultLifecycle.class).to(LifecycleNoWarn.class).asEagerSingleton();
bind(Service1.class).asEagerSingleton();
bind(Service2.class).asEagerSingleton();
}
diff --git a/beatrix/src/test/resources/Catalog-Entitlement-Testplan.txt b/beatrix/src/test/resources/Catalog-Entitlement-Testplan.txt
new file mode 100644
index 0000000..6d5867f
--- /dev/null
+++ b/beatrix/src/test/resources/Catalog-Entitlement-Testplan.txt
@@ -0,0 +1,112 @@
+ * 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.
+
+
+NOTES
+=====
+
+Events: Create, change, cancel, migrate
+Validate: BillingEvents, SubscriptionTransition
+Rules:
+ Cancellation
+ Action Policy: When to cancel (Immediate/End-of-term)
+ Creation
+ Alignment: How to align phases in a bundle
+ Change plan behavior
+ Action Policy: When to change plan (Immediate/End-of-term)
+ Alignment: How to align phases
+ Pricelist: Which pricelist to pick when moving between plans
+ Billing alignment
+ Subscription BCD, Bundle BCD, Account BCD
+Phases - timing
+Prices - multi-currency, fixed vs recurring prices
+Pricelists - particularly pricelist change rules
+Catalog changes new subscriptions / existing subscriptions
+Price change
+
+TESTS
+=====
+
+BASEPLAN TESTS
+ * Create a single phase recurring plan
+ - check for creation event (timing?)
+ - check for no termination event
+ - check pricing (different currencies)
+ - check BDC (subscription, account, timezone)
+ * Create a single phase fixed length plan
+ - check for creation event (timing - different request dates)
+ - check for termination event (timing - different lengths?)
+ - check price (fixed vs recurring)
+ * Create a two phase event use a fixed price and a recurring price
+ - check for phase change (timing)
+ - check prices change
+ * Create a multi-phase plan
+ - check for phase events
+ - check price changes
+ * Create multiple base plans in a single bundle - should fail
+
+ * Change base plan once
+ - check plan change policy (immediate, eot)
+ - check alignments of new plan with old
+ - check move between pricelists
+ - check that phases progress successfully after change
+ - check obsolete events are removed
+ * Change base plan multiple times
+ - check that alignment occurs correctly
+ - check phases progress correctly
+ - check obsolete events are removed
+
+ * Cancellation of a single phase plan
+ - check creation and timing of termination event
+ * Cancellation of a multi-phase plan
+ - check creation of termination event
+ - check removal of events beyond termination event
+ * Change a cancelled base plan - should fail
+
+ * Migration to a single phase plan
+ - check migration event occurs when it should
+ * Migration to a multi-phase plan
+ - check migration event occurs when it should
+ - check migration into different phase
+ - check alignment of phases can be correctly controlled
+ * Migration to a fixed duration plan
+ - check migration event occurs when it should
+ - check termination event occurs when it should
+
+
+
+STANDALONE TEST
+ * Create multiple plans in a single bundle
+ - check plans can be created
+ - check cannot add a base plan
+ - check BCD at subscription bundle level
+
+
+PRICE CHANGE TEST
+ * Price change on a single phase base plan
+ - check new subscriptions get price after effective date
+ - check changed subscriptions get price after effective date
+ - check existing subscriptions ONLY get it after ESED
+ - check that if ESED is missing existing subs are grandfathered for ever
+ * Price change on a multi-phase subscription
+ - check price change applies correctly to correct phases
+ * Multiple price changes
+ - check multiple price changes with overlapping dates
+
+
+ADD-ON TESTS
+ * Add-on creation alignment
+ * Add-on cancel with base plan
+
+
\ No newline at end of file
beatrix/src/test/resources/catalogSample.xml 641(+641 -0)
diff --git a/beatrix/src/test/resources/catalogSample.xml b/beatrix/src/test/resources/catalogSample.xml
new file mode 100644
index 0000000..325f731
--- /dev/null
+++ b/beatrix/src/test/resources/catalogSample.xml
@@ -0,0 +1,641 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+ ~ 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.
+ -->
+
+<!--
+Use cases covered so far:
+ Tiered Product (Pistol/Shotgun/Assault-Rifle)
+ Multiple changeEvent plan policies
+ Multiple PlanAlignment (see below, trial add-on alignments and rescue discount package)
+ Product transition rules
+ Add on (Scopes, Hoster)
+ Multi-pack addon (Extra-Ammo)
+ Addon Trial aligned to base plan (holster-monthly-regular)
+ Addon Trial aligned to creation (holster-monthly-special)
+ Rescue discount package (assault-rifle-annual-rescue)
+ Plan phase with a reccurring and a one off (refurbish-maintenance)
+ Phan with more than 2 phase (gunclub discount plans)
+
+Use Cases to do:
+ Tiered Add On
+ Riskfree period
+
+
+
+ -->
+<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+
+ <effectiveDate>2011-01-01T00:00:00+00:00</effectiveDate>
+ <catalogName>Firearms</catalogName>
+
+ <currencies>
+ <currency>USD</currency>
+ <currency>EUR</currency>
+ <currency>GBP</currency>
+ </currencies>
+
+ <products>
+ <product name="Pistol">
+ <category>BASE</category>
+ <available>
+ <addonProduct>Telescopic-Scope</addonProduct>
+ <addonProduct>Laser-Scope</addonProduct>
+ </available>
+ </product>
+ <product name="Shotgun">
+ <category>BASE</category>
+ </product>
+ <product name="Assault-Rifle">
+ <category>BASE</category>
+ <included>
+ <addonProduct>Telescopic-Scope</addonProduct>
+ </included>
+ <available>
+ <addonProduct>Laser-Scope</addonProduct>
+ </available>
+ </product>
+ <product name="Telescopic-Scope">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Laser-Scope">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Holster">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Extra-Ammo">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Refurbish-Maintenance">
+ <category>ADD_ON</category>
+ </product>
+ </products>
+
+ <rules>
+ <changePolicy>
+ <changePolicyCase>
+ <phaseType>TRIAL</phaseType>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <toProduct>Pistol</toProduct>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <toPriceList>rescue</toPriceList>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromProduct>Pistol</fromProduct>
+ <toProduct>Shotgun</toProduct>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromProduct>Assault-Rifle</fromProduct>
+ <toProduct>Shotgun</toProduct>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromBillingPeriod>MONTHLY</fromBillingPeriod>
+ <toProduct>Assault-Rifle</toProduct>
+ <toBillingPeriod>MONTHLY</toBillingPeriod>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <toProduct>Assault-Rifle</toProduct>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromBillingPeriod>MONTHLY</fromBillingPeriod>
+ <toBillingPeriod>ANNUAL</toBillingPeriod>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromBillingPeriod>ANNUAL</fromBillingPeriod>
+ <toBillingPeriod>MONTHLY</toBillingPeriod>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ </changePolicy>
+ <changeAlignment>
+ <changeAlignmentCase>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </changeAlignmentCase>
+ <changeAlignmentCase>
+ <toPriceList>rescue</toPriceList>
+ <alignment>CHANGE_OF_PLAN</alignment>
+ </changeAlignmentCase>
+ <changeAlignmentCase>
+ <fromPriceList>rescue</fromPriceList>
+ <toPriceList>rescue</toPriceList>
+ <alignment>CHANGE_OF_PRICELIST</alignment>
+ </changeAlignmentCase>
+ </changeAlignment>
+ <cancelPolicy>
+ <cancelPolicyCase>
+ <policy>END_OF_TERM</policy>
+ </cancelPolicyCase>
+ <cancelPolicyCase>
+ <phaseType>TRIAL</phaseType>
+ <policy>IMMEDIATE</policy>
+ </cancelPolicyCase>
+ </cancelPolicy>
+ <createAlignment>
+ <createAlignmentCase>
+ <alignment>START_OF_BUNDLE</alignment>
+ </createAlignmentCase>
+ </createAlignment>
+ <billingAlignment>
+ <billingAlignmentCase>
+ <productCategory>ADD_ON</productCategory>
+ <alignment>BUNDLE</alignment>
+ </billingAlignmentCase>
+ <billingAlignmentCase>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <alignment>SUBSCRIPTION</alignment>
+ </billingAlignmentCase>
+ <billingAlignmentCase>
+ <alignment>ACCOUNT</alignment>
+ </billingAlignmentCase>
+ </billingAlignment>
+ <priceList>
+ <priceListCase>
+ <fromPriceList>rescue</fromPriceList>
+ <toPriceList>DEFAULT</toPriceList>
+ </priceListCase>
+ </priceList>
+ </rules>
+
+ <plans>
+ <plan name="pistol-monthly-no-trial">
+ <product>Pistol</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>GBP</currency><value>29.95</value></price>
+ <price><currency>EUR</currency><value>29.95</value></price>
+ <price><currency>USD</currency><value>29.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-monthly">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ <!-- no price implies $0 -->
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>GBP</currency><value>29.95</value></price>
+ <price><currency>EUR</currency><value>29.95</value></price>
+ <price><currency>USD</currency><value>29.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-monthly">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ <!-- no price implies $0 -->
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ <number>-1</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>249.95</value></price>
+ <price><currency>EUR</currency><value>149.95</value></price>
+ <price><currency>GBP</currency><value>169.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-monthly">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ <!-- no price implies $0 -->
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>599.95</value></price>
+ <price><currency>EUR</currency><value>349.95</value></price>
+ <price><currency>GBP</currency><value>399.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-annual">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-annual">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>2399.95</value></price>
+ <price><currency>EUR</currency><value>1499.95</value></price>
+ <price><currency>GBP</currency><value>1699.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>5999.95</value></price>
+ <price><currency>EUR</currency><value>3499.95</value></price>
+ <price><currency>GBP</currency><value>3999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-annual-gunclub-discount">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>9.95</value></price>
+ <price><currency>EUR</currency><value>9.95</value></price>
+ <price><currency>GBP</currency><value>9.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-annual-gunclub-discount">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>19.95</value></price>
+ <price><currency>EUR</currency><value>49.95</value></price>
+ <price><currency>GBP</currency><value>69.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>2399.95</value></price>
+ <price><currency>EUR</currency><value>1499.95</value></price>
+ <price><currency>GBP</currency><value>1699.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual-gunclub-discount">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>99.95</value></price>
+ <price><currency>EUR</currency><value>99.95</value></price>
+ <price><currency>GBP</currency><value>99.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>5999.95</value></price>
+ <price><currency>EUR</currency><value>3499.95</value></price>
+ <price><currency>GBP</currency><value>3999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="laser-scope-monthly">
+ <product>Laser-Scope</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>1999.95</value></price>
+ <price><currency>EUR</currency><value>1499.95</value></price>
+ <price><currency>GBP</currency><value>1999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="telescopic-scope-monthly">
+ <product>Telescopic-Scope</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>999.95</value></price>
+ <price><currency>EUR</currency><value>499.95</value></price>
+ <price><currency>GBP</currency><value>999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="extra-ammo-monthly">
+ <product>Extra-Ammo</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>999.95</value></price>
+ <price><currency>EUR</currency><value>499.95</value></price>
+ <price><currency>GBP</currency><value>999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ <plansAllowedInBundle>-1</plansAllowedInBundle> <!-- arbitrary number of these (multipack) -->
+ </plan>
+ <plan name="holster-monthly-regular">
+ <product>Holster</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="holster-monthly-special">
+ <product>Holster</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual-rescue">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>YEARS</unit>
+ <number>1</number>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>5999.95</value></price>
+ <price><currency>EUR</currency><value>3499.95</value></price>
+ <price><currency>GBP</currency><value>3999.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>5999.95</value></price>
+ <price><currency>EUR</currency><value>3499.95</value></price>
+ <price><currency>GBP</currency><value>3999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="refurbish-maintenance">
+ <product>Refurbish-Maintenance</product>
+ <finalPhase type="FIXEDTERM">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>12</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ <fixedPrice>
+ <price><currency>USD</currency><value>599.95</value></price>
+ <price><currency>EUR</currency><value>599.95</value></price>
+ <price><currency>GBP</currency><value>599.95</value></price>
+ </fixedPrice>
+ </finalPhase>
+ </plan>
+ </plans>
+ <priceLists>
+ <defaultPriceList name="DEFAULT">
+ <plans>
+ <plan>pistol-monthly</plan>
+ <plan>shotgun-monthly</plan>
+ <plan>assault-rifle-monthly</plan>
+ <plan>pistol-annual</plan>
+ <plan>shotgun-annual</plan>
+ <plan>assault-rifle-annual</plan>
+ <plan>laser-scope-monthly</plan>
+ <plan>telescopic-scope-monthly</plan>
+ <plan>extra-ammo-monthly</plan>
+ <plan>holster-monthly-regular</plan>
+ <plan>refurbish-maintenance</plan>
+ </plans>
+ </defaultPriceList>
+ <childPriceList name="gunclubDiscount">
+ <plans>
+ <plan>pistol-monthly</plan>
+ <plan>shotgun-monthly</plan>
+ <plan>assault-rifle-monthly</plan>
+ <plan>pistol-annual-gunclub-discount</plan>
+ <plan>shotgun-annual-gunclub-discount</plan>
+ <plan>assault-rifle-annual-gunclub-discount</plan>
+ <plan>holster-monthly-special</plan>
+ </plans>
+ </childPriceList>
+ <childPriceList name="rescue">
+ <plans>
+ <plan>assault-rifle-annual-rescue</plan>
+ </plans>
+ </childPriceList>
+ </priceLists>
+
+</catalog>
beatrix/src/test/resources/log4j.xml 4(+4 -0)
diff --git a/beatrix/src/test/resources/log4j.xml b/beatrix/src/test/resources/log4j.xml
index 75abc76..ac530a1 100644
--- a/beatrix/src/test/resources/log4j.xml
+++ b/beatrix/src/test/resources/log4j.xml
@@ -29,6 +29,10 @@
<level value="info"/>
</logger>
+ <logger name="com.ning.billing.util.notificationq">
+ <level value="info"/>
+ </logger>
+
<root>
<priority value="info"/>
<appender-ref ref="stdout"/>
diff --git a/beatrix/src/test/resources/resource.properties b/beatrix/src/test/resources/resource.properties
new file mode 100644
index 0000000..d63334b
--- /dev/null
+++ b/beatrix/src/test/resources/resource.properties
@@ -0,0 +1,7 @@
+killbill.catalog.uri=file:src/test/resources/catalogSample.xml
+killbill.entitlement.dao.claim.time=60000
+killbill.entitlement.dao.ready.max=1
+killbill.entitlement.engine.notifications.sleep=500
+user.timezone=UTC
+
+
catalog/pom.xml 2(+1 -1)
diff --git a/catalog/pom.xml b/catalog/pom.xml
index 1324140..635fac3 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.3-SNAPSHOT</version>
+ <version>0.1.5-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-catalog</artifactId>
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
index 7f88f07..b824617 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
@@ -28,6 +28,7 @@ import com.ning.billing.lifecycle.LifecycleHandlerType;
import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
public class DefaultCatalogService implements KillbillService, Provider<Catalog>, CatalogService {
+ private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultCatalogService.class);
private static final String CATALOG_SERVICE_NAME = "catalog-service";
@@ -54,7 +55,7 @@ public class DefaultCatalogService implements KillbillService, Provider<Catalog>
System.out.println("Really really::" + config.getCatalogURI());
String url = config.getCatalogURI();
catalog = loader.load(url);
-
+
//catalog = XMLLoader.getObjectFromProperty(config.getCatalogURI(), Catalog.class);
isInitialized = true;
} catch (Exception e) {
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
index be3ea55..878497b 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
@@ -20,6 +20,7 @@ import com.ning.billing.catalog.api.Duration;
import com.ning.billing.catalog.api.TimeUnit;
import com.ning.billing.util.config.ValidatingConfig;
import com.ning.billing.util.config.ValidationErrors;
+import org.joda.time.DateTime;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@@ -49,7 +50,25 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
return number;
}
- @Override
+ @Override
+ public DateTime addToDateTime(DateTime dateTime) {
+ if (number < 0) {return null;}
+
+ switch (unit) {
+ case DAYS:
+ return dateTime.plusDays(number);
+ case MONTHS:
+ return dateTime.plusMonths(number);
+ case YEARS:
+ return dateTime.plusYears(number);
+ case UNLIMITED:
+ return dateTime.plusYears(100);
+ }
+
+ return null;
+ }
+
+ @Override
public ValidationErrors validate(StandaloneCatalog catalog, ValidationErrors errors) {
//TODO MDW - Validation TimeUnit UNLIMITED iff number == -1
return errors;
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
index 6990d3c..1c505f9 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
@@ -42,7 +42,7 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
/* (non-Javadoc)
- * @see com.ning.billing.catalog.IInternationalPrice#getPrices()
+ * @see com.ning.billing.catalog.InternationalPrice#getPrices()
*/
@Override
public Price[] getPrices() {
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
index 4afe278..6d00162 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
@@ -17,7 +17,13 @@
package com.ning.billing.catalog;
import com.ning.billing.ErrorCode;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.util.config.ValidatingConfig;
import com.ning.billing.util.config.ValidationError;
import com.ning.billing.util.config.ValidationErrors;
@@ -38,7 +44,7 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
private DefaultDuration duration;
@XmlElement(required=true)
- private BillingPeriod billingPeriod = BillingPeriod.NO_BILLING_PERIOD;
+ private BillingPeriod billingPeriod;
@XmlElement(required=false)
private DefaultInternationalPrice recurringPrice;
@@ -127,30 +133,31 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
public ValidationErrors validate(StandaloneCatalog catalog, ValidationErrors errors) {
//Validation: check for nulls
if(billingPeriod == null) {
- errors.add(new ValidationError(String.format("Phase %s of plan %s has a reccurring price but no billing period", type.toString(), plan.getName()),
+ errors.add(new ValidationError(String.format("Phase %s of plan %s has a recurring price but no billing period", type.toString(), plan.getName()),
catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
}
-
+
//Validation: if there is a recurring price there must be a billing period
- if(recurringPrice != null && (billingPeriod == null || billingPeriod ==BillingPeriod.NO_BILLING_PERIOD)) {
- errors.add(new ValidationError(String.format("Phase %s of plan %s has a reccurring price but no billing period", type.toString(), plan.getName()),
+ if((recurringPrice != null) && (billingPeriod == null || billingPeriod == BillingPeriod.NO_BILLING_PERIOD)) {
+ errors.add(new ValidationError(String.format("Phase %s of plan %s has a recurring price but no billing period", type.toString(), plan.getName()),
catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
}
- //Validation: if there is no reccuring price there should be no billing period
- if(recurringPrice == null && billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
- errors.add(new ValidationError(String.format("Phase %s of plan %s has no reccurring price but does have a billing period. The billing period should be set to '%s'",
+
+ //Validation: if there is no recurring price there should be no billing period
+ if((recurringPrice == null) && billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
+ errors.add(new ValidationError(String.format("Phase %s of plan %s has no recurring price but does have a billing period. The billing period should be set to '%s'",
type.toString(), plan.getName(), BillingPeriod.NO_BILLING_PERIOD),
catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
}
- //Validation: there must be at least one of reccuringPrice or fixedPrice
- if(recurringPrice == null && fixedPrice == null) {
- errors.add(new ValidationError(String.format("Phase %s of plan %s has neither a reccurring price or a fixed price.",
+ //Validation: there must be at least one of recurringPrice or fixedPrice
+ if((recurringPrice == null) && fixedPrice == null) {
+ errors.add(new ValidationError(String.format("Phase %s of plan %s has neither a recurring price or a fixed price.",
type.toString(), plan.getName()),
catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
}
- return errors;
-
+ //TODO : if there BP is set to NO_BILLING_PERIOD there must be a recurring price
+ return errors;
}
@Override
@@ -164,7 +171,7 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
return this;
}
- protected DefaultPlanPhase setReccuringPrice(DefaultInternationalPrice price) {
+ protected DefaultPlanPhase setRecurringPrice(DefaultInternationalPrice price) {
this.recurringPrice = price;
return this;
}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPrice.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPrice.java
index d9d26a6..9b5f00b 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPrice.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPrice.java
@@ -35,6 +35,16 @@ public class DefaultPrice extends ValidatingConfig<StandaloneCatalog> implements
@XmlElement(required=true,nillable=true)
private BigDecimal value;
+ public DefaultPrice() {
+ // for serialization support
+ }
+
+ public DefaultPrice(final BigDecimal value, final Currency currency) {
+ // for sanity support
+ this.value = value;
+ this.currency = currency;
+ }
+
/* (non-Javadoc)
* @see com.ning.billing.catalog.IPrice#getCurrency()
*/
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
index 48de5d4..aba447d 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
@@ -23,7 +23,13 @@ import com.ning.billing.util.config.ValidatingConfig;
import com.ning.billing.util.config.ValidationError;
import com.ning.billing.util.config.ValidationErrors;
-import javax.xml.bind.annotation.*;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlID;
+import javax.xml.bind.annotation.XmlIDREF;
@XmlAccessorType(XmlAccessType.NONE)
public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implements PriceList {
@@ -36,8 +42,7 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
private Boolean retired = false;
@XmlElementWrapper(name="plans", required=true)
- @XmlElement(name="plan", required=true)
- @XmlIDREF
+ @XmlIDREF @XmlElement(name="plan", required=true)
private DefaultPlan[] plans;
public DefaultPriceList(){}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
index bf8cfd4..ff3868c 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
@@ -21,15 +21,20 @@ import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.util.config.ValidatingConfig;
import com.ning.billing.util.config.ValidationErrors;
-import javax.xml.bind.annotation.*;
-
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlID;
+import javax.xml.bind.annotation.XmlIDREF;
import java.net.URI;
@XmlAccessorType(XmlAccessType.NONE)
public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implements Product {
private static final DefaultProduct[] EMPTY_PRODUCT_LIST = new DefaultProduct[0];
- @XmlAttribute (required=true)
+ @XmlAttribute(required=true)
@XmlID
private String name;
@@ -42,7 +47,7 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
@XmlElementWrapper(name="included", required=false)
@XmlIDREF @XmlElement(name="addonProduct", required=true)
private DefaultProduct[] included = EMPTY_PRODUCT_LIST;
-
+
@XmlElementWrapper(name="available", required=false)
@XmlIDREF @XmlElement(name="addonProduct", required=true)
private DefaultProduct[] available = EMPTY_PRODUCT_LIST;
diff --git a/catalog/src/main/java/com/ning/billing/catalog/rules/CaseChange.java b/catalog/src/main/java/com/ning/billing/catalog/rules/CaseChange.java
index f101ef1..963dc2f 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/rules/CaseChange.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/rules/CaseChange.java
@@ -19,7 +19,12 @@ package com.ning.billing.catalog.rules;
import com.ning.billing.catalog.DefaultPriceList;
import com.ning.billing.catalog.DefaultProduct;
import com.ning.billing.catalog.StandaloneCatalog;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.util.config.ValidatingConfig;
import com.ning.billing.util.config.ValidationErrors;
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java b/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
index 765d4bd..5fc51b8 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
@@ -19,7 +19,11 @@ package com.ning.billing.catalog;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.catalog.rules.*;
+import com.ning.billing.catalog.rules.CaseCancelPolicy;
+import com.ning.billing.catalog.rules.CaseChangePlanAlignment;
+import com.ning.billing.catalog.rules.CaseChangePlanPolicy;
+import com.ning.billing.catalog.rules.CaseCreateAlignment;
+import com.ning.billing.catalog.rules.PlanRules;
import java.util.Date;
@@ -60,7 +64,7 @@ public class MockCatalog extends StandaloneCatalog {
DefaultProduct[] products = getCurrentProducts();
DefaultPlan[] plans = new DefaultPlan[products.length];
for(int i = 0; i < products.length; i++) {
- DefaultPlanPhase phase = new DefaultPlanPhase().setPhaseType(PhaseType.EVERGREEN).setBillingPeriod(BillingPeriod.MONTHLY).setReccuringPrice(new DefaultInternationalPrice());
+ DefaultPlanPhase phase = new DefaultPlanPhase().setPhaseType(PhaseType.EVERGREEN).setBillingPeriod(BillingPeriod.MONTHLY).setRecurringPrice(new DefaultInternationalPrice());
plans[i] = new MockPlan().setName(products[i].getName().toLowerCase() + "-plan").setProduct(products[i]).setFinalPhase(phase);
}
setPlans(plans);
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java b/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java
index 773c58b..cc3a679 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java
@@ -19,17 +19,16 @@ package com.ning.billing.catalog;
import com.ning.billing.catalog.api.Currency;
import java.math.BigDecimal;
-import java.util.Date;
public class MockInternationalPrice extends DefaultInternationalPrice {
-
- MockInternationalPrice() {
+
+ public MockInternationalPrice() {
setPrices(new DefaultPrice[] {
- new DefaultPrice().setCurrency(Currency.USD).setValue(new BigDecimal(1))
+ new DefaultPrice().setCurrency(Currency.USD).setValue(new BigDecimal(1))
});
}
-
- MockInternationalPrice(DefaultPrice... price) {
+
+ public MockInternationalPrice(DefaultPrice... price) {
setPrices(price);
}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java b/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
index b9e9afe..53c73fe 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
@@ -34,6 +34,14 @@ public class MockPlan extends DefaultPlan {
setPlansAllowedInBundle(1);
}
+ public MockPlan(String planName) {
+ setName(planName);
+ setProduct(new MockProduct());
+ setFinalPhase(new MockPlanPhase(this));
+ setInitialPhases(null);
+ setPlansAllowedInBundle(1);
+ }
+
public MockPlan(MockPlanPhase mockPlanPhase) {
setName("test-plan");
setProduct(new MockProduct());
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java b/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
index fb244eb..d4ae5ff 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
@@ -18,8 +18,11 @@ package com.ning.billing.catalog;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.TimeUnit;
+import javax.annotation.Nullable;
+
public class MockPlanPhase extends DefaultPlanPhase {
public MockPlanPhase(
@@ -31,16 +34,34 @@ public class MockPlanPhase extends DefaultPlanPhase {
setBillingPeriod(billingPeriod);
setPhaseType(type);
setDuration(duration);
- setReccuringPrice(recurringPrice);
+ setRecurringPrice(recurringPrice);
setFixedPrice(fixedPrice);
}
public MockPlanPhase() {
- setBillingPeriod(BillingPeriod.MONTHLY);
- setPhaseType(PhaseType.EVERGREEN);
+ this(new MockInternationalPrice(), null);
+ }
+
+ public MockPlanPhase(@Nullable MockInternationalPrice recurringPrice,
+ @Nullable MockInternationalPrice fixedPrice) {
+ this(recurringPrice, fixedPrice, BillingPeriod.MONTHLY);
+ }
+
+ public MockPlanPhase(@Nullable MockInternationalPrice recurringPrice,
+ @Nullable MockInternationalPrice fixedPrice,
+ BillingPeriod billingPeriod) {
+ this(recurringPrice, fixedPrice, billingPeriod, PhaseType.EVERGREEN);
+ }
+
+ public MockPlanPhase(@Nullable MockInternationalPrice recurringPrice,
+ @Nullable MockInternationalPrice fixedPrice,
+ BillingPeriod billingPeriod,
+ PhaseType phaseType) {
+ setBillingPeriod(billingPeriod);
+ setPhaseType(phaseType);
setDuration(new DefaultDuration().setNumber(-1).setUnit(TimeUnit.UNLIMITED));
- setReccuringPrice(new MockInternationalPrice());
- setFixedPrice(null);
+ setRecurringPrice(recurringPrice);
+ setFixedPrice(fixedPrice);
setPlan(new MockPlan(this));
}
@@ -48,10 +69,17 @@ public class MockPlanPhase extends DefaultPlanPhase {
setBillingPeriod(BillingPeriod.MONTHLY);
setPhaseType(PhaseType.EVERGREEN);
setDuration(new DefaultDuration().setNumber(-1).setUnit(TimeUnit.UNLIMITED));
- setReccuringPrice(new MockInternationalPrice());
+ setRecurringPrice(new MockInternationalPrice());
setFixedPrice(null);
setPlan(mockPlan);
}
-
+ public MockPlanPhase(Plan plan, PhaseType phaseType) {
+ setBillingPeriod(BillingPeriod.MONTHLY);
+ setPhaseType(phaseType);
+ setDuration(new DefaultDuration().setNumber(-1).setUnit(TimeUnit.UNLIMITED));
+ setRecurringPrice(new MockInternationalPrice());
+ setFixedPrice(null);
+ setPlan(plan);
+ }
}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCaseChange.java b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCaseChange.java
index f684503..ef1eb17 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCaseChange.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCaseChange.java
@@ -21,9 +21,14 @@ import com.ning.billing.catalog.DefaultPriceList;
import com.ning.billing.catalog.DefaultProduct;
import com.ning.billing.catalog.MockCatalog;
import com.ning.billing.catalog.StandaloneCatalog;
-import com.ning.billing.catalog.api.*;
-import com.ning.billing.catalog.rules.TestCase.CaseResult;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -39,10 +44,10 @@ public class TestCaseChange {
private Result result;
public CaseChangeResult(DefaultProduct from, DefaultProduct to,
- ProductCategory fromProductCategory, ProductCategory toProductCategory,
- BillingPeriod fromBP, BillingPeriod toBP,
+ ProductCategory fromProductCategory, ProductCategory toProductCategory,
+ BillingPeriod fromBP, BillingPeriod toBP,
DefaultPriceList fromPriceList, DefaultPriceList toPriceList,
- PhaseType fromType,
+ PhaseType fromType,
Result result) {
setFromProduct(from);
setToProduct(to);
@@ -63,7 +68,7 @@ public class TestCaseChange {
}
}
@Test(enabled=true)
- public void testBasic() throws CatalogApiException{
+ public void testBasic() throws CatalogApiException {
MockCatalog cat = new MockCatalog();
DefaultProduct product1 = cat.getCurrentProducts()[0];
@@ -1043,8 +1048,8 @@ public class TestCaseChange {
String fromPriceListName, String toPriceListName,
PhaseType phaseType, StandaloneCatalog cat){
try{
- cr.getResult(new PlanPhaseSpecifier(fromProductName, fromProductCategory, fromBp, fromPriceListName, phaseType),
- new PlanSpecifier(toProductName, toProductCategory, toBp, toPriceListName),cat);
+ cr.getResult(new PlanPhaseSpecifier(fromProductName, fromProductCategory, fromBp, fromPriceListName, phaseType),
+ new PlanSpecifier(toProductName, toProductCategory, toBp, toPriceListName),cat);
Assert.fail("Expecting an exception");
} catch (CatalogApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.CAT_PRICE_LIST_NOT_FOUND.getCode());
diff --git a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCasePhase.java b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCasePhase.java
index 02f7ab5..1b28da7 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCasePhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCasePhase.java
@@ -21,7 +21,11 @@ import com.ning.billing.catalog.DefaultPriceList;
import com.ning.billing.catalog.DefaultProduct;
import com.ning.billing.catalog.MockCatalog;
import com.ning.billing.catalog.StandaloneCatalog;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -195,7 +199,7 @@ public class TestCasePhase {
}
@Test(enabled=true)
- public void testOrder() throws CatalogApiException{
+ public void testOrder() throws CatalogApiException {
MockCatalog cat = new MockCatalog();
DefaultProduct product = cat.getCurrentProducts()[0];
diff --git a/catalog/src/test/java/com/ning/billing/catalog/rules/TestPlanRules.java b/catalog/src/test/java/com/ning/billing/catalog/rules/TestPlanRules.java
index 7ac9cc0..e9617ea 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/rules/TestPlanRules.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/rules/TestPlanRules.java
@@ -19,8 +19,17 @@ package com.ning.billing.catalog.rules;
import com.ning.billing.catalog.DefaultPriceList;
import com.ning.billing.catalog.DefaultProduct;
import com.ning.billing.catalog.MockCatalog;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.ActionPolicy;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.IllegalPlanChange;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanAlignmentChange;
+import com.ning.billing.catalog.api.PlanChangeResult;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java b/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
index d778c4b..9b07bfe 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
@@ -32,17 +32,17 @@ public class TestPlanPhase {
public void testValidation() {
log.info("Testing Plan Phase Validation");
- DefaultPlanPhase pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.MONTHLY).setReccuringPrice(null).setFixedPrice(new DefaultInternationalPrice());
+ DefaultPlanPhase pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.MONTHLY).setRecurringPrice(null).setFixedPrice(new DefaultInternationalPrice());
ValidationErrors errors = pp.validate(new MockCatalog(), new ValidationErrors());
errors.log(log);
Assert.assertEquals(errors.size(), 1);
- pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD).setReccuringPrice(new MockInternationalPrice());
+ pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD).setRecurringPrice(new MockInternationalPrice());
errors = pp.validate(new MockCatalog(), new ValidationErrors());
errors.log(log);
Assert.assertEquals(errors.size(), 1);
- pp = new MockPlanPhase().setReccuringPrice(null).setFixedPrice(null).setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD);
+ pp = new MockPlanPhase().setRecurringPrice(null).setFixedPrice(null).setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD);
errors = pp.validate(new MockCatalog(), new ValidationErrors());
errors.log(log);
Assert.assertEquals(errors.size(), 1);
entitlement/pom.xml 8(+7 -1)
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 4ed28e9..83388f8 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.3-SNAPSHOT</version>
+ <version>0.1.5-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-entitlement</artifactId>
@@ -48,6 +48,12 @@
<dependency>
<groupId>com.ning.billing</groupId>
<artifactId>killbill-catalog</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-catalog</artifactId>
<scope>test</scope>
</dependency>
<dependency>
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java
index a228a37..a5199a9 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java
@@ -16,16 +16,6 @@ w * Copyright 2010-2011 Ning, Inc.
package com.ning.billing.entitlement.api.billing;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.UUID;
-
-import org.joda.time.DateTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import com.google.inject.Inject;
import com.ning.billing.ErrorCode;
import com.ning.billing.account.api.Account;
@@ -42,18 +32,32 @@ import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
import com.ning.billing.entitlement.api.user.SubscriptionData;
import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
import com.ning.billing.entitlement.api.user.SubscriptionTransition;
import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.engine.dao.SubscriptionSqlDao;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
- private Logger log = LoggerFactory.getLogger(DefaultEntitlementBillingApi.class);
-
+ private static final Logger log = LoggerFactory.getLogger(DefaultEntitlementBillingApi.class);
+
private final EntitlementDao dao;
private final AccountUserApi accountApi;
private final CatalogService catalogService;
@Inject
- public DefaultEntitlementBillingApi(EntitlementDao dao, AccountUserApi accountApi, CatalogService catalogService) {
+ public DefaultEntitlementBillingApi(final EntitlementDao dao, final AccountUserApi accountApi, final CatalogService catalogService) {
super();
this.dao = dao;
this.accountApi = accountApi;
@@ -62,48 +66,60 @@ public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
@Override
public SortedSet<BillingEvent> getBillingEventsForAccount(
- UUID accountId) {
-
+ final UUID accountId) {
+
List<SubscriptionBundle> bundles = dao.getSubscriptionBundleForAccount(accountId);
List<Subscription> subscriptions = new ArrayList<Subscription>();
- for (SubscriptionBundle bundle: bundles) {
+ for (final SubscriptionBundle bundle: bundles) {
subscriptions.addAll(dao.getSubscriptions(bundle.getId()));
}
- SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
- for (Subscription subscription: subscriptions) {
- for (SubscriptionTransition transition : subscription.getAllTransitions()) {
+ SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
+ for (final Subscription subscription: subscriptions) {
+ for (final SubscriptionTransition transition : subscription.getAllTransitions()) {
try {
- result.add(new DefaultBillingEvent(transition, subscription, calculateBCD(transition, accountId)));
+ BillingEvent event = new DefaultBillingEvent(transition, subscription, calculateBCD(transition, accountId));
+ result.add(event);
} catch (CatalogApiException e) {
- log.error("Failing to identify catalog components while creating BillingEvent from transition: " +
+ log.error("Failing to identify catalog components while creating BillingEvent from transition: " +
transition.getId().toString(), e);
+ } catch (Exception e) {
+ log.warn("Failed while getting BillingEvent", e);
}
}
}
return result;
}
-
- private int calculateBCD(SubscriptionTransition transition, UUID accountId) throws CatalogApiException {
+
+ @Override
+ public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+ return dao.getAccountIdFromSubscriptionId(subscriptionId);
+ }
+
+ private int calculateBCD(final SubscriptionTransition transition, final UUID accountId) throws CatalogApiException {
Catalog catalog = catalogService.getFullCatalog();
- Plan plan = transition.getNextPlan();
+ Plan plan = (transition.getTransitionType() != SubscriptionTransitionType.CANCEL) ?
+ transition.getNextPlan() : transition.getPreviousPlan();
Product product = plan.getProduct();
- PlanPhase phase = transition.getNextPhase();
-
+ PlanPhase phase = (transition.getTransitionType() != SubscriptionTransitionType.CANCEL) ?
+ transition.getNextPhase() : transition.getPreviousPhase();
+
BillingAlignment alignment = catalog.billingAlignment(
- new PlanPhaseSpecifier(product.getName(),
- product.getCategory(),
- phase.getBillingPeriod(),
- transition.getNextPriceList(),
- phase.getPhaseType()),
+ new PlanPhaseSpecifier(product.getName(),
+ product.getCategory(),
+ phase.getBillingPeriod(),
+ transition.getNextPriceList(),
+ phase.getPhaseType()),
transition.getRequestedTransitionTime());
int result = 0;
- Account account = accountApi.getAccountById(accountId);
+
+ Account account = accountApi.getAccountById(accountId);
+
switch (alignment) {
- case ACCOUNT :
+ case ACCOUNT :
result = account.getBillCycleDay();
break;
- case BUNDLE :
+ case BUNDLE :
SubscriptionBundle bundle = dao.getSubscriptionBundleFromId(transition.getBundleId());
//TODO result = bundle.getStartDate().toDateTime(account.getTimeZone()).getDayOfMonth();
result = bundle.getStartDate().getDayOfMonth();
@@ -118,20 +134,32 @@ public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
throw new CatalogApiException(ErrorCode.CAT_INVALID_BILLING_ALIGNMENT, alignment.toString());
}
return result;
-
+
}
-
@Override
- public void setChargedThroughDate(UUID subscriptionId, DateTime ctd) {
+ public void setChargedThroughDate(final UUID subscriptionId, final DateTime ctd) {
SubscriptionData subscription = (SubscriptionData) dao.getSubscriptionFromId(subscriptionId);
- if (subscription == null) {
- new EntitlementBillingApiException(String.format("Unknown subscription %s", subscriptionId));
- }
SubscriptionBuilder builder = new SubscriptionBuilder(subscription)
.setChargedThroughDate(ctd)
.setPaidThroughDate(subscription.getPaidThroughDate());
+
dao.updateSubscription(new SubscriptionData(builder));
}
+
+ @Override
+ public void setChargedThroughDateFromTransaction(final Transmogrifier transactionalDao, final UUID subscriptionId, final DateTime ctd) {
+ SubscriptionSqlDao subscriptionSqlDao = transactionalDao.become(SubscriptionSqlDao.class);
+ SubscriptionData subscription = (SubscriptionData) subscriptionSqlDao.getSubscriptionFromId(subscriptionId.toString());
+
+ if (subscription == null) {
+ log.warn("Subscription not found when setting CTD.");
+ } else {
+ Date paidThroughDate = (subscription.getPaidThroughDate() == null) ? null : subscription.getPaidThroughDate().toDate();
+
+ subscriptionSqlDao.updateSubscription(subscriptionId.toString(), subscription.getActiveVersion(),
+ ctd.toDate(), paidThroughDate);
+ }
+ }
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
index d8a0100..05a063d 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java
@@ -111,7 +111,7 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
// Not implemented yet
break;
case STANDALONE:
- // Not implemented yet
+ data = createStandaloneSubscriptionMigrationData(bundleData.getId(), curSub.getCategory(), curSub.getSubscriptionCases(), now);
break;
default:
throw new EntitlementMigrationApiException(String.format("Unkown product type ", curSub.getCategory()));
@@ -144,6 +144,22 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
return new SubscriptionMigrationData(subscriptionData, toEvents(subscriptionData, now, events));
}
+ private SubscriptionMigrationData createStandaloneSubscriptionMigrationData(UUID bundleId, ProductCategory productCategory,
+ EntitlementSubscriptionMigrationCase [] input, DateTime now)
+ throws EntitlementMigrationApiException {
+ TimedMigration [] events = migrationAligner.getEventsMigration(input, now);
+ DateTime migrationStartDate= events[0].getEventTime();
+ List<EntitlementEvent> emptyEvents = Collections.emptyList();
+ SubscriptionData subscriptionData = factory.createSubscription(new SubscriptionBuilder()
+ .setId(UUID.randomUUID())
+ .setBundleId(bundleId)
+ .setCategory(productCategory)
+ .setBundleStartDate(migrationStartDate)
+ .setStartDate(migrationStartDate),
+ emptyEvents);
+ return new SubscriptionMigrationData(subscriptionData, toEvents(subscriptionData, now, events));
+ }
+
private List<EntitlementEvent> toEvents(SubscriptionData subscriptionData, DateTime now, TimedMigration [] migrationEvents) {
List<EntitlementEvent> events = new ArrayList<EntitlementEvent>(migrationEvents.length);
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/test/DefaultEntitlementTestApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/test/DefaultEntitlementTestApi.java
index 19b851d..2843ec1 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/test/DefaultEntitlementTestApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/test/DefaultEntitlementTestApi.java
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.test;
import com.google.inject.Inject;
import com.ning.billing.config.EntitlementConfig;
import com.ning.billing.entitlement.engine.core.Engine;
+import com.ning.billing.entitlement.exceptions.EntitlementError;
import com.ning.billing.util.notificationq.NotificationQueue;
import com.ning.billing.util.notificationq.NotificationQueueService;
import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotificationQueue;
@@ -26,6 +27,7 @@ import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotifi
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+
import java.util.UUID;
public class DefaultEntitlementTestApi implements EntitlementTestApi {
@@ -43,11 +45,13 @@ public class DefaultEntitlementTestApi implements EntitlementTestApi {
@Override
public void doProcessReadyEvents(UUID [] subscriptionsIds, Boolean recursive, Boolean oneEventOnly) {
+ if (recursive || oneEventOnly) {
+ throw new EntitlementError("Not implemented");
+ }
if (config.isEventProcessingOff()) {
log.warn("Running event processing loop");
NotificationQueue queue = getNotificationQueue();
queue.processReadyNotification();
-
}
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
index 42d34ad..ed05b02 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
@@ -89,7 +89,6 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
@Override
public Subscription createSubscription(UUID bundleId, PlanPhaseSpecifier spec, DateTime requestedDate) throws EntitlementUserApiException {
-
try {
String realPriceList = (spec.getPriceListName() == null) ? PriceListSet.DEFAULT_PRICELIST_NAME : spec.getPriceListName();
DateTime now = clock.getUTCNow();
@@ -102,7 +101,6 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
Plan plan = catalogService.getFullCatalog().findPlan(spec.getProductName(), spec.getBillingPeriod(), realPriceList, requestedDate);
-
PlanPhase phase = (plan.getInitialPhases() != null) ? plan.getInitialPhases()[0] : plan.getFinalPhase();
if (phase == null) {
throw new EntitlementError(String.format("No initial PlanPhase for Product %s, term %s and set %s does not exist in the catalog",
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 272f234..342c9cc 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
@@ -30,7 +30,12 @@ import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
public class SubscriptionData implements Subscription {
@@ -99,29 +104,29 @@ public class SubscriptionData implements Subscription {
@Override
public SubscriptionState getState() {
- return (transitions == null) ? null : getLatestTranstion().getNextState();
+ return (getLatestTransition() == null) ? null : getLatestTransition().getNextState();
}
@Override
public PlanPhase getCurrentPhase() {
- return (transitions == null) ? null : getLatestTranstion().getNextPhase();
+ return (getLatestTransition() == null) ? null : getLatestTransition().getNextPhase();
}
@Override
public Plan getCurrentPlan() {
- return (transitions == null) ? null : getLatestTranstion().getNextPlan();
+ return (getLatestTransition() == null) ? null : getLatestTransition().getNextPlan();
}
@Override
public String getCurrentPriceList() {
- return (transitions == null) ? null : getLatestTranstion().getNextPriceList();
+ return (getLatestTransition() == null) ? null : getLatestTransition().getNextPriceList();
}
@Override
public DateTime getEndDate() {
- SubscriptionTransition latestTransition = getLatestTranstion();
+ SubscriptionTransition latestTransition = getLatestTransition();
if (latestTransition.getNextState() == SubscriptionState.CANCELLED) {
return latestTransition.getEffectiveTransitionTime();
}
@@ -195,7 +200,7 @@ public class SubscriptionData implements Subscription {
return null;
}
- public SubscriptionTransition getLatestTranstion() {
+ public SubscriptionTransition getLatestTransition() {
if (transitions == null) {
return null;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
index 7bd1124..ce70ba7 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
@@ -18,7 +18,6 @@ package com.ning.billing.entitlement.engine.core;
import java.util.UUID;
-
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,12 +45,12 @@ import com.ning.billing.entitlement.exceptions.EntitlementError;
import com.ning.billing.lifecycle.LifecycleHandlerType;
import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
import com.ning.billing.util.notificationq.NotificationConfig;
import com.ning.billing.util.notificationq.NotificationQueue;
import com.ning.billing.util.notificationq.NotificationQueueService;
-import com.ning.billing.util.notificationq.NotificationQueueService.NotficationQueueAlreadyExists;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists;
import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
public class Engine implements EventListener, EntitlementService {
@@ -59,10 +58,6 @@ public class Engine implements EventListener, EntitlementService {
public static final String NOTIFICATION_QUEUE_NAME = "subscription-events";
public static final String ENTITLEMENT_SERVICE_NAME = "entitlement-service";
- private final long MAX_NOTIFICATION_THREAD_WAIT_MS = 10000; // 10 secs
- private final long NOTIFICATION_THREAD_WAIT_INCREMENT_MS = 1000; // 1 sec
- private final long NANO_TO_MS = (1000 * 1000);
-
private final static Logger log = LoggerFactory.getLogger(Engine.class);
private final Clock clock;
@@ -72,19 +67,17 @@ public class Engine implements EventListener, EntitlementService {
private final EntitlementBillingApi billingApi;
private final EntitlementTestApi testApi;
private final EntitlementMigrationApi migrationApi;
- private final EventBus eventBus;
+ private final Bus eventBus;
private final EntitlementConfig config;
private final NotificationQueueService notificationQueueService;
- private boolean startedNotificationThread;
- private boolean stoppedNotificationThread;
private NotificationQueue subscritionEventQueue;
@Inject
public Engine(Clock clock, EntitlementDao dao, PlanAligner planAligner,
EntitlementConfig config, DefaultEntitlementUserApi userApi,
DefaultEntitlementBillingApi billingApi, DefaultEntitlementTestApi testApi,
- DefaultEntitlementMigrationApi migrationApi, EventBus eventBus,
+ DefaultEntitlementMigrationApi migrationApi, Bus eventBus,
NotificationQueueService notificationQueueService) {
super();
this.clock = clock;
@@ -108,8 +101,6 @@ public class Engine implements EventListener, EntitlementService {
public void initialize() {
try {
- this.stoppedNotificationThread = false;
- this.startedNotificationThread = false;
subscritionEventQueue = notificationQueueService.createNotificationQueue(ENTITLEMENT_SERVICE_NAME,
NOTIFICATION_QUEUE_NAME,
new NotificationQueueHandler() {
@@ -122,21 +113,6 @@ public class Engine implements EventListener, EntitlementService {
processEventReady(event);
}
}
-
- @Override
- public void completedQueueStop() {
- synchronized (this) {
- stoppedNotificationThread = true;
- this.notifyAll();
- }
- }
- @Override
- public void completedQueueStart() {
- synchronized (this) {
- startedNotificationThread = true;
- this.notifyAll();
- }
- }
},
new NotificationConfig() {
@Override
@@ -156,7 +132,7 @@ public class Engine implements EventListener, EntitlementService {
return config.getDaoMaxReadyEvents();
}
});
- } catch (NotficationQueueAlreadyExists e) {
+ } catch (NotificationQueueAlreadyExists e) {
throw new RuntimeException(e);
}
}
@@ -164,16 +140,13 @@ public class Engine implements EventListener, EntitlementService {
@LifecycleHandlerType(LifecycleLevel.START_SERVICE)
public void start() {
subscritionEventQueue.startQueue();
- waitForNotificationStartCompletion();
}
@LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
public void stop() {
if (subscritionEventQueue != null) {
subscritionEventQueue.stopQueue();
- waitForNotificationStopCompletion();
- }
- startedNotificationThread = false;
+ }
}
@Override
@@ -218,43 +191,6 @@ public class Engine implements EventListener, EntitlementService {
}
}
- private void waitForNotificationStartCompletion() {
- waitForNotificationEventCompletion(true);
- }
-
- private void waitForNotificationStopCompletion() {
- waitForNotificationEventCompletion(false);
- }
-
- private void waitForNotificationEventCompletion(boolean startEvent) {
-
- long ini = System.nanoTime();
- synchronized(this) {
- do {
- if ((startEvent ? startedNotificationThread : stoppedNotificationThread)) {
- break;
- }
- try {
- this.wait(NOTIFICATION_THREAD_WAIT_INCREMENT_MS);
- } catch (InterruptedException e ) {
- Thread.currentThread().interrupt();
- throw new EntitlementError(e);
- }
- } while (!(startEvent ? startedNotificationThread : stoppedNotificationThread) &&
- (System.nanoTime() - ini) / NANO_TO_MS < MAX_NOTIFICATION_THREAD_WAIT_MS);
-
- if (!(startEvent ? startedNotificationThread : stoppedNotificationThread)) {
- log.error("Could not {} notification thread in {} msec !!!",
- (startEvent ? "start" : "stop"),
- MAX_NOTIFICATION_THREAD_WAIT_MS);
- throw new EntitlementError("Failed to start service!!");
- }
- log.info("Notification thread has been {} in {} ms",
- (startEvent ? "started" : "stopped"),
- (System.nanoTime() - ini) / NANO_TO_MS);
- }
- }
-
private void insertNextPhaseEvent(SubscriptionData subscription) {
try {
DateTime now = clock.getUTCNow();
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 ea62b84..c9ddf90 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
@@ -16,7 +16,9 @@
package com.ning.billing.entitlement.engine.dao;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
@@ -40,6 +42,8 @@ public interface EntitlementDao {
public Subscription getSubscriptionFromId(UUID subscriptionId);
+ // Account retrieval
+ public UUID getAccountIdFromSubscriptionId(UUID subscriptionId);
// Subscription retrieval
public Subscription getBaseSubscription(UUID bundleId);
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 72a71d8..e5f15ed 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
@@ -21,9 +21,18 @@ import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.UUID;
-
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.api.migration.AccountMigrationData;
import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
import com.ning.billing.entitlement.api.migration.AccountMigrationData.SubscriptionMigrationData;
@@ -44,14 +53,7 @@ import com.ning.billing.util.notificationq.NotificationKey;
import com.ning.billing.util.notificationq.NotificationQueue;
import com.ning.billing.util.notificationq.NotificationQueueService;
import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotificationQueue;
-
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.DBI;
-import org.skife.jdbi.v2.Transaction;
-import org.skife.jdbi.v2.TransactionStatus;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import sun.jkernel.Bundle;
public class EntitlementSqlDao implements EntitlementDao {
@@ -66,7 +68,8 @@ public class EntitlementSqlDao implements EntitlementDao {
private final NotificationQueueService notificationQueueService;
@Inject
- public EntitlementSqlDao(DBI dbi, Clock clock, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
+ public EntitlementSqlDao(final IDBI dbi, final Clock clock, final SubscriptionFactory factory,
+ final NotificationQueueService notificationQueueService) {
this.clock = clock;
this.factory = factory;
this.subscriptionsDao = dbi.onDemand(SubscriptionSqlDao.class);
@@ -76,18 +79,18 @@ public class EntitlementSqlDao implements EntitlementDao {
}
@Override
- public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
+ public SubscriptionBundle getSubscriptionBundleFromKey(final String bundleKey) {
return bundlesDao.getBundleFromKey(bundleKey);
}
@Override
public List<SubscriptionBundle> getSubscriptionBundleForAccount(
- UUID accountId) {
+ final UUID accountId) {
return bundlesDao.getBundleFromAccount(accountId.toString());
}
@Override
- public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
+ public SubscriptionBundle getSubscriptionBundleFromId(final UUID bundleId) {
return bundlesDao.getBundleFromId(bundleId.toString());
}
@@ -103,11 +106,34 @@ public class EntitlementSqlDao implements EntitlementDao {
}
@Override
- public Subscription getSubscriptionFromId(UUID subscriptionId) {
+ public Subscription getSubscriptionFromId(final UUID subscriptionId) {
return buildSubscription(subscriptionsDao.getSubscriptionFromId(subscriptionId.toString()));
}
@Override
+ public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+ Subscription subscription = subscriptionsDao.getSubscriptionFromId(subscriptionId.toString());
+ if (subscription == null) {
+ log.error(String.format(ErrorCode.ENT_INVALID_SUBSCRIPTION_ID.getFormat(), subscriptionId.toString()));
+ return null;
+ }
+
+ UUID bundleId = subscription.getBundleId();
+ if (bundleId == null) {
+ log.error(String.format(ErrorCode.ENT_GET_NO_BUNDLE_FOR_SUBSCRIPTION.getFormat(), subscriptionId.toString()));
+ return null;
+ }
+
+ SubscriptionBundle bundle = bundlesDao.getBundleFromId(bundleId.toString());
+ if (bundle == null) {
+ log.error(String.format(ErrorCode.ENT_GET_INVALID_BUNDLE_ID.getFormat(), bundleId.toString()));
+ return null;
+ }
+
+ return bundle.getAccountId();
+ }
+
+ @Override
public Subscription getBaseSubscription(final UUID bundleId) {
List<Subscription> subscriptions = subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString());
@@ -194,9 +220,9 @@ public class EntitlementSqlDao implements EntitlementDao {
dao.insertSubscription(subscription);
// STEPH batch as well
- EventSqlDao eventsDaoFromSameTranscation = dao.become(EventSqlDao.class);
+ EventSqlDao eventsDaoFromSameTransaction = dao.become(EventSqlDao.class);
for (final EntitlementEvent cur : initialEvents) {
- eventsDaoFromSameTranscation.insertEvent(cur);
+ eventsDaoFromSameTransaction.insertEvent(cur);
recordFutureNotificationFromTransaction(dao,
cur.getEffectiveDate(),
new NotificationKey() {
@@ -417,9 +443,9 @@ public class EntitlementSqlDao implements EntitlementDao {
private void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao, final DateTime effectiveDate, final NotificationKey notificationKey) {
try {
- NotificationQueue subscritionEventQueue = notificationQueueService.getNotificationQueue(Engine.ENTITLEMENT_SERVICE_NAME,
+ NotificationQueue subscriptionEventQueue = notificationQueueService.getNotificationQueue(Engine.ENTITLEMENT_SERVICE_NAME,
Engine.NOTIFICATION_QUEUE_NAME);
- subscritionEventQueue.recordFutureNotificationFromTransaction(transactionalDao, effectiveDate, notificationKey);
+ subscriptionEventQueue.recordFutureNotificationFromTransaction(transactionalDao, effectiveDate, notificationKey);
} catch (NoSuchNotificationQueue e) {
throw new RuntimeException(e);
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java b/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
index d2776ad..c27a8f4 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
@@ -40,6 +40,7 @@ import com.ning.billing.entitlement.engine.dao.EntitlementSqlDao;
public class EntitlementModule extends AbstractModule {
+
protected void installConfig() {
final EntitlementConfig config = new ConfigurationObjectFactory(System.getProperties()).build(EntitlementConfig.class);
bind(EntitlementConfig.class).toInstance(config);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
index 6bee471..39f6c48 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
@@ -19,7 +19,7 @@ package com.ning.billing.entitlement.api;
import com.google.common.base.Joiner;
import com.google.common.eventbus.Subscribe;
import com.ning.billing.entitlement.api.user.SubscriptionTransition;
-import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.bus.Bus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,7 +45,7 @@ public class ApiTestListener {
PHASE
}
- public ApiTestListener(EventBus eventBus) {
+ public ApiTestListener(Bus eventBus) {
this.nextExpectedEvent = new Stack<NextEvent>();
this.completed = false;
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java
index 68cae5a..4d57dac 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java
@@ -16,11 +16,10 @@
package com.ning.billing.entitlement.api.billing;
-import java.util.Collection;
import java.util.List;
import java.util.UUID;
-
import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
@@ -32,110 +31,112 @@ class BrainDeadMockEntitlementDao implements EntitlementDao {
@Override
public List<SubscriptionBundle> getSubscriptionBundleForAccount(
- UUID accountId) {
+ final UUID accountId) {
throw new UnsupportedOperationException();
}
@Override
- public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
+ public SubscriptionBundle getSubscriptionBundleFromKey(final String bundleKey) {
throw new UnsupportedOperationException();
}
@Override
- public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
+ public SubscriptionBundle getSubscriptionBundleFromId(final UUID bundleId) {
throw new UnsupportedOperationException();
}
@Override
public SubscriptionBundle createSubscriptionBundle(
- SubscriptionBundleData bundle) {
+ final SubscriptionBundleData bundle) {
throw new UnsupportedOperationException();
}
@Override
- public Subscription getSubscriptionFromId(UUID subscriptionId) {
+ public Subscription getSubscriptionFromId(final UUID subscriptionId) {
throw new UnsupportedOperationException();
}
- @Override
- public Subscription getBaseSubscription(UUID bundleId) {
- throw new UnsupportedOperationException();
+ @Override
+ public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public Subscription getBaseSubscription(final UUID bundleId) {
+ throw new UnsupportedOperationException();
}
@Override
- public List<Subscription> getSubscriptions(UUID bundleId) {
+ public List<Subscription> getSubscriptions(final UUID bundleId) {
throw new UnsupportedOperationException();
-
}
@Override
- public List<Subscription> getSubscriptionsForKey(String bundleKey) {
+ public List<Subscription> getSubscriptionsForKey(final String bundleKey) {
throw new UnsupportedOperationException();
}
@Override
- public void updateSubscription(SubscriptionData subscription) {
+ public void updateSubscription(final SubscriptionData subscription) {
throw new UnsupportedOperationException();
}
@Override
- public void createNextPhaseEvent(UUID subscriptionId,
- EntitlementEvent nextPhase) {
+ public void createNextPhaseEvent(final UUID subscriptionId,
+ final EntitlementEvent nextPhase) {
throw new UnsupportedOperationException();
}
@Override
public List<EntitlementEvent> getEventsForSubscription(
- UUID subscriptionId) {
+ final UUID subscriptionId) {
throw new UnsupportedOperationException();
}
@Override
public List<EntitlementEvent> getPendingEventsForSubscription(
- UUID subscriptionId) {
+ final UUID subscriptionId) {
throw new UnsupportedOperationException();
}
@Override
- public void createSubscription(SubscriptionData subscription,
- List<EntitlementEvent> initialEvents) {
+ public void createSubscription(final SubscriptionData subscription,
+ final List<EntitlementEvent> initialEvents) {
throw new UnsupportedOperationException();
}
@Override
- public void cancelSubscription(UUID subscriptionId,
- EntitlementEvent cancelEvent) {
+ public void cancelSubscription(final UUID subscriptionId,
+ final EntitlementEvent cancelEvent) {
throw new UnsupportedOperationException();
-
}
@Override
- public void uncancelSubscription(UUID subscriptionId,
- List<EntitlementEvent> uncancelEvents) {
+ public void uncancelSubscription(final UUID subscriptionId,
+ final List<EntitlementEvent> uncancelEvents) {
throw new UnsupportedOperationException();
}
@Override
- public void changePlan(UUID subscriptionId,
- List<EntitlementEvent> changeEvents) {
+ public void changePlan(final UUID subscriptionId,
+ final List<EntitlementEvent> changeEvents) {
throw new UnsupportedOperationException();
}
@Override
- public void migrate(UUID acountId, AccountMigrationData data) {
+ public void migrate(final UUID acountId, final AccountMigrationData data) {
throw new UnsupportedOperationException();
}
@Override
- public void undoMigration(UUID accountId) {
- throw new UnsupportedOperationException();
+ public void undoMigration(final UUID accountId) {
+ throw new UnsupportedOperationException();
}
- @Override
- public EntitlementEvent getEventById(UUID eventId) {
- throw new UnsupportedOperationException();
- }
-
+
+ @Override
+ public EntitlementEvent getEventById(final UUID eventId) {
+ throw new UnsupportedOperationException();
+ }
}
\ No newline at end of file
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java
new file mode 100644
index 0000000..e55ba79
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java
@@ -0,0 +1,144 @@
+/*
+ * 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.billing;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+
+public class BrainDeadSubscription implements Subscription {
+
+ @Override
+ public void cancel(DateTime requestedDate, boolean eot)
+ throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void uncancel() throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void changePlan(String productName, BillingPeriod term,
+ String planSet, DateTime requestedDate)
+ throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+
+
+ }
+
+ @Override
+ public void pause() throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+
+
+ }
+
+ @Override
+ public void resume() throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public UUID getId() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public UUID getBundleId() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public SubscriptionState getState() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public DateTime getStartDate() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public DateTime getEndDate() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public Plan getCurrentPlan() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public String getCurrentPriceList() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public PlanPhase getCurrentPhase() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public DateTime getChargedThroughDate() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public DateTime getPaidThroughDate() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public List<SubscriptionTransition> getActiveTransitions() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public List<SubscriptionTransition> getAllTransitions() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public SubscriptionTransition getPendingTransition() {
+ throw new UnsupportedOperationException();
+
+ }
+
+}
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
new file mode 100644
index 0000000..100d184
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java
@@ -0,0 +1,154 @@
+/*
+ * 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.billing;
+
+import java.math.BigDecimal;
+import java.util.Iterator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.DefaultPrice;
+import com.ning.billing.catalog.MockInternationalPrice;
+import com.ning.billing.catalog.MockPlan;
+import com.ning.billing.catalog.MockPlanPhase;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.catalog.api.PhaseType;
+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;
+
+public class TestDefaultBillingEvent {
+ public static final UUID ID_ZERO = new UUID(0L,0L);
+ public static final UUID ID_ONE = new UUID(0L,1L);
+ public static final UUID ID_TWO = new UUID(0L,2L);
+
+ @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,
+ BillingModeType.IN_ADVANCE, "Test Event 1", type);
+ }
+
+ private MockPlanPhase createMockMonthlyPlanPhase(@Nullable final BigDecimal recurringRate,
+ final BigDecimal fixedRate, PhaseType phaseType) {
+ return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+ new MockInternationalPrice(new DefaultPrice(fixedRate, Currency.USD)),
+ BillingPeriod.MONTHLY, phaseType);
+ }
+
+ private Subscription subscription(final UUID id) {
+ return new BrainDeadSubscription() {
+ public UUID getId() {
+ return id;
+ }
+ };
+ }
+
+}
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 2de7872..91ddc91 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
@@ -42,6 +42,7 @@ import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.catalog.glue.CatalogModule;
import com.ning.billing.entitlement.api.TestApiBase;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
@@ -57,6 +58,8 @@ import com.ning.billing.lifecycle.KillbillService.ServiceException;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.glue.ClockModule;
+import static org.testng.Assert.assertTrue;
+
public class TestDefaultEntitlementBillingApi {
private static final UUID zeroId = new UUID(0L,0L);
private static final UUID oneId = new UUID(1L,0L);
@@ -84,7 +87,7 @@ public class TestDefaultEntitlementBillingApi {
((DefaultCatalogService)catalogService).loadCatalog();
}
- @BeforeMethod
+ @BeforeMethod(alwaysRun=true)
public void setupEveryTime() {
bundles = new ArrayList<SubscriptionBundle>();
final SubscriptionBundle bundle = new SubscriptionBundleData( zeroId,"TestKey", oneId, new DateTime().minusDays(4));
@@ -92,13 +95,11 @@ public class TestDefaultEntitlementBillingApi {
transitions = new ArrayList<SubscriptionTransition>();
-
-
subscriptions = new ArrayList<Subscription>();
SubscriptionBuilder builder = new SubscriptionBuilder();
subscriptionStartDate = new DateTime().minusDays(3);
- builder.setStartDate(subscriptionStartDate);
+ builder.setStartDate(subscriptionStartDate).setId(oneId);
subscription = new SubscriptionData(builder) {
public List<SubscriptionTransition> getAllTransitions() {
return transitions;
@@ -122,33 +123,42 @@ public class TestDefaultEntitlementBillingApi {
return subscription;
}
-
- @Override
+
+ @Override
+ public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
return bundle;
}
-
-
};
+ assertTrue(true);
}
- @Test
+ @Test(enabled=true, groups="fast")
public void testBillingEventsEmpty() {
EntitlementDao dao = new BrainDeadMockEntitlementDao() {
public List<SubscriptionBundle> getSubscriptionBundleForAccount(
UUID accountId) {
return new ArrayList<SubscriptionBundle>();
}
-
- };
+
+ @Override
+ public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+ throw new UnsupportedOperationException();
+ }
+
+ };
AccountUserApi accountApi = new BrainDeadAccountUserApi() ;
DefaultEntitlementBillingApi api = new DefaultEntitlementBillingApi(dao,accountApi,catalogService);
SortedSet<BillingEvent> events = api.getBillingEventsForAccount(new UUID(0L,0L));
Assert.assertEquals(events.size(), 0);
}
- @Test
+ @Test(enabled=true, groups="fast")
public void testBillingEventsNoBillingPeriod() throws CatalogApiException {
DateTime now = clock.getUTCNow();
DateTime then = now.minusDays(1);
@@ -173,7 +183,7 @@ public class TestDefaultEntitlementBillingApi {
checkFirstEvent(events, nextPlan, 32, oneId, now, nextPhase, ApiEventType.CREATE.toString());
}
- @Test
+ @Test(enabled=true, groups="fast")
public void testBillingEventsAnual() throws CatalogApiException {
DateTime now = clock.getUTCNow();
DateTime then = now.minusDays(1);
@@ -198,11 +208,11 @@ public class TestDefaultEntitlementBillingApi {
checkFirstEvent(events, nextPlan, subscription.getStartDate().getDayOfMonth(), oneId, now, nextPhase, ApiEventType.CREATE.toString());
}
- @Test
+ @Test(enabled=true, groups="fast")
public void testBillingEventsMonthly() throws CatalogApiException {
DateTime now = clock.getUTCNow();
DateTime then = now.minusDays(1);
- Plan nextPlan = catalogService.getFullCatalog().findPlan("shotgun-annual", now);
+ Plan nextPlan = catalogService.getFullCatalog().findPlan("shotgun-monthly", now);
PlanPhase nextPhase = nextPlan.getAllPhases()[1];
String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
SubscriptionTransition t = new SubscriptionTransitionData(
@@ -223,11 +233,11 @@ public class TestDefaultEntitlementBillingApi {
checkFirstEvent(events, nextPlan, 32, oneId, now, nextPhase, ApiEventType.CREATE.toString());
}
- @Test
+ @Test(enabled=true, groups="fast")
public void testBillingEventsAddOn() throws CatalogApiException {
DateTime now = clock.getUTCNow();
DateTime then = now.minusDays(1);
- Plan nextPlan = catalogService.getFullCatalog().findPlan("shotgun-annual", now);
+ Plan nextPlan = catalogService.getFullCatalog().findPlan("laser-scope-monthly", now);
PlanPhase nextPhase = nextPlan.getAllPhases()[0];
String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
SubscriptionTransition t = new SubscriptionTransitionData(
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
index b0483bb..912f186 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
@@ -67,8 +67,8 @@ import com.ning.billing.entitlement.events.user.ApiEventType;
import com.ning.billing.lifecycle.KillbillService.ServiceException;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-import com.ning.billing.util.eventbus.EventBusService;
+import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.bus.BusService;
public abstract class TestApiBase {
@@ -87,7 +87,7 @@ public abstract class TestApiBase {
protected EntitlementConfig config;
protected EntitlementDao dao;
protected ClockMock clock;
- protected EventBusService busService;
+ protected BusService busService;
protected AccountData accountData;
protected Catalog catalog;
@@ -109,8 +109,8 @@ public abstract class TestApiBase {
@AfterClass(groups={"setup"})
public void tearDown() {
try {
- busService.getEventBus().register(testListener);
- ((DefaultEventBusService) busService).stopBus();
+ busService.getBus().register(testListener);
+ ((DefaultBusService) busService).stopBus();
} catch (Exception e) {
log.warn("Failed to tearDown test properly ", e);
}
@@ -125,14 +125,13 @@ public abstract class TestApiBase {
entitlementService = g.getInstance(EntitlementService.class);
catalogService = g.getInstance(CatalogService.class);
- busService = g.getInstance(EventBusService.class);
+ busService = g.getInstance(BusService.class);
config = g.getInstance(EntitlementConfig.class);
dao = g.getInstance(EntitlementDao.class);
clock = (ClockMock) g.getInstance(Clock.class);
try {
-
((DefaultCatalogService) catalogService).loadCatalog();
- ((DefaultEventBusService) busService).startBus();
+ ((DefaultBusService) busService).startBus();
((Engine) entitlementService).initialize();
init();
} catch (EntitlementUserApiException e) {
@@ -152,7 +151,7 @@ public abstract class TestApiBase {
assertNotNull(catalog);
- testListener = new ApiTestListener(busService.getEventBus());
+ testListener = new ApiTestListener(busService.getBus());
entitlementApi = entitlementService.getUserApi();
billingApi = entitlementService.getBillingApi();
migrationApi = entitlementService.getMigrationApi();
@@ -170,7 +169,7 @@ public abstract class TestApiBase {
clock.resetDeltaFromReality();
((MockEntitlementDao) dao).reset();
try {
- busService.getEventBus().register(testListener);
+ busService.getBus().register(testListener);
UUID accountId = UUID.randomUUID();
bundle = entitlementApi.createBundleForAccount(accountId, "myDefaultBundle");
} catch (Exception e) {
@@ -242,6 +241,11 @@ public abstract class TestApiBase {
public int getNumber() {
return days;
}
+
+ @Override
+ public DateTime addToDateTime(DateTime dateTime) {
+ return null;
+ }
};
return result;
}
@@ -256,6 +260,11 @@ public abstract class TestApiBase {
public int getNumber() {
return months;
}
+
+ @Override
+ public DateTime addToDateTime(DateTime dateTime) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
};
return result;
}
@@ -271,6 +280,11 @@ public abstract class TestApiBase {
public int getNumber() {
return years;
}
+
+ @Override
+ public DateTime addToDateTime(DateTime dateTime) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
};
return result;
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
index 3326bc8..1e725bf 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
@@ -22,6 +22,7 @@ import static org.testng.Assert.assertTrue;
import static org.testng.Assert.assertFalse;
import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.util.clock.DefaultClock;
import org.joda.time.DateTime;
import org.testng.Assert;
@@ -80,7 +81,7 @@ public abstract class TestUserApiCancel extends TestApiBase {
}
- protected void testCancelSubscriptionEOTWithChargeThroughDate() {
+ protected void testCancelSubscriptionEOTWithChargeThroughDate() throws EntitlementBillingApiException {
log.info("Starting testCancelSubscriptionEOTWithChargeThroughDate");
try {
@@ -176,7 +177,7 @@ public abstract class TestUserApiCancel extends TestApiBase {
// Similar test to testCancelSubscriptionEOTWithChargeThroughDate except we uncancel and check things
// are as they used to be and we can move forward without hitting cancellation
//
- protected void testUncancel() {
+ protected void testUncancel() throws EntitlementBillingApiException {
log.info("Starting testUncancel");
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
index 630d925..dbcc680 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.user;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
import org.testng.annotations.Test;
@@ -38,7 +39,7 @@ public class TestUserApiCancelMemory extends TestUserApiCancel {
@Override
@Test(enabled=true, groups={"fast"})
- public void testCancelSubscriptionEOTWithChargeThroughDate() {
+ public void testCancelSubscriptionEOTWithChargeThroughDate() throws EntitlementBillingApiException {
super.testCancelSubscriptionEOTWithChargeThroughDate();
}
@@ -50,7 +51,7 @@ public class TestUserApiCancelMemory extends TestUserApiCancel {
@Override
@Test(enabled=true, groups={"fast"})
- public void testUncancel() {
+ public void testUncancel() throws EntitlementBillingApiException {
super.testUncancel();
}
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java
index 87491c7..840f357 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.user;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleSql;
import org.testng.annotations.Test;
@@ -33,7 +34,7 @@ public class TestUserApiCancelSql extends TestUserApiCancel {
}
@Test(enabled= false, groups={"stress"})
- public void stressTest() {
+ public void stressTest() throws EntitlementBillingApiException {
for (int i = 0; i < MAX_STRESS_ITERATIONS; i++) {
cleanupTest();
setupTest();
@@ -55,7 +56,7 @@ public class TestUserApiCancelSql extends TestUserApiCancel {
@Override
@Test(enabled=true, groups={"sql"})
- public void testCancelSubscriptionEOTWithChargeThroughDate() {
+ public void testCancelSubscriptionEOTWithChargeThroughDate() throws EntitlementBillingApiException {
super.testCancelSubscriptionEOTWithChargeThroughDate();
}
@@ -67,7 +68,7 @@ public class TestUserApiCancelSql extends TestUserApiCancel {
@Override
@Test(enabled=true, groups={"sql"})
- public void testUncancel() {
+ public void testUncancel() throws EntitlementBillingApiException {
super.testUncancel();
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
index 51c3d8b..78616be 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
@@ -37,6 +37,7 @@ import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.TestApiBase;
import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.events.EntitlementEvent;
import com.ning.billing.entitlement.events.user.ApiEvent;
import com.ning.billing.util.clock.DefaultClock;
@@ -100,12 +101,12 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
}
- protected void testChangePlanBundleAlignEOTWithChargeThroughDate() {
+ protected void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
testChangePlanBundleAlignEOTWithChargeThroughDate("Shotgun", BillingPeriod.ANNUAL, "gunclubDiscount", "Pistol", BillingPeriod.ANNUAL, "gunclubDiscount");
}
private void testChangePlanBundleAlignEOTWithChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet,
- String toProd, BillingPeriod toTerm, String toPlanSet) {
+ String toProd, BillingPeriod toTerm, String toPlanSet) throws EntitlementBillingApiException {
log.info("Starting testChangeSubscriptionEOTWithChargeThroughDate");
try {
@@ -216,12 +217,12 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
}
- protected void testChangePlanChangePlanAlignEOTWithChargeThroughDate() {
+ protected void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
tChangePlanChangePlanAlignEOTWithChargeThroughDate("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, "Assault-Rifle", BillingPeriod.ANNUAL, "rescue");
}
private void tChangePlanChangePlanAlignEOTWithChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet,
- String toProd, BillingPeriod toTerm, String toPlanSet) {
+ String toProd, BillingPeriod toTerm, String toPlanSet) throws EntitlementBillingApiException {
log.info("Starting testChangePlanBundleAlignEOTWithChargeThroughDate");
@@ -297,7 +298,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
}
}
- protected void testMultipleChangeLastIMM() {
+ protected void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
try {
SubscriptionData subscription = createSubscription("Assault-Rifle", BillingPeriod.MONTHLY, "gunclubDiscount");
@@ -344,7 +345,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
}
}
- protected void testMultipleChangeLastEOT() {
+ protected void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
try {
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
index aecaaac..253da07 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.user;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
import org.testng.annotations.Test;
@@ -38,7 +39,7 @@ public class TestUserApiChangePlanMemory extends TestUserApiChangePlan {
@Override
@Test(enabled=true, groups={"fast"})
- public void testChangePlanBundleAlignEOTWithChargeThroughDate() {
+ public void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
super.testChangePlanBundleAlignEOTWithChargeThroughDate();
}
@@ -50,20 +51,20 @@ public class TestUserApiChangePlanMemory extends TestUserApiChangePlan {
@Override
@Test(enabled=true, groups={"fast"})
- public void testMultipleChangeLastIMM() {
+ public void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
super.testMultipleChangeLastIMM();
}
@Override
@Test(enabled=true, groups={"fast"})
- public void testMultipleChangeLastEOT() {
+ public void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
super.testMultipleChangeLastEOT();
}
// Set to false until we implement rescue example.
@Override
@Test(enabled=false, groups={"fast"})
- public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() {
+ public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
super.testChangePlanChangePlanAlignEOTWithChargeThroughDate();
}
}
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 f5ad803..92aa652 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
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.user;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleSql;
import org.testng.annotations.Test;
@@ -32,7 +33,7 @@ public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
}
@Test(enabled= true, groups={"stress"})
- public void stressTest() {
+ public void stressTest() throws EntitlementBillingApiException {
for (int i = 0; i < MAX_STRESS_ITERATIONS; i++) {
cleanupTest();
setupTest();
@@ -60,7 +61,7 @@ public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
@Override
@Test(enabled=true, groups={"sql"})
- public void testChangePlanBundleAlignEOTWithChargeThroughDate() {
+ public void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
super.testChangePlanBundleAlignEOTWithChargeThroughDate();
}
@@ -72,20 +73,20 @@ public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
@Override
@Test(enabled=true, groups={"sql"})
- public void testMultipleChangeLastIMM() {
+ public void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
super.testMultipleChangeLastIMM();
}
@Override
@Test(enabled=true, groups={"sql"})
- public void testMultipleChangeLastEOT() {
+ public void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
super.testMultipleChangeLastEOT();
}
// rescue not implemented yet
@Override
@Test(enabled=false, groups={"sql"})
- public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() {
+ public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
super.testChangePlanChangePlanAlignEOTWithChargeThroughDate();
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
index 4a8b4b1..7fceab9 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
@@ -29,6 +29,7 @@ import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.TestApiBase;
import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleSql;
import com.ning.billing.util.clock.DefaultClock;
import org.joda.time.DateTime;
@@ -64,7 +65,7 @@ public class TestUserApiDemos extends TestApiBase {
* 8. Cancel EOT
*/
@Test(enabled=true, groups="demos")
- public void testDemo1() {
+ public void testDemo1() throws EntitlementBillingApiException {
try {
System.out.println("DEMO 1 START");
@@ -189,7 +190,7 @@ public class TestUserApiDemos extends TestApiBase {
}
@Test(enabled= true, groups={"stress"})
- public void stressTest() {
+ public void stressTest() throws EntitlementBillingApiException {
for (int i = 0; i < 100; i++) {
cleanupTest();
setupTest();
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
index 84d1031..f69b36a 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
@@ -26,6 +26,7 @@ import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
import com.ning.billing.entitlement.api.TestApiBase;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleSql;
import com.ning.billing.util.clock.DefaultClock;
import org.joda.time.DateTime;
@@ -42,7 +43,7 @@ public class TestUserApiScenarios extends TestApiBase {
}
@Test(enabled=true)
- public void testChangeIMMCancelUncancelChangeEOT() {
+ public void testChangeIMMCancelUncancelChangeEOT() throws EntitlementBillingApiException {
log.info("Starting testChangeIMMCancelUncancelChangeEOT");
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 86e458f..6395470 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
@@ -16,20 +16,30 @@
package com.ning.billing.entitlement.engine.dao;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.TreeSet;
+import java.util.UUID;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.catalog.api.TimeUnit;
import com.ning.billing.config.EntitlementConfig;
-
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.api.migration.AccountMigrationData;
import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
import com.ning.billing.entitlement.api.migration.AccountMigrationData.SubscriptionMigrationData;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.entitlement.api.user.SubscriptionData;
import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
import com.ning.billing.entitlement.api.user.SubscriptionFactory;
-
import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
import com.ning.billing.entitlement.engine.core.Engine;
import com.ning.billing.entitlement.events.EntitlementEvent;
@@ -38,20 +48,10 @@ import com.ning.billing.entitlement.events.user.ApiEvent;
import com.ning.billing.entitlement.events.user.ApiEventType;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.notificationq.NotificationKey;
-import com.ning.billing.util.notificationq.NotificationLifecycle;
import com.ning.billing.util.notificationq.NotificationQueue;
import com.ning.billing.util.notificationq.NotificationQueueService;
import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotificationQueue;
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.*;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlementDao {
protected final static Logger log = LoggerFactory.getLogger(EntitlementDao.class);
@@ -65,7 +65,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
private final NotificationQueueService notificationQueueService;
@Inject
- public MockEntitlementDaoMemory(Clock clock, EntitlementConfig config, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
+ public MockEntitlementDaoMemory(final Clock clock, final EntitlementConfig config,
+ final SubscriptionFactory factory,
+ final NotificationQueueService notificationQueueService) {
super();
this.clock = clock;
this.config = config;
@@ -84,9 +86,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public List<SubscriptionBundle> getSubscriptionBundleForAccount(UUID accountId) {
+ public List<SubscriptionBundle> getSubscriptionBundleForAccount(final UUID accountId) {
List<SubscriptionBundle> results = new ArrayList<SubscriptionBundle>();
- for (SubscriptionBundle cur : bundles) {
+ for (final SubscriptionBundle cur : bundles) {
if (cur.getAccountId().equals(accountId)) {
results.add(cur);
}
@@ -95,8 +97,8 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
- for (SubscriptionBundle cur : bundles) {
+ public SubscriptionBundle getSubscriptionBundleFromId(final UUID bundleId) {
+ for (final SubscriptionBundle cur : bundles) {
if (cur.getId().equals(bundleId)) {
return cur;
}
@@ -105,8 +107,8 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
- for (SubscriptionBundle cur : bundles) {
+ public SubscriptionBundle getSubscriptionBundleFromKey(final String bundleKey) {
+ for (final SubscriptionBundle cur : bundles) {
if (cur.getKey().equals(bundleKey)) {
return cur;
}
@@ -116,14 +118,14 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
@Override
- public SubscriptionBundle createSubscriptionBundle(SubscriptionBundleData bundle) {
+ public SubscriptionBundle createSubscriptionBundle(final SubscriptionBundleData bundle) {
bundles.add(bundle);
return getSubscriptionBundleFromId(bundle.getId());
}
@Override
- public Subscription getSubscriptionFromId(UUID subscriptionId) {
- for (Subscription cur : subscriptions) {
+ public Subscription getSubscriptionFromId(final UUID subscriptionId) {
+ for (final Subscription cur : subscriptions) {
if (cur.getId().equals(subscriptionId)) {
return buildSubscription((SubscriptionData) cur);
}
@@ -132,9 +134,14 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public List<Subscription> getSubscriptionsForKey(String bundleKey) {
+ public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<Subscription> getSubscriptionsForKey(final String bundleKey) {
- for (SubscriptionBundle cur : bundles) {
+ for (final SubscriptionBundle cur : bundles) {
if (cur.getKey().equals(bundleKey)) {
return getSubscriptions(cur.getId());
}
@@ -144,11 +151,11 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
@Override
- public void createSubscription(SubscriptionData subscription, List<EntitlementEvent> initalEvents) {
+ public void createSubscription(final SubscriptionData subscription, final List<EntitlementEvent> initialEvents) {
synchronized(events) {
- events.addAll(initalEvents);
- for (final EntitlementEvent cur : initalEvents) {
+ events.addAll(initialEvents);
+ for (final EntitlementEvent cur : initialEvents) {
recordFutureNotificationFromTransaction(null, cur.getEffectiveDate(), new NotificationKey() {
@Override
public String toString() {
@@ -162,10 +169,10 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public List<Subscription> getSubscriptions(UUID bundleId) {
+ public List<Subscription> getSubscriptions(final UUID bundleId) {
List<Subscription> results = new ArrayList<Subscription>();
- for (Subscription cur : subscriptions) {
+ for (final Subscription cur : subscriptions) {
if (cur.getBundleId().equals(bundleId)) {
results.add(buildSubscription((SubscriptionData) cur));
}
@@ -174,10 +181,10 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public List<EntitlementEvent> getEventsForSubscription(UUID subscriptionId) {
+ public List<EntitlementEvent> getEventsForSubscription(final UUID subscriptionId) {
synchronized(events) {
List<EntitlementEvent> results = new LinkedList<EntitlementEvent>();
- for (EntitlementEvent cur : events) {
+ for (final EntitlementEvent cur : events) {
if (cur.getSubscriptionId().equals(subscriptionId)) {
results.add(cur);
}
@@ -187,10 +194,10 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public List<EntitlementEvent> getPendingEventsForSubscription(UUID subscriptionId) {
+ public List<EntitlementEvent> getPendingEventsForSubscription(final UUID subscriptionId) {
synchronized(events) {
List<EntitlementEvent> results = new LinkedList<EntitlementEvent>();
- for (EntitlementEvent cur : events) {
+ for (final EntitlementEvent cur : events) {
if (cur.isActive() &&
cur.getEffectiveDate().isAfter(clock.getUTCNow()) &&
cur.getSubscriptionId().equals(subscriptionId)) {
@@ -203,8 +210,8 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
@Override
- public Subscription getBaseSubscription(UUID bundleId) {
- for (Subscription cur : subscriptions) {
+ public Subscription getBaseSubscription(final UUID bundleId) {
+ for (final Subscription cur : subscriptions) {
if (cur.getBundleId().equals(bundleId) &&
cur.getCurrentPlan().getProduct().getCategory() == ProductCategory.BASE) {
return buildSubscription((SubscriptionData) cur);
@@ -214,19 +221,19 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public void createNextPhaseEvent(UUID subscriptionId, EntitlementEvent nextPhase) {
+ public void createNextPhaseEvent(final UUID subscriptionId, final EntitlementEvent nextPhase) {
cancelNextPhaseEvent(subscriptionId);
insertEvent(nextPhase);
}
- private Subscription buildSubscription(SubscriptionData in) {
+ private Subscription buildSubscription(final SubscriptionData in) {
return factory.createSubscription(new SubscriptionBuilder(in), getEventsForSubscription(in.getId()));
}
@Override
- public void updateSubscription(SubscriptionData subscription) {
+ public void updateSubscription(final SubscriptionData subscription) {
boolean found = false;
Iterator<Subscription> it = subscriptions.iterator();
@@ -244,7 +251,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public void cancelSubscription(UUID subscriptionId, EntitlementEvent cancelEvent) {
+ public void cancelSubscription(final UUID subscriptionId, final EntitlementEvent cancelEvent) {
synchronized (cancelEvent) {
cancelNextPhaseEvent(subscriptionId);
insertEvent(cancelEvent);
@@ -252,7 +259,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public void changePlan(UUID subscriptionId, List<EntitlementEvent> changeEvents) {
+ public void changePlan(final UUID subscriptionId, final List<EntitlementEvent> changeEvents) {
synchronized(events) {
cancelNextChangeEvent(subscriptionId);
cancelNextPhaseEvent(subscriptionId);
@@ -280,7 +287,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
}
- private void cancelNextPhaseEvent(UUID subscriptionId) {
+ private void cancelNextPhaseEvent(final UUID subscriptionId) {
Subscription curSubscription = getSubscriptionFromId(subscriptionId);
if (curSubscription.getCurrentPhase() == null ||
@@ -307,7 +314,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
- private void cancelNextChangeEvent(UUID subscriptionId) {
+ private void cancelNextChangeEvent(final UUID subscriptionId) {
synchronized(events) {
@@ -328,7 +335,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public void uncancelSubscription(UUID subscriptionId, List<EntitlementEvent> uncancelEvents) {
+ public void uncancelSubscription(final UUID subscriptionId, final List<EntitlementEvent> uncancelEvents) {
synchronized (events) {
boolean foundCancel = false;
@@ -346,7 +353,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
}
if (foundCancel) {
- for (EntitlementEvent cur : uncancelEvents) {
+ for (final EntitlementEvent cur : uncancelEvents) {
insertEvent(cur);
}
}
@@ -360,9 +367,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
undoMigration(accountId);
- for (BundleMigrationData curBundle : accountData.getData()) {
+ for (final BundleMigrationData curBundle : accountData.getData()) {
SubscriptionBundleData bundleData = curBundle.getData();
- for (SubscriptionMigrationData curSubscription : curBundle.getSubscriptions()) {
+ for (final SubscriptionMigrationData curSubscription : curBundle.getSubscriptions()) {
SubscriptionData subData = curSubscription.getData();
for (final EntitlementEvent curEvent : curSubscription.getInitialEvents()) {
events.add(curEvent);
@@ -382,15 +389,15 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public void undoMigration(UUID accountId) {
+ public void undoMigration(final UUID accountId) {
synchronized(events) {
List<SubscriptionBundle> allBundles = getSubscriptionBundleForAccount(accountId);
- for (SubscriptionBundle bundle : allBundles) {
+ for (final SubscriptionBundle bundle : allBundles) {
List<Subscription> allSubscriptions = getSubscriptions(bundle.getId());
- for (Subscription subscription : allSubscriptions) {
+ for (final Subscription subscription : allSubscriptions) {
List<EntitlementEvent> allEvents = getEventsForSubscription(subscription.getId());
- for (EntitlementEvent event : allEvents) {
+ for (final EntitlementEvent event : allEvents) {
events.remove(event);
}
subscriptions.remove(subscription);
@@ -402,9 +409,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public EntitlementEvent getEventById(UUID eventId) {
+ public EntitlementEvent getEventById(final UUID eventId) {
synchronized(events) {
- for (EntitlementEvent cur : events) {
+ for (final EntitlementEvent cur : events) {
if (cur.getId().equals(eventId)) {
return cur;
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
index 503fd1a..2204274 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
@@ -16,25 +16,24 @@
package com.ning.billing.entitlement.engine.dao;
-import com.google.inject.Inject;
-import com.ning.billing.config.EntitlementConfig;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.notificationq.NotificationQueueService;
-
-import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.Transaction;
import org.skife.jdbi.v2.TransactionStatus;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import com.google.inject.Inject;
+import com.ning.billing.entitlement.api.user.SubscriptionFactory;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+
public class MockEntitlementDaoSql extends EntitlementSqlDao implements MockEntitlementDao {
private final ResetSqlDao resetDao;
@Inject
- public MockEntitlementDaoSql(DBI dbi, Clock clock, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
+ public MockEntitlementDaoSql(IDBI dbi, Clock clock, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
super(dbi, clock, factory, notificationQueueService);
this.resetDao = dbi.onDemand(ResetSqlDao.class);
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java
index a00183b..d46fe83 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java
@@ -19,21 +19,16 @@ package com.ning.billing.entitlement.glue;
import com.ning.billing.account.glue.AccountModuleWithMocks;
import com.ning.billing.catalog.glue.CatalogModule;
import com.ning.billing.util.clock.MockClockModule;
-import com.ning.billing.util.glue.EventBusModule;
+import com.ning.billing.util.glue.BusModule;
public class MockEngineModule extends EntitlementModule {
- protected void installModulesForTests() {
- install(new EventBusModule());
- install(new CatalogModule());
- install(new AccountModuleWithMocks());
- install(new MockClockModule());
- }
-
@Override
protected void configure() {
super.configure();
- installModulesForTests();
+ install(new BusModule());
+ install(new CatalogModule());
+ install(new AccountModuleWithMocks());
+ install(new MockClockModule());
}
-
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java
index 2532c6f..e9e6134 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java
@@ -25,7 +25,7 @@ import com.ning.billing.util.clock.ClockMock;
import com.ning.billing.util.glue.NotificationQueueModule;
import org.skife.config.ConfigurationObjectFactory;
-import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
public class MockEngineModuleSql extends MockEngineModule {
@@ -36,7 +36,7 @@ public class MockEngineModuleSql extends MockEngineModule {
}
protected void installDBI() {
- bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+ bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
bind(DbiConfig.class).toInstance(config);
}
invoice/pom.xml 17(+15 -2)
diff --git a/invoice/pom.xml b/invoice/pom.xml
index cdc1725..e685de0 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.3-SNAPSHOT</version>
+ <version>0.1.5-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-invoice</artifactId>
@@ -21,6 +21,11 @@
<packaging>jar</packaging>
<dependencies>
<dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>com.ning.billing</groupId>
<artifactId>killbill-api</artifactId>
</dependency>
@@ -48,7 +53,6 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
-
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
@@ -94,6 +98,15 @@
<artifactId>guice</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.jayway.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
</build>
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java b/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
index b7bc657..6f033db 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
@@ -17,19 +17,31 @@
package com.ning.billing.invoice.api;
import com.google.inject.Inject;
+import com.ning.billing.invoice.InvoiceListener;
+import com.ning.billing.invoice.notification.NextBillingDateNotifier;
import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import com.ning.billing.util.bus.Bus;
public class DefaultInvoiceService implements InvoiceService {
- private static final String INVOICE_SERVICE_NAME = "invoice-service";
+
+ public static final String INVOICE_SERVICE_NAME = "invoice-service";
private final InvoiceUserApi userApi;
private final InvoicePaymentApi paymentApi;
+ private final NextBillingDateNotifier dateNotifier;
+ private final InvoiceListener invoiceListener;
+ private final Bus eventBus;
@Inject
- public DefaultInvoiceService(InvoiceUserApi userApi, InvoicePaymentApi paymentApi) {
+ public DefaultInvoiceService(InvoiceListener invoiceListener, Bus eventBus, InvoiceUserApi userApi, InvoicePaymentApi paymentApi, NextBillingDateNotifier dateNotifier) {
+ this.invoiceListener = invoiceListener;
+ this.eventBus = eventBus;
this.userApi = userApi;
this.paymentApi = paymentApi;
+ this.dateNotifier = dateNotifier;
}
+
@Override
public String getName() {
return INVOICE_SERVICE_NAME;
@@ -47,5 +59,34 @@ public class DefaultInvoiceService implements InvoiceService {
@LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.INIT_SERVICE)
public void initialize() {
+ dateNotifier.initialize();
+ }
+
+ @LifecycleHandlerType(LifecycleLevel.START_SERVICE)
+ public void start() {
+ dateNotifier.start();
+ }
+
+ @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.REGISTER_EVENTS)
+ public void registerForNotifications() {
+ try {
+ eventBus.register(invoiceListener);
+ } catch (Bus.EventBusException e) {
+ throw new RuntimeException("Unable to register to the EventBus!", e);
+ }
+ }
+
+ @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.UNREGISTER_EVENTS)
+ public void unregisterForNotifications() {
+ try {
+ eventBus.unregister(invoiceListener);
+ } catch (Bus.EventBusException e) {
+ throw new RuntimeException("Unable to unregister to the EventBus!", e);
+ }
+ }
+
+ @LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
+ public void stop() {
+ dateNotifier.stop();
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
index 31006ea..340e682 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
@@ -17,24 +17,24 @@
package com.ning.billing.invoice.api.invoice;
-import java.math.BigDecimal;
-import java.util.List;
-import java.util.UUID;
-
-import org.joda.time.DateTime;
-
import com.google.inject.Inject;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoicePayment;
import com.ning.billing.invoice.api.InvoicePaymentApi;
import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.payment.api.InvoicePayment;
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
private final InvoiceDao dao;
@Inject
- public DefaultInvoicePaymentApi(InvoiceDao dao) {
+ public DefaultInvoicePaymentApi(final InvoiceDao dao) {
this.dao = dao;
}
@@ -49,18 +49,18 @@ public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
// }
@Override
- public List<Invoice> getInvoicesByAccount(UUID accountId) {
- return dao.getInvoicesByAccount(accountId.toString());
+ public List<Invoice> getInvoicesByAccount(final UUID accountId) {
+ return dao.getInvoicesByAccount(accountId);
}
@Override
- public Invoice getInvoice(UUID invoiceId) {
- return dao.getById(invoiceId.toString());
+ public Invoice getInvoice(final UUID invoiceId) {
+ return dao.getById(invoiceId);
}
@Override
public Invoice getInvoiceForPaymentAttemptId(UUID paymentAttemptId) {
- String invoiceIdStr = dao.getInvoiceIdByPaymentAttemptId(paymentAttemptId);
+ UUID invoiceIdStr = dao.getInvoiceIdByPaymentAttemptId(paymentAttemptId);
return invoiceIdStr == null ? null : dao.getById(invoiceIdStr);
}
@@ -70,15 +70,14 @@ public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
}
@Override
- public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate) {
- InvoicePayment invoicePayment = new InvoicePayment(invoiceId, amountOutstanding, currency, paymentAttemptId, paymentAttemptDate);
+ public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate) {
+ InvoicePayment invoicePayment = new DefaultInvoicePayment(paymentAttemptId, invoiceId, paymentAttemptDate, amount, currency, null, null);
dao.notifyOfPaymentAttempt(invoicePayment);
}
@Override
public void notifyOfPaymentAttempt(UUID invoiceId, UUID paymentAttemptId, DateTime paymentAttemptDate) {
- InvoicePayment invoicePayment = new InvoicePayment(invoiceId, null, null, paymentAttemptId, paymentAttemptDate);
+ InvoicePayment invoicePayment = new DefaultInvoicePayment(paymentAttemptId, invoiceId, paymentAttemptDate);
dao.notifyOfPaymentAttempt(invoicePayment);
}
-
-}
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/test/DefaultInvoiceTestApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/test/DefaultInvoiceTestApi.java
new file mode 100644
index 0000000..bbec57f
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/test/DefaultInvoiceTestApi.java
@@ -0,0 +1,35 @@
+/*
+ * 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.api.test;
+
+import com.google.inject.Inject;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.dao.InvoiceDao;
+
+public class DefaultInvoiceTestApi implements InvoiceTestApi {
+ private final InvoiceDao invoiceDao;
+
+ @Inject
+ public DefaultInvoiceTestApi(InvoiceDao invoiceDao) {
+ this.invoiceDao = invoiceDao;
+ }
+
+ @Override
+ public void create(Invoice invoice) {
+ invoiceDao.create(invoice);
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
index 3bf9846..629f939 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -20,46 +20,60 @@ import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
+import com.ning.billing.invoice.api.InvoicePayment;
import org.joda.time.DateTime;
-
import com.google.inject.Inject;
import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.api.InvoiceUserApi;
import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.payment.api.InvoicePayment;
public class DefaultInvoiceUserApi implements InvoiceUserApi {
private final InvoiceDao dao;
@Inject
- public DefaultInvoiceUserApi(InvoiceDao dao) {
+ public DefaultInvoiceUserApi(final InvoiceDao dao) {
this.dao = dao;
}
@Override
- public List<UUID> getInvoicesForPayment(DateTime targetDate, int numberOfDays) {
- return dao.getInvoicesForPayment(targetDate.toDate(), numberOfDays);
+ public List<UUID> getInvoicesForPayment(final DateTime targetDate, final int numberOfDays) {
+ return dao.getInvoicesForPayment(targetDate, numberOfDays);
}
@Override
- public List<Invoice> getInvoicesByAccount(UUID accountId) {
- return dao.getInvoicesByAccount(accountId.toString());
+ public List<Invoice> getInvoicesByAccount(final UUID accountId) {
+ return dao.getInvoicesByAccount(accountId);
}
@Override
- public Invoice getInvoice(UUID invoiceId) {
- return dao.getById(invoiceId.toString());
+ public List<Invoice> getInvoicesByAccount(final UUID accountId, final DateTime fromDate) {
+ return dao.getInvoicesByAccount(accountId, fromDate);
}
@Override
public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
dao.notifyOfPaymentAttempt(invoicePayment);
}
-
- @Override
+
+ @Override
public BigDecimal getAccountBalance(UUID accountId) {
BigDecimal result = dao.getAccountBalance(accountId);
return result == null ? BigDecimal.ZERO : result;
}
+ @Override
+ public List<InvoiceItem> getInvoiceItemsByAccount(final UUID accountId) {
+ return dao.getInvoiceItemsByAccount(accountId);
+ }
+
+ @Override
+ public Invoice getInvoice(final UUID invoiceId) {
+ return dao.getById(invoiceId);
+ }
+
+ @Override
+ public List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final DateTime upToDate) {
+ return dao.getUnpaidInvoicesByAccountId(accountId, upToDate);
+ }
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
index 35f03c2..22849d1 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
@@ -17,53 +17,92 @@
package com.ning.billing.invoice.dao;
import java.math.BigDecimal;
-import java.util.Date;
+import java.util.Collection;
import java.util.List;
import java.util.UUID;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
+import org.joda.time.DateTime;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.Transaction;
import org.skife.jdbi.v2.TransactionStatus;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import com.google.inject.Inject;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceCreationNotification;
import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
import com.ning.billing.invoice.api.user.DefaultInvoiceCreationNotification;
-import com.ning.billing.payment.api.InvoicePayment;
-import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.invoice.notification.NextBillingDateNotifier;
+import com.ning.billing.util.bus.Bus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class DefaultInvoiceDao implements InvoiceDao {
- private final InvoiceSqlDao invoiceDao;
-
- private final EventBus eventBus;
private final static Logger log = LoggerFactory.getLogger(DefaultInvoiceDao.class);
+ private final InvoiceSqlDao invoiceSqlDao;
+ private final InvoiceItemSqlDao invoiceItemSqlDao;
+ private final InvoicePaymentSqlDao invoicePaymentSqlDao;
+ private final NextBillingDateNotifier notifier;
+ private final EntitlementBillingApi entitlementBillingApi;
+
+ private final Bus eventBus;
+
@Inject
- public DefaultInvoiceDao(final IDBI dbi, final EventBus eventBus) {
- this.invoiceDao = dbi.onDemand(InvoiceSqlDao.class);
+ public DefaultInvoiceDao(final IDBI dbi, final Bus eventBus,
+ final NextBillingDateNotifier notifier, final EntitlementBillingApi entitlementBillingApi) {
+ this.invoiceSqlDao = dbi.onDemand(InvoiceSqlDao.class);
+ this.invoiceItemSqlDao = dbi.onDemand(InvoiceItemSqlDao.class);
+ this.invoicePaymentSqlDao = dbi.onDemand(InvoicePaymentSqlDao.class);
this.eventBus = eventBus;
+ this.notifier = notifier;
+ this.entitlementBillingApi = entitlementBillingApi;
}
@Override
- public List<Invoice> getInvoicesByAccount(final String accountId) {
- return invoiceDao.getInvoicesByAccount(accountId);
+ public List<Invoice> getInvoicesByAccount(final UUID accountId) {
+ return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+ @Override
+ public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+ List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId.toString());
+
+ getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+ getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
+
+ return invoices;
+ }
+ });
+ }
+
+ @Override
+ public List<Invoice> getInvoicesByAccount(final UUID accountId, final DateTime fromDate) {
+ return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+ @Override
+ public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+ List<Invoice> invoices = invoiceDao.getInvoicesByAccountAfterDate(accountId.toString(), fromDate.toDate());
+
+ getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+ getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
+
+ return invoices;
+ }
+ });
+ }
+
+ @Override
+ public List<InvoiceItem> getInvoiceItemsByAccount(final UUID accountId) {
+ return invoiceItemSqlDao.getInvoiceItemsByAccount(accountId.toString());
}
@Override
public List<Invoice> get() {
- return invoiceDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+ return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
@Override
public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
List<Invoice> invoices = invoiceDao.get();
- InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
- for (Invoice invoice : invoices) {
- List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
- invoice.add(invoiceItems);
- }
+ getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+ getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
return invoices;
}
@@ -71,16 +110,20 @@ public class DefaultInvoiceDao implements InvoiceDao {
}
@Override
- public Invoice getById(final String invoiceId) {
- return invoiceDao.inTransaction(new Transaction<Invoice, InvoiceSqlDao>() {
+ public Invoice getById(final UUID invoiceId) {
+ return invoiceSqlDao.inTransaction(new Transaction<Invoice, InvoiceSqlDao>() {
@Override
public Invoice inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
- Invoice invoice = invoiceDao.getById(invoiceId);
+ Invoice invoice = invoiceDao.getById(invoiceId.toString());
if (invoice != null) {
InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
- List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoiceId);
- invoice.add(invoiceItems);
+ List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoiceId.toString());
+ invoice.addInvoiceItems(invoiceItems);
+
+ InvoicePaymentSqlDao invoicePaymentSqlDao = invoiceDao.become(InvoicePaymentSqlDao.class);
+ List<InvoicePayment> invoicePayments = invoicePaymentSqlDao.getPaymentsForInvoice(invoiceId.toString());
+ invoice.addPayments(invoicePayments);
}
return invoice;
@@ -90,76 +133,134 @@ public class DefaultInvoiceDao implements InvoiceDao {
@Override
public void create(final Invoice invoice) {
- invoiceDao.inTransaction(new Transaction<Void, InvoiceSqlDao>() {
- @Override
- public Void inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+ invoiceSqlDao.inTransaction(new Transaction<Void, InvoiceSqlDao>() {
+ @Override
+ public Void inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+
+ // STEPH this seems useless
Invoice currentInvoice = invoiceDao.getById(invoice.getId().toString());
if (currentInvoice == null) {
invoiceDao.create(invoice);
- List<InvoiceItem> invoiceItems = invoice.getItems();
+ List<InvoiceItem> invoiceItems = invoice.getInvoiceItems();
InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
- invoiceItemDao.create(invoiceItems);
+ invoiceItemDao.batchCreateFromTransaction(invoiceItems);
+
+ notifyOfFutureBillingEvents(invoiceSqlDao, invoiceItems);
+ setChargedThroughDates(invoiceSqlDao, invoiceItems);
+
+
+ // STEPH Why do we need that? Are the payments not always null at this point?
+ List<InvoicePayment> invoicePayments = invoice.getPayments();
+ InvoicePaymentSqlDao invoicePaymentSqlDao = invoiceDao.become(InvoicePaymentSqlDao.class);
+ invoicePaymentSqlDao.batchCreateFromTransaction(invoicePayments);
InvoiceCreationNotification event;
event = new DefaultInvoiceCreationNotification(invoice.getId(), invoice.getAccountId(),
- invoice.getAmountOutstanding(), invoice.getCurrency(),
+ invoice.getBalance(), invoice.getCurrency(),
invoice.getInvoiceDate());
- eventBus.post(event);
+ eventBus.postFromTransaction(event, invoiceDao);
}
return null;
- }
- });
+ }
+ });
}
@Override
- public List<Invoice> getInvoicesBySubscription(final String subscriptionId) {
- return invoiceDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
- @Override
- public List<Invoice> inTransaction(InvoiceSqlDao invoiceDao, TransactionStatus status) throws Exception {
- List<Invoice> invoices = invoiceDao.getInvoicesBySubscription(subscriptionId);
+ public List<Invoice> getInvoicesBySubscription(final UUID subscriptionId) {
+ return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+ @Override
+ public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+ List<Invoice> invoices = invoiceDao.getInvoicesBySubscription(subscriptionId.toString());
- InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
- for (Invoice invoice : invoices) {
- List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
- invoice.add(invoiceItems);
- }
+ getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+ getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
- return invoices;
- }
+ return invoices;
+ }
});
}
@Override
- public List<UUID> getInvoicesForPayment(Date targetDate, int numberOfDays) {
- return invoiceDao.getInvoicesForPayment(targetDate, numberOfDays);
+ public List<UUID> getInvoicesForPayment(final DateTime targetDate, final int numberOfDays) {
+ return invoiceSqlDao.getInvoicesForPayment(targetDate.toDate(), numberOfDays);
+ }
+
+ @Override
+ public BigDecimal getAccountBalance(final UUID accountId) {
+ return invoiceSqlDao.getAccountBalance(accountId.toString());
}
@Override
public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
- invoiceDao.notifyOfPaymentAttempt(invoicePayment);
+ invoicePaymentSqlDao.notifyOfPaymentAttempt(invoicePayment);
}
@Override
- public void test() {
- invoiceDao.test();
+ public List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final DateTime upToDate) {
+ return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+ @Override
+ public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+ List<Invoice> invoices = invoiceSqlDao.getUnpaidInvoicesByAccountId(accountId.toString(), upToDate.toDate());
+
+ getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+ getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
+
+ return invoices;
+ }
+ });
}
@Override
- public String getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId) {
- return invoiceDao.getInvoiceIdByPaymentAttemptId(paymentAttemptId.toString());
+ public UUID getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId) {
+ return invoiceSqlDao.getInvoiceIdByPaymentAttemptId(paymentAttemptId.toString());
}
@Override
public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
- return invoiceDao.getInvoicePayment(paymentAttemptId);
+ return invoicePaymentSqlDao.getInvoicePayment(paymentAttemptId);
+ }
+
+ @Override
+ public void test() {
+ invoiceSqlDao.test();
}
- @Override
- public BigDecimal getAccountBalance(UUID accountId) {
- return invoiceDao.getAccountBalance(accountId.toString());
- }
+ private void getInvoiceItemsWithinTransaction(final List<Invoice> invoices, final InvoiceSqlDao invoiceDao) {
+ InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
+ for (final Invoice invoice : invoices) {
+ List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
+ invoice.addInvoiceItems(invoiceItems);
+ }
+ }
+
+ private void getInvoicePaymentsWithinTransaction(final List<Invoice> invoices, final InvoiceSqlDao invoiceDao) {
+ InvoicePaymentSqlDao invoicePaymentSqlDao = invoiceDao.become(InvoicePaymentSqlDao.class);
+ for (final Invoice invoice : invoices) {
+ String invoiceId = invoice.getId().toString();
+ List<InvoicePayment> invoicePayments = invoicePaymentSqlDao.getPaymentsForInvoice(invoiceId);
+ invoice.addPayments(invoicePayments);
+ }
+ }
+ private void notifyOfFutureBillingEvents(final InvoiceSqlDao dao, final List<InvoiceItem> invoiceItems) {
+ for (final InvoiceItem item : invoiceItems) {
+ if ((item.getEndDate() != null) &&
+ (item.getRecurringAmount() == null || item.getRecurringAmount().compareTo(BigDecimal.ZERO) >= 0)) {
+ notifier.insertNextBillingNotification(dao, item.getSubscriptionId(), item.getEndDate());
+ }
+ }
+ }
+
+ private void setChargedThroughDates(final InvoiceSqlDao dao, final Collection<InvoiceItem> invoiceItems) {
+ for (InvoiceItem item : invoiceItems) {
+ if ((item.getEndDate() != null) &&
+ (item.getRecurringAmount() == null || item.getRecurringAmount().compareTo(BigDecimal.ZERO) >= 0)) {
+ log.info("Setting CTD for invoice item {} to {}", item.getId().toString(), item.getEndDate().toString());
+ entitlementBillingApi.setChargedThroughDateFromTransaction(dao, item.getSubscriptionId(), item.getEndDate());
+ }
+ }
+ }
}
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
index 322c286..7a7c280 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
@@ -16,36 +16,42 @@
package com.ning.billing.invoice.dao;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import org.joda.time.DateTime;
+
import java.math.BigDecimal;
-import java.util.Date;
import java.util.List;
import java.util.UUID;
-import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.payment.api.InvoicePayment;
-
public interface InvoiceDao {
void create(Invoice invoice);
- Invoice getById(final String id);
+ Invoice getById(final UUID id);
List<Invoice> get();
- List<Invoice> getInvoicesByAccount(final String accountId);
+ List<Invoice> getInvoicesByAccount(final UUID accountId);
- List<Invoice> getInvoicesBySubscription(final String subscriptionId);
+ List<Invoice> getInvoicesByAccount(final UUID accountId, final DateTime fromDate);
- List<UUID> getInvoicesForPayment(final Date targetDate,
+ List<InvoiceItem> getInvoiceItemsByAccount(final UUID accountId);
+
+ List<Invoice> getInvoicesBySubscription(final UUID subscriptionId);
+
+ List<UUID> getInvoicesForPayment(final DateTime targetDate,
final int numberOfDays);
- String getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId);
+ UUID getInvoiceIdByPaymentAttemptId(final UUID paymentAttemptId);
- void test();
+ InvoicePayment getInvoicePayment(final UUID paymentAttemptId);
- InvoicePayment getInvoicePayment(UUID paymentAttemptId);
+ void notifyOfPaymentAttempt(final InvoicePayment invoicePayment);
- void notifyOfPaymentAttempt(InvoicePayment invoicePayment);
+ BigDecimal getAccountBalance(final UUID accountId);
- BigDecimal getAccountBalance(UUID accountId);
+ List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final DateTime upToDate);
+ void test();
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
index 6f4e47b..a76cd06 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
@@ -19,17 +19,26 @@ package com.ning.billing.invoice.dao;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.model.DefaultInvoiceItem;
-import com.ning.billing.util.entity.EntityCollectionDao;
import com.ning.billing.util.entity.EntityDao;
import org.joda.time.DateTime;
import org.skife.jdbi.v2.SQLStatement;
import org.skife.jdbi.v2.StatementContext;
-import org.skife.jdbi.v2.sqlobject.*;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
+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 org.skife.jdbi.v2.tweak.ResultSetMapper;
-import java.lang.annotation.*;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -56,25 +65,29 @@ public interface InvoiceItemSqlDao extends EntityDao<InvoiceItem> {
@SqlUpdate
void update(@InvoiceItemBinder final InvoiceItem invoiceItem);
- @SqlBatch
- void create(@InvoiceItemBinder final List<InvoiceItem> items);
+ @SqlBatch(transactional=false)
+ void batchCreateFromTransaction(@InvoiceItemBinder final List<InvoiceItem> items);
@BindingAnnotation(InvoiceItemBinder.InvoiceItemBinderFactory.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface InvoiceItemBinder {
public static class InvoiceItemBinderFactory implements BinderFactory {
+ @Override
public Binder build(Annotation annotation) {
return new Binder<InvoiceItemBinder, InvoiceItem>() {
+ @Override
public void bind(SQLStatement q, InvoiceItemBinder bind, InvoiceItem item) {
q.bind("id", item.getId().toString());
q.bind("invoiceId", item.getInvoiceId().toString());
q.bind("subscriptionId", item.getSubscriptionId().toString());
+ q.bind("planName", item.getPlanName());
+ q.bind("phaseName", item.getPhaseName());
q.bind("startDate", item.getStartDate().toDate());
q.bind("endDate", item.getEndDate().toDate());
- q.bind("description", item.getDescription());
- q.bind("amount", item.getAmount());
- q.bind("rate", item.getRate());
+ q.bind("recurringAmount", item.getRecurringAmount());
+ q.bind("recurringRate", item.getRecurringRate());
+ q.bind("fixedAmount", item.getFixedAmount());
q.bind("currency", item.getCurrency().toString());
}
};
@@ -88,14 +101,17 @@ public interface InvoiceItemSqlDao extends EntityDao<InvoiceItem> {
UUID id = UUID.fromString(result.getString("id"));
UUID invoiceId = UUID.fromString(result.getString("invoice_id"));
UUID subscriptionId = UUID.fromString(result.getString("subscription_id"));
+ String planName = result.getString("plan_name");
+ String phaseName = result.getString("phase_name");
DateTime startDate = new DateTime(result.getTimestamp("start_date"));
DateTime endDate = new DateTime(result.getTimestamp("end_date"));
- String description = result.getString("description");
- BigDecimal amount = result.getBigDecimal("amount");
- BigDecimal rate = result.getBigDecimal("rate");
+ BigDecimal recurringAmount = result.getBigDecimal("recurring_amount");
+ BigDecimal recurringRate = result.getBigDecimal("recurring_rate");
+ BigDecimal fixedAmount = result.getBigDecimal("fixed_amount");
Currency currency = Currency.valueOf(result.getString("currency"));
- return new DefaultInvoiceItem(id, invoiceId, subscriptionId, startDate, endDate, description, amount, rate , currency);
+ return new DefaultInvoiceItem(id, invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
+ recurringAmount, recurringRate, fixedAmount, currency);
}
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
new file mode 100644
index 0000000..7179ec1
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
@@ -0,0 +1,147 @@
+/*
+ * 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 java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.catalog.api.Currency;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.*;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.invoice.api.InvoicePayment;
+
+@ExternalizedSqlViaStringTemplate3
+@RegisterMapper(InvoicePaymentSqlDao.InvoicePaymentMapper.class)
+public interface InvoicePaymentSqlDao {
+ @SqlQuery
+ public InvoicePayment getByPaymentAttemptId(@Bind("paymentAttempt") final String paymentAttemptId);
+
+ @SqlQuery
+ public List<InvoicePayment> get();
+
+ @SqlUpdate
+ public void create(@InvoicePaymentBinder InvoicePayment invoicePayment);
+
+ @SqlBatch(transactional=false)
+ void batchCreateFromTransaction(@InvoicePaymentBinder List<InvoicePayment> items);
+
+ @SqlUpdate
+ public void update(@InvoicePaymentBinder InvoicePayment invoicePayment);
+
+ @SqlQuery
+ public List<InvoicePayment> getPaymentsForInvoice(@Bind("invoiceId") String invoiceId);
+
+ @SqlQuery
+ InvoicePayment getInvoicePayment(@Bind("paymentAttemptId") UUID paymentAttemptId);
+
+ @SqlUpdate
+ void notifyOfPaymentAttempt(@InvoicePaymentBinder InvoicePayment invoicePayment);
+
+ public static class InvoicePaymentMapper implements ResultSetMapper<InvoicePayment> {
+ private DateTime getDate(ResultSet rs, String fieldName) throws SQLException {
+ final Timestamp resultStamp = rs.getTimestamp(fieldName);
+ return rs.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
+ }
+
+ @Override
+ public InvoicePayment map(int index, ResultSet result, StatementContext context) throws SQLException {
+ final UUID paymentAttemptId = UUID.fromString(result.getString("payment_attempt_id"));
+ final UUID invoiceId = UUID.fromString(result.getString("invoice_id"));
+ final DateTime paymentAttemptDate = getDate(result, "payment_attempt_date");
+ final BigDecimal amount = result.getBigDecimal("amount");
+ final String currencyString = result.getString("currency");
+ final Currency currency = (currencyString == null) ? null : Currency.valueOf(currencyString);
+ final DateTime createdDate = getDate(result, "created_date");
+ final DateTime updatedDate = getDate(result, "updated_date");
+
+ return new InvoicePayment() {
+ private final DateTime now = new DateTime();
+
+ @Override
+ public UUID getPaymentAttemptId() {
+ return paymentAttemptId;
+ }
+ @Override
+ public UUID getInvoiceId() {
+ return invoiceId;
+ }
+ @Override
+ public DateTime getPaymentAttemptDate() {
+ return paymentAttemptDate;
+ }
+ @Override
+ public BigDecimal getAmount() {
+ return amount;
+ }
+ @Override
+ public Currency getCurrency() {
+ return currency;
+ }
+ @Override
+ public DateTime getCreatedDate() {
+ return createdDate ;
+ }
+ @Override
+ public DateTime getUpdatedDate() {
+ return updatedDate;
+ }
+ };
+ }
+ }
+
+ @BindingAnnotation(InvoicePaymentBinder.InvoicePaymentBinderFactory.class)
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.PARAMETER})
+ public @interface InvoicePaymentBinder {
+ public static class InvoicePaymentBinderFactory implements BinderFactory {
+ @Override
+ public Binder build(Annotation annotation) {
+ return new Binder<InvoicePaymentBinder, InvoicePayment>() {
+ @Override
+ public void bind(SQLStatement q, InvoicePaymentBinder bind, InvoicePayment payment) {
+ q.bind("invoiceId", payment.getInvoiceId().toString());
+ q.bind("paymentAttemptId", payment.getPaymentAttemptId().toString());
+ q.bind("paymentAttemptDate", payment.getPaymentAttemptDate().toDate());
+ q.bind("amount", payment.getAmount());
+ Currency currency = payment.getCurrency();
+ q.bind("currency", (currency == null) ? null : currency.toString());
+ DateTime createdDate = payment.getCreatedDate();
+ q.bind("createdDate", (createdDate == null) ? new DateTime().toDate() : createdDate.toDate());
+ DateTime updatedDate = payment.getUpdatedDate();
+ q.bind("updatedDate", (updatedDate == null) ? new DateTime().toDate() : updatedDate.toDate());
+ }
+ };
+ }
+ }
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
index 1f37102..05be556 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
@@ -16,22 +16,12 @@
package com.ning.billing.invoice.dao;
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.math.BigDecimal;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.UUID;
-
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.util.UuidMapper;
+import com.ning.billing.util.entity.EntityDao;
import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
import org.skife.jdbi.v2.SQLStatement;
import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.sqlobject.Bind;
@@ -47,16 +37,20 @@ 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.Currency;
-import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.model.DefaultInvoice;
-import com.ning.billing.payment.api.InvoicePayment;
-import com.ning.billing.util.UuidMapper;
-import com.ning.billing.util.entity.EntityDao;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
@ExternalizedSqlViaStringTemplate3()
-@RegisterMapper({UuidMapper.class, InvoiceSqlDao.InvoiceMapper.class})
+@RegisterMapper(InvoiceSqlDao.InvoiceMapper.class)
public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<InvoiceSqlDao>, Transmogrifier, CloseMe {
@Override
@SqlUpdate
@@ -70,43 +64,28 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
List<Invoice> getInvoicesByAccount(@Bind("accountId") final String accountId);
@SqlQuery
- List<Invoice> getInvoicesBySubscription(@Bind("subscriptionId") final String subscriptionId);
+ List<Invoice> getInvoicesByAccountAfterDate(@Bind("accountId") final String accountId,
+ @Bind("fromDate") final Date fromDate);
@SqlQuery
- String getInvoiceIdByPaymentAttemptId(@Bind("paymentAttemptId") final String paymentAttemptId);
+ List<Invoice> getInvoicesBySubscription(@Bind("subscriptionId") final String subscriptionId);
@SqlQuery
- List<UUID> getInvoicesForPayment(@Bind("targetDate") final Date targetDate,
- @Bind("numberOfDays") final int numberOfDays);
+ @RegisterMapper(UuidMapper.class)
+ UUID getInvoiceIdByPaymentAttemptId(@Bind("paymentAttemptId") final String paymentAttemptId);
@SqlQuery
- InvoicePayment getInvoicePayment(@Bind("paymentAttemptId") UUID paymentAttemptId);
+ @RegisterMapper(UuidMapper.class)
+ List<UUID> getInvoicesForPayment(@Bind("targetDate") final Date targetDate,
+ @Bind("numberOfDays") final int numberOfDays);
- @SqlUpdate
- void notifyOfPaymentAttempt(@Bind(binder = InvoicePaymentBinder.class) InvoicePayment invoicePayment);
-
@SqlQuery
@RegisterMapper(BalanceMapper.class)
BigDecimal getAccountBalance(@Bind("accountId") final String accountId);
- public static class BalanceMapper implements ResultSetMapper<BigDecimal> {
- @Override
- public BigDecimal map(final int index, final ResultSet result, final StatementContext context) throws SQLException {
- BigDecimal amount_invoiced = result.getBigDecimal("amount_invoiced");
- BigDecimal amount_paid = result.getBigDecimal("amount_paid");
-
- if (amount_invoiced == null) {
- amount_invoiced = BigDecimal.ZERO;
- }
-
- if (amount_paid == null) {
- amount_paid = BigDecimal.ZERO;
- }
-
- return amount_invoiced.subtract(amount_paid);
- };
- }
-
+ @SqlQuery
+ List<Invoice> getUnpaidInvoicesByAccountId(@Bind("accountId") final String accountId,
+ @Bind("upToDate") final Date upToDate);
@BindingAnnotation(InvoiceBinder.InvoiceBinderFactory.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
@@ -122,7 +101,7 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
q.bind("invoiceDate", invoice.getInvoiceDate().toDate());
q.bind("targetDate", invoice.getTargetDate().toDate());
q.bind("amountPaid", invoice.getAmountPaid());
- q.bind("amountOutstanding", invoice.getAmountOutstanding());
+ q.bind("amountOutstanding", invoice.getBalance());
DateTime last_payment_date = invoice.getLastPaymentAttempt();
q.bind("lastPaymentAttempt", last_payment_date == null ? null : last_payment_date.toDate());
q.bind("currency", invoice.getCurrency().toString());
@@ -139,51 +118,27 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
UUID accountId = UUID.fromString(result.getString("account_id"));
DateTime invoiceDate = new DateTime(result.getTimestamp("invoice_date"));
DateTime targetDate = new DateTime(result.getTimestamp("target_date"));
- BigDecimal amountPaid = result.getBigDecimal("amount_paid");
- if (amountPaid == null) {
- amountPaid = BigDecimal.ZERO;
- }
- Timestamp lastPaymentAttemptTimeStamp = result.getTimestamp("last_payment_attempt");
- DateTime lastPaymentAttempt = lastPaymentAttemptTimeStamp == null ? null : new DateTime(lastPaymentAttemptTimeStamp);
Currency currency = Currency.valueOf(result.getString("currency"));
- return new DefaultInvoice(id, accountId, invoiceDate, targetDate, currency, lastPaymentAttempt, amountPaid, new ArrayList<InvoiceItem>());
+ return new DefaultInvoice(id, accountId, invoiceDate, targetDate, currency);
}
}
- @SqlUpdate
- void notifyFailedPayment(@Bind(binder = InvoicePaymentBinder.class) InvoicePayment invoicePayment);
-
- public static final class InvoicePaymentBinder implements Binder<Bind, InvoicePayment> {
-
+ public static class BalanceMapper implements ResultSetMapper<BigDecimal> {
@Override
- public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, InvoicePayment invoicePayment) {
- stmt.bind("invoice_id", invoicePayment.getInvoiceId().toString());
- stmt.bind("amount", invoicePayment.getAmount());
- stmt.bind("currency", invoicePayment.getCurrency().toString());
- stmt.bind("payment_attempt_id", invoicePayment.getPaymentAttemptId().toString());
- stmt.bind("payment_attempt_date", invoicePayment.getPaymentAttemptDate() == null ? null : invoicePayment.getPaymentAttemptDate().toDate());
- }
- }
-
-
- public static class InvoicePaymentMapper implements ResultSetMapper<InvoicePayment> {
-
- private DateTime getDate(ResultSet rs, String fieldName) throws SQLException {
- final Timestamp resultStamp = rs.getTimestamp(fieldName);
- return rs.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
- }
+ public BigDecimal map(final int index, final ResultSet result, final StatementContext context) throws SQLException {
+ BigDecimal amountInvoiced = result.getBigDecimal("amount_invoiced");
+ BigDecimal amountPaid = result.getBigDecimal("amount_paid");
- @Override
- public InvoicePayment map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
+ if (amountInvoiced == null) {
+ amountInvoiced = BigDecimal.ZERO;
+ }
- UUID invoiceId = UUID.fromString(rs.getString("invoice_id"));
- BigDecimal amount = rs.getBigDecimal("amount");
- Currency currency = Currency.valueOf(rs.getString("currency"));
- UUID paymentAttemptId = UUID.fromString(rs.getString("payment_attempt_id"));
- DateTime paymentAttemptDate = getDate(rs, "payment_attempt_date");
+ if (amountPaid == null) {
+ amountPaid = BigDecimal.ZERO;
+ }
- return new InvoicePayment(invoiceId, amount, currency, paymentAttemptId, paymentAttemptDate);
+ return amountInvoiced.subtract(amountPaid);
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java b/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
index 63ed8fb..372952b 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
@@ -16,14 +16,26 @@
package com.ning.billing.invoice.glue;
+import org.skife.config.ConfigurationObjectFactory;
+
import com.google.inject.AbstractModule;
+import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.invoice.InvoiceListener;
+import com.ning.billing.invoice.api.DefaultInvoiceService;
import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.api.InvoiceService;
import com.ning.billing.invoice.api.InvoiceUserApi;
import com.ning.billing.invoice.api.invoice.DefaultInvoicePaymentApi;
import com.ning.billing.invoice.api.user.DefaultInvoiceUserApi;
import com.ning.billing.invoice.dao.DefaultInvoiceDao;
import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
+import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.invoice.notification.DefaultNextBillingDateNotifier;
+import com.ning.billing.invoice.notification.NextBillingDateNotifier;
import com.ning.billing.util.glue.ClockModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
+
public class InvoiceModule extends AbstractModule {
protected void installInvoiceDao() {
@@ -37,15 +49,42 @@ public class InvoiceModule extends AbstractModule {
protected void installInvoicePaymentApi() {
bind(InvoicePaymentApi.class).to(DefaultInvoicePaymentApi.class).asEagerSingleton();
}
-
+
protected void installClock() {
install(new ClockModule());
}
+ protected void installConfig() {
+ final InvoiceConfig config = new ConfigurationObjectFactory(System.getProperties()).build(InvoiceConfig.class);
+ bind(InvoiceConfig.class).toInstance(config);
+ }
+
+ protected void installInvoiceService() {
+ bind(InvoiceService.class).to(DefaultInvoiceService.class).asEagerSingleton();
+ }
+
+ protected void installNotifier() {
+ bind(NextBillingDateNotifier.class).to(DefaultNextBillingDateNotifier.class).asEagerSingleton();
+ }
+
+ protected void installGlobalLocker() {
+ install(new GlobalLockerModule());
+ }
+
+ protected void installInvoiceListener() {
+ bind(InvoiceListener.class).asEagerSingleton();
+ }
+
@Override
protected void configure() {
+ installInvoiceService();
+ installNotifier();
+ installInvoiceListener();
+ bind(InvoiceGenerator.class).to(DefaultInvoiceGenerator.class).asEagerSingleton();
+ installConfig();
installInvoiceDao();
installInvoiceUserApi();
installInvoicePaymentApi();
+ installGlobalLocker();
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
new file mode 100644
index 0000000..a86e063
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
@@ -0,0 +1,181 @@
+/*
+ * 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;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+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.InvoiceDao;
+import com.ning.billing.invoice.model.BillingEventSet;
+import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.invoice.model.InvoiceItemList;
+import com.ning.billing.invoice.notification.NextBillingDateEvent;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.globalLocker.GlobalLock;
+import com.ning.billing.util.globalLocker.GlobalLocker;
+import com.ning.billing.util.globalLocker.GlobalLocker.LockerService;
+import com.ning.billing.util.globalLocker.LockFailedException;
+
+public class InvoiceListener {
+
+
+ private final static Logger log = LoggerFactory.getLogger(InvoiceListener.class);
+ private final static int NB_LOCK_TRY = 5;
+
+ private final InvoiceGenerator generator;
+ private final EntitlementBillingApi entitlementBillingApi;
+ private final AccountUserApi accountUserApi;
+ private final InvoiceDao invoiceDao;
+ private final Clock clock;
+ private final GlobalLocker locker;
+
+ private final static boolean VERBOSE_OUTPUT = false;
+
+ @Inject
+ public InvoiceListener(final InvoiceGenerator generator, final AccountUserApi accountUserApi,
+ final EntitlementBillingApi entitlementBillingApi,
+ final InvoiceDao invoiceDao,
+ final GlobalLocker locker,
+ final Clock clock) {
+ this.generator = generator;
+ this.entitlementBillingApi = entitlementBillingApi;
+ this.accountUserApi = accountUserApi;
+ this.invoiceDao = invoiceDao;
+ this.locker = locker;
+ this.clock = clock;
+ }
+
+ @Subscribe
+ public void handleSubscriptionTransition(final SubscriptionTransition transition) {
+ processSubscription(transition);
+ }
+
+ @Subscribe
+ public void handleNextBillingDateEvent(final NextBillingDateEvent event) {
+ // STEPH should we use the date of the event instead?
+ processSubscription(event.getSubscriptionId(), clock.getUTCNow());
+ }
+
+ private void processSubscription(final SubscriptionTransition transition) {
+ UUID subscriptionId = transition.getSubscriptionId();
+ DateTime targetDate = transition.getEffectiveTransitionTime();
+ log.info("Got subscription transition from InvoiceListener. id: " + subscriptionId.toString() + "; targetDate: " + targetDate.toString());
+ log.info("Transition type: " + transition.getTransitionType().toString());
+ processSubscription(subscriptionId, targetDate);
+ }
+
+ private void processSubscription(final UUID subscriptionId, final DateTime targetDate) {
+
+ if (subscriptionId == null) {
+ log.error("Failed handling entitlement change.", new InvoiceApiException(ErrorCode.INVOICE_INVALID_TRANSITION));
+ return;
+ }
+
+ UUID accountId = entitlementBillingApi.getAccountIdFromSubscriptionId(subscriptionId);
+ if (accountId == null) {
+ log.error("Failed handling entitlement change.",
+ new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, subscriptionId.toString()));
+ return;
+ }
+
+ GlobalLock lock = null;
+ try {
+ lock = locker.lockWithNumberOfTries(LockerService.INVOICE, accountId.toString(), NB_LOCK_TRY);
+
+ processAccountWithLock(accountId, targetDate);
+
+ } catch (LockFailedException e) {
+ // Not good!
+ log.error(String.format("Failed to process invoice for account %s, subscription %s, targetDate %s",
+ accountId.toString(), subscriptionId.toString(), targetDate), e);
+ } finally {
+ if (lock != null) {
+ lock.release();
+ }
+ }
+ }
+
+ private void processAccountWithLock(final UUID accountId, final DateTime targetDate) {
+
+ Account account = accountUserApi.getAccountById(accountId);
+ if (account == null) {
+ log.error("Failed handling entitlement change.",
+ new InvoiceApiException(ErrorCode.INVOICE_ACCOUNT_ID_INVALID, accountId.toString()));
+ return;
+ }
+
+ SortedSet<BillingEvent> events = entitlementBillingApi.getBillingEventsForAccount(accountId);
+ BillingEventSet billingEvents = new BillingEventSet(events);
+
+ Currency targetCurrency = account.getCurrency();
+
+ List<InvoiceItem> items = invoiceDao.getInvoiceItemsByAccount(accountId);
+ InvoiceItemList invoiceItemList = new InvoiceItemList(items);
+ Invoice invoice = generator.generateInvoice(accountId, billingEvents, invoiceItemList, targetDate, targetCurrency);
+
+ if (invoice == null) {
+ log.info("Generated null invoice.");
+ outputDebugData(events, invoiceItemList);
+ } else {
+ log.info("Generated invoice {} with {} items.", invoice.getId().toString(), invoice.getNumberOfItems());
+
+ if (VERBOSE_OUTPUT) {
+ log.info("New items");
+ for (InvoiceItem item : invoice.getInvoiceItems()) {
+ log.info(item.toString());
+ }
+ }
+ outputDebugData(events, invoiceItemList);
+
+ if (invoice.getNumberOfItems() > 0) {
+ invoiceDao.create(invoice);
+ }
+ }
+ }
+
+ private void outputDebugData(Collection<BillingEvent> events, Collection<InvoiceItem> invoiceItemList) {
+ if (VERBOSE_OUTPUT) {
+ log.info("Events");
+ for (BillingEvent event : events) {
+ log.info(event.toString());
+ }
+
+ log.info("Existing items");
+ for (InvoiceItem item : invoiceItemList) {
+ log.info(item.toString());
+ }
+ }
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java b/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java
index 361a151..0ce3b52 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java
@@ -19,8 +19,18 @@ package com.ning.billing.invoice.model;
import com.ning.billing.entitlement.api.billing.BillingEvent;
import java.util.ArrayList;
+import java.util.Collection;
public class BillingEventSet extends ArrayList<BillingEvent> {
+ public BillingEventSet() {
+ super();
+ }
+
+ public BillingEventSet(Collection<BillingEvent> events) {
+ super();
+ addAll(events);
+ }
+
public BillingEvent getLast() {
if (this.size() == 0) {return null;}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java b/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java
index 949f711..ffdf806 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java
@@ -27,6 +27,10 @@ public abstract class BillingModeBase implements BillingMode {
if (endDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+ if (billingPeriod == BillingPeriod.NO_BILLING_PERIOD) {
+ return BigDecimal.ZERO;
+ }
+
BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
@@ -56,10 +60,6 @@ public abstract class BillingModeBase implements BillingMode {
return precedingProRation.add(numberOfBillingPeriods);
}
- DateTime buildDate(final int year, final int month, final int day) {
- return new DateTime(year, month, day, 0, 0, 0, 0);
- }
-
boolean isNotBetween(DateTime targetDate, DateTime startDate, DateTime endDate) {
return (targetDate.isBefore(startDate) || !targetDate.isBefore(endDate));
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java b/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java
index 9f21ca6..b1e2fa9 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java
@@ -33,7 +33,11 @@ public class DateRange {
* @return whether the DateRange contains (inclusively) the DateTime in question
*/
public boolean contains(DateTime date) {
- return (!date.isBefore(startDate)) && (!date.isAfter(endDate));
+ if (endDate == null) {
+ return date.compareTo(startDate) >= 0;
+ }
+
+ return !date.isBefore(startDate) && !date.isAfter(endDate);
}
public boolean overlaps(DateRange range) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java
index 17926f7..8ee40e3 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java
@@ -16,6 +16,13 @@
package com.ning.billing.invoice.model;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.util.clock.DefaultClock;
+import org.joda.time.DateTime;
+
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@@ -29,55 +36,65 @@ import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.util.clock.DefaultClock;
public class DefaultInvoice implements Invoice {
- private final InvoiceItemList items = new InvoiceItemList();
+ private final InvoiceItemList invoiceItems = new InvoiceItemList();
+ private final List<InvoicePayment> payments = new ArrayList<InvoicePayment>();
private final UUID id;
private final UUID accountId;
private final DateTime invoiceDate;
private final DateTime targetDate;
private final Currency currency;
- private final BigDecimal amountPaid;
- private final DateTime lastPaymentAttempt;
public DefaultInvoice(UUID accountId, DateTime targetDate, Currency currency) {
- this(UUID.randomUUID(), accountId, new DefaultClock().getUTCNow(), targetDate, currency, null, BigDecimal.ZERO, new ArrayList<InvoiceItem>());
+ this(UUID.randomUUID(), accountId, new DefaultClock().getUTCNow(), targetDate, currency);
}
public DefaultInvoice(UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime targetDate,
- Currency currency, DateTime lastPaymentAttempt, BigDecimal amountPaid) {
- this(invoiceId, accountId, invoiceDate, targetDate, currency, lastPaymentAttempt, amountPaid, new ArrayList<InvoiceItem>());
- }
-
- public DefaultInvoice(UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime targetDate,
- Currency currency, DateTime lastPaymentAttempt, BigDecimal amountPaid,
- List<InvoiceItem> invoiceItems) {
+ Currency currency) {
this.id = invoiceId;
this.accountId = accountId;
this.invoiceDate = invoiceDate;
this.targetDate = targetDate;
this.currency = currency;
- this.lastPaymentAttempt= lastPaymentAttempt;
- this.amountPaid = amountPaid;
- this.items.addAll(invoiceItems);
}
@Override
- public boolean add(InvoiceItem item) {
- return items.add(item);
+ public boolean addInvoiceItem(final InvoiceItem item) {
+ return invoiceItems.add(item);
}
@Override
- public boolean add(List<InvoiceItem> items) {
- return this.items.addAll(items);
+ public boolean addInvoiceItems(final List<InvoiceItem> items) {
+ return this.invoiceItems.addAll(items);
}
@Override
- public List<InvoiceItem> getItems() {
- return items;
+ public List<InvoiceItem> getInvoiceItems() {
+ return invoiceItems;
}
@Override
public int getNumberOfItems() {
- return items.size();
+ return invoiceItems.size();
+ }
+
+ @Override
+ public boolean addPayment(final InvoicePayment payment) {
+ return payments.add(payment);
+ }
+
+ @Override
+ public boolean addPayments(final List<InvoicePayment> payments) {
+ return this.payments.addAll(payments);
+ }
+
+ @Override
+ public List<InvoicePayment> getPayments() {
+ return payments;
+ }
+
+ @Override
+ public int getNumberOfPayments() {
+ return payments.size();
}
@Override
@@ -107,21 +124,40 @@ public class DefaultInvoice implements Invoice {
@Override
public DateTime getLastPaymentAttempt() {
+ DateTime lastPaymentAttempt = null;
+
+ for (final InvoicePayment paymentAttempt : payments) {
+ DateTime paymentAttemptDate = paymentAttempt.getPaymentAttemptDate();
+ if (lastPaymentAttempt == null) {
+ lastPaymentAttempt = paymentAttemptDate;
+ }
+
+ if (lastPaymentAttempt.isBefore(paymentAttemptDate)) {
+ lastPaymentAttempt = paymentAttemptDate;
+ }
+ }
+
return lastPaymentAttempt;
}
@Override
public BigDecimal getAmountPaid() {
+ BigDecimal amountPaid = BigDecimal.ZERO;
+ for (final InvoicePayment payment : payments) {
+ if (payment.getAmount() != null) {
+ amountPaid = amountPaid.add(payment.getAmount());
+ }
+ }
return amountPaid;
}
@Override
public BigDecimal getTotalAmount() {
- return items.getTotalAmount();
+ return invoiceItems.getTotalAmount();
}
@Override
- public BigDecimal getAmountOutstanding() {
+ public BigDecimal getBalance() {
return getTotalAmount().subtract(getAmountPaid());
}
@@ -131,6 +167,7 @@ public class DefaultInvoice implements Invoice {
return false;
}
+ DateTime lastPaymentAttempt = getLastPaymentAttempt();
if (lastPaymentAttempt == null) {
return true;
}
@@ -140,7 +177,7 @@ public class DefaultInvoice implements Invoice {
@Override
public String toString() {
- return "DefaultInvoice [items=" + items + ", id=" + id + ", accountId=" + accountId + ", invoiceDate=" + invoiceDate + ", targetDate=" + targetDate + ", currency=" + currency + ", amountPaid=" + amountPaid + ", lastPaymentAttempt=" + lastPaymentAttempt + "]";
+ return "DefaultInvoice [items=" + invoiceItems + ", payments=" + payments + ", id=" + id + ", accountId=" + accountId + ", invoiceDate=" + invoiceDate + ", targetDate=" + targetDate + ", currency=" + currency + ", amountPaid=" + getAmountPaid() + ", lastPaymentAttempt=" + getLastPaymentAttempt() + "]";
}
}
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 34a63b2..c83be80 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
@@ -16,18 +16,17 @@
package com.ning.billing.invoice.model;
-
import java.math.BigDecimal;
-import java.util.ArrayList;
import java.util.Collections;
-import java.util.List;
+import java.util.Iterator;
import java.util.UUID;
-
import org.joda.time.DateTime;
+import org.joda.time.Days;
+import org.joda.time.Duration;
+import org.joda.time.Period;
import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.CatalogApiException;
import com.ning.billing.catalog.api.Currency;
@@ -36,61 +35,82 @@ import com.ning.billing.entitlement.api.billing.BillingModeType;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceItem;
+import javax.annotation.Nullable;
+
public class DefaultInvoiceGenerator implements InvoiceGenerator {
- private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceGenerator.class);
+ private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceGenerator.class);
+
@Override
- public Invoice generateInvoice(final UUID accountId, final BillingEventSet events, final InvoiceItemList existingItems, final DateTime targetDate, final Currency targetCurrency) {
- if (events == null) {return new DefaultInvoice(accountId, targetDate, targetCurrency);}
- if (events.size() == 0) {return new DefaultInvoice(accountId, targetDate, targetCurrency);}
+ public Invoice generateInvoice(final UUID accountId, final BillingEventSet events,
+ @Nullable final InvoiceItemList existingItems, final DateTime targetDate,
+ final Currency targetCurrency) {
+ if (events == null) {
+ return null;
+ }
+
+ if (events.size() == 0) {
+ return null;
+ }
DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, targetCurrency);
InvoiceItemList currentItems = generateInvoiceItems(events, invoice.getId(), targetDate, targetCurrency);
InvoiceItemList itemsToPost = reconcileInvoiceItems(invoice.getId(), currentItems, existingItems);
- invoice.add(itemsToPost);
- return invoice;
+ if (itemsToPost.size() == 0) {
+ return null;
+ } else {
+ invoice.addInvoiceItems(itemsToPost);
+ return invoice;
+ }
}
- private InvoiceItemList reconcileInvoiceItems(final UUID invoiceId, final InvoiceItemList currentInvoiceItems, final InvoiceItemList existingInvoiceItems) {
+ private InvoiceItemList reconcileInvoiceItems(final UUID invoiceId, final InvoiceItemList currentInvoiceItems,
+ final InvoiceItemList existingInvoiceItems) {
+ if ((existingInvoiceItems == null) || (existingInvoiceItems.size() == 0)) {
+ return currentInvoiceItems;
+ }
+
InvoiceItemList currentItems = new InvoiceItemList();
- for (InvoiceItem item : currentInvoiceItems) {
+ for (final InvoiceItem item : currentInvoiceItems) {
currentItems.add(new DefaultInvoiceItem(item, invoiceId));
}
+ // STEPH why clone? Why cast?
InvoiceItemList existingItems = (InvoiceItemList) existingInvoiceItems.clone();
Collections.sort(currentItems);
Collections.sort(existingItems);
- List<InvoiceItem> existingItemsToRemove = new ArrayList<InvoiceItem>();
+ for (final InvoiceItem currentItem : currentItems) {
+ Iterator<InvoiceItem> it = existingItems.iterator();
- for (InvoiceItem currentItem : currentItems) {
// see if there are any existing items that are covered by the current item
- for (InvoiceItem existingItem : existingItems) {
+ while (it.hasNext()) {
+ InvoiceItem existingItem = it.next();
+ // STEPH this is more like 'contained' that 'duplicates'
if (currentItem.duplicates(existingItem)) {
currentItem.subtract(existingItem);
- existingItemsToRemove.add(existingItem);
+ it.remove();
}
}
}
- existingItems.removeAll(existingItemsToRemove);
-
// remove cancelling pairs of invoice items
existingItems.removeCancellingPairs();
- // remove zero-dollar invoice items
- currentItems.removeZeroDollarItems();
-
// add existing items that aren't covered by current items as credit items
- for (InvoiceItem existingItem : existingItems) {
- currentItems.add(existingItem.asCredit(invoiceId));
+ for (final InvoiceItem existingItem : existingItems) {
+ // STEPH do we really want to credit if that has not been paid yet?
+ currentItems.add(existingItem.asCredit(existingItem.getInvoiceId()));
}
+ currentItems.cleanupDuplicatedItems();
+
return currentItems;
}
- private InvoiceItemList generateInvoiceItems(BillingEventSet events, UUID invoiceId, DateTime targetDate, Currency targetCurrency) {
+ private InvoiceItemList generateInvoiceItems(final BillingEventSet events, final UUID invoiceId,
+ final DateTime targetDate, final Currency targetCurrency) {
InvoiceItemList items = new InvoiceItemList();
// sort events; this relies on the sort order being by subscription id then start date
@@ -102,7 +122,8 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
BillingEvent thisEvent = events.get(i);
BillingEvent nextEvent = events.get(i + 1);
- if (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) {
+
+ if (thisEvent.getSubscription().getId().equals(nextEvent.getSubscription().getId())) {
processEvents(invoiceId, thisEvent, nextEvent, items, targetDate, targetCurrency);
} else {
processEvent(invoiceId, thisEvent, items, targetDate, targetCurrency);
@@ -117,62 +138,101 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
return items;
}
- private void processEvent(UUID invoiceId, BillingEvent event, List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) {
+ private void processEvent(final UUID invoiceId, final BillingEvent event, final InvoiceItemList items,
+ final DateTime targetDate, final Currency targetCurrency) {
try {
- //TODO: Jeff getPrice() -> getRecurringPrice()
- BigDecimal rate = event.getRecurringPrice(targetCurrency);
- BigDecimal invoiceItemAmount = calculateInvoiceItemAmount(event, targetDate, rate);
- BillingMode billingMode = getBillingMode(event.getBillingMode());
- DateTime billThroughDate = billingMode.calculateEffectiveEndDate(event.getEffectiveDate(), targetDate, event.getBillCycleDay(), event.getBillingPeriod());
+ // STEPH should not that check apply to next method as well?
+ if (event.getEffectiveDate().compareTo(targetDate) > 0) {
+ return;
+ }
+
+ BigDecimal recurringRate = event.getRecurringPrice() == null ? null : event.getRecurringPrice().getPrice(targetCurrency);
+ BigDecimal fixedPrice = event.getFixedPrice() == null ? null : event.getFixedPrice().getPrice(targetCurrency);
+
+ BigDecimal numberOfBillingPeriods;
+ BigDecimal recurringAmount = null;
+ DateTime billThroughDate;
+
+ if (recurringRate == null) {
+ billThroughDate = event.getPlanPhase().getDuration().addToDateTime(event.getEffectiveDate());
+ } else {
+ numberOfBillingPeriods = calculateNumberOfBillingPeriods(event, targetDate);
+ recurringAmount = numberOfBillingPeriods.multiply(recurringRate);
+ BillingMode billingMode = getBillingMode(event.getBillingMode());
+ billThroughDate = billingMode.calculateEffectiveEndDate(event.getEffectiveDate(), targetDate, event.getBillCycleDay(), event.getBillingPeriod());
+ }
+
+ BigDecimal effectiveFixedPrice = items.hasInvoiceItemForPhase(event.getPlanPhase().getName()) ? null : fixedPrice;
- addInvoiceItem(invoiceId, items, event, billThroughDate, invoiceItemAmount, rate, targetCurrency);
+ // STEPH don't we also need to check for if (Days.daysBetween(firstEvent.getEffectiveDate(), billThroughDate).getDays() > 0)
+ addInvoiceItem(invoiceId, items, event, billThroughDate, recurringAmount, recurringRate, effectiveFixedPrice, targetCurrency);
} catch (CatalogApiException e) {
- log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s",
- invoiceId.toString(),
+ // STEPH same remark for catalog exception.
+ log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s",
+ invoiceId.toString(),
ISODateTimeFormat.basicDateTime().print(event.getEffectiveDate())), e);
}
}
- private void processEvents(UUID invoiceId, BillingEvent firstEvent, BillingEvent secondEvent, List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) {
- //TODO: Jeff getPrice() -> getRecurringPrice()
+ private void processEvents(final UUID invoiceId, final BillingEvent firstEvent, final BillingEvent secondEvent,
+ final InvoiceItemList items, final DateTime targetDate, final Currency targetCurrency) {
try {
- BigDecimal rate = firstEvent.getRecurringPrice(targetCurrency);
- BigDecimal invoiceItemAmount = calculateInvoiceItemAmount(firstEvent, secondEvent, targetDate, rate);
- BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
- DateTime billThroughDate = billingMode.calculateEffectiveEndDate(firstEvent.getEffectiveDate(), secondEvent.getEffectiveDate(), targetDate, firstEvent.getBillCycleDay(), firstEvent.getBillingPeriod());
+ BigDecimal recurringRate = firstEvent.getRecurringPrice() == null ? null : firstEvent.getRecurringPrice().getPrice(targetCurrency);
+ BigDecimal fixedPrice = firstEvent.getFixedPrice() == null ? null : firstEvent.getFixedPrice().getPrice(targetCurrency);
- addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, invoiceItemAmount, rate, targetCurrency);
+ BigDecimal numberOfBillingPeriods;
+ BigDecimal recurringAmount = null;
+ DateTime billThroughDate;
+
+ if (recurringRate == null) {
+ // since it's fixed price only, the following event dictates the end date, regardless of when it takes place
+ billThroughDate = secondEvent.getEffectiveDate();
+ } else {
+ numberOfBillingPeriods = calculateNumberOfBillingPeriods(firstEvent, secondEvent, targetDate);
+ recurringAmount = numberOfBillingPeriods.multiply(recurringRate);
+ BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
+ billThroughDate = billingMode.calculateEffectiveEndDate(firstEvent.getEffectiveDate(), secondEvent.getEffectiveDate(), targetDate, firstEvent.getBillCycleDay(), firstEvent.getBillingPeriod());
+ }
+
+ if (Days.daysBetween(firstEvent.getEffectiveDate(), billThroughDate).getDays() > 0) {
+ BigDecimal effectiveFixedPrice = items.hasInvoiceItemForPhase(firstEvent.getPlanPhase().getName()) ? null : fixedPrice;
+ addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, recurringAmount, recurringRate, effectiveFixedPrice, targetCurrency);
+ }
} catch (CatalogApiException e) {
- log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s",
- invoiceId.toString(),
+
+ // STEPH That needs to be thrown so we stop that invoice generation
+ log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s",
+ invoiceId.toString(),
ISODateTimeFormat.basicDateTime().print(firstEvent.getEffectiveDate())), e);
}
}
- private void addInvoiceItem(UUID invoiceId, List<InvoiceItem> items, BillingEvent event, DateTime billThroughDate, BigDecimal amount, BigDecimal rate, Currency currency) {
- if (!(amount.compareTo(BigDecimal.ZERO) == 0)) {
- DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, event.getSubscription().getId(), event.getEffectiveDate(), billThroughDate, event.getDescription(), amount, rate, currency);
- items.add(item);
- }
+ private void addInvoiceItem(final UUID invoiceId, final InvoiceItemList items, final BillingEvent event,
+ final DateTime billThroughDate, final BigDecimal amount, final BigDecimal rate,
+ final BigDecimal fixedAmount, final Currency currency) {
+ DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, event.getSubscription().getId(),
+ event.getPlan().getName(), event.getPlanPhase().getName(), event.getEffectiveDate(),
+ billThroughDate, amount, rate, fixedAmount, currency);
+ items.add(item);
+ System.out.println(item);
}
- private BigDecimal calculateInvoiceItemAmount(BillingEvent event, DateTime targetDate, BigDecimal rate){
+ private BigDecimal calculateNumberOfBillingPeriods(final BillingEvent event, final DateTime targetDate){
BillingMode billingMode = getBillingMode(event.getBillingMode());
DateTime startDate = event.getEffectiveDate();
int billingCycleDay = event.getBillCycleDay();
BillingPeriod billingPeriod = event.getBillingPeriod();
try {
- BigDecimal numberOfBillingCycles;
- numberOfBillingCycles = billingMode.calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, billingPeriod);
- return numberOfBillingCycles.multiply(rate);
+ return billingMode.calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, billingPeriod);
} catch (InvalidDateSequenceException e) {
// TODO: Jeff -- log issue
return BigDecimal.ZERO;
}
}
- private BigDecimal calculateInvoiceItemAmount(BillingEvent firstEvent, BillingEvent secondEvent, DateTime targetDate, BigDecimal rate) {
+ private BigDecimal calculateNumberOfBillingPeriods(final BillingEvent firstEvent, final BillingEvent secondEvent,
+ final DateTime targetDate) {
BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
DateTime startDate = firstEvent.getEffectiveDate();
int billingCycleDay = firstEvent.getBillCycleDay();
@@ -181,16 +241,14 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
DateTime endDate = secondEvent.getEffectiveDate();
try {
- BigDecimal numberOfBillingCycles;
- numberOfBillingCycles = billingMode.calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, billingPeriod);
- return numberOfBillingCycles.multiply(rate);
+ return billingMode.calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, billingPeriod);
} catch (InvalidDateSequenceException e) {
// TODO: Jeff -- log issue
return BigDecimal.ZERO;
}
}
- private BillingMode getBillingMode(BillingModeType billingModeType) {
+ private BillingMode getBillingMode(final BillingModeType billingModeType) {
switch (billingModeType) {
case IN_ADVANCE:
return new InAdvanceBillingMode();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
index eb54aeb..1c1cdef 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
@@ -17,9 +17,12 @@
package com.ning.billing.invoice.model;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
import com.ning.billing.invoice.api.InvoiceItem;
import org.joda.time.DateTime;
+import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.util.UUID;
@@ -27,26 +30,37 @@ public class DefaultInvoiceItem implements InvoiceItem {
private final UUID id;
private final UUID invoiceId;
private final UUID subscriptionId;
+ private final String planName;
+ private final String phaseName;
private DateTime startDate;
private DateTime endDate;
- private final String description;
- private BigDecimal amount;
- private final BigDecimal rate;
+ private BigDecimal recurringAmount;
+ private final BigDecimal recurringRate;
+ private final BigDecimal fixedAmount;
private final Currency currency;
- public DefaultInvoiceItem(UUID invoiceId, UUID subscriptionId, DateTime startDate, DateTime endDate, String description, BigDecimal amount, BigDecimal rate, Currency currency) {
- this(UUID.randomUUID(), invoiceId, subscriptionId, startDate, endDate, description, amount, rate, currency);
+ public DefaultInvoiceItem(UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+ DateTime startDate, DateTime endDate,
+ BigDecimal recurringAmount, BigDecimal recurringRate,
+ BigDecimal fixedAmount, Currency currency) {
+ this(UUID.randomUUID(), invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
+ recurringAmount, recurringRate, fixedAmount, currency);
}
- public DefaultInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, DateTime startDate, DateTime endDate, String description, BigDecimal amount, BigDecimal rate, Currency currency) {
+ public DefaultInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+ DateTime startDate, DateTime endDate,
+ BigDecimal recurringAmount, BigDecimal recurringRate,
+ BigDecimal fixedAmount, Currency currency) {
this.id = id;
this.invoiceId = invoiceId;
this.subscriptionId = subscriptionId;
+ this.planName = planName;
+ this.phaseName = phaseName;
this.startDate = startDate;
this.endDate = endDate;
- this.description = description;
- this.amount = amount;
- this.rate = rate;
+ this.recurringAmount = recurringAmount;
+ this.recurringRate = recurringRate;
+ this.fixedAmount = fixedAmount;
this.currency = currency;
}
@@ -54,17 +68,22 @@ public class DefaultInvoiceItem implements InvoiceItem {
this.id = UUID.randomUUID();
this.invoiceId = invoiceId;
this.subscriptionId = that.getSubscriptionId();
+ this.planName = that.getPlanName();
+ this.phaseName = that.getPhaseName();
this.startDate = that.getStartDate();
this.endDate = that.getEndDate();
- this.description = that.getDescription();
- this.amount = that.getAmount();
- this.rate = that.getRate();
+ this.recurringAmount = that.getRecurringAmount();
+ this.recurringRate = that.getRecurringRate();
+ this.fixedAmount = that.getFixedAmount();
this.currency = that.getCurrency();
}
@Override
public InvoiceItem asCredit(UUID invoiceId) {
- return new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, description, amount.negate(), rate, currency);
+ BigDecimal recurringAmountNegated = recurringAmount == null ? null : recurringAmount.negate();
+ BigDecimal fixedAmountNegated = fixedAmount == null ? null : fixedAmount.negate();
+ return new DefaultInvoiceItem(invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
+ recurringAmountNegated, recurringRate, fixedAmountNegated, currency);
}
@Override
@@ -83,6 +102,16 @@ public class DefaultInvoiceItem implements InvoiceItem {
}
@Override
+ public String getPlanName() {
+ return planName;
+ }
+
+ @Override
+ public String getPhaseName() {
+ return phaseName;
+ }
+
+ @Override
public DateTime getStartDate() {
return startDate;
}
@@ -93,18 +122,18 @@ public class DefaultInvoiceItem implements InvoiceItem {
}
@Override
- public String getDescription() {
- return description;
+ public BigDecimal getRecurringAmount() {
+ return recurringAmount;
}
@Override
- public BigDecimal getAmount() {
- return amount;
+ public BigDecimal getRecurringRate() {
+ return recurringRate;
}
@Override
- public BigDecimal getRate() {
- return rate;
+ public BigDecimal getFixedAmount() {
+ return fixedAmount;
}
@Override
@@ -113,11 +142,21 @@ public class DefaultInvoiceItem implements InvoiceItem {
}
@Override
- public int compareTo(InvoiceItem invoiceItem) {
- int compareSubscriptions = getSubscriptionId().compareTo(invoiceItem.getSubscriptionId());
+ public int compareTo(InvoiceItem that) {
+ int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
if (compareSubscriptions == 0) {
- return getStartDate().compareTo(invoiceItem.getStartDate());
+ // move null end dates to the end of the set
+ if ((this.endDate != null) && (that.getEndDate() == null)) {
+ return -1;
+ }
+
+ if ((this.endDate == null) && (that.getEndDate() != null)) {
+ return 1;
+ }
+
+ int compareStartDates = getStartDate().compareTo(that.getStartDate());
+ return compareStartDates;
} else {
return compareSubscriptions;
}
@@ -128,44 +167,139 @@ public class DefaultInvoiceItem implements InvoiceItem {
public void subtract(InvoiceItem that) {
if (this.startDate.equals(that.getStartDate()) && this.endDate.equals(that.getEndDate())) {
this.startDate = this.endDate;
- this.amount = this.amount.subtract(that.getAmount());
+ this.recurringAmount = safeSubtract(this.recurringAmount, that.getRecurringAmount());
} else {
if (this.startDate.equals(that.getStartDate())) {
this.startDate = that.getEndDate();
- this.amount = this.amount.subtract(that.getAmount());
+ this.recurringAmount = safeSubtract(this.recurringAmount, that.getRecurringAmount());
}
if (this.endDate.equals(that.getEndDate())) {
this.endDate = that.getStartDate();
- this.amount = this.amount.subtract(that.getAmount());
+ this.recurringAmount = safeSubtract(this.recurringAmount, that.getRecurringAmount());
+ }
+ }
+ }
+
+ private BigDecimal safeSubtract(BigDecimal minuend, BigDecimal subtrahend) {
+ // minuend - subtrahend == difference
+ if (minuend == null) {
+ if (subtrahend == null) {
+ return BigDecimal.ZERO;
+ } else {
+ return subtrahend.negate();
+ }
+ } else {
+ if (subtrahend == null) {
+ return minuend;
+ } else {
+ return minuend.subtract(subtrahend);
}
}
+
}
@Override
public boolean duplicates(InvoiceItem that) {
- if(!this.getSubscriptionId().equals(that.getSubscriptionId())) {return false;}
- if(!this.getRate().equals(that.getRate())) {return false;}
- if(!this.getCurrency().equals(that.getCurrency())) {return false;}
+ if (!this.getSubscriptionId().equals(that.getSubscriptionId())) {return false;}
+
+ if (!this.planName.equals(that.getPlanName())) {return false;}
+ if (!this.phaseName.equals(that.getPhaseName())) {return false;}
+
+ if (!compareNullableBigDecimal(this.getRecurringRate(), that.getRecurringRate())) {return false;}
+
+ if (!this.getCurrency().equals(that.getCurrency())) {return false;}
+
+ if ((this.endDate == null) && (that.getEndDate() == null) && (this.startDate.compareTo(that.getStartDate()) == 0)) {
+ return true;
+ }
DateRange thisDateRange = new DateRange(this.getStartDate(), this.getEndDate());
- return thisDateRange.contains(that.getStartDate()) && thisDateRange.contains(that.getEndDate());
+ return thisDateRange.contains(that.getStartDate()) && (that.getEndDate() == null || thisDateRange.contains(that.getEndDate()));
+ }
+
+ private boolean compareNullableBigDecimal(@Nullable BigDecimal value1, @Nullable BigDecimal value2) {
+ if ((value1 == null) && (value2 != null)) {return false;}
+ if ((value1 != null) && (value2 == null)) {return false;}
+
+ if ((value1 != null) && (value2 != null)) {
+ if (value1.compareTo(value2) != 0) {return false;}
+ }
+
+ return true;
}
/**
* indicates whether the supplied item is a cancelling item for this item
- * @param that
- * @return
+ * @param that the InvoiceItem to be examined
+ * @return true if the two invoice items cancel each other out (same subscription, same date range, sum of amounts = 0)
*/
@Override
public boolean cancels(InvoiceItem that) {
if(!this.getSubscriptionId().equals(that.getSubscriptionId())) {return false;}
if(!this.getEndDate().equals(that.getEndDate())) {return false;}
if(!this.getStartDate().equals(that.getStartDate())) {return false;}
- if(!this.getAmount().equals(that.getAmount().negate())) {return false;}
- if(!this.getRate().equals(that.getRate())) {return false;}
+
+ if (!safeCheckForZeroSum(this.getRecurringAmount(), that.getRecurringAmount())) {return false;}
+
+ if (!safeCheckForEquality(this.getRecurringRate(), that.getRecurringRate())) {return false;}
+
+ if (!safeCheckForZeroSum(this.getFixedAmount(), that.getFixedAmount())) {return false;}
if(!this.getCurrency().equals(that.getCurrency())) {return false;}
return true;
}
+
+ private boolean safeCheckForZeroSum(final BigDecimal value1, final BigDecimal value2) {
+ if ((value1 == null) && (value2 == null)) {return true;}
+ if ((value1 == null) ^ (value2 == null)) {return false;}
+ return (value1.add(value2).compareTo(BigDecimal.ZERO) == 0);
+ }
+
+ private boolean safeCheckForEquality(final BigDecimal value1, final BigDecimal value2) {
+ if ((value1 == null) && (value2 == null)) {return true;}
+ if ((value1 == null) ^ (value2 == null)) {return false;}
+ return (value1.compareTo(value2) == 0);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("InvoiceItem = {").append("id = ").append(id.toString()).append(", ");
+ sb.append("invoiceId = ").append(invoiceId.toString()).append(", ");
+ sb.append("subscriptionId = ").append(subscriptionId.toString()).append(", ");
+ sb.append("planName = ").append(planName).append(", ");
+ sb.append("phaseName = ").append(phaseName).append(", ");
+ sb.append("startDate = ").append(startDate.toString()).append(", ");
+ if (endDate != null) {
+ sb.append("endDate = ").append(endDate.toString()).append(", ");
+ } else {
+ sb.append("endDate = null");
+ }
+ sb.append("recurringAmount = ");
+ if (recurringAmount == null) {
+ sb.append("null");
+ } else {
+ sb.append(recurringAmount.toString());
+ }
+ sb.append(", ");
+
+ sb.append("recurringRate = ");
+ if (recurringRate == null) {
+ sb.append("null");
+ } else {
+ sb.append(recurringRate.toString());
+ }
+ sb.append(", ");
+
+ sb.append("fixedAmount = ");
+ if (fixedAmount == null) {
+ sb.append("null");
+ } else {
+ sb.append(fixedAmount.toString());
+ }
+
+ sb.append("}");
+ return sb.toString();
+ }
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
index 37b5820..b8f5c42 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
@@ -20,6 +20,7 @@ import com.ning.billing.catalog.api.BillingPeriod;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.Months;
+import org.joda.time.MutableDateTime;
import java.math.BigDecimal;
@@ -50,20 +51,23 @@ public class InAdvanceBillingMode extends BillingModeBase {
protected DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay) {
int lastDayOfMonth = date.dayOfMonth().getMaximumValue();
- DateTime proposedDate;
+
+ MutableDateTime tmp = date.toMutableDateTime();
if (billingCycleDay > lastDayOfMonth) {
- proposedDate = buildDate(date.getYear(), date.getMonthOfYear(), lastDayOfMonth);
+ tmp.setDayOfMonth(lastDayOfMonth);
} else {
- proposedDate = buildDate(date.getYear(), date.getMonthOfYear(), billingCycleDay);
+ tmp.setDayOfMonth(billingCycleDay);
}
+ DateTime proposedDate = tmp.toDateTime();
while (proposedDate.isBefore(date)) {
+ // STEPH could be an annual ?
proposedDate = proposedDate.plusMonths(1);
}
-
return proposedDate;
}
+ @Override
protected DateTime calculateBillingCycleDateAfter(final DateTime date, final DateTime billingCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
DateTime proposedDate = billingCycleDate;
@@ -74,9 +78,13 @@ public class InAdvanceBillingMode extends BillingModeBase {
int lastDayOfMonth = proposedDate.dayOfMonth().getMaximumValue();
if (lastDayOfMonth < billingCycleDay) {
- proposedDate = buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfMonth);
+ proposedDate = new DateTime(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfMonth,
+ proposedDate.getHourOfDay(), proposedDate.getMinuteOfHour(),
+ proposedDate.getSecondOfMinute(), proposedDate.getMillisOfSecond());
} else {
- proposedDate = buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay);
+ proposedDate = new DateTime(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay,
+ proposedDate.getHourOfDay(), proposedDate.getMinuteOfHour(),
+ proposedDate.getSecondOfMinute(), proposedDate.getMillisOfSecond());
}
}
}
@@ -100,9 +108,13 @@ public class InAdvanceBillingMode extends BillingModeBase {
if (proposedDate.dayOfMonth().get() < billingCycleDay) {
int lastDayOfTheMonth = proposedDate.dayOfMonth().getMaximumValue();
if (lastDayOfTheMonth < billingCycleDay) {
- return buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfTheMonth);
+ return new DateTime(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfTheMonth,
+ proposedDate.getHourOfDay(), proposedDate.getMinuteOfHour(),
+ proposedDate.getSecondOfMinute(), proposedDate.getMillisOfSecond());
} else {
- return buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay);
+ return new DateTime(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay,
+ proposedDate.getHourOfDay(), proposedDate.getMinuteOfHour(),
+ proposedDate.getSecondOfMinute(), proposedDate.getMillisOfSecond());
}
} else {
return proposedDate;
@@ -114,7 +126,12 @@ public class InAdvanceBillingMode extends BillingModeBase {
DateTime nextBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
DateTime previousBillingCycleDate = nextBillingCycleDate.plusMonths(-billingPeriod.getNumberOfMonths());
- BigDecimal daysInPeriod = new BigDecimal(Days.daysBetween(previousBillingCycleDate, nextBillingCycleDate).getDays());
+ int daysBetween = Days.daysBetween(previousBillingCycleDate, nextBillingCycleDate).getDays();
+ if (daysBetween == 0) {
+ return BigDecimal.ZERO;
+ }
+
+ BigDecimal daysInPeriod = new BigDecimal(daysBetween);
BigDecimal days = new BigDecimal(Days.daysBetween(startDate, nextBillingCycleDate).getDays());
return days.divide(daysInPeriod, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
index b4d81a7..626993e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
@@ -20,8 +20,9 @@ import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
import org.joda.time.DateTime;
+import javax.annotation.Nullable;
import java.util.UUID;
public interface InvoiceGenerator {
- public Invoice generateInvoice(UUID accountId, BillingEventSet events, InvoiceItemList items, DateTime targetDate, Currency targetCurrency);
+ public Invoice generateInvoice(UUID accountId, BillingEventSet events, @Nullable InvoiceItemList items, DateTime targetDate, Currency targetCurrency);
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
index fb82f06..f35f8b1 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
@@ -16,36 +16,40 @@
package com.ning.billing.invoice.model;
-import com.ning.billing.invoice.api.InvoiceItem;
-
import java.math.BigDecimal;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
+import com.ning.billing.invoice.api.InvoiceItem;
public class InvoiceItemList extends ArrayList<InvoiceItem> {
private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
+ private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMethod();
- public BigDecimal getTotalAmount() {
- // TODO: Jeff -- naive implementation, assumes all invoice items share the same currency
- BigDecimal total = new BigDecimal("0");
-
- for (InvoiceItem item : this) {
- total = total.add(item.getAmount());
- }
+ public InvoiceItemList() {
+ super();
+ }
- return total.setScale(NUMBER_OF_DECIMALS);
+ public InvoiceItemList(final List<InvoiceItem> invoiceItems) {
+ super();
+ this.addAll(invoiceItems);
}
- public void removeZeroDollarItems() {
- List<InvoiceItem> itemsToRemove = new ArrayList<InvoiceItem>();
+ public BigDecimal getTotalAmount() {
+ // naive implementation, assumes all invoice items share the same currency
+ BigDecimal total = BigDecimal.ZERO.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
- for (InvoiceItem item : this) {
- if (item.getAmount().compareTo(BigDecimal.ZERO) == 0) {
- itemsToRemove.add(item);
+ for (final InvoiceItem item : this) {
+ if (item.getRecurringAmount() != null) {
+ total = total.add(item.getRecurringAmount());
+ }
+
+ if (item.getFixedAmount() != null) {
+ total = total.add(item.getFixedAmount());
}
}
- this.removeAll(itemsToRemove);
+ return total.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
}
public void removeCancellingPairs() {
@@ -64,4 +68,36 @@ public class InvoiceItemList extends ArrayList<InvoiceItem> {
this.removeAll(itemsToRemove);
}
+
+ /*
+ * removes items from the list that have a recurring amount of zero, but a recurring rate that is not zero
+ */
+ public void cleanupDuplicatedItems() {
+ Iterator<InvoiceItem> iterator = this.iterator();
+ while (iterator.hasNext()) {
+ InvoiceItem item = iterator.next();
+
+ boolean fixedAmountNull = (item.getFixedAmount() == null);
+ boolean recurringRateNull = (item.getRecurringRate() == null);
+ boolean recurringAmountZero = (item.getRecurringRate() !=null) && (item.getRecurringAmount().compareTo(BigDecimal.ZERO) == 0);
+
+ if (fixedAmountNull && (recurringRateNull || recurringAmountZero)) {
+ iterator.remove();
+ } else if (item.getEndDate() != null && item.getStartDate().compareTo(item.getEndDate()) == 0) {
+ iterator.remove();
+ } else if (item.getFixedAmount() == null && item.getRecurringAmount() == null) {
+ iterator.remove();
+ }
+ }
+ }
+
+ public boolean hasInvoiceItemForPhase(final String phaseName) {
+ for (final InvoiceItem item : this) {
+ if (item.getPhaseName().equals(phaseName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
new file mode 100644
index 0000000..3fd03b2
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
@@ -0,0 +1,144 @@
+/*
+ * 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.notification;
+
+import java.util.UUID;
+
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.invoice.api.DefaultInvoiceService;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+import com.ning.billing.util.notificationq.NotificationConfig;
+import com.ning.billing.util.notificationq.NotificationKey;
+import com.ning.billing.util.notificationq.NotificationQueue;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+
+public class DefaultNextBillingDateNotifier implements NextBillingDateNotifier {
+
+ private final static Logger log = LoggerFactory.getLogger(DefaultNextBillingDateNotifier.class);
+
+ private static final String NEXT_BILLING_DATE_NOTIFIER_QUEUE = "next-billing-date-queue";
+
+ private final Bus eventBus;
+ private final NotificationQueueService notificationQueueService;
+ private final InvoiceConfig config;
+ private final EntitlementDao entitlementDao;
+
+ private NotificationQueue nextBillingQueue;
+
+ @Inject
+ public DefaultNextBillingDateNotifier(NotificationQueueService notificationQueueService, Bus eventBus,
+ InvoiceConfig config, EntitlementDao entitlementDao){
+ this.notificationQueueService = notificationQueueService;
+ this.config = config;
+ this.eventBus = eventBus;
+ this.entitlementDao = entitlementDao;
+ }
+
+ @Override
+ public void initialize() {
+ try {
+ nextBillingQueue = notificationQueueService.createNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
+ NEXT_BILLING_DATE_NOTIFIER_QUEUE,
+ new NotificationQueueHandler() {
+ @Override
+ public void handleReadyNotification(String notificationKey) {
+ UUID subscriptionId;
+ try {
+ UUID key = UUID.fromString(notificationKey);
+ Subscription subscription = entitlementDao.getSubscriptionFromId(key);
+ if (subscription == null) {
+ log.warn("Next Billing Date Notification Queue handled spurious notification (key: " + key + ")" );
+ } else {
+ processEvent(key);
+ }
+ } catch (IllegalArgumentException e) {
+ log.error("The key returned from the NextBillingNotificationQueue is not a valid UUID", e);
+ return;
+ }
+
+ }
+ },
+ new NotificationConfig() {
+ @Override
+ public boolean isNotificationProcessingOff() {
+ return config.isEventProcessingOff();
+ }
+ @Override
+ public long getNotificationSleepTimeMs() {
+ return config.getNotificationSleepTimeMs();
+ }
+ @Override
+ public int getDaoMaxReadyEvents() {
+ return config.getDaoMaxReadyEvents();
+ }
+ @Override
+ public long getDaoClaimTimeMs() {
+ return config.getDaoMaxReadyEvents();
+ }
+ });
+ } catch (NotificationQueueAlreadyExists e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void start() {
+ nextBillingQueue.startQueue();
+ }
+
+ @Override
+ public void stop() {
+ if (nextBillingQueue != null) {
+ nextBillingQueue.stopQueue();
+ }
+ }
+
+ private void processEvent(UUID subscriptionId) {
+ try {
+ eventBus.post(new NextBillingDateEvent(subscriptionId));
+ } catch (EventBusException e) {
+ log.error("Failed to post entitlement event " + subscriptionId, e);
+ }
+ }
+
+ @Override
+ public void insertNextBillingNotification(final Transmogrifier transactionalDao, final UUID subscriptionId, final DateTime futureNotificationTime) {
+ if (nextBillingQueue != null) {
+ log.info("Queuing next billing date notification. id: {}, timestamp: {}", subscriptionId.toString(), futureNotificationTime.toString());
+
+ nextBillingQueue.recordFutureNotificationFromTransaction(transactionalDao, futureNotificationTime, new NotificationKey(){
+ @Override
+ public String toString() {
+ return subscriptionId.toString();
+ }
+ });
+ } else {
+ log.error("Attempting to put items on a non-existent queue (NextBillingDateNotifier).");
+ }
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateEvent.java b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateEvent.java
new file mode 100644
index 0000000..659d9e9
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateEvent.java
@@ -0,0 +1,34 @@
+/*
+ * 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.notification;
+
+import java.util.UUID;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public class NextBillingDateEvent implements BusEvent{
+ private final UUID subscriptionId;
+
+ public NextBillingDateEvent(UUID subscriptionId) {
+ super();
+ this.subscriptionId = subscriptionId;
+ }
+
+ public UUID getSubscriptionId() {
+ return subscriptionId;
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateNotifier.java
new file mode 100644
index 0000000..d33dc61
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateNotifier.java
@@ -0,0 +1,35 @@
+/*
+ * 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.notification;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+public interface NextBillingDateNotifier {
+
+ public void initialize();
+
+ public void start();
+
+ public void stop();
+
+ public void insertNextBillingNotification(Transmogrifier transactionalDao,
+ UUID subscriptionId, DateTime futureNotificationTime);
+
+}
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
index 4dc783f..451ebe7 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
@@ -1,39 +1,61 @@
group InvoiceItemDao;
+invoiceItemFields(prefix) ::= <<
+ <prefix>id,
+ <prefix>invoice_id,
+ <prefix>subscription_id,
+ <prefix>plan_name,
+ <prefix>phase_name,
+ <prefix>start_date,
+ <prefix>end_date,
+ <prefix>recurring_amount,
+ <prefix>recurring_rate,
+ <prefix>fixed_amount,
+ <prefix>currency
+>>
+
getById() ::= <<
- SELECT id, invoice_id, subscription_id, start_date, end_date, description, amount, rate, currency
+ SELECT <invoiceItemFields()>
FROM invoice_items
WHERE id = :id;
>>
getInvoiceItemsByInvoice() ::= <<
- SELECT id, invoice_id, subscription_id, start_date, end_date, description, amount, rate, currency
+ SELECT <invoiceItemFields()>
FROM invoice_items
WHERE invoice_id = :invoiceId;
>>
getInvoiceItemsByAccount() ::= <<
- SELECT ii.id, ii.invoice_id, ii.subscription_id, ii.start_date, ii.end_date, ii.description, ii.amount, ii.rate, ii.currency
+ SELECT <invoiceItemFields("ii.")>
FROM invoice_items ii
INNER JOIN invoices i ON i.id = ii.invoice_id
WHERE i.account_id = :accountId;
>>
getInvoiceItemsBySubscription() ::= <<
- SELECT id, invoice_id, subscription_id, start_date, end_date, description, amount, rate, currency
+ SELECT <invoiceItemFields()>
FROM invoice_items
WHERE subscription_id = :subscriptionId;
>>
create() ::= <<
- INSERT INTO invoice_items(id, invoice_id, subscription_id, start_date, end_date, description, amount, rate, currency)
- VALUES(:id, :invoiceId, :subscriptionId, :startDate, :endDate, :description, :amount, :rate, :currency);
+ INSERT INTO invoice_items(<invoiceItemFields()>)
+ VALUES(:id, :invoiceId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
+ :recurringAmount, :recurringRate, :fixedAmount, :currency);
+>>
+
+batchCreateFromTransaction() ::= <<
+ INSERT INTO invoice_items(<invoiceItemFields()>)
+ VALUES(:id, :invoiceId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
+ :recurringAmount, :recurringRate, :fixedAmount, :currency);
>>
update() ::= <<
UPDATE invoice_items
- SET invoice_id = :invoiceId, subscription_id = :subscriptionId, start_date = :startDate, end_date = :endDate,
- description = :description, amount = :amount, rate = :rate, currency = :currency
+ SET invoice_id = :invoiceId, subscription_id = :subscriptionId, plan_name = :planName, phase_name = :phaseName,
+ start_date = :startDate, end_date = :endDate, recurring_amount = :recurringAmount, recurring_rate = :recurringRate,
+ fixed_amount = :fixedAmount, currency = :currency
WHERE id = :id;
>>
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
new file mode 100644
index 0000000..2172573
--- /dev/null
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
@@ -0,0 +1,61 @@
+group InvoicePayment;
+
+invoicePaymentFields(prefix) ::= <<
+ <prefix>invoice_id,
+ <prefix>payment_attempt_id,
+ <prefix>payment_attempt_date,
+ <prefix>amount,
+ <prefix>currency,
+ <prefix>created_date,
+ <prefix>updated_date
+>>
+
+create() ::= <<
+ INSERT INTO invoice_payments(<invoicePaymentFields()>)
+ VALUES(:invoiceId, :paymentAttemptId, :paymentAttemptDate, :amount, :currency, :createdDate, :updatedDate);
+>>
+
+batchCreateFromTransaction() ::= <<
+ INSERT INTO invoice_payments(<invoicePaymentFields()>)
+ VALUES(:invoiceId, :paymentAttemptId, :paymentAttemptDate, :amount, :currency, :createdDate, :updatedDate);
+>>
+
+
+update() ::= <<
+ UPDATE invoice_payments
+ SET payment_date = :paymentAttemptDate, amount = :amount, currency = :currency, created_date = :createdDate, updated_date = :updatedDate
+ WHERE invoice_id = :invoiceId, payment_attempt_id = :paymentAttemptId;
+>>
+
+getByPaymentAttemptId() ::= <<
+ SELECT <invoicePaymentFields()>
+ FROM invoice_payments
+ WHERE payment_id = :paymentAttemptId;
+>>
+
+get() ::= <<
+ SELECT <invoicePaymentFields()>
+ FROM invoice_payments;
+>>
+
+getPaymentsForInvoice() ::= <<
+ SELECT <invoicePaymentFields()>
+ FROM invoice_payments
+ WHERE invoice_id = :invoiceId;
+>>
+
+notifyOfPaymentAttempt() ::= <<
+ INSERT INTO invoice_payments(<invoicePaymentFields()>)
+ VALUES(:invoiceId, :paymentAttemptId, :paymentAttemptDate, :amount, :currency, NOW(), NOW());
+>>
+
+getInvoicePayment() ::= <<
+ SELECT <invoicePaymentFields()>
+ FROM invoice_payments
+ WHERE payment_id = :payment_id;
+>>
+
+test() ::= <<
+ SELECT 1 FROM invoice_payments;
+>>
+;
\ No newline at end of file
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
index 5d5725d..10beb12 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
@@ -1,34 +1,39 @@
group InvoiceDao;
+invoiceFields(prefix) ::= <<
+ <prefix>id,
+ <prefix>account_id,
+ <prefix>invoice_date,
+ <prefix>target_date,
+ <prefix>currency
+>>
+
get() ::= <<
- SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
- SUM(ip.amount) AS amount_paid, MAX(ip.payment_attempt_date) AS last_payment_attempt
- FROM invoices i
- LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
- LEFT JOIN invoice_items ii ON ii.invoice_id = i.id
- GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency
- ORDER BY i.invoice_date ASC;
+ SELECT <invoiceFields()>
+ FROM invoices
+ ORDER BY target_date ASC;
>>
getInvoicesByAccount() ::= <<
- SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
- SUM(ip.amount) AS amount_paid, MAX(ip.payment_attempt_date) AS last_payment_attempt
- FROM invoices i
- LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
- LEFT JOIN invoice_items ii ON ii.invoice_id = i.id
- WHERE i.account_id = :accountId
- GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency
- ORDER BY i.invoice_date ASC;
+ SELECT <invoiceFields()>
+ FROM invoices
+ WHERE account_id = :accountId
+ ORDER BY target_date ASC;
+>>
+
+getInvoicesByAccountAfterDate() ::= <<
+ SELECT <invoiceFields()>
+ FROM invoices
+ WHERE account_id = :accountId AND target_date >= :fromDate
+ ORDER BY target_date ASC;
>>
getInvoicesBySubscription() ::= <<
- SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
- SUM(ip.amount) AS amount_paid, MAX(ip.payment_attempt_date) AS last_payment_attempt
+ SELECT <invoiceFields("i.")>
FROM invoices i
LEFT JOIN invoice_items ii ON i.id = ii.invoice_id
- LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
WHERE ii.subscription_id = :subscriptionId
- GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency;
+ GROUP BY <invoiceFields("i.")>;
>>
getInvoicesForPayment() ::= <<
@@ -37,23 +42,29 @@ getInvoicesForPayment() ::= <<
LEFT JOIN invoice_payment_summary ips ON ips.invoice_id = i.id
LEFT JOIN invoice_item_summary iis ON iis.invoice_id = i.id
WHERE ((ips.last_payment_date IS NULL) OR (DATEDIFF(:targetDate, ips.last_payment_date) >= :numberOfDays))
- AND ((ips.total_paid IS NULL) OR (iis.total_amount >= ips.total_paid))
- AND ((iis.total_amount IS NOT NULL) AND (iis.total_amount > 0))
- GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency;
+ AND ((ips.total_paid IS NULL) OR (iis.amount_invoiced >= ips.total_paid))
+ AND ((iis.amount_invoiced IS NOT NULL) AND (iis.amount_invoiced > 0))
+ GROUP BY <invoiceFields("i.")>;
>>
getById() ::= <<
- SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
- SUM(ip.amount) AS amount_paid, MAX(ip.payment_attempt_date) AS last_payment_attempt
+ SELECT <invoiceFields()>
+ FROM invoices
+ WHERE id = :id;
+>>
+
+getAccountBalance() ::= <<
+ SELECT SUM(iis.amount_invoiced) AS amount_invoiced,
+ SUM(ips.total_paid) AS amount_paid
FROM invoices i
- LEFT JOIN invoice_items ii ON i.id = ii.invoice_id
- LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
- WHERE i.id = :id
- GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency;
+ LEFT JOIN invoice_payment_summary ips ON i.id = ips.invoice_id
+ LEFT JOIN invoice_item_summary iis ON i.id = iis.invoice_id
+ WHERE i.account_id = :accountId
+ GROUP BY i.account_id;
>>
create() ::= <<
- INSERT INTO invoices(id, account_id, invoice_date, target_date, currency)
+ INSERT INTO invoices(<invoiceFields()>)
VALUES (:id, :accountId, :invoiceDate, :targetDate, :currency);
>>
@@ -70,26 +81,18 @@ update() ::= <<
WHERE id = :id;
>>
-notifyOfPaymentAttempt() ::= <<
- INSERT INTO invoice_payments(invoice_id, payment_attempt_id, payment_attempt_date, amount, currency, created_date, updated_date)
- VALUES(:invoice_id, :payment_attempt_id, :payment_attempt_date, :amount, :currency, NOW(), NOW());
->>
-
-getInvoicePayment() ::= <<
- SELECT invoice_id, payment_attempt_id, payment_attempt_date, amount, currency, created_date, updated_date
- FROM invoice_payments
- WHERE payment_id = :payment_id
->>
-
-getAccountBalance() ::= <<
- SELECT SUM(iis.total_amount) AS amount_invoiced, SUM(ips.total_paid) AS amount_paid
+getUnpaidInvoicesByAccountId() ::= <<
+ SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency
FROM invoices i
LEFT JOIN invoice_payment_summary ips ON i.id = ips.invoice_id
LEFT JOIN invoice_item_summary iis ON i.id = iis.invoice_id
- WHERE i.account_id = :accountId
- GROUP BY i.account_id;
+ WHERE i.account_id = :accountId AND NOT (i.target_date > :upToDate)
+ GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency
+ HAVING (SUM(iis.amount_invoiced) > SUM(ips.total_paid)) OR (SUM(ips.total_paid) IS NULL)
+ ORDER BY i.target_date ASC;
>>
+
test() ::= <<
SELECT 1
FROM invoices;
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
index 651e9d4..28f3c89 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
@@ -3,16 +3,24 @@ CREATE TABLE invoice_items (
id char(36) NOT NULL,
invoice_id char(36) NOT NULL,
subscription_id char(36) NOT NULL,
+ plan_name varchar(50) NOT NULL,
+ phase_name varchar(50) 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,
+ recurring_amount numeric(10,4) NULL,
+ recurring_rate numeric(10,4) NULL,
+ fixed_amount numeric(10,4) NULL,
currency char(3) NOT NULL,
PRIMARY KEY(id)
) ENGINE=innodb;
CREATE INDEX invoice_items_subscription_id ON invoice_items(subscription_id ASC);
+DROP TABLE IF EXISTS invoice_locking;
+CREATE TABLE invoice_locking (
+ account_id char(36) NOT NULL,
+ PRIMARY KEY(account_id)
+) ENGINE = innodb;
+
DROP TABLE IF EXISTS invoices;
CREATE TABLE invoices (
id char(36) NOT NULL,
@@ -39,12 +47,16 @@ CREATE UNIQUE INDEX invoice_payments_unique ON invoice_payments(invoice_id, paym
DROP VIEW IF EXISTS invoice_payment_summary;
CREATE VIEW invoice_payment_summary AS
-SELECT invoice_id, SUM(amount) AS total_paid, MAX(payment_attempt_date) AS last_payment_date
+SELECT invoice_id,
+ CASE WHEN SUM(amount) IS NULL THEN 0 ELSE SUM(amount) END AS total_paid,
+ MAX(payment_attempt_date) AS last_payment_date
FROM invoice_payments
GROUP BY invoice_id;
DROP VIEW IF EXISTS invoice_item_summary;
CREATE VIEW invoice_item_summary AS
-SELECT invoice_id, SUM(amount) AS total_amount
+SELECT invoice_id,
+ CASE WHEN SUM(recurring_amount) IS NULL THEN 0 ELSE SUM(recurring_amount) END
+ + CASE WHEN SUM(fixed_amount) IS NULL THEN 0 ELSE SUM(fixed_amount) END AS amount_invoiced
FROM invoice_items
-GROUP BY invoice_id;
\ No newline at end of file
+GROUP BY invoice_id;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
index 37d5f0c..e8f66b1 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
@@ -22,10 +22,10 @@ import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
import org.joda.time.DateTime;
import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.payment.api.InvoicePayment;
public class MockInvoicePaymentApi implements InvoicePaymentApi
{
@@ -90,13 +90,13 @@ public class MockInvoicePaymentApi implements InvoicePaymentApi
@Override
public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate) {
- InvoicePayment invoicePayment = new InvoicePayment(invoiceId, amountOutstanding, currency, paymentAttemptId, paymentAttemptDate);
+ InvoicePayment invoicePayment = new DefaultInvoicePayment(paymentAttemptId, invoiceId, paymentAttemptDate, amountOutstanding, currency);
notifyOfPaymentAttempt(invoicePayment);
}
@Override
public void notifyOfPaymentAttempt(UUID invoiceId, UUID paymentAttemptId, DateTime paymentAttemptDate) {
- InvoicePayment invoicePayment = new InvoicePayment(invoiceId, null, null, paymentAttemptId, paymentAttemptDate);
+ InvoicePayment invoicePayment = new DefaultInvoicePayment(paymentAttemptId, invoiceId, paymentAttemptDate);
notifyOfPaymentAttempt(invoicePayment);
}
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
index ed5497f..4779495 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
@@ -16,10 +16,12 @@
package com.ning.billing.invoice.dao;
+import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.io.IOException;
+import com.ning.billing.invoice.tests.InvoicingTestBase;
import org.apache.commons.io.IOUtils;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
@@ -28,32 +30,40 @@ import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
import com.ning.billing.invoice.glue.InvoiceModuleWithEmbeddedDb;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-import com.ning.billing.util.eventbus.EventBusService;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.bus.DefaultBusService;
-public abstract class InvoiceDaoTestBase {
+public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
protected InvoiceDao invoiceDao;
protected InvoiceItemSqlDao invoiceItemDao;
- private InvoiceModuleWithEmbeddedDb module;
+ protected InvoicePaymentSqlDao invoicePaymentDao;
+ protected InvoiceModuleWithEmbeddedDb module;
- @BeforeClass()
+ @BeforeClass(alwaysRun = true)
protected void setup() throws IOException {
// Health check test to make sure MySQL is setup properly
try {
-
module = new InvoiceModuleWithEmbeddedDb();
- final String ddl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
- module.createDb(ddl);
+ final String invoiceDdl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+ final String entitlementDdl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+
+ module.startDb();
+ module.initDb(invoiceDdl);
+ module.initDb(entitlementDdl);
final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module);
invoiceDao = injector.getInstance(InvoiceDao.class);
invoiceDao.test();
- invoiceItemDao = module.getInvoiceItemDao();
+ invoiceItemDao = module.getInvoiceItemSqlDao();
+
+ invoicePaymentDao = module.getInvoicePaymentSqlDao();
+
+ BusService busService = injector.getInstance(BusService.class);
+ ((DefaultBusService) busService).startBus();
- EventBusService busService = injector.getInstance(EventBusService.class);
- ((DefaultEventBusService) busService).startBus();
+ assertTrue(true);
}
catch (Throwable t) {
fail(t.toString());
@@ -61,8 +71,8 @@ public abstract class InvoiceDaoTestBase {
}
@AfterClass(alwaysRun = true)
- public void stopMysql()
- {
+ protected void tearDown() {
module.stopDb();
+ assertTrue(true);
}
}
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 859e09b..2369467 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
@@ -16,31 +16,46 @@
package com.ning.billing.invoice.dao;
-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 java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.testng.annotations.Test;
-
+import com.ning.billing.catalog.DefaultPrice;
+import com.ning.billing.catalog.MockInternationalPrice;
+import com.ning.billing.catalog.MockPlan;
+import com.ning.billing.catalog.MockPlanPhase;
+import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.entitlement.api.billing.DefaultBillingEvent;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.invoice.model.BillingEventSet;
import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
import com.ning.billing.invoice.model.DefaultInvoiceItem;
-import com.ning.billing.payment.api.InvoicePayment;
+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.util.clock.Clock;
import com.ning.billing.util.clock.DefaultClock;
+import org.joda.time.DateTime;
+import org.testng.annotations.Test;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import static org.testng.Assert.*;
+import static org.testng.Assert.assertEquals;
@Test(groups = {"invoicing", "invoicing-invoiceDao"})
public class InvoiceDaoTests extends InvoiceDaoTestBase {
private final int NUMBER_OF_DAY_BETWEEN_RETRIES = 8;
+ private final Clock clock = new DefaultClock();
@Test
public void testCreationAndRetrievalByAccount() {
@@ -50,7 +65,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
invoiceDao.create(invoice);
- List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId.toString());
+ List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId);
assertNotNull(invoices);
assertEquals(invoices.size(), 1);
Invoice thisInvoice = invoices.get(0);
@@ -69,33 +84,33 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
UUID subscriptionId = UUID.randomUUID();
DateTime startDate = new DateTime(2010, 1, 1, 0, 0, 0, 0);
DateTime endDate = new DateTime(2010, 4, 1, 0, 0, 0, 0);
- InvoiceItem invoiceItem = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, "test", new BigDecimal("21.00"), new BigDecimal("7.00"), Currency.USD);
- invoice.add(invoiceItem);
+ InvoiceItem invoiceItem = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, new BigDecimal("21.00"), new BigDecimal("7.00"), null, Currency.USD);
+ invoice.addInvoiceItem(invoiceItem);
invoiceDao.create(invoice);
- Invoice savedInvoice = invoiceDao.getById(invoiceId.toString());
+ Invoice savedInvoice = invoiceDao.getById(invoiceId);
assertNotNull(savedInvoice);
assertEquals(savedInvoice.getTotalAmount().compareTo(new BigDecimal("21.00")), 0);
- assertEquals(savedInvoice.getAmountOutstanding().compareTo(new BigDecimal("21.00")), 0);
+ assertEquals(savedInvoice.getBalance().compareTo(new BigDecimal("21.00")), 0);
assertEquals(savedInvoice.getAmountPaid(), BigDecimal.ZERO);
- assertEquals(savedInvoice.getItems().size(), 1);
+ assertEquals(savedInvoice.getInvoiceItems().size(), 1);
BigDecimal paymentAmount = new BigDecimal("11.00");
UUID paymentAttemptId = UUID.randomUUID();
- invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoiceId, paymentAmount, Currency.USD, paymentAttemptId, new DateTime(DateTimeZone.UTC).plusDays(12)));
+ invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttemptId, invoiceId, clock.getUTCNow().plusDays(12), paymentAmount, Currency.USD));
- Invoice retrievedInvoice = invoiceDao.getById(invoiceId.toString());
+ Invoice retrievedInvoice = invoiceDao.getById(invoiceId);
assertNotNull(retrievedInvoice);
- assertEquals(retrievedInvoice.getItems().size(), 1);
+ assertEquals(retrievedInvoice.getInvoiceItems().size(), 1);
assertEquals(retrievedInvoice.getTotalAmount().compareTo(new BigDecimal("21.00")), 0);
- assertEquals(retrievedInvoice.getAmountOutstanding().compareTo(new BigDecimal("10.00")), 0);
+ assertEquals(retrievedInvoice.getBalance().compareTo(new BigDecimal("10.00")), 0);
assertEquals(retrievedInvoice.getAmountPaid().compareTo(new BigDecimal("11.00")), 0);
}
@Test
public void testRetrievalForNonExistentInvoiceId() {
- Invoice invoice = invoiceDao.getById(UUID.randomUUID().toString());
+ Invoice invoice = invoiceDao.getById(UUID.randomUUID());
assertNull(invoice);
}
@@ -110,12 +125,12 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
BigDecimal paymentAmount = new BigDecimal("14.0");
invoiceDao.create(invoice);
+ invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttemptId, invoice.getId(), paymentAttemptDate, paymentAmount, Currency.USD));
- invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(), paymentAmount, Currency.USD, paymentAttemptId, paymentAttemptDate));
-
- invoice = invoiceDao.getById(invoice.getId().toString());
+ invoice = invoiceDao.getById(invoice.getId());
assertEquals(invoice.getAmountPaid().compareTo(paymentAmount), 0);
- assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
+ assertEquals(invoice.getLastPaymentAttempt().compareTo(paymentAttemptDate), 0);
+ assertEquals(invoice.getNumberOfPayments(), 1);
}
@Test
@@ -127,10 +142,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
DateTime paymentAttemptDate = new DateTime(2011, 6, 24, 12, 14, 36, 0);
invoiceDao.create(invoice);
- invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(), UUID.randomUUID(), paymentAttemptDate));
+ invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(invoice.getId(), paymentAttemptDate));
- invoice = invoiceDao.getById(invoice.getId().toString());
- assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
+ invoice = invoiceDao.getById(invoice.getId());
+ assertEquals(invoice.getLastPaymentAttempt().compareTo(paymentAttemptDate), 0);
}
@Test
@@ -139,14 +154,14 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
// determine the number of existing invoices available for payment (to avoid side effects from other tests)
- List<UUID> invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ List<UUID> invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
int existingInvoiceCount = invoices.size();
UUID accountId = UUID.randomUUID();
Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
invoiceDao.create(invoice);
- invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
assertEquals(invoices.size(), existingInvoiceCount);
}
@@ -166,73 +181,72 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
BigDecimal rate = new BigDecimal("9.0");
BigDecimal amount = rate.multiply(new BigDecimal("3.0"));
- DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, targetDate, endDate, "test", amount, rate, Currency.USD);
- invoice.add(item);
+ DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", targetDate, endDate, amount, rate, null, Currency.USD);
+ invoice.addInvoiceItem(item);
invoiceDao.create(invoice);
// ensure that the number of invoices for payment has increased by 1
int count;
- invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
List<Invoice> invoicesDue = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate);
count = invoicesDue.size();
assertEquals(invoices.size(), count);
// attempt a payment; ensure that the number of invoices for payment has decreased by 1
// (no retries for NUMBER_OF_DAYS_BETWEEN_RETRIES days)
- invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(), UUID.randomUUID(), notionalDate));
- invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(invoice.getId(), notionalDate));
+ invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
assertEquals(invoices.size(), count);
// advance clock by NUMBER_OF_DAYS_BETWEEN_RETRIES days
// ensure that number of invoices for payment has increased by 1 (retry)
notionalDate = notionalDate.plusDays(NUMBER_OF_DAY_BETWEEN_RETRIES);
- invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
assertEquals(invoices.size(), count);
// post successful partial payment; ensure that number of invoices for payment has decreased by 1
- invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(), new BigDecimal("22.0000"), Currency.USD, UUID.randomUUID(), notionalDate));
+ invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(UUID.randomUUID(), invoice.getId(), notionalDate, new BigDecimal("22.0000"), Currency.USD));
- invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
assertEquals(invoices.size(), count);
// get invoice; verify amount paid is correct
- invoice = invoiceDao.getById(invoiceId.toString());
+ invoice = invoiceDao.getById(invoiceId);
assertEquals(invoice.getAmountPaid().compareTo(new BigDecimal("22.0")), 0);
// advance clock NUMBER_OF_DAYS_BETWEEN_RETRIES days
// ensure that number of invoices for payment has increased by 1 (retry)
notionalDate = notionalDate.plusDays(NUMBER_OF_DAY_BETWEEN_RETRIES);
- invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
assertEquals(invoices.size(), count);
// post completed payment; ensure that the number of invoices for payment has decreased by 1
- invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(), new BigDecimal("5.0000"), Currency.USD, UUID.randomUUID(), notionalDate));
+ invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(UUID.randomUUID(), invoice.getId(), notionalDate, new BigDecimal("5.0000"), Currency.USD));
- invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
assertEquals(invoices.size(), count);
// get invoice; verify amount paid is correct
- invoice = invoiceDao.getById(invoiceId.toString());
- count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
+ invoice = invoiceDao.getById(invoiceId);
assertEquals(invoice.getAmountPaid().compareTo(new BigDecimal("27.0")), 0);
// advance clock by NUMBER_OF_DAYS_BETWEEN_RETRIES days
// ensure that the number of invoices for payment hasn't changed
notionalDate = notionalDate.plusDays(NUMBER_OF_DAY_BETWEEN_RETRIES);
- invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
assertEquals(invoices.size(), count);
}
- private List<Invoice> getInvoicesDueForPaymentAttempt(List<Invoice> invoices, DateTime date) {
+ private List<Invoice> getInvoicesDueForPaymentAttempt(final List<Invoice> invoices, final DateTime date) {
List<Invoice> invoicesDue= new ArrayList<Invoice>();
- for (Invoice invoice : invoices) {
+ for (final Invoice invoice : invoices) {
if (invoice.isDueForPayment(date, NUMBER_OF_DAY_BETWEEN_RETRIES)) {
invoicesDue.add(invoice);
}
@@ -262,16 +276,16 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
DateTime endDate = startDate.plusMonths(1);
- DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoiceId1, subscriptionId1, startDate, endDate, "test A", rate1, rate1, Currency.USD);
+ DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoiceId1, subscriptionId1, "test plan", "test A", startDate, endDate, rate1, rate1, null, Currency.USD);
invoiceItemDao.create(item1);
- DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoiceId1, subscriptionId2, startDate, endDate, "test B", rate2, rate2, Currency.USD);
+ DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoiceId1, subscriptionId2, "test plan", "test B", startDate, endDate, rate2, rate2, null, Currency.USD);
invoiceItemDao.create(item2);
- DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoiceId1, subscriptionId3, startDate, endDate, "test C", rate3, rate3, Currency.USD);
+ DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoiceId1, subscriptionId3, "test plan", "test C", startDate, endDate, rate3, rate3, null, Currency.USD);
invoiceItemDao.create(item3);
- DefaultInvoiceItem item4 = new DefaultInvoiceItem(invoiceId1, subscriptionId4, startDate, endDate, "test D", rate4, rate4, Currency.USD);
+ DefaultInvoiceItem item4 = new DefaultInvoiceItem(invoiceId1, subscriptionId4, "test plan", "test D", startDate, endDate, rate4, rate4, null, Currency.USD);
invoiceItemDao.create(item4);
// create invoice 2 (subscriptions 1-3)
@@ -283,29 +297,58 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
startDate = endDate;
endDate = startDate.plusMonths(1);
- DefaultInvoiceItem item5 = new DefaultInvoiceItem(invoiceId2, subscriptionId1, startDate, endDate, "test A", rate1, rate1, Currency.USD);
+ DefaultInvoiceItem item5 = new DefaultInvoiceItem(invoiceId2, subscriptionId1, "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
invoiceItemDao.create(item5);
- DefaultInvoiceItem item6 = new DefaultInvoiceItem(invoiceId2, subscriptionId2, startDate, endDate, "test B", rate2, rate2, Currency.USD);
+ DefaultInvoiceItem item6 = new DefaultInvoiceItem(invoiceId2, subscriptionId2, "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
invoiceItemDao.create(item6);
- DefaultInvoiceItem item7 = new DefaultInvoiceItem(invoiceId2, subscriptionId3, startDate, endDate, "test C", rate3, rate3, Currency.USD);
+ DefaultInvoiceItem item7 = new DefaultInvoiceItem(invoiceId2, subscriptionId3, "test plan", "test phase C", startDate, endDate, rate3, rate3, null, Currency.USD);
invoiceItemDao.create(item7);
// check that each subscription returns the correct number of invoices
- List<Invoice> items1 = invoiceDao.getInvoicesBySubscription(subscriptionId1.toString());
+ List<Invoice> items1 = invoiceDao.getInvoicesBySubscription(subscriptionId1);
assertEquals(items1.size(), 2);
- List<Invoice> items2 = invoiceDao.getInvoicesBySubscription(subscriptionId2.toString());
+ List<Invoice> items2 = invoiceDao.getInvoicesBySubscription(subscriptionId2);
assertEquals(items2.size(), 2);
- List<Invoice> items3 = invoiceDao.getInvoicesBySubscription(subscriptionId3.toString());
+ List<Invoice> items3 = invoiceDao.getInvoicesBySubscription(subscriptionId3);
assertEquals(items3.size(), 2);
- List<Invoice> items4 = invoiceDao.getInvoicesBySubscription(subscriptionId4.toString());
+ List<Invoice> items4 = invoiceDao.getInvoicesBySubscription(subscriptionId4);
assertEquals(items4.size(), 1);
}
-
+
+ @Test
+ public void testGetInvoicesForAccountAfterDate() {
+ UUID accountId = UUID.randomUUID();
+ DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
+ Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD);
+ invoiceDao.create(invoice1);
+
+ DateTime targetDate2 = new DateTime(2011, 12, 6, 0, 0, 0, 0);
+ Invoice invoice2 = new DefaultInvoice(accountId, targetDate2, Currency.USD);
+ invoiceDao.create(invoice2);
+
+
+ List<Invoice> invoices;
+ invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 1, 1, 0, 0, 0, 0));
+ assertEquals(invoices.size(), 2);
+
+ invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 10, 6, 0, 0, 0, 0));
+ assertEquals(invoices.size(), 2);
+
+ invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 10, 11, 0, 0, 0, 0));
+ assertEquals(invoices.size(), 1);
+
+ invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 12, 6, 0, 0, 0, 0));
+ assertEquals(invoices.size(), 1);
+
+ invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2012, 1, 1, 0, 0, 0, 0));
+ assertEquals(invoices.size(), 0);
+ }
+
@Test
public void testAccountBalance() {
UUID accountId = UUID.randomUUID();
@@ -319,23 +362,20 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
BigDecimal rate1 = new BigDecimal("17.0");
BigDecimal rate2 = new BigDecimal("42.0");
- DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test A", rate1, rate1, Currency.USD);
+ DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
invoiceItemDao.create(item1);
- DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test B", rate2, rate2, Currency.USD);
+ DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
invoiceItemDao.create(item2);
BigDecimal payment1 = new BigDecimal("48.0");
-
- // TODO - reenable when DefaultInvoicePayement is visible in this branch
- //InvoicePayment payment = new DefaultInvoicePayment(invoice1.getId(), new DateTime(), payment1, Currency.USD);
- //invoicePaymentDao.create(payment);
- //
- //BigDecimal balance = invoiceDao.getAccountBalance(accountId);
- //assertEquals(balance.compareTo(rate1.add(rate2).subtract(payment1)), 0);
+ InvoicePayment payment = new DefaultInvoicePayment(invoice1.getId(), new DateTime(), payment1, Currency.USD);
+ invoicePaymentDao.create(payment);
+ BigDecimal balance = invoiceDao.getAccountBalance(accountId);
+ assertEquals(balance.compareTo(rate1.add(rate2).subtract(payment1)), 0);
}
-
+
@Test
public void testAccountBalanceWithNoPayments() {
UUID accountId = UUID.randomUUID();
@@ -349,17 +389,16 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
BigDecimal rate1 = new BigDecimal("17.0");
BigDecimal rate2 = new BigDecimal("42.0");
- DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test A", rate1, rate1, Currency.USD);
+ DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
invoiceItemDao.create(item1);
- DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test B", rate2, rate2, Currency.USD);
+ DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
invoiceItemDao.create(item2);
BigDecimal balance = invoiceDao.getAccountBalance(accountId);
assertEquals(balance.compareTo(rate1.add(rate2)), 0);
}
-
@Test
public void testAccountBalanceWithNoInvoiceItems() {
UUID accountId = UUID.randomUUID();
@@ -367,12 +406,153 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD);
invoiceDao.create(invoice1);
- // TODO - reenable when DefaultInvoicePayement is visible in this branch
- //BigDecimal payment1 = new BigDecimal("48.0");
- //InvoicePayment payment = new DefaultInvoicePayment(invoice1.getId(), new DateTime(), payment1, Currency.USD);
- //invoicePaymentDao.create(payment);
- //
- //BigDecimal balance = invoiceDao.getAccountBalance(accountId);
- //assertEquals(balance.compareTo(BigDecimal.ZERO.subtract(payment1)), 0);
+ BigDecimal payment1 = new BigDecimal("48.0");
+ InvoicePayment payment = new DefaultInvoicePayment(invoice1.getId(), new DateTime(), payment1, Currency.USD);
+ invoicePaymentDao.create(payment);
+
+ BigDecimal balance = invoiceDao.getAccountBalance(accountId);
+ assertEquals(balance.compareTo(BigDecimal.ZERO.subtract(payment1)), 0);
+ }
+
+ @Test
+ public void testGetUnpaidInvoicesByAccountId() {
+ UUID accountId = UUID.randomUUID();
+ DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
+ Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD);
+ invoiceDao.create(invoice1);
+
+ DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
+ DateTime endDate = startDate.plusMonths(1);
+
+ BigDecimal rate1 = new BigDecimal("17.0");
+ BigDecimal rate2 = new BigDecimal("42.0");
+
+ DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
+ invoiceItemDao.create(item1);
+
+ DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
+ invoiceItemDao.create(item2);
+
+ DateTime upToDate;
+ Collection<Invoice> invoices;
+
+ upToDate = new DateTime(2011, 1, 1, 0, 0, 0, 0);
+ invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate);
+ assertEquals(invoices.size(), 0);
+
+ upToDate = new DateTime(2012, 1, 1, 0, 0, 0, 0);
+ invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate);
+ assertEquals(invoices.size(), 1);
+
+ DateTime targetDate2 = new DateTime(2011, 7, 1, 0, 0, 0, 0);
+ Invoice invoice2 = new DefaultInvoice(accountId, targetDate2, Currency.USD);
+ invoiceDao.create(invoice2);
+
+ DateTime startDate2 = new DateTime(2011, 6, 1, 0, 0, 0, 0);
+ DateTime endDate2 = startDate2.plusMonths(3);
+
+ BigDecimal rate3 = new BigDecimal("21.0");
+
+ DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoice2.getId(), UUID.randomUUID(), "test plan", "test phase C", startDate2, endDate2, rate3, rate3, null, Currency.USD);
+ invoiceItemDao.create(item3);
+
+ upToDate = new DateTime(2011, 1, 1, 0, 0, 0, 0);
+ invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate);
+ assertEquals(invoices.size(), 0);
+
+ upToDate = new DateTime(2012, 1, 1, 0, 0, 0, 0);
+ invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate);
+ assertEquals(invoices.size(), 2);
+ }
+
+ /*
+ *
+ * this test verifies that immediate changes give the correct results
+ *
+ */
+ @Test
+ public void testInvoiceGenerationForImmediateChanges() {
+ InvoiceGenerator generator = new DefaultInvoiceGenerator();
+
+ UUID accountId = UUID.randomUUID();
+ InvoiceItemList invoiceItemList = new InvoiceItemList();
+ DateTime targetDate = new DateTime(2011, 2, 16, 0, 0, 0, 0);
+
+ // generate first invoice
+ DefaultPrice price1 = new DefaultPrice(TEN, Currency.USD);
+ MockInternationalPrice recurringPrice = new MockInternationalPrice(price1);
+ MockPlanPhase phase1 = new MockPlanPhase(recurringPrice, null, BillingPeriod.MONTHLY, PhaseType.TRIAL);
+ MockPlan plan1 = new MockPlan(phase1);
+
+ Subscription subscription = new MockSubscription();
+ 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,
+ "testEvent1", SubscriptionTransitionType.CREATE);
+
+ BillingEventSet events = new BillingEventSet();
+ events.add(event1);
+
+ Invoice invoice1 = generator.generateInvoice(accountId, events, invoiceItemList, targetDate, Currency.USD);
+ assertEquals(invoice1.getBalance(), TEN);
+ invoiceItemList.addAll(invoice1.getInvoiceItems());
+
+ // generate second invoice
+ DefaultPrice price2 = new DefaultPrice(TWENTY, Currency.USD);
+ MockInternationalPrice recurringPrice2 = new MockInternationalPrice(price2);
+ MockPlanPhase phase2 = new MockPlanPhase(recurringPrice, null, BillingPeriod.MONTHLY, PhaseType.TRIAL);
+ MockPlan plan2 = new MockPlan(phase2);
+
+ DateTime effectiveDate2 = new DateTime(2011, 2, 15, 0, 0, 0, 0);
+ BillingEvent event2 = new DefaultBillingEvent(subscription, effectiveDate2, plan2, phase2, null,
+ recurringPrice2, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
+ "testEvent2", SubscriptionTransitionType.CREATE);
+ events.add(event2);
+
+ // second invoice should be for one half (14/28 days) the difference between the rate plans
+ // this is a temporary state, since it actually contains an adjusting item that properly belong to invoice 1
+ Invoice invoice2 = generator.generateInvoice(accountId, events, invoiceItemList, targetDate, Currency.USD);
+ assertEquals(invoice2.getBalance(), FIVE);
+ invoiceItemList.addAll(invoice2.getInvoiceItems());
+
+ invoiceDao.create(invoice1);
+ invoiceDao.create(invoice2);
+
+ Invoice savedInvoice1 = invoiceDao.getById(invoice1.getId());
+ assertEquals(savedInvoice1.getTotalAmount(), ZERO);
+
+ Invoice savedInvoice2 = invoiceDao.getById(invoice2.getId());
+ assertEquals(savedInvoice2.getTotalAmount(), FIFTEEN);
+ }
+
+ @Test
+ public void testInvoiceForFreeTrial() {
+ DefaultPrice price = new DefaultPrice(BigDecimal.ZERO, Currency.USD);
+ MockInternationalPrice recurringPrice = new MockInternationalPrice(price);
+ MockPlanPhase phase = new MockPlanPhase(recurringPrice, null);
+ MockPlan plan = new MockPlan(phase);
+
+ Subscription subscription = new MockSubscription();
+ DateTime effectiveDate = buildDateTime(2011, 1, 1);
+
+ BillingEvent event = new DefaultBillingEvent(subscription, effectiveDate, plan, phase, null,
+ recurringPrice, BillingPeriod.MONTHLY, 15, BillingModeType.IN_ADVANCE,
+ "testEvent", SubscriptionTransitionType.CREATE);
+ BillingEventSet events = new BillingEventSet();
+ events.add(event);
+
+ DateTime targetDate = buildDateTime(2011, 1, 15);
+ InvoiceGenerator generator = new DefaultInvoiceGenerator();
+ Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
+ assertEquals(invoice.getNumberOfItems(), 1);
+ assertEquals(invoice.getTotalAmount().compareTo(ZERO), 0);
+ }
+
+ @Test
+ public void testInvoiceForEmptyEventSet() {
+ InvoiceGenerator generator = new DefaultInvoiceGenerator();
+ BillingEventSet events = new BillingEventSet();
+ Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, new DateTime(), Currency.USD);
+ assertNull(invoice);
}
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
index dc2afd8..b2c2f23 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
@@ -45,7 +45,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
DateTime endDate = new DateTime(2011, 11, 1, 0, 0, 0, 0);
BigDecimal rate = new BigDecimal("20.00");
- InvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, "test", rate, rate, Currency.USD);
+ InvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, rate, rate, rate, Currency.USD);
invoiceItemDao.create(item);
InvoiceItem thisItem = invoiceItemDao.getById(item.getId().toString());
@@ -55,9 +55,9 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
assertEquals(thisItem.getSubscriptionId(), item.getSubscriptionId());
assertEquals(thisItem.getStartDate(), item.getStartDate());
assertEquals(thisItem.getEndDate(), item.getEndDate());
- assertEquals(thisItem.getDescription(), item.getDescription());
- assertEquals(thisItem.getAmount().compareTo(item.getAmount()), 0);
- assertEquals(thisItem.getRate().compareTo(item.getRate()), 0);
+ assertEquals(thisItem.getRecurringAmount().compareTo(item.getRecurringAmount()), 0);
+ assertEquals(thisItem.getRecurringRate().compareTo(item.getRecurringRate()), 0);
+ assertEquals(thisItem.getFixedAmount().compareTo(item.getFixedAmount()), 0);
assertEquals(thisItem.getCurrency(), item.getCurrency());
}
@@ -69,7 +69,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
for (int i = 0; i < 3; i++) {
UUID invoiceId = UUID.randomUUID();
- DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate.plusMonths(i), startDate.plusMonths(i + 1), "test", rate, rate, Currency.USD);
+ DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate.plusMonths(i), startDate.plusMonths(i + 1), rate, rate, null, Currency.USD);
invoiceItemDao.create(item);
}
@@ -86,7 +86,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
for (int i = 0; i < 5; i++) {
UUID subscriptionId = UUID.randomUUID();
BigDecimal amount = rate.multiply(new BigDecimal(i + 1));
- DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, startDate.plusMonths(1), "test", amount, amount, Currency.USD);
+ DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), amount, amount, amount, Currency.USD);
invoiceItemDao.create(item);
}
@@ -107,7 +107,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
BigDecimal rate = new BigDecimal("20.00");
UUID subscriptionId = UUID.randomUUID();
- DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, startDate.plusMonths(1), "test", rate, rate, Currency.USD);
+ DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), rate, rate, rate, Currency.USD);
invoiceItemDao.create(item);
List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsByAccount(accountId.toString());
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
index 2ef150c..cf6c112 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
@@ -18,150 +18,133 @@ package com.ning.billing.invoice.dao;
import java.math.BigDecimal;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.UUID;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.util.bus.Bus;
import org.joda.time.DateTime;
import com.google.inject.Inject;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.api.user.DefaultInvoiceCreationNotification;
-import com.ning.billing.invoice.model.DefaultInvoice;
-import com.ning.billing.payment.api.InvoicePayment;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
public class MockInvoiceDao implements InvoiceDao {
- private final EventBus eventBus;
+ private final Bus eventBus;
private final Object monitor = new Object();
- private final Map<String, Invoice> invoices = new LinkedHashMap<String, Invoice>();
- private final Map<UUID, InvoicePayment> invoicePayments = new LinkedHashMap<UUID, InvoicePayment>();
+ private final Map<UUID, Invoice> invoices = new LinkedHashMap<UUID, Invoice>();
@Inject
- public MockInvoiceDao(EventBus eventBus) {
+ public MockInvoiceDao(Bus eventBus) {
this.eventBus = eventBus;
}
@Override
public void create(Invoice invoice) {
synchronized (monitor) {
- invoices.put(invoice.getId().toString(), invoice);
+ invoices.put(invoice.getId(), invoice);
}
try {
eventBus.post(new DefaultInvoiceCreationNotification(invoice.getId(), invoice.getAccountId(),
- invoice.getAmountOutstanding(), invoice.getCurrency(),
+ invoice.getBalance(), invoice.getCurrency(),
invoice.getInvoiceDate()));
}
- catch (EventBusException ex) {
+ catch (Bus.EventBusException ex) {
throw new RuntimeException(ex);
}
}
- private Invoice munge(Invoice invoice) {
- if (invoice == null) {
- return null;
- }
-
- DateTime lastPaymentDate = null;
- BigDecimal amountPaid = new BigDecimal("0");
-
- for (InvoicePayment invoicePayment : invoicePayments.values()) {
- if (invoicePayment.getInvoiceId().equals(invoice.getId().toString())) {
- if (lastPaymentDate == null || lastPaymentDate.isBefore(invoicePayment.getPaymentAttemptDate())) {
- lastPaymentDate = invoicePayment.getPaymentAttemptDate();
- }
- if (invoicePayment.getAmount() != null) {
- amountPaid.add(invoicePayment.getAmount());
- }
- }
+ @Override
+ public Invoice getById(UUID id) {
+ synchronized (monitor) {
+ return invoices.get(id);
}
- return new DefaultInvoice(invoice.getId(),
- invoice.getAccountId(),
- invoice.getInvoiceDate(),
- invoice.getTargetDate(),
- invoice.getCurrency(),
- lastPaymentDate,
- amountPaid,
- invoice.getItems());
}
- private List<Invoice> munge(Collection<Invoice> invoices) {
- List<Invoice> result = new ArrayList<Invoice>();
- for (Invoice invoice : invoices) {
- result.add(munge(invoice));
+ @Override
+ public List<Invoice> get() {
+ synchronized (monitor) {
+ return new ArrayList<Invoice>(invoices.values());
}
- return result;
}
@Override
- public Invoice getById(String id) {
+ public List<Invoice> getInvoicesByAccount(UUID accountId) {
+ List<Invoice> result = new ArrayList<Invoice>();
+
synchronized (monitor) {
- return munge(invoices.get(id));
+ for (Invoice invoice : invoices.values()) {
+ if (accountId.equals(invoice.getAccountId())) {
+ result.add(invoice);
+ }
+ }
}
+ return result;
}
@Override
- public List<Invoice> get() {
+ public List<Invoice> getInvoicesByAccount(UUID accountId, DateTime fromDate) {
+ List<Invoice> invoicesForAccount = new ArrayList<Invoice>();
+
synchronized (monitor) {
- return munge(invoices.values());
+ for (Invoice invoice : get()) {
+ if (accountId.equals(invoice.getAccountId()) && !invoice.getTargetDate().isBefore(fromDate)) {
+ invoicesForAccount.add(invoice);
+ }
+ }
}
+
+ return invoicesForAccount;
}
@Override
- public List<Invoice> getInvoicesByAccount(String accountId) {
- List<Invoice> result = new ArrayList<Invoice>();
+ public List<InvoiceItem> getInvoiceItemsByAccount(UUID accountId) {
+ List<InvoiceItem> invoiceItemsForAccount = new ArrayList<InvoiceItem>();
synchronized (monitor) {
- for (Invoice invoice : invoices.values()) {
- if (accountId.equals(invoice.getAccountId().toString())) {
- result.add(invoice);
+ for (Invoice invoice : get()) {
+ if (accountId.equals(invoice.getAccountId())) {
+ invoiceItemsForAccount.addAll(invoice.getInvoiceItems());
}
}
}
- return munge(result);
+
+ return invoiceItemsForAccount;
}
@Override
- public List<Invoice> getInvoicesBySubscription(String subscriptionId) {
+ public List<Invoice> getInvoicesBySubscription(UUID subscriptionId) {
List<Invoice> result = new ArrayList<Invoice>();
synchronized (monitor) {
for (Invoice invoice : invoices.values()) {
- for (InvoiceItem item : invoice.getItems()) {
- if (subscriptionId.equals(item.getSubscriptionId().toString())) {
+ for (InvoiceItem item : invoice.getInvoiceItems()) {
+ if (subscriptionId.equals(item.getSubscriptionId())) {
result.add(invoice);
break;
}
}
}
}
- return munge(result);
+ return result;
}
@Override
- public List<UUID> getInvoicesForPayment(Date targetDate, int numberOfDays) {
- Set<UUID> result = new LinkedHashSet<UUID>();
+ public List<UUID> getInvoicesForPayment(DateTime targetDate, int numberOfDays) {
+ List<UUID> result = new ArrayList<UUID>();
synchronized (monitor) {
- for (InvoicePayment invoicePayment : invoicePayments.values()) {
- Invoice invoice = invoices.get(invoicePayment.getInvoiceId());
- if ((invoice != null) &&
- (((invoicePayment.getPaymentAttemptDate() == null) || !invoicePayment.getPaymentAttemptDate().plusDays(numberOfDays).isAfter(targetDate.getTime())) &&
- (invoice.getTotalAmount() != null) && invoice.getTotalAmount().doubleValue() >= 0) &&
- ((invoicePayment.getAmount() == null) || invoicePayment.getAmount().doubleValue() >= invoice.getTotalAmount().doubleValue())) {
- result.add(invoice.getId());
+ for (Invoice invoice : invoices.values()) {
+ if (invoice.isDueForPayment(targetDate, numberOfDays)) {
+ result.add(invoice.getId());
}
}
}
- return new ArrayList<UUID>(result);
+ return result;
}
@Override
@@ -169,11 +152,13 @@ public class MockInvoiceDao implements InvoiceDao {
}
@Override
- public String getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId) {
+ public UUID getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId) {
synchronized(monitor) {
- for (InvoicePayment invoicePayment : invoicePayments.values()) {
- if (paymentAttemptId.toString().equals(invoicePayment.getPaymentAttemptId())) {
- return invoicePayment.getInvoiceId().toString();
+ for (Invoice invoice : invoices.values()) {
+ for (InvoicePayment payment : invoice.getPayments()) {
+ if (paymentAttemptId.equals(payment.getPaymentAttemptId())) {
+ return invoice.getId();
+ }
}
}
}
@@ -183,24 +168,51 @@ public class MockInvoiceDao implements InvoiceDao {
@Override
public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
synchronized(monitor) {
- return invoicePayments.get(paymentAttemptId);
+ for (Invoice invoice : invoices.values()) {
+ for (InvoicePayment payment : invoice.getPayments()) {
+ if (paymentAttemptId.equals(payment.getPaymentAttemptId())) {
+ return payment;
+ }
+ }
+ }
}
+
+ return null;
}
@Override
public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
- synchronized (monitor) {
- invoicePayments.put(invoicePayment.getPaymentAttemptId(), invoicePayment);
- }
+ synchronized (monitor) {
+ Invoice invoice = invoices.get(invoicePayment.getInvoiceId());
+ if (invoice != null) {
+ invoice.addPayment(invoicePayment);
+ }
+ }
+ }
+
+ @Override
+ public BigDecimal getAccountBalance(UUID accountId) {
+ BigDecimal balance = BigDecimal.ZERO;
+
+ for (Invoice invoice : get()) {
+ if (accountId.equals(invoice.getAccountId())) {
+ balance = balance.add(invoice.getBalance());
+ }
+ }
+
+ return balance;
}
- @Override
- public BigDecimal getAccountBalance(UUID accountId) {
- List<Invoice> invoices = getInvoicesByAccount(accountId.toString());
- BigDecimal result = BigDecimal.ZERO;
- for(Invoice invoice : invoices) {
- result = result.add(invoice.getAmountOutstanding());
- }
- return result;
- }
+ @Override
+ public List<Invoice> getUnpaidInvoicesByAccountId(UUID accountId, DateTime upToDate) {
+ List<Invoice> unpaidInvoices = new ArrayList<Invoice>();
+
+ for (Invoice invoice : get()) {
+ if (accountId.equals(invoice.getAccountId()) && (invoice.getBalance().compareTo(BigDecimal.ZERO) > 0)) {
+ unpaidInvoices.add(invoice);
+ }
+ }
+
+ return unpaidInvoices;
+ }
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java
new file mode 100644
index 0000000..005a605
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java
@@ -0,0 +1,122 @@
+/*
+ * 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.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+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 org.joda.time.DateTime;
+
+import java.util.List;
+import java.util.UUID;
+
+public class MockSubscription implements Subscription {
+ private UUID subscriptionId = UUID.randomUUID();
+
+ @Override
+ public void cancel(DateTime requestedDate, boolean eot) throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void uncancel() throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void changePlan(String productName, BillingPeriod term, String planSet, DateTime requestedDate) throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void pause() throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void resume() throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public UUID getId() {
+ return subscriptionId;
+ }
+
+ @Override
+ public UUID getBundleId() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SubscriptionState getState() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DateTime getStartDate() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DateTime getEndDate() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Plan getCurrentPlan() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getCurrentPriceList() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public PlanPhase getCurrentPhase() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DateTime getChargedThroughDate() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DateTime getPaidThroughDate() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<SubscriptionTransition> getActiveTransitions() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<SubscriptionTransition> getAllTransitions() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SubscriptionTransition getPendingTransition() {
+ throw new UnsupportedOperationException();
+ }
+}
\ No newline at end of file
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
index 3705289..b252531 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
@@ -16,19 +16,33 @@
package com.ning.billing.invoice.glue;
+import java.io.IOException;
+
+import com.ning.billing.invoice.api.test.InvoiceTestApi;
+import com.ning.billing.invoice.api.test.DefaultInvoiceTestApi;
+import com.ning.billing.invoice.dao.InvoicePaymentSqlDao;
+import com.ning.billing.util.glue.GlobalLockerModule;
+import org.skife.jdbi.v2.IDBI;
+import com.ning.billing.account.glue.AccountModule;
+import com.ning.billing.catalog.glue.CatalogModule;
import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.glue.EntitlementModule;
import com.ning.billing.invoice.dao.InvoiceItemSqlDao;
-import com.ning.billing.util.glue.EventBusModule;
-import org.skife.jdbi.v2.IDBI;
-
-import java.io.IOException;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.DefaultClock;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.notificationq.MockNotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService;
public class InvoiceModuleWithEmbeddedDb extends InvoiceModule {
private final MysqlTestingHelper helper = new MysqlTestingHelper();
private IDBI dbi;
- public void createDb(String ddl) throws IOException {
+ public void startDb() throws IOException {
helper.startMysql();
+ }
+
+ public void initDb(final String ddl) throws IOException {
helper.initDb(ddl);
}
@@ -36,15 +50,34 @@ public class InvoiceModuleWithEmbeddedDb extends InvoiceModule {
helper.stopMysql();
}
- public InvoiceItemSqlDao getInvoiceItemDao() {
+ public InvoiceItemSqlDao getInvoiceItemSqlDao() {
return dbi.onDemand(InvoiceItemSqlDao.class);
}
+ public InvoicePaymentSqlDao getInvoicePaymentSqlDao() {
+ return dbi.onDemand(InvoicePaymentSqlDao.class);
+ }
+
+ private void installNotificationQueue() {
+ bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
+ }
+
@Override
public void configure() {
dbi = helper.getDBI();
bind(IDBI.class).toInstance(dbi);
+
+ bind(Clock.class).to(DefaultClock.class).asEagerSingleton();
+ installNotificationQueue();
+ install(new AccountModule());
+ install(new CatalogModule());
+ install(new EntitlementModule());
+ install(new GlobalLockerModule());
+
super.configure();
- install(new EventBusModule());
+
+ bind(InvoiceTestApi.class).to(DefaultInvoiceTestApi.class).asEagerSingleton();
+
+ install(new BusModule());
}
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
index d01301b..e5e699b 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
@@ -18,11 +18,34 @@ package com.ning.billing.invoice.glue;
import com.ning.billing.invoice.dao.InvoiceDao;
import com.ning.billing.invoice.dao.MockInvoiceDao;
+import com.ning.billing.util.globalLocker.GlobalLocker;
+import com.ning.billing.util.globalLocker.MockGlobalLocker;
public class InvoiceModuleWithMocks extends InvoiceModule {
@Override
protected void installInvoiceDao() {
bind(MockInvoiceDao.class).asEagerSingleton();
bind(InvoiceDao.class).to(MockInvoiceDao.class);
+ bind(GlobalLocker.class).to(MockGlobalLocker.class).asEagerSingleton();
+ }
+
+ @Override
+ protected void installGlobalLocker() {
+ bind(GlobalLocker.class).to(MockGlobalLocker.class).asEagerSingleton();
+ }
+
+ @Override
+ protected void installInvoiceListener() {
+
+ }
+
+ @Override
+ protected void installNotifier() {
+
+ }
+
+ @Override
+ protected void installInvoiceService() {
+
}
}
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
new file mode 100644
index 0000000..d2fcdee
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
@@ -0,0 +1,164 @@
+/*
+ * 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.notification;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+import com.ning.billing.catalog.DefaultCatalogService;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.config.CatalogConfig;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.engine.dao.EntitlementSqlDao;
+import com.ning.billing.util.bus.InMemoryBus;
+import org.apache.commons.io.IOUtils;
+import org.joda.time.DateTime;
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.lifecycle.KillbillService.ServiceException;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.notificationq.DefaultNotificationQueueService;
+import com.ning.billing.util.notificationq.DummySqlTest;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
+
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+public class TestNextBillingDateNotifier {
+ private static Logger log = LoggerFactory.getLogger(TestNextBillingDateNotifier.class);
+ private Clock clock;
+ private DefaultNextBillingDateNotifier notifier;
+ private DummySqlTest dao;
+ private Bus eventBus;
+ private MysqlTestingHelper helper;
+
+ @BeforeClass(groups={"setup"})
+ public void setup() throws ServiceException, IOException, ClassNotFoundException, SQLException {
+ //TestApiBase.loadSystemPropertiesFromClasspath("/entitlement.properties");
+ final Injector g = Guice.createInjector(Stage.PRODUCTION, new AbstractModule() {
+ protected void configure() {
+ bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+ bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+ bind(NotificationQueueService.class).to(DefaultNotificationQueueService.class).asEagerSingleton();
+ final InvoiceConfig invoiceConfig = new ConfigurationObjectFactory(System.getProperties()).build(InvoiceConfig.class);
+ bind(InvoiceConfig.class).toInstance(invoiceConfig);
+ final CatalogConfig catalogConfig = new ConfigurationObjectFactory(System.getProperties()).build(CatalogConfig.class);
+ bind(CatalogConfig.class).toInstance(catalogConfig);
+ bind(CatalogService.class).to(DefaultCatalogService.class).asEagerSingleton();
+ final MysqlTestingHelper helper = new MysqlTestingHelper();
+ bind(MysqlTestingHelper.class).toInstance(helper);
+ IDBI dbi = helper.getDBI();
+ bind(IDBI.class).toInstance(dbi);
+ bind(EntitlementDao.class).to(EntitlementSqlDao.class).asEagerSingleton();
+ }
+ });
+
+ clock = g.getInstance(Clock.class);
+ IDBI dbi = g.getInstance(IDBI.class);
+ dao = dbi.onDemand(DummySqlTest.class);
+ eventBus = g.getInstance(Bus.class);
+ helper = g.getInstance(MysqlTestingHelper.class);
+ notifier = new DefaultNextBillingDateNotifier(g.getInstance(NotificationQueueService.class), eventBus, g.getInstance(InvoiceConfig.class), g.getInstance(EntitlementDao.class));
+ startMysql();
+ }
+
+ private void startMysql() throws IOException, ClassNotFoundException, SQLException {
+ final String ddl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+ final String testDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl_test.sql"));
+ final String entitlementDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+ helper.startMysql();
+ helper.initDb(ddl);
+ helper.initDb(testDdl);
+ helper.initDb(entitlementDdl);
+ }
+
+ public static class NextBillingEventListener {
+ private int eventCount=0;
+ private NextBillingDateEvent event;
+
+ public int getEventCount() {
+ return eventCount;
+ }
+
+ @Subscribe
+ public synchronized void processEvent(NextBillingDateEvent event) {
+ eventCount++;
+ this.event = event;
+ //log.debug("Got event {} {}", event.name, event.value);
+ }
+
+ public NextBillingDateEvent getLatestEvent() {
+ return event;
+ }
+ }
+
+ @Test(enabled=false, groups="slow")
+ public void test() throws Exception {
+ final UUID subscriptionId = new UUID(0L,1000L);
+ final DateTime now = new DateTime();
+ final DateTime readyTime = now.plusMillis(2000);
+
+ final NextBillingEventListener listener = new NextBillingEventListener();
+ eventBus.start();
+ notifier.initialize();
+ notifier.start();
+
+ eventBus.register(listener);
+ dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+ @Override
+ public Void inTransaction(DummySqlTest transactional,
+ TransactionStatus status) throws Exception {
+
+ notifier.insertNextBillingNotification(transactional, subscriptionId, readyTime);
+ return null;
+ }
+ });
+
+ // Move time in the future after the notification effectiveDate
+ ((ClockMock) clock).setDeltaFromReality(3000);
+
+
+ await().atMost(1, MINUTES).until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return listener.getEventCount() == 1;
+ }
+ });
+
+ Assert.assertEquals(listener.getEventCount(), 1);
+ Assert.assertEquals(listener.getLatestEvent().getSubscriptionId(), subscriptionId);
+ }
+}
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 bf88c72..11f8d62 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
@@ -16,18 +16,14 @@
package com.ning.billing.invoice.tests;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-
-import java.math.BigDecimal;
-import java.util.UUID;
-
-import org.joda.time.DateTime;
-import org.testng.annotations.Test;
-
-import com.ning.billing.catalog.MockCatalog;
+import com.ning.billing.catalog.DefaultPrice;
+import com.ning.billing.catalog.MockInternationalPrice;
+import com.ning.billing.catalog.MockPlan;
+import com.ning.billing.catalog.MockPlanPhase;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.entitlement.api.billing.BillingEvent;
@@ -36,26 +32,37 @@ import com.ning.billing.entitlement.api.billing.DefaultBillingEvent;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionData;
import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
import com.ning.billing.invoice.api.Invoice;
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.DefaultInvoiceItem;
import com.ning.billing.invoice.model.InvoiceGenerator;
import com.ning.billing.invoice.model.InvoiceItemList;
+import org.joda.time.DateTime;
+import org.testng.annotations.Test;
+
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
-@Test(groups = {"invoicing", "invoiceGenerator"})
+@Test(groups = {"fast", "invoicing", "invoiceGenerator"})
public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
private final InvoiceGenerator generator = new DefaultInvoiceGenerator();
@Test
public void testWithNullEventSetAndNullInvoiceSet() {
UUID accountId = UUID.randomUUID();
- Invoice invoice = generator.generateInvoice(accountId, null, null, new DateTime(), Currency.USD);
+ Invoice invoice = generator.generateInvoice(accountId, new BillingEventSet(), new InvoiceItemList(), new DateTime(), Currency.USD);
- assertNotNull(invoice);
- assertEquals(invoice.getNumberOfItems(), 0);
- assertEquals(invoice.getTotalAmount(), ZERO);
+ assertNull(invoice);
}
@Test
@@ -66,9 +73,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
UUID accountId = UUID.randomUUID();
Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, new DateTime(), Currency.USD);
- assertNotNull(invoice);
- assertEquals(invoice.getNumberOfItems(), 0);
- assertEquals(invoice.getTotalAmount(), ZERO);
+ assertNull(invoice);
}
@Test
@@ -77,13 +82,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
DateTime startDate = buildDateTime(2011, 9, 1);
- MockCatalog catalog = new MockCatalog();
- Plan plan = catalog.getCurrentPlans()[0];
- PlanPhase phase = plan.getAllPhases()[0];
+
+ Plan plan = new MockPlan();
+ BigDecimal rate1 = TEN;
+ PlanPhase phase = createMockMonthlyPlanPhase(rate1);
- BillingEvent event = new DefaultBillingEvent(sub, startDate, plan, phase,
- new InternationalPriceMock(ZERO),new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE, "Test");
+ BillingEvent event = createBillingEvent(sub.getId(), startDate, plan, phase, 1);
events.add(event);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -95,6 +99,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
assertNotNull(invoice);
assertEquals(invoice.getNumberOfItems(), 1);
assertEquals(invoice.getTotalAmount(), TWENTY);
+ assertEquals(invoice.getInvoiceItems().get(0).getSubscriptionId(), sub.getId());
}
@Test
@@ -103,13 +108,11 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
DateTime startDate = buildDateTime(2011, 9, 1);
- MockCatalog catalog = new MockCatalog();
- Plan plan = catalog.getCurrentPlans()[0];
- PlanPhase phase = plan.getAllPhases()[0];
+
+ Plan plan = new MockPlan();
BigDecimal rate = TEN;
- BillingEvent event = new DefaultBillingEvent(sub, startDate, plan, phase,
- new InternationalPriceMock(ZERO), new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
- 15, BillingModeType.IN_ADVANCE,"Test");
+ PlanPhase phase = createMockMonthlyPlanPhase(rate);
+ BillingEvent event = createBillingEvent(sub.getId(), startDate, plan, phase, 15);
events.add(event);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -131,24 +134,20 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
public void testTwoMonthlySubscriptionsWithAlignedBillingDates() {
BillingEventSet events = new BillingEventSet();
- MockCatalog catalog = new MockCatalog();
- Plan plan1 = catalog.getCurrentPlans()[0];
- PlanPhase phase1 = plan1.getAllPhases()[0];
- Plan plan2 = catalog.getCurrentPlans()[1];
- PlanPhase phase2 = plan2.getAllPhases()[0];
+ Plan plan1 = new MockPlan();
+ BigDecimal rate1 = FIVE;
+ PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
+
+ Plan plan2 = new MockPlan();
+ BigDecimal rate2 = TEN;
+ PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
- BillingEvent event1 = new DefaultBillingEvent(sub, buildDateTime(2011, 9, 1),
- plan1,phase1,
- new InternationalPriceMock(ZERO), new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE, "Test");
+ BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1, phase1, 1);
events.add(event1);
- BillingEvent event2 = new DefaultBillingEvent(sub, buildDateTime(2011, 10, 1),
- plan2,phase2,
- new InternationalPriceMock(ZERO), new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE,"Test");
+ BillingEvent event2 = createBillingEvent(sub.getId(), buildDateTime(2011, 10, 1), plan2, phase2, 1);
events.add(event2);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -158,29 +157,25 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
assertNotNull(invoice);
assertEquals(invoice.getNumberOfItems(), 2);
- assertEquals(invoice.getTotalAmount(), FIVE.multiply(TWO).add(TEN).setScale(NUMBER_OF_DECIMALS));
+ assertEquals(invoice.getTotalAmount(), rate1.add(rate2).setScale(NUMBER_OF_DECIMALS));
}
@Test
public void testOnePlan_TwoMonthlyPhases_ChangeImmediate() {
BillingEventSet events = new BillingEventSet();
- MockCatalog catalog = new MockCatalog();
- Plan plan1 = catalog.getCurrentPlans()[0];
- PlanPhase phase1 = plan1.getAllPhases()[0];
+ Plan plan1 = new MockPlan();
+ BigDecimal rate1 = FIVE;
+ PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
- BillingEvent event1 = new DefaultBillingEvent(sub, buildDateTime(2011, 9, 1),
- plan1,phase1,
- new InternationalPriceMock(ZERO),new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE,"Test");
+ BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1,phase1, 1);
events.add(event1);
- BillingEvent event2 = new DefaultBillingEvent(sub, buildDateTime(2011, 10, 15),
- plan1,phase1, //technically should be a different phase but it doesn't impact the test
- new InternationalPriceMock(ZERO),new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
- 15, BillingModeType.IN_ADVANCE,"Test");
+ BigDecimal rate2 = TEN;
+ PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
+ BillingEvent event2 = createBillingEvent(sub.getId(), buildDateTime(2011, 10, 15), plan1, phase2, 15);
events.add(event2);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -197,8 +192,8 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
BigDecimal numberOfCyclesEvent2 = TWO;
BigDecimal expectedValue;
- expectedValue = numberOfCyclesEvent1.multiply(FIVE);
- expectedValue = expectedValue.add(numberOfCyclesEvent2.multiply(TEN));
+ expectedValue = numberOfCyclesEvent1.multiply(rate1);
+ expectedValue = expectedValue.add(numberOfCyclesEvent2.multiply(rate2));
expectedValue = expectedValue.setScale(NUMBER_OF_DECIMALS);
assertEquals(invoice.getTotalAmount(), expectedValue);
@@ -208,28 +203,22 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
public void testOnePlan_ThreeMonthlyPhases_ChangeEOT() {
BillingEventSet events = new BillingEventSet();
- MockCatalog catalog = new MockCatalog();
- Plan plan1 = catalog.getCurrentPlans()[0];
- PlanPhase phase1 = plan1.getAllPhases()[0];
-
+ Plan plan1 = new MockPlan();
+ BigDecimal rate1 = FIVE;
+ PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
- BillingEvent event1 = new DefaultBillingEvent(sub, buildDateTime(2011, 9, 1),
- plan1,phase1,
- new InternationalPriceMock(ZERO),new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE,"Test");
+ BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1, phase1, 1);
events.add(event1);
- BillingEvent event2 = new DefaultBillingEvent(sub, buildDateTime(2011, 10, 1),
- plan1,phase1, //technically should be a different phase but it doesn't impact the test
- new InternationalPriceMock(ZERO),new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE,"Test");
+ BigDecimal rate2 = TEN;
+ PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
+ BillingEvent event2 = createBillingEvent(sub.getId(), buildDateTime(2011, 10, 1), plan1, phase2, 1);
events.add(event2);
- BillingEvent event3 = new DefaultBillingEvent(sub, buildDateTime(2011, 11, 1),
- plan1,phase1, //technically should be a different phase but it doesn't impact the test
- new InternationalPriceMock(ZERO),new InternationalPriceMock(THIRTY), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE,"Test");
+ BigDecimal rate3 = THIRTY;
+ PlanPhase phase3 = createMockMonthlyPlanPhase(rate3);
+ BillingEvent event3 = createBillingEvent(sub.getId(), buildDateTime(2011, 11, 1), plan1, phase3, 1);
events.add(event3);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -239,7 +228,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
assertNotNull(invoice);
assertEquals(invoice.getNumberOfItems(), 3);
- assertEquals(invoice.getTotalAmount(), FIVE.add(TEN).add(TWO.multiply(THIRTY)).setScale(NUMBER_OF_DECIMALS));
+ assertEquals(invoice.getTotalAmount(), rate1.add(rate2).add(TWO.multiply(rate3)).setScale(NUMBER_OF_DECIMALS));
}
@Test
@@ -248,30 +237,24 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
DateTime startDate = buildDateTime(2011, 9, 1);
-
- MockCatalog catalog = new MockCatalog();
- Plan plan1 = catalog.getCurrentPlans()[0];
- PlanPhase phase1 = plan1.getAllPhases()[0];
-
+
+ Plan plan1 = new MockPlan();
BigDecimal rate = FIVE;
- BillingEvent event1 = new DefaultBillingEvent(sub, startDate,
- plan1,phase1,
- new InternationalPriceMock(ZERO),new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE,"Test");
+ PlanPhase phase1 = createMockMonthlyPlanPhase(rate);
+
+ BillingEvent event1 = createBillingEvent(sub.getId(), startDate, plan1, phase1, 1);
events.add(event1);
+ DateTime targetDate = buildDateTime(2011, 12, 1);
+ UUID accountId = UUID.randomUUID();
+ Invoice invoice1 = generator.generateInvoice(accountId, events, null, targetDate, Currency.USD);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
- InvoiceItem invoiceItem = new DefaultInvoiceItem(UUID.randomUUID(), sub.getId(), startDate, buildDateTime(2012, 1, 1), "",
- rate.multiply(FOUR), rate, Currency.USD);
- existingInvoiceItems.add(invoiceItem);
+ existingInvoiceItems.addAll(invoice1.getInvoiceItems());
- DateTime targetDate = buildDateTime(2011, 12, 3);
- UUID accountId = UUID.randomUUID();
- Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
+ targetDate = buildDateTime(2011, 12, 3);
+ Invoice invoice2 = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
- assertNotNull(invoice);
- assertEquals(invoice.getNumberOfItems(), 0);
- assertEquals(invoice.getTotalAmount(), ZERO);
+ assertNull(invoice2);
}
@Test
@@ -283,35 +266,44 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
// plan 5: addon to plan 2, with bill cycle alignment to plan; immediate cancellation
UUID subscriptionId1 = UUID.randomUUID();
- String planName1 = "Change from trial to discount with immediate cancellation";
- String plan1PhaseName1 = "Trial"; String plan1PhaseName2 = "Discount"; String plan1phase3 = "Cancel";
+ UUID subscriptionId2 = UUID.randomUUID();
+ UUID subscriptionId3 = UUID.randomUUID();
+ UUID subscriptionId4 = UUID.randomUUID();
+ UUID subscriptionId5 = UUID.randomUUID();
+
+ Plan plan1 = new MockPlan("Change from trial to discount with immediate cancellation");
+ PlanPhase plan1Phase1 = createMockMonthlyPlanPhase(EIGHT, PhaseType.TRIAL);
+ PlanPhase plan1Phase2 = createMockMonthlyPlanPhase(TWELVE, PhaseType.DISCOUNT);
+ PlanPhase plan1Phase3 = createMockMonthlyPlanPhase();
DateTime plan1StartDate = buildDateTime(2011, 1, 5);
DateTime plan1PhaseChangeDate = buildDateTime(2011, 4, 5);
DateTime plan1CancelDate = buildDateTime(2011, 4, 29);
- UUID subscriptionId2 = UUID.randomUUID();
- String planName2 = "Change phase from trial to discount to evergreen";
- String plan2PhaseName1 = "Trial"; String plan2PhaseName2 = "Discount"; String plan2PhaseName3 = "Evergreen";
+ Plan plan2 = new MockPlan("Change phase from trial to discount to evergreen");
+ PlanPhase plan2Phase1 = createMockMonthlyPlanPhase(TWENTY, PhaseType.TRIAL);
+ PlanPhase plan2Phase2 = createMockMonthlyPlanPhase(THIRTY, PhaseType.DISCOUNT);
+ PlanPhase plan2Phase3 = createMockMonthlyPlanPhase(FORTY, PhaseType.EVERGREEN);
DateTime plan2StartDate = buildDateTime(2011, 3, 10);
DateTime plan2PhaseChangeToDiscountDate = buildDateTime(2011, 6, 10);
DateTime plan2PhaseChangeToEvergreenDate = buildDateTime(2011, 9, 10);
- UUID subscriptionId3 = UUID.randomUUID();
- String planName3 = "Upgrade with immediate change, BCD = 31";
- String plan3PhaseName1 = "Evergreen monthly"; String plan3PhaseName2 = "Evergreen annual";
+ Plan plan3 = new MockPlan("Upgrade with immediate change, BCD = 31");
+ PlanPhase plan3Phase1 = createMockMonthlyPlanPhase(TEN, PhaseType.EVERGREEN);
+ PlanPhase plan3Phase2 = createMockAnnualPlanPhase(ONE_HUNDRED, PhaseType.EVERGREEN);
DateTime plan3StartDate = buildDateTime(2011, 5, 20);
DateTime plan3UpgradeToAnnualDate = buildDateTime(2011, 7, 31);
- UUID subscriptionId4 = UUID.randomUUID();
- String planName4a = "Plan change effective EOT; plan 1";
- String planName4b = "Plan change effective EOT; plan 2";
- String plan4PhaseName = "Evergreen";
+ Plan plan4a = new MockPlan("Plan change effective EOT; plan 1");
+ Plan plan4b = new MockPlan("Plan change effective EOT; plan 2");
+ PlanPhase plan4aPhase1 = createMockMonthlyPlanPhase(FIFTEEN);
+ PlanPhase plan4bPhase1 = createMockMonthlyPlanPhase(TWENTY_FOUR);
+
DateTime plan4StartDate = buildDateTime(2011, 6, 7);
DateTime plan4ChangeOfPlanDate = buildDateTime(2011, 8, 7);
- UUID subscriptionId5 = UUID.randomUUID();
- String planName5 = "Add-on";
- String plan5PhaseName1 = "Evergreen"; String plan5PhaseName2 = "Cancel";
+ Plan plan5 = new MockPlan("Add-on");
+ PlanPhase plan5Phase1 = createMockMonthlyPlanPhase(TWENTY);
+ PlanPhase plan5Phase2 = createMockMonthlyPlanPhase();
DateTime plan5StartDate = buildDateTime(2011, 6, 21);
DateTime plan5CancelDate = buildDateTime(2011, 10, 7);
@@ -320,7 +312,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
BillingEventSet events = new BillingEventSet();
// on 1/5/2011, create subscription 1 (trial)
- events.add(createBillingEvent(subscriptionId1, plan1StartDate, planName1, plan1PhaseName1, EIGHT, 5));
+ events.add(createBillingEvent(subscriptionId1, plan1StartDate, plan1, plan1Phase1, 5));
expectedAmount = EIGHT;
testInvoiceGeneration(events, invoiceItems, plan1StartDate, 1, expectedAmount);
@@ -333,12 +325,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 3, 5), 1, expectedAmount);
// on 3/10/2011, create subscription 2 (trial)
- events.add(createBillingEvent(subscriptionId2, plan2StartDate, planName2, plan2PhaseName1, TWENTY, 10));
+ events.add(createBillingEvent(subscriptionId2, plan2StartDate, plan2, plan2Phase1, 10));
expectedAmount = TWENTY;
testInvoiceGeneration(events, invoiceItems, plan2StartDate, 1, expectedAmount);
// on 4/5/2011, invoice subscription 1 (discount)
- events.add(createBillingEvent(subscriptionId1, plan1PhaseChangeDate, planName1, plan1PhaseName2, TWELVE, 5));
+ events.add(createBillingEvent(subscriptionId1, plan1PhaseChangeDate, plan1, plan1Phase2, 5));
expectedAmount = TWELVE;
testInvoiceGeneration(events, invoiceItems, plan1PhaseChangeDate, 1, expectedAmount);
@@ -347,7 +339,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 4, 10), 1, expectedAmount);
// on 4/29/2011, cancel subscription 1
- events.add(createBillingEvent(subscriptionId1, plan1CancelDate, planName1, plan1phase3, ZERO, 5));
+ events.add(createBillingEvent(subscriptionId1, plan1CancelDate, plan1, plan1Phase3, 5));
expectedAmount = TWELVE.multiply(SIX.divide(THIRTY, NUMBER_OF_DECIMALS, ROUNDING_METHOD)).negate().setScale(NUMBER_OF_DECIMALS);
testInvoiceGeneration(events, invoiceItems, plan1CancelDate, 2, expectedAmount);
@@ -356,17 +348,17 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 5, 10), 1, expectedAmount);
// on 5/20/2011, create subscription 3 (monthly)
- events.add(createBillingEvent(subscriptionId3, plan3StartDate, planName3, plan3PhaseName1, TEN, 20));
+ events.add(createBillingEvent(subscriptionId3, plan3StartDate, plan3, plan3Phase1, 20));
expectedAmount = TEN;
testInvoiceGeneration(events, invoiceItems, plan3StartDate, 1, expectedAmount);
// on 6/7/2011, create subscription 4
- events.add(createBillingEvent(subscriptionId4, plan4StartDate, planName4a, plan4PhaseName, FIFTEEN, 7));
+ events.add(createBillingEvent(subscriptionId4, plan4StartDate, plan4a, plan4aPhase1, 7));
expectedAmount = FIFTEEN;
testInvoiceGeneration(events, invoiceItems, plan4StartDate, 1, expectedAmount);
// on 6/10/2011, invoice subscription 2 (discount)
- events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToDiscountDate, planName2, plan2PhaseName2, THIRTY, 10));
+ events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToDiscountDate, plan2, plan2Phase2, 10));
expectedAmount = THIRTY;
testInvoiceGeneration(events, invoiceItems, plan2PhaseChangeToDiscountDate, 1, expectedAmount);
@@ -375,7 +367,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 6, 20), 1, expectedAmount);
// on 6/21/2011, create add-on (subscription 5)
- events.add(createBillingEvent(subscriptionId5, plan5StartDate, planName5, plan5PhaseName1, TWENTY, 10));
+ events.add(createBillingEvent(subscriptionId5, plan5StartDate, plan5, plan5Phase1, 10));
expectedAmount = TWENTY.multiply(NINETEEN.divide(THIRTY, NUMBER_OF_DECIMALS, ROUNDING_METHOD)).setScale(NUMBER_OF_DECIMALS);
testInvoiceGeneration(events, invoiceItems, plan5StartDate, 1, expectedAmount);
@@ -392,14 +384,14 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 7, 20), 1, expectedAmount);
// on 7/31/2011, convert subscription 3 to annual
- events.add(createAnnualBillingEvent(subscriptionId3, plan3UpgradeToAnnualDate, planName3, plan3PhaseName2, ONE_HUNDRED, 31));
+ events.add(createBillingEvent(subscriptionId3, plan3UpgradeToAnnualDate, plan3, plan3Phase2, 31));
expectedAmount = ONE_HUNDRED.subtract(TEN);
expectedAmount = expectedAmount.add(TEN.multiply(ELEVEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)));
expectedAmount = expectedAmount.setScale(NUMBER_OF_DECIMALS);
testInvoiceGeneration(events, invoiceItems, plan3UpgradeToAnnualDate, 3, expectedAmount);
// on 8/7/2011, invoice subscription 4 (plan 2)
- events.add(createBillingEvent(subscriptionId4, plan4ChangeOfPlanDate, planName4b, plan4PhaseName, TWENTY_FOUR, 7));
+ events.add(createBillingEvent(subscriptionId4, plan4ChangeOfPlanDate, plan4b, plan4bPhase1, 7));
expectedAmount = TWENTY_FOUR;
testInvoiceGeneration(events, invoiceItems, plan4ChangeOfPlanDate, 1, expectedAmount);
@@ -412,12 +404,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 9, 7), 1, expectedAmount);
// on 9/10/2011, invoice plan 2 (evergreen), invoice subscription 5
- events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToEvergreenDate, planName2, plan2PhaseName3, FORTY, 10));
+ events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToEvergreenDate, plan2, plan2Phase3, 10));
expectedAmount = FORTY.add(TWENTY);
testInvoiceGeneration(events, invoiceItems, plan2PhaseChangeToEvergreenDate, 2, expectedAmount);
// on 10/7/2011, invoice subscription 4 (plan 2), cancel subscription 5
- events.add(createBillingEvent(subscriptionId5, plan5CancelDate, planName5, plan5PhaseName2, ZERO, 10));
+ events.add(createBillingEvent(subscriptionId5, plan5CancelDate, plan5, plan5Phase2, 10));
expectedAmount = TWENTY_FOUR.add(TWENTY.multiply(THREE.divide(THIRTY)).negate().setScale(NUMBER_OF_DECIMALS));
testInvoiceGeneration(events, invoiceItems, plan5CancelDate, 3, expectedAmount);
@@ -426,37 +418,175 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 10, 10), 1, expectedAmount);
}
- private DefaultBillingEvent createBillingEvent(UUID subscriptionId, DateTime startDate, String planName, String planPhaseName,
- BigDecimal rate, int billCycleDay) {
- MockCatalog catalog = new MockCatalog();
- Plan plan = catalog.getCurrentPlans()[0];
- PlanPhase phase = plan.getAllPhases()[0];
- Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
- return new DefaultBillingEvent(sub, startDate, plan, phase,
- new InternationalPriceMock(ZERO),new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
- billCycleDay, BillingModeType.IN_ADVANCE,"Test");
+ @Test
+ public void testZeroDollarEvents() {
+ Plan plan = new MockPlan();
+ PlanPhase planPhase = createMockMonthlyPlanPhase(ZERO);
+ BillingEventSet events = new BillingEventSet();
+ DateTime targetDate = buildDateTime(2011, 1, 1);
+ events.add(createBillingEvent(UUID.randomUUID(), targetDate, plan, planPhase, 1));
+
+ InvoiceGenerator invoiceGenerator = new DefaultInvoiceGenerator();
+ Invoice invoice = invoiceGenerator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
+
+ assertEquals(invoice.getNumberOfItems(), 1);
}
- private DefaultBillingEvent createAnnualBillingEvent(UUID subscriptionId, DateTime startDate, String planName, String planPhaseName,
- BigDecimal rate, int billCycleDay) {
- MockCatalog catalog = new MockCatalog();
- Plan plan = catalog.getCurrentPlans()[0];
- PlanPhase phase = plan.getAllPhases()[0];
- Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
- return new DefaultBillingEvent(sub, startDate, plan, phase,
- new InternationalPriceMock(ZERO),new InternationalPriceMock(rate), BillingPeriod.ANNUAL,
- billCycleDay, BillingModeType.IN_ADVANCE,"Test");
+ @Test
+ public void testEndDateIsCorrect() {
+ Plan plan = new MockPlan();
+ PlanPhase planPhase = createMockMonthlyPlanPhase(ZERO);
+ BillingEventSet events = new BillingEventSet();
+ DateTime targetDate = new DateTime();
+ events.add(createBillingEvent(UUID.randomUUID(), targetDate, plan, planPhase, targetDate.getDayOfMonth()));
+
+ InvoiceGenerator invoiceGenerator = new DefaultInvoiceGenerator();
+ Invoice invoice = invoiceGenerator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
+ InvoiceItem item = invoice.getInvoiceItems().get(0);
+
+ // end date of the invoice item should be equal to exactly one month later
+ assertEquals(item.getEndDate().compareTo(targetDate.plusMonths(1)), 0);
+ }
+
+ @Test
+ public void testImmediateChange() {
+ UUID accountId = UUID.randomUUID();
+ Subscription subscription = new MockSubscription();
+ Plan plan1 = new MockPlan("plan 1");
+ PlanPhase plan1phase1 = new MockPlanPhase(plan1, PhaseType.TRIAL);
+ PlanPhase plan1phase2 = new MockPlanPhase(plan1, PhaseType.DISCOUNT);
+
+ Plan plan2 = new MockPlan("plan 2");
+ PlanPhase plan2phase1 = new MockPlanPhase(plan2, PhaseType.TRIAL);
+ PlanPhase plan2phase2 = new MockPlanPhase(plan2, PhaseType.DISCOUNT);
+
+ InternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(ZERO, Currency.USD));
+ InternationalPrice cheapPrice = new MockInternationalPrice(new DefaultPrice(ONE, Currency.USD));
+ InternationalPrice lessCheapPrice = new MockInternationalPrice(new DefaultPrice(FOUR, Currency.USD));
+
+ BillingEventSet events = new BillingEventSet();
+
+ BillingEvent event1 = new DefaultBillingEvent(subscription, new DateTime("2012-01-31T00:02:04.000Z"),
+ plan1, plan1phase1,
+ zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, 1,
+ BillingModeType.IN_ADVANCE, "Test Event 1",
+ SubscriptionTransitionType.CREATE);
+ BillingEvent event2 = new DefaultBillingEvent(subscription, new DateTime("2012-04-30T00:02:04.000Z"),
+ plan1, plan1phase2,
+ null, cheapPrice, BillingPeriod.MONTHLY, 1,
+ BillingModeType.IN_ADVANCE, "Test Event 2",
+ SubscriptionTransitionType.PHASE);
+ events.add(event1);
+ events.add(event2);
+
+ Invoice invoice1 = generator.generateInvoice(accountId, events, null, new DateTime("2012-01-31T00:02:04.000Z"), Currency.USD);
+ assertNotNull(invoice1);
+ assertEquals(invoice1.getNumberOfItems(), 1);
+
+ BillingEvent event3 = new DefaultBillingEvent(subscription, new DateTime("2012-01-31T00:02:04.000Z"),
+ plan2, plan2phase1,
+ zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, 1,
+ BillingModeType.IN_ADVANCE, "Test Event 3",
+ SubscriptionTransitionType.CHANGE);
+ BillingEvent event4 = new DefaultBillingEvent(subscription, new DateTime("2012-04-30T00:02:04.000Z"),
+ plan2, plan2phase2,
+ null, lessCheapPrice, BillingPeriod.MONTHLY, 1,
+ BillingModeType.IN_ADVANCE, "Test Event 4",
+ SubscriptionTransitionType.PHASE);
+
+ events.add(event3);
+ events.add(event4);
+
+ InvoiceItemList items = new InvoiceItemList(invoice1.getInvoiceItems());
+ Invoice invoice2 = generator.generateInvoice(accountId, events, items, new DateTime("2012-01-31T00:02:04.000Z"), Currency.USD);
+
+ assertNotNull(invoice2);
+ assertEquals(invoice2.getNumberOfItems(), 2);
+ assertEquals(invoice2.getInvoiceItems().get(0).getPlanName(), plan2.getName());
}
- private void testInvoiceGeneration(BillingEventSet events, InvoiceItemList existingInvoiceItems, DateTime targetDate, int expectedNumberOfItems, BigDecimal expectedAmount) {
+ @Test
+ private void testFixedPriceLifeCycle() {
+ UUID accountId = UUID.randomUUID();
+ Subscription subscription = new MockSubscription();
+ Plan plan = new MockPlan("plan 1");
+ MockInternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(ZERO, Currency.USD));
+ MockInternationalPrice cheapPrice = new MockInternationalPrice(new DefaultPrice(ONE, Currency.USD));
+
+ PlanPhase phase1 = new MockPlanPhase(null, zeroPrice, BillingPeriod.NO_BILLING_PERIOD, PhaseType.TRIAL);
+ PlanPhase phase2 = new MockPlanPhase(cheapPrice, null, BillingPeriod.MONTHLY, PhaseType.DISCOUNT);
+
+ DateTime changeDate = new DateTime("2012-04-1T00:00:00.000-08:00");
+
+ BillingEventSet events = new BillingEventSet();
+
+ BillingEvent event1 = new DefaultBillingEvent(subscription, new DateTime("2012-01-1T00:00:00.000-08:00"),
+ plan, phase1,
+ zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, 1,
+ BillingModeType.IN_ADVANCE, "Test Event 1",
+ SubscriptionTransitionType.CREATE);
+
+ BillingEvent event2 = new DefaultBillingEvent(subscription, changeDate,
+ plan, phase2,
+ zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, 1,
+ BillingModeType.IN_ADVANCE, "Test Event 2",
+ SubscriptionTransitionType.PHASE);
+
+ events.add(event2);
+ events.add(event1);
+ Invoice invoice1 = generator.generateInvoice(accountId, events, null, new DateTime("2012-02-01T00:01:00.000-08:00"), Currency.USD);
+ assertNotNull(invoice1);
+ assertEquals(invoice1.getNumberOfItems(), 1);
+ assertEquals(invoice1.getInvoiceItems().get(0).getEndDate().compareTo(changeDate), 0);
+ }
+
+ private MockPlanPhase createMockMonthlyPlanPhase() {
+ return new MockPlanPhase(null, null, BillingPeriod.MONTHLY);
+ }
+
+ private MockPlanPhase createMockMonthlyPlanPhase(@Nullable final BigDecimal recurringRate) {
+ return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+ null, BillingPeriod.MONTHLY);
+ }
+
+ private MockPlanPhase createMockMonthlyPlanPhase(@Nullable final BigDecimal recurringRate,
+ @Nullable final BigDecimal fixedRate, PhaseType phaseType) {
+ return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+ new MockInternationalPrice(new DefaultPrice(fixedRate, Currency.USD)),
+ BillingPeriod.MONTHLY, phaseType);
+ }
+
+ private MockPlanPhase createMockMonthlyPlanPhase(final BigDecimal recurringRate, final PhaseType phaseType) {
+ return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+ null, BillingPeriod.MONTHLY, phaseType);
+ }
+
+ private MockPlanPhase createMockAnnualPlanPhase(final BigDecimal recurringRate, final PhaseType phaseType) {
+ return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+ null, BillingPeriod.ANNUAL, phaseType);
+ }
+
+ private DefaultBillingEvent createBillingEvent(final UUID subscriptionId, final DateTime startDate,
+ final Plan plan, final PlanPhase planPhase, final int billCycleDay) {
+ Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(subscriptionId));
+
+ return new DefaultBillingEvent(sub, startDate, plan, planPhase,
+ planPhase.getFixedPrice(),
+ planPhase.getRecurringPrice(), planPhase.getBillingPeriod(),
+ billCycleDay, BillingModeType.IN_ADVANCE,"Test", SubscriptionTransitionType.CREATE);
+ }
+
+ private void testInvoiceGeneration(final BillingEventSet events, final InvoiceItemList existingInvoiceItems,
+ final DateTime targetDate, final int expectedNumberOfItems,
+ final BigDecimal expectedAmount) {
Currency currency = Currency.USD;
UUID accountId = UUID.randomUUID();
Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, currency);
- existingInvoiceItems.addAll(invoice.getItems());
assertNotNull(invoice);
assertEquals(invoice.getNumberOfItems(), expectedNumberOfItems);
+
+ existingInvoiceItems.addAll(invoice.getInvoiceItems());
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?
-}
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java
index 84bf795..64fa95c 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class DoubleProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java
index f612f10..8d0eb06 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java
@@ -22,7 +22,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class GenericProRationTests extends GenericProRationTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java
index f44876e..a009fba 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class LeadingProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java
index 274a8e7..f882005 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class ProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java
index 0689c69..e0216b5 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class TrailingProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java
index d4e4c24..60892e6 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java
@@ -22,7 +22,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBase {
/**
* used for testing cancellation in less than a single billing period
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java
index 4799e40..240b8aa 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class DoubleProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java
index eb3b1c9..8b40db8 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java
@@ -22,7 +22,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class GenericProRationTests extends GenericProRationTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java
index 5db6911..998e566 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class LeadingProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
index 13c75df..c3748d1 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class ProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java
index 9a8e635..6a5e5ef 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class TrailingProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java
index 14392a8..18bd096 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java
@@ -21,7 +21,7 @@ import com.ning.billing.invoice.model.InAdvanceBillingMode;
import com.ning.billing.invoice.tests.ProRationTestBase;
import org.testng.annotations.Test;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public abstract class ProRationInAdvanceTestBase extends ProRationTestBase {
@Override
protected BillingMode getBillingMode() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java
index 68e29fd..c1d6085 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class DoubleProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java
index 8306716..c4237a6 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java
@@ -22,7 +22,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class GenericProRationTests extends GenericProRationTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java
index fe614a9..04ec683 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class LeadingProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java
index c99aa5c..e13db0d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class ProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java
index 89e138e..8f63010 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class TrailingProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
index 9de01bd..b38d076 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
@@ -28,7 +28,7 @@ import java.math.BigDecimal;
import static org.testng.Assert.assertEquals;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class ValidationProRationTests extends ProRationTestBase {
protected BillingPeriod getBillingPeriod() {
return BillingPeriod.MONTHLY;
invoice/src/test/resources/log4j.xml 36(+36 -0)
diff --git a/invoice/src/test/resources/log4j.xml b/invoice/src/test/resources/log4j.xml
new file mode 100644
index 0000000..33b9662
--- /dev/null
+++ b/invoice/src/test/resources/log4j.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+ <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
+ <param name="Target" value="System.out"/>
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%p %d{ISO8601} %X{trace} %t %c %m%n"/>
+ </layout>
+ </appender>
+
+
+ <logger name="com.ning.billing.invoice">
+ <level value="info"/>
+ </logger>
+
+ <root>
+ <priority value="info"/>
+ <appender-ref ref="stdout"/>
+ </root>
+</log4j:configuration>
payment/pom.xml 2(+1 -1)
diff --git a/payment/pom.xml b/payment/pom.xml
index 5baa4d4..3e050f0 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.3-SNAPSHOT</version>
+ <version>0.1.5-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-payment</artifactId>
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
index 5a0e2f1..5d9f58f 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
@@ -32,6 +32,7 @@ import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.AccountUserApi;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
import com.ning.billing.payment.dao.PaymentDao;
import com.ning.billing.payment.provider.PaymentProviderPlugin;
import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
@@ -175,7 +176,7 @@ public class DefaultPaymentApi implements PaymentApi {
for (String invoiceId : invoiceIds) {
Invoice invoice = invoicePaymentApi.getInvoice(UUID.fromString(invoiceId));
- if (invoice.getAmountOutstanding().compareTo(BigDecimal.ZERO) == 0 ) {
+ if (invoice.getBalance().compareTo(BigDecimal.ZERO) == 0 ) {
// TODO: send a notification that invoice was ignored?
log.info("Received invoice for payment with outstanding amount of 0 {} ", invoice);
}
@@ -222,12 +223,12 @@ public class DefaultPaymentApi implements PaymentApi {
}
}
- invoicePaymentApi.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(),
- paymentInfo == null ? null : paymentInfo.getAmount(),
-// paymentInfo.getRefundAmount(), TODO
- paymentInfo == null ? null : invoice.getCurrency(),
- paymentAttempt.getPaymentAttemptId(),
- paymentAttempt.getPaymentAttemptDate()));
+ invoicePaymentApi.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttempt.getPaymentAttemptId(),
+ invoice.getId(),
+ paymentAttempt.getPaymentAttemptDate(),
+ paymentInfo == null ? null : paymentInfo.getAmount(),
+// paymentInfo.getRefundAmount(), TODO
+ paymentInfo == null ? null : invoice.getCurrency()));
}
}
@@ -258,4 +259,14 @@ public class DefaultPaymentApi implements PaymentApi {
throw new UnsupportedOperationException();
}
+ @Override
+ public List<PaymentInfo> getPaymentInfo(List<String> invoiceIds) {
+ return paymentDao.getPaymentInfo(invoiceIds);
+ }
+
+ @Override
+ public PaymentAttempt getPaymentAttemptForInvoiceId(String invoiceId) {
+ return paymentDao.getPaymentAttemptForInvoiceId(invoiceId);
+ }
+
}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
index 9cc46a4..9a96631 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
@@ -16,6 +16,7 @@
package com.ning.billing.payment.dao;
+import java.util.List;
import java.util.UUID;
import org.skife.jdbi.v2.IDBI;
@@ -44,6 +45,12 @@ public class DefaultPaymentDao implements PaymentDao {
}
@Override
+ public PaymentAttempt createPaymentAttempt(PaymentAttempt paymentAttempt) {
+ sqlDao.insertPaymentAttempt(paymentAttempt);
+ return paymentAttempt;
+ }
+
+ @Override
public PaymentAttempt createPaymentAttempt(Invoice invoice) {
final PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
@@ -67,15 +74,13 @@ public class DefaultPaymentDao implements PaymentDao {
}
@Override
- public PaymentAttempt getPaymentAttemptById(UUID paymentAttemptId) {
- // TODO Auto-generated method stub
- return null;
+ public List<PaymentInfo> getPaymentInfo(List<String> invoiceIds) {
+ return sqlDao.getPaymentInfos(invoiceIds);
}
@Override
- public void updatePaymentAttempt(PaymentAttempt updatedPaymentAttempt) {
- // TODO Auto-generated method stub
-
+ public List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(List<String> invoiceIds) {
+ return sqlDao.getPaymentAttemptsForInvoiceIds(invoiceIds);
}
}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
index a70098b..4d4a411 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
@@ -16,6 +16,7 @@
package com.ning.billing.payment.dao;
+import java.util.List;
import java.util.UUID;
import com.ning.billing.invoice.api.Invoice;
@@ -25,10 +26,12 @@ import com.ning.billing.payment.api.PaymentInfo;
public interface PaymentDao {
PaymentAttempt createPaymentAttempt(Invoice invoice);
+ PaymentAttempt createPaymentAttempt(PaymentAttempt paymentAttempt);
void savePaymentInfo(PaymentInfo right);
PaymentAttempt getPaymentAttemptForPaymentId(String paymentId);
+ List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(List<String> invoiceIds);
void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, String paymentId);
@@ -36,8 +39,6 @@ public interface PaymentDao {
void updatePaymentInfo(String paymentMethodType, String paymentId, String cardType, String cardCountry);
- PaymentAttempt getPaymentAttemptById(UUID paymentAttemptId);
-
- void updatePaymentAttempt(PaymentAttempt updatedPaymentAttempt);
+ List<PaymentInfo> getPaymentInfo(List<String> invoiceIds);
}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
index 972ee64..13b5f51 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
@@ -21,6 +21,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Date;
+import java.util.List;
import java.util.UUID;
import org.joda.time.DateTime;
@@ -37,6 +38,7 @@ 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 org.skife.jdbi.v2.unstable.BindIn;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.payment.api.PaymentAttempt;
@@ -55,6 +57,10 @@ public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, CloseMe, Tr
@Mapper(PaymentAttemptMapper.class)
PaymentAttempt getPaymentAttemptForInvoiceId(@Bind("invoice_id") String invoiceId);
+ @SqlQuery
+ @Mapper(PaymentAttemptMapper.class)
+ List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(@BindIn("invoiceIds") List<String> invoiceIds);
+
@SqlUpdate
void updatePaymentAttemptWithPaymentId(@Bind("payment_attempt_id") String paymentAttemptId,
@Bind("payment_id") String paymentId);
@@ -65,6 +71,10 @@ public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, CloseMe, Tr
@Bind("card_type") String cardType,
@Bind("card_country") String cardCountry);
+ @SqlQuery
+ @Mapper(PaymentInfoMapper.class)
+ List<PaymentInfo> getPaymentInfos(@BindIn("invoiceIds") List<String> invoiceIds);
+
@SqlUpdate
void insertPaymentInfo(@Bind(binder = PaymentInfoBinder.class) PaymentInfo paymentInfo);
diff --git a/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java b/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java
index 62fce18..1c8e9f3 100644
--- a/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java
+++ b/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java
@@ -41,7 +41,7 @@ public class PaymentAttempt {
this.paymentAttemptId = paymentAttemptId;
this.accountId = invoice.getAccountId();
this.invoiceId = invoice.getId();
- this.paymentAttemptAmount = invoice.getAmountOutstanding();
+ this.paymentAttemptAmount = invoice.getBalance();
this.paymentAttemptDate = new DateTime(DateTimeZone.UTC);
this.retryCount = retryCount;
this.nextRetryDate = nextRetryDate;
diff --git a/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
index 466f297..2c86e3b 100644
--- a/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
@@ -31,17 +31,15 @@ import com.ning.billing.payment.api.Either;
import com.ning.billing.payment.api.PaymentApi;
import com.ning.billing.payment.api.PaymentError;
import com.ning.billing.payment.api.PaymentInfo;
-import com.ning.billing.payment.provider.PaymentProviderPlugin;
import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
public class RequestProcessor {
public static final String PAYMENT_PROVIDER_KEY = "paymentProvider";
private final AccountUserApi accountUserApi;
private final PaymentApi paymentApi;
- private final PaymentProviderPluginRegistry pluginRegistry;
- private final EventBus eventBus;
+ private final Bus eventBus;
private static final Logger log = LoggerFactory.getLogger(RequestProcessor.class);
@@ -49,10 +47,9 @@ public class RequestProcessor {
public RequestProcessor(AccountUserApi accountUserApi,
PaymentApi paymentApi,
PaymentProviderPluginRegistry pluginRegistry,
- EventBus eventBus) {
+ Bus eventBus) {
this.accountUserApi = accountUserApi;
this.paymentApi = paymentApi;
- this.pluginRegistry = pluginRegistry;
this.eventBus = eventBus;
}
@@ -81,20 +78,4 @@ public class RequestProcessor {
throw new RuntimeException(ex);
}
}
-
- @Subscribe
- public void receivePaymentInfoRequest(PaymentInfoRequest paymentInfoRequest) throws EventBusException {
- final Account account = accountUserApi.getAccountById(paymentInfoRequest.getAccountId());
- if (account == null) {
- log.info("could not process payment info request: could not find a valid account for event {}", paymentInfoRequest);
- }
- else {
- final String paymentProviderName = account.getFieldValue(PAYMENT_PROVIDER_KEY);
- final PaymentProviderPlugin plugin = pluginRegistry.getPlugin(paymentProviderName);
-
- Either<PaymentError, PaymentInfo> result = plugin.getPaymentInfo(paymentInfoRequest.getPaymentId());
-
- eventBus.post(result.isLeft() ? result.getLeft() : result.getRight());
- }
- }
}
diff --git a/payment/src/main/java/com/ning/billing/payment/RetryService.java b/payment/src/main/java/com/ning/billing/payment/RetryService.java
new file mode 100644
index 0000000..49acce3
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/RetryService.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+import com.google.inject.Inject;
+import com.ning.billing.lifecycle.KillbillService;
+import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import com.ning.billing.payment.setup.PaymentConfig;
+import com.ning.billing.util.notificationq.NotificationKey;
+import com.ning.billing.util.notificationq.NotificationQueue;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+
+public class RetryService implements KillbillService {
+ public static final String SERVICE_NAME = "retry-service";
+ public static final String QUEUE_NAME = "retry-events";
+
+ private final NotificationQueueService notificationQueueService;
+ private final PaymentConfig config;
+ private NotificationQueue retryQueue;
+
+ @Inject
+ public RetryService(NotificationQueueService notificationQueueService, PaymentConfig config) {
+ this.notificationQueueService = notificationQueueService;
+ this.config = config;
+ }
+
+ @Override
+ public String getName() {
+ return SERVICE_NAME;
+ }
+
+ @LifecycleHandlerType(LifecycleLevel.INIT_SERVICE)
+ public void initialize() throws NotificationQueueAlreadyExists {
+ retryQueue = notificationQueueService.createNotificationQueue(SERVICE_NAME, QUEUE_NAME, new NotificationQueueHandler() {
+ @Override
+ public void handleReadyNotification(String notificationKey) {
+ retry(notificationKey);
+ }
+ },
+ config);
+ }
+
+ @LifecycleHandlerType(LifecycleLevel.START_SERVICE)
+ public void start() {
+ retryQueue.startQueue();
+ }
+
+ @LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
+ public void stop() {
+ if (retryQueue != null) {
+ retryQueue.stopQueue();
+ }
+ }
+
+ public void scheduleRetry(Transmogrifier transactionalDao, PaymentAttempt paymentAttempt, DateTime timeOfRetry) {
+ final String id = paymentAttempt.getPaymentAttemptId().toString();
+
+ NotificationKey key = new NotificationKey() {
+ @Override
+ public String toString() {
+ return id;
+ }
+ };
+ retryQueue.recordFutureNotificationFromTransaction(transactionalDao, timeOfRetry, key);
+ }
+
+ private void retry(String paymentAttemptId) {
+ // TODO
+ }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.java b/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.java
index 83cb89b..af0b3d8 100644
--- a/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.java
+++ b/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.java
@@ -23,7 +23,9 @@ import org.skife.config.Default;
import org.skife.config.DefaultNull;
import org.skife.config.TimeSpan;
-public interface PaymentConfig {
+import com.ning.billing.util.notificationq.NotificationConfig;
+
+public interface PaymentConfig extends NotificationConfig {
@Config("killbill.payment.provider.default")
@DefaultNull
public String getDefaultPaymentProvider();
@@ -32,14 +34,20 @@ public interface PaymentConfig {
@DefaultNull
public List<String> getPaymentRetryDays();
- @Config("killbill.payment.retry.pause")
- // payment retry job is off by default
- @DefaultNull
- TimeSpan getPaymentRetrySchedulePause();
+ @Config("killbill.payment.dao.claim.time")
+ @Default("60000")
+ public long getDaoClaimTimeMs();
+
+ @Config("killbill.payment.dao.ready.max")
+ @Default("10")
+ public int getDaoMaxReadyEvents();
+
+ @Config("killbill.payment.engine.notifications.sleep")
+ @Default("500")
+ public long getNotificationSleepTimeMs();
- @Config("killbill.payment.retry.claim.timeout")
- // if payment retry job is on, then retry abandoned payment attempts after some period of time
- @Default("1h")
- TimeSpan getPaymentRetryClaimTimeout();
+ @Config("killbill.payment.engine.events.off")
+ @Default("false")
+ public boolean isNotificationProcessingOff();
}
diff --git a/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java b/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
index 3c05c13..b8af029 100644
--- a/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
+++ b/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
@@ -21,6 +21,8 @@ import java.util.Properties;
import org.skife.config.ConfigurationObjectFactory;
import com.google.inject.AbstractModule;
+import com.ning.billing.payment.RequestProcessor;
+import com.ning.billing.payment.RetryService;
import com.ning.billing.payment.api.DefaultPaymentApi;
import com.ning.billing.payment.api.PaymentApi;
import com.ning.billing.payment.dao.DefaultPaymentDao;
@@ -45,6 +47,10 @@ public class PaymentModule extends AbstractModule {
protected void installPaymentProviderPlugins(PaymentConfig config) {
}
+ protected void installRetryEngine() {
+ bind(RetryService.class).asEagerSingleton();
+ }
+
@Override
protected void configure() {
final ConfigurationObjectFactory factory = new ConfigurationObjectFactory(props);
@@ -53,6 +59,8 @@ public class PaymentModule extends AbstractModule {
bind(PaymentConfig.class).toInstance(paymentConfig);
bind(PaymentProviderPluginRegistry.class).asEagerSingleton();
bind(PaymentApi.class).to(DefaultPaymentApi.class).asEagerSingleton();
+ bind(RequestProcessor.class).asEagerSingleton();
+ bind(PaymentService.class).asEagerSingleton();
installPaymentProviderPlugins(paymentConfig);
installPaymentDao();
}
diff --git a/payment/src/main/java/com/ning/billing/payment/setup/PaymentService.java b/payment/src/main/java/com/ning/billing/payment/setup/PaymentService.java
new file mode 100644
index 0000000..5113068
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/setup/PaymentService.java
@@ -0,0 +1,57 @@
+/*
+ * 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.payment.setup;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.lifecycle.KillbillService;
+import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.payment.RequestProcessor;
+import com.ning.billing.util.bus.Bus;
+
+public class PaymentService implements KillbillService {
+ private static final Logger log = LoggerFactory.getLogger(PaymentService.class);
+
+ private static final String SERVICE_NAME = "payment-service";
+
+ private final RequestProcessor requestProcessor;
+ private final Bus eventBus;
+
+ @Inject
+ public PaymentService(final RequestProcessor requestProcessor, final Bus eventBus) {
+ this.requestProcessor = requestProcessor;
+ this.eventBus = eventBus;
+ }
+
+ @Override
+ public String getName() {
+ return SERVICE_NAME;
+ }
+
+ @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.REGISTER_EVENTS)
+ public void registerForNotifications() {
+ try {
+ eventBus.register(requestProcessor);
+ }
+ catch (Bus.EventBusException e) {
+ log.error("Unable to register with the EventBus!", e);
+ }
+ }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java b/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java
index d82a9a7..3cbe802 100644
--- a/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java
+++ b/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java
@@ -20,11 +20,11 @@ import javax.annotation.Nullable;
import com.google.common.eventbus.Subscribe;
import com.google.common.util.concurrent.AbstractFuture;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
public class EventBusFuture<T, V extends EventBusResponse<T>> extends AbstractFuture<V> {
- public static <V, W extends EventBusRequest<V>, X extends EventBusResponse<V>> EventBusFuture<V, X> post(final EventBus eventBus, final W event) throws EventBusException {
+ public static <V, W extends EventBusRequest<V>, X extends EventBusResponse<V>> EventBusFuture<V, X> post(final Bus eventBus, final W event) throws EventBusException {
final EventBusFuture<V, X> responseFuture = new EventBusFuture<V, X>(eventBus, event.getId());
eventBus.register(responseFuture);
@@ -32,10 +32,10 @@ public class EventBusFuture<T, V extends EventBusResponse<T>> extends AbstractFu
return responseFuture;
}
- private final EventBus eventBus;
+ private final Bus eventBus;
private final T requestId;
- private EventBusFuture(EventBus eventBus, T requestId) {
+ private EventBusFuture(Bus eventBus, T requestId) {
this.eventBus = eventBus;
this.requestId = requestId;
}
diff --git a/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.java b/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.java
index b9dab5c..a895afc 100644
--- a/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.java
+++ b/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.java
@@ -16,8 +16,8 @@
package com.ning.billing.payment.util;
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
-public interface EventBusRequest<T> extends EventBusNotification {
+public interface EventBusRequest<T> extends BusEvent {
T getId();
}
diff --git a/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.java b/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.java
index d8a70b1..ff5b62c 100644
--- a/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.java
+++ b/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.java
@@ -16,8 +16,8 @@
package com.ning.billing.payment.util;
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
-public interface EventBusResponse<T> extends EventBusNotification {
+public interface EventBusResponse<T> extends BusEvent {
T getRequestId();
}
diff --git a/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
index a62c366..aea2272 100644
--- a/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
+++ b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
@@ -24,6 +24,7 @@ paymentInfoFields(prefix) ::= <<
<prefix>payment_type,
<prefix>status,
<prefix>reference_id,
+ <prefix>payment_method_id,
<prefix>payment_method,
<prefix>card_type,
<prefix>card_country,
@@ -43,6 +44,12 @@ getPaymentAttemptForPaymentId() ::= <<
WHERE payment_id = :payment_id
>>
+getPaymentAttemptsForInvoiceIds(invoiceIds) ::= <<
+ SELECT <paymentAttemptFields()>
+ FROM payment_attempts
+ WHERE invoice_id in (<invoiceIds>)
+>>
+
getPaymentAttemptForInvoiceId() ::= <<
SELECT <paymentAttemptFields()>
FROM payment_attempts
@@ -58,7 +65,7 @@ updatePaymentAttemptWithPaymentId() ::= <<
insertPaymentInfo() ::= <<
INSERT INTO payments (<paymentInfoFields()>)
- VALUES (:payment_id, :amount, :refund_amount, :bank_identification_number, :payment_number, :payment_type, :status, :reference_id, :payment_method, :card_type, :card_country, :effective_dt, :created_dt, :updated_dt);
+ VALUES (:payment_id, :amount, :refund_amount, :bank_identification_number, :payment_number, :payment_type, :status, :reference_id, :payment_method_id, :payment_method, :card_type, :card_country, :effective_dt, :created_dt, :updated_dt);
>>
updatePaymentInfo() ::= <<
@@ -68,4 +75,11 @@ updatePaymentInfo() ::= <<
card_country = :card_country,
updated_dt = NOW()
WHERE payment_id = :payment_id
+>>
+
+getPaymentInfos(invoiceIds) ::= <<
+ SELECT <paymentInfoFields("p.")>
+ FROM payments p, payment_attempts pa
+ WHERE pa.invoice_id in (<invoiceIds>)
+ AND pa.payment_id = p.payment_id
>>
\ No newline at end of file
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
index 896ffcc..786ef34 100644
--- a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
@@ -41,12 +41,12 @@ import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.model.DefaultInvoiceItem;
import com.ning.billing.payment.TestHelper;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
public abstract class TestPaymentApi {
@Inject
- private EventBus eventBus;
+ private Bus eventBus;
@Inject
protected PaymentApi paymentApi;
@Inject
@@ -70,14 +70,15 @@ public abstract class TestPaymentApi {
final BigDecimal amount = new BigDecimal("10.00");
final UUID subscriptionId = UUID.randomUUID();
- invoice.add(new DefaultInvoiceItem(invoice.getId(),
- subscriptionId,
- now,
- now.plusMonths(1),
- "Test",
- amount,
- new BigDecimal("1.0"),
- Currency.USD));
+ invoice.addInvoiceItem(new DefaultInvoiceItem(invoice.getId(),
+ subscriptionId,
+ "test plan", "test phase",
+ now,
+ now.plusMonths(1),
+ amount,
+ new BigDecimal("1.0"),
+ null,
+ Currency.USD));
List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account.getExternalKey(), Arrays.asList(invoice.getId().toString()));
@@ -100,6 +101,16 @@ public abstract class TestPaymentApi {
assertEquals(paymentAttempt.getPaymentId(), paymentInfo.getPaymentId());
assertEquals(paymentAttempt.getPaymentAttemptDate().withMillisOfSecond(0).withSecondOfMinute(0), now.withMillisOfSecond(0).withSecondOfMinute(0));
+ List<PaymentInfo> paymentInfos = paymentApi.getPaymentInfo(Arrays.asList(invoice.getId().toString()));
+ assertNotNull(paymentInfos);
+ assertTrue(paymentInfos.size() > 0);
+
+ PaymentInfo paymentInfoFromGet = paymentInfos.get(0);
+ assertEquals(paymentInfo, paymentInfoFromGet);
+
+ PaymentAttempt paymentAttemptFromGet = paymentApi.getPaymentAttemptForInvoiceId(invoice.getId().toString());
+ assertEquals(paymentAttempt, paymentAttemptFromGet);
+
}
private PaymentProviderAccount setupAccountWithPaymentMethod() throws AccountApiException {
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
index 35ebceb..071ce94 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
@@ -16,10 +16,14 @@
package com.ning.billing.payment.dao;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.payment.api.PaymentAttempt;
import com.ning.billing.payment.api.PaymentInfo;
@@ -46,6 +50,12 @@ public class MockPaymentDao implements PaymentDao {
}
@Override
+ public PaymentAttempt createPaymentAttempt(PaymentAttempt paymentAttempt) {
+ paymentAttempts.put(paymentAttempt.getPaymentAttemptId(), paymentAttempt);
+ return paymentAttempt;
+ }
+
+ @Override
public void savePaymentInfo(PaymentInfo paymentInfo) {
payments.put(paymentInfo.getPaymentId(), paymentInfo);
}
@@ -63,7 +73,7 @@ public class MockPaymentDao implements PaymentDao {
@Override
public PaymentAttempt getPaymentAttemptForInvoiceId(String invoiceId) {
for (PaymentAttempt paymentAttempt : paymentAttempts.values()) {
- if (invoiceId.equals(paymentAttempt.getInvoiceId())) {
+ if (invoiceId.equals(paymentAttempt.getInvoiceId().toString())) {
return paymentAttempt;
}
}
@@ -77,15 +87,31 @@ public class MockPaymentDao implements PaymentDao {
}
@Override
- public PaymentAttempt getPaymentAttemptById(UUID paymentAttemptId) {
- // TODO Auto-generated method stub
- return null;
+ public List<PaymentInfo> getPaymentInfo(List<String> invoiceIds) {
+ List<PaymentAttempt> attempts = getPaymentAttemptsForInvoiceIds(invoiceIds);
+ List<PaymentInfo> paymentsToReturn = new ArrayList<PaymentInfo>(invoiceIds.size());
+
+ for (final PaymentAttempt attempt : attempts) {
+ paymentsToReturn.addAll(Collections2.filter(payments.values(), new Predicate<PaymentInfo>() {
+ @Override
+ public boolean apply(PaymentInfo input) {
+ return input.getPaymentId().equals(attempt.getPaymentId());
+ }
+ }));
+ }
+ return paymentsToReturn;
}
@Override
- public void updatePaymentAttempt(PaymentAttempt updatedPaymentAttempt) {
- // TODO Auto-generated method stub
-
+ public List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(List<String> invoiceIds) {
+ List<PaymentAttempt> paymentAttempts = new ArrayList<PaymentAttempt>(invoiceIds.size());
+ for (String invoiceId : invoiceIds) {
+ PaymentAttempt attempt = getPaymentAttemptForInvoiceId(invoiceId);
+ if (attempt != null) {
+ paymentAttempts.add(attempt);
+ }
+ }
+ return paymentAttempts;
}
}
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
index 6c57c77..7d65b8b 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
@@ -17,17 +17,22 @@
package com.ning.billing.payment.dao;
import java.math.BigDecimal;
+import java.util.Arrays;
import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
+import org.testng.Assert;
import org.testng.annotations.Test;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.payment.api.PaymentAttempt;
import com.ning.billing.payment.api.PaymentInfo;
public abstract class TestPaymentDao {
- protected PaymentDao dao;
+ protected PaymentDao paymentDao;
@Test
public void testCreatePayment() {
@@ -44,7 +49,7 @@ public abstract class TestPaymentDao {
.setEffectiveDate(new DateTime(DateTimeZone.UTC))
.build();
- dao.savePaymentInfo(paymentInfo);
+ paymentDao.savePaymentInfo(paymentInfo);
}
@Test
@@ -62,10 +67,51 @@ public abstract class TestPaymentDao {
.setEffectiveDate(new DateTime(DateTimeZone.UTC))
.build();
- dao.savePaymentInfo(paymentInfo);
+ paymentDao.savePaymentInfo(paymentInfo);
- dao.updatePaymentInfo("CreditCard", paymentInfo.getPaymentId(), "Visa", "US");
+ paymentDao.updatePaymentInfo("CreditCard", paymentInfo.getPaymentId(), "Visa", "US");
}
+ @Test
+ public void testGetPaymentForInvoice() throws AccountApiException {
+ final UUID invoiceId = UUID.randomUUID();
+ final UUID paymentAttemptId = UUID.randomUUID();
+ final UUID accountId = UUID.randomUUID();
+ final String paymentId = UUID.randomUUID().toString();
+ final BigDecimal invoiceAmount = BigDecimal.TEN;
+
+ final DateTime now = new DateTime(DateTimeZone.UTC);
+
+ PaymentAttempt originalPaymenAttempt = new PaymentAttempt(paymentAttemptId, invoiceId, accountId, invoiceAmount, Currency.USD, now, now, paymentId, null, null);
+
+ PaymentAttempt attempt = paymentDao.createPaymentAttempt(originalPaymenAttempt);
+
+ PaymentAttempt attempt2 = paymentDao.getPaymentAttemptForInvoiceId(invoiceId.toString());
+
+ Assert.assertEquals(attempt, attempt2);
+
+ PaymentAttempt attempt3 = paymentDao.getPaymentAttemptsForInvoiceIds(Arrays.asList(invoiceId.toString())).get(0);
+
+ Assert.assertEquals(attempt, attempt3);
+
+ PaymentInfo originalPaymentInfo = new PaymentInfo.Builder().setPaymentId(paymentId)
+ .setAmount(invoiceAmount)
+ .setStatus("Processed")
+ .setBankIdentificationNumber("1234")
+ .setPaymentNumber("12345")
+ .setPaymentMethodId("12345")
+ .setReferenceId("12345")
+ .setType("Electronic")
+ .setCreatedDate(now)
+ .setUpdatedDate(now)
+ .setEffectiveDate(now)
+ .build();
+
+ paymentDao.savePaymentInfo(originalPaymentInfo);
+ PaymentInfo paymentInfo = paymentDao.getPaymentInfo(Arrays.asList(invoiceId.toString())).get(0);
+
+ Assert.assertEquals(originalPaymentInfo, paymentInfo);
+ }
+
}
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java
index da48c03..19ca39d 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java
@@ -26,28 +26,25 @@ import org.testng.annotations.Test;
import com.ning.billing.dbi.MysqlTestingHelper;
-public class TestPaymentDaoWithEmbeddedDb
-{
- @Test(enabled = true, groups = { "slow", "database" })
- public class TestPaymentDaoWithEmbeddedDB extends TestPaymentDao {
- private final MysqlTestingHelper helper = new MysqlTestingHelper();
-
- @BeforeClass(alwaysRun = true)
- public void startMysql() throws IOException {
- final String paymentddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
-
- helper.startMysql();
- helper.initDb(paymentddl);
- }
-
- @AfterClass(alwaysRun = true)
- public void stopMysql() {
- helper.stopMysql();
- }
-
- @BeforeMethod(alwaysRun = true)
- public void setUp() throws IOException {
- dao = new DefaultPaymentDao(helper.getDBI());
- }
+@Test(enabled = true, groups = { "slow", "database" })
+public class TestPaymentDaoWithEmbeddedDb extends TestPaymentDao {
+ private final MysqlTestingHelper helper = new MysqlTestingHelper();
+
+ @BeforeClass(alwaysRun = true)
+ public void startMysql() throws IOException {
+ final String paymentddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+
+ helper.startMysql();
+ helper.initDb(paymentddl);
+ }
+
+ @AfterClass(alwaysRun = true)
+ public void stopMysql() {
+ helper.stopMysql();
+ }
+
+ @BeforeMethod(alwaysRun = true)
+ public void setUp() throws IOException {
+ paymentDao = new DefaultPaymentDao(helper.getDBI());
}
}
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithMock.java b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithMock.java
index f5af240..6e31f90 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithMock.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithMock.java
@@ -25,6 +25,6 @@ import org.testng.annotations.Test;
public class TestPaymentDaoWithMock extends TestPaymentDao {
@BeforeMethod(alwaysRun = true)
public void setUp() throws IOException {
- dao = new MockPaymentDao();
+ paymentDao = new MockPaymentDao();
}
}
\ No newline at end of file
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
index 97e6136..375bcfd 100644
--- a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -46,7 +46,7 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
@Override
public Either<PaymentError, PaymentInfo> processInvoice(Account account, Invoice invoice) {
PaymentInfo payment = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString())
- .setAmount(invoice.getAmountOutstanding())
+ .setAmount(invoice.getBalance())
.setStatus("Processed")
.setBankIdentificationNumber("1234")
.setCreatedDate(new DateTime())
@@ -55,7 +55,6 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
.setReferenceId("12345")
.setType("Electronic")
.build();
-
payments.put(payment.getPaymentId(), payment);
return Either.right(payment);
}
diff --git a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
index 39a080c..9d9372c 100644
--- a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
@@ -16,12 +16,12 @@
package com.ning.billing.payment.setup;
+import com.ning.billing.util.bus.Bus;
import org.apache.commons.collections.MapUtils;
import com.google.common.collect.ImmutableMap;
import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.MemoryEventBus;
+import com.ning.billing.util.bus.InMemoryBus;
public class PaymentTestModuleWithEmbeddedDb extends PaymentModule {
public PaymentTestModuleWithEmbeddedDb() {
@@ -36,6 +36,6 @@ public class PaymentTestModuleWithEmbeddedDb extends PaymentModule {
@Override
protected void configure() {
super.configure();
- bind(EventBus.class).to(MemoryEventBus.class).asEagerSingleton();
+ bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
}
}
diff --git a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
index ce7f502..144afa4 100644
--- a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
@@ -16,6 +16,7 @@
package com.ning.billing.payment.setup;
+import com.ning.billing.util.bus.InMemoryBus;
import org.apache.commons.collections.MapUtils;
import com.google.common.collect.ImmutableMap;
@@ -23,11 +24,11 @@ import com.ning.billing.account.dao.AccountDao;
import com.ning.billing.account.dao.MockAccountDao;
import com.ning.billing.invoice.dao.InvoiceDao;
import com.ning.billing.invoice.dao.MockInvoiceDao;
+
import com.ning.billing.payment.dao.MockPaymentDao;
import com.ning.billing.payment.dao.PaymentDao;
import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.MemoryEventBus;
+import com.ning.billing.util.bus.Bus;
public class PaymentTestModuleWithMocks extends PaymentModule {
public PaymentTestModuleWithMocks() {
@@ -47,7 +48,7 @@ public class PaymentTestModuleWithMocks extends PaymentModule {
@Override
protected void configure() {
super.configure();
- bind(EventBus.class).to(MemoryEventBus.class).asEagerSingleton();
+ bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
bind(MockAccountDao.class).asEagerSingleton();
bind(AccountDao.class).to(MockAccountDao.class);
bind(MockInvoiceDao.class).asEagerSingleton();
diff --git a/payment/src/test/java/com/ning/billing/payment/TestHelper.java b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
index 0d9789c..fd7f5c1 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestHelper.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
@@ -80,16 +80,18 @@ public class TestHelper {
DateTime targetDate,
Currency currency,
InvoiceItem... items) {
- Invoice invoice = new DefaultInvoice(UUID.randomUUID(), account.getId(), new DateTime(), targetDate, currency, null, new BigDecimal("0"));
+ Invoice invoice = new DefaultInvoice(UUID.randomUUID(), account.getId(), new DateTime(), targetDate, currency);
for (InvoiceItem item : items) {
- invoice.add(new DefaultInvoiceItem(invoice.getId(),
+ invoice.addInvoiceItem(new DefaultInvoiceItem(invoice.getId(),
item.getSubscriptionId(),
+ item.getPlanName(),
+ item.getPhaseName(),
item.getStartDate(),
item.getEndDate(),
- item.getDescription(),
- item.getAmount(),
- item.getRate(),
+ item.getRecurringAmount(),
+ item.getRecurringRate(),
+ item.getFixedAmount(),
item.getCurrency()));
}
invoiceDao.create(invoice);
@@ -100,7 +102,7 @@ public class TestHelper {
final DateTime now = new DateTime(DateTimeZone.UTC);
final UUID subscriptionId = UUID.randomUUID();
final BigDecimal amount = new BigDecimal("10.00");
- final InvoiceItem item = new DefaultInvoiceItem(null, subscriptionId, now, now.plusMonths(1), "Test", amount, new BigDecimal("1.0"), Currency.USD);
+ final InvoiceItem item = new DefaultInvoiceItem(null, subscriptionId, "test plan", "test phase", now, now.plusMonths(1), amount, new BigDecimal("1.0"), null, Currency.USD);
return createTestInvoice(account, now, Currency.USD, item);
}
diff --git a/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java b/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java
index c486c2b..80de67f 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java
@@ -20,6 +20,7 @@ import static org.testng.Assert.assertNotNull;
import java.util.UUID;
+import com.ning.billing.invoice.api.InvoicePayment;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Guice;
@@ -32,16 +33,15 @@ import com.ning.billing.account.glue.AccountModuleWithMocks;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoicePaymentApi;
import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
-import com.ning.billing.payment.api.InvoicePayment;
import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
@Test
@Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
public class TestNotifyInvoicePaymentApi {
@Inject
- private EventBus eventBus;
+ private Bus eventBus;
@Inject
private RequestProcessor invoiceProcessor;
@Inject
@@ -69,7 +69,7 @@ public class TestNotifyInvoicePaymentApi {
PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
invoicePaymentApi.notifyOfPaymentAttempt(invoice.getId(),
- invoice.getAmountOutstanding(),
+ invoice.getBalance(),
invoice.getCurrency(),
paymentAttempt.getPaymentAttemptId(),
paymentAttempt.getPaymentAttemptDate());
diff --git a/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java b/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
index 4f83f50..8768117 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
@@ -26,7 +26,9 @@ import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.Callable;
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
import org.apache.commons.io.IOUtils;
+import org.joda.time.DateTime;
import org.skife.jdbi.v2.IDBI;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
@@ -44,21 +46,21 @@ import com.ning.billing.account.glue.AccountModule;
import com.ning.billing.dbi.MysqlTestingHelper;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoicePaymentApi;
-import com.ning.billing.invoice.glue.InvoiceModule;
import com.ning.billing.payment.api.PaymentApi;
import com.ning.billing.payment.api.PaymentAttempt;
import com.ning.billing.payment.api.PaymentError;
import com.ning.billing.payment.api.PaymentInfo;
import com.ning.billing.payment.setup.PaymentTestModuleWithEmbeddedDb;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+import com.ning.billing.util.clock.MockClockModule;
public class TestPaymentInvoiceIntegration {
// create payment for received invoice and save it -- positive and negative
// check that notification for payment attempt is created
// check that invoice-payment is saved
@Inject
- private EventBus eventBus;
+ private Bus eventBus;
@Inject
private RequestProcessor invoiceProcessor;
@Inject
@@ -95,7 +97,8 @@ public class TestPaymentInvoiceIntegration {
public void setUp() throws EventBusException {
Injector injector = Guice.createInjector(new PaymentTestModuleWithEmbeddedDb(),
new AccountModule(),
- new InvoiceModule(),
+ new InvoiceModuleWithMocks(),
+ new MockClockModule(),
new AbstractModule() {
@Override
protected void configure() {
@@ -145,8 +148,12 @@ public class TestPaymentInvoiceIntegration {
Assert.assertNotNull(invoiceForPayment);
Assert.assertEquals(invoiceForPayment.getId(), invoice.getId());
Assert.assertEquals(invoiceForPayment.getAccountId(), account.getId());
- Assert.assertTrue(invoiceForPayment.getLastPaymentAttempt().isEqual(paymentAttempt.getPaymentAttemptDate()));
- Assert.assertEquals(invoiceForPayment.getAmountOutstanding().floatValue(), new BigDecimal("0").floatValue());
- Assert.assertEquals(invoiceForPayment.getAmountPaid().floatValue(), invoice.getAmountOutstanding().floatValue());
+
+ DateTime invoicePaymentAttempt = invoiceForPayment.getLastPaymentAttempt();
+ DateTime correctedDate = invoicePaymentAttempt.minus(invoicePaymentAttempt.millisOfSecond().get());
+ Assert.assertTrue(correctedDate.isEqual(paymentAttempt.getPaymentAttemptDate()));
+
+ Assert.assertEquals(invoiceForPayment.getBalance().floatValue(), new BigDecimal("0").floatValue());
+ Assert.assertEquals(invoiceForPayment.getAmountPaid().floatValue(), invoice.getAmountPaid().floatValue());
}
}
diff --git a/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java b/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java
index 3395421..99870b7 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java
@@ -18,7 +18,6 @@ package com.ning.billing.payment;
import static com.jayway.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.MINUTES;
-import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
@@ -37,13 +36,13 @@ import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
import com.ning.billing.payment.api.PaymentError;
import com.ning.billing.payment.api.PaymentInfo;
import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
@Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
public class TestPaymentProvider {
@Inject
- private EventBus eventBus;
+ private Bus eventBus;
@Inject
private RequestProcessor invoiceProcessor;
@Inject
@@ -58,6 +57,8 @@ public class TestPaymentProvider {
eventBus.start();
eventBus.register(invoiceProcessor);
eventBus.register(paymentInfoReceiver);
+
+ assertTrue(true);
}
@AfterMethod(alwaysRun = true)
@@ -65,6 +66,8 @@ public class TestPaymentProvider {
eventBus.unregister(invoiceProcessor);
eventBus.unregister(paymentInfoReceiver);
eventBus.stop();
+
+ assertTrue(true);
}
@Test
@@ -86,23 +89,5 @@ public class TestPaymentProvider {
assertFalse(paymentInfoReceiver.getProcessedPayments().isEmpty());
assertTrue(paymentInfoReceiver.getErrors().isEmpty());
- final PaymentInfo paymentInfo = paymentInfoReceiver.getProcessedPayments().get(0);
- final PaymentInfoRequest paymentInfoRequest = new PaymentInfoRequest(account.getId(), paymentInfo.getPaymentId());
-
- paymentInfoReceiver.clear();
- eventBus.post(paymentInfoRequest);
- await().atMost(5, MINUTES).until(new Callable<Boolean>() {
- @Override
- public Boolean call() throws Exception {
- List<PaymentInfo> processedPayments = paymentInfoReceiver.getProcessedPayments();
- List<PaymentError> errors = paymentInfoReceiver.getErrors();
-
- return processedPayments.size() == 1 || errors.size() == 1;
- }
- });
-
- assertFalse(paymentInfoReceiver.getProcessedPayments().isEmpty());
- assertTrue(paymentInfoReceiver.getErrors().isEmpty());
- assertEquals(paymentInfoReceiver.getProcessedPayments().get(0), paymentInfo);
}
}
diff --git a/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java b/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java
index 3e446cf..0e76771 100644
--- a/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java
+++ b/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java
@@ -27,8 +27,8 @@ import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.eventbus.Subscribe;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.MemoryEventBus;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.InMemoryBus;
@Test
public class TestSyncWaitOnEventBus {
@@ -70,11 +70,11 @@ public class TestSyncWaitOnEventBus {
}
}
- private EventBus eventBus;
+ private Bus eventBus;
@BeforeMethod(alwaysRun = true)
public void setUp() throws Exception {
- eventBus = new MemoryEventBus();
+ eventBus = new InMemoryBus();
eventBus.start();
eventBus.register(new Object() {
@Subscribe
pom.xml 6(+3 -3)
diff --git a/pom.xml b/pom.xml
index eb22569..b6f6e78 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,7 @@
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
<packaging>pom</packaging>
- <version>0.1.3-SNAPSHOT</version>
+ <version>0.1.5-SNAPSHOT</version>
<name>killbill</name>
<description>Library for managing recurring subscriptions and the associated billing</description>
<url>http://github.com/ning/killbill</url>
@@ -232,7 +232,7 @@
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi</artifactId>
- <version>2.27</version>
+ <version>2.31.2</version>
</dependency>
<dependency>
<groupId>org.skife.config</groupId>
@@ -425,7 +425,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
- <version>2.6</version>
+ <version>2.11</version>
<configuration>
<useManifestOnlyJar>false</useManifestOnlyJar>
<systemPropertyVariables>
util/pom.xml 7(+6 -1)
diff --git a/util/pom.xml b/util/pom.xml
index 2b23ebb..b16ecfa 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.3-SNAPSHOT</version>
+ <version>0.1.5-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-util</artifactId>
@@ -97,6 +97,11 @@
<artifactId>management-dbfiles</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.jayway.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
<plugins>
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/FieldStoreDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/FieldStoreDao.java
index 680a826..14c123a 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/FieldStoreDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/FieldStoreDao.java
@@ -33,6 +33,7 @@ import org.skife.jdbi.v2.sqlobject.BinderFactory;
import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
import org.skife.jdbi.v2.sqlobject.SqlBatch;
import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+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;
@@ -42,10 +43,11 @@ import com.ning.billing.util.entity.EntityCollectionDao;
@ExternalizedSqlViaStringTemplate3
@RegisterMapper(FieldStoreDao.CustomFieldMapper.class)
-public interface FieldStoreDao extends EntityCollectionDao<CustomField>, Transmogrifier {
+public interface FieldStoreDao extends EntityCollectionDao<CustomField>, Transactional<FieldStoreDao>, Transmogrifier {
+
@Override
- @SqlBatch
- public void save(@Bind("objectId") final String objectId,
+ @SqlBatch(transactional=false)
+ public void batchSaveFromTransaction(@Bind("objectId") final String objectId,
@Bind("objectType") final String objectType,
@CustomFieldBinder final List<CustomField> entities);
@@ -65,8 +67,10 @@ public interface FieldStoreDao extends EntityCollectionDao<CustomField>, Transmo
@Target({ElementType.PARAMETER})
public @interface CustomFieldBinder {
public static class CustomFieldBinderFactory implements BinderFactory {
+ @Override
public Binder build(Annotation annotation) {
return new Binder<CustomFieldBinder, CustomField>() {
+ @Override
public void bind(SQLStatement q, CustomFieldBinder bind, CustomField customField) {
q.bind("id", customField.getId().toString());
q.bind("fieldName", customField.getName());
diff --git a/util/src/main/java/com/ning/billing/util/entity/EntityCollectionDao.java b/util/src/main/java/com/ning/billing/util/entity/EntityCollectionDao.java
index 8134203..11d149c 100644
--- a/util/src/main/java/com/ning/billing/util/entity/EntityCollectionDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/EntityCollectionDao.java
@@ -26,8 +26,9 @@ import java.util.List;
* @param <T>
*/
public interface EntityCollectionDao<T extends Entity> {
- @SqlBatch
- public void save(@Bind("objectId") final String objectId,
+
+ @SqlBatch(transactional=false)
+ public void batchSaveFromTransaction(@Bind("objectId") final String objectId,
@Bind("objectType") final String objectType,
@BindBean final List<T> entities);
diff --git a/util/src/main/java/com/ning/billing/util/globalLocker/GlobalLock.java b/util/src/main/java/com/ning/billing/util/globalLocker/GlobalLock.java
new file mode 100644
index 0000000..62a61a5
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globalLocker/GlobalLock.java
@@ -0,0 +1,22 @@
+/*
+ * 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.util.globalLocker;
+
+public interface GlobalLock
+{
+ public void release();
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/globalLocker/LockFailedException.java b/util/src/main/java/com/ning/billing/util/globalLocker/LockFailedException.java
new file mode 100644
index 0000000..341edc3
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globalLocker/LockFailedException.java
@@ -0,0 +1,21 @@
+/*
+ * 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.util.globalLocker;
+
+public class LockFailedException extends RuntimeException
+{
+}
diff --git a/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLocker.java b/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLocker.java
new file mode 100644
index 0000000..762ddc7
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLocker.java
@@ -0,0 +1,106 @@
+/*
+ * 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.util.globalLocker;
+
+import com.google.inject.Inject;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MySqlGlobalLocker implements GlobalLocker {
+
+ private final static Logger logger = LoggerFactory.getLogger(MySqlGlobalLocker.class);
+
+ private final static long DEFAULT_TIMEOUT = 3L; // 3 seconds
+
+ private final IDBI dbi;
+ private long timeout;
+
+ @Inject
+ public MySqlGlobalLocker(IDBI dbi) {
+ this.dbi = dbi;
+ this.timeout = DEFAULT_TIMEOUT;
+ }
+
+ public void setTimeout(final long timeout) {
+ this.timeout = timeout;
+ }
+
+ @Override
+ public GlobalLock lockWithNumberOfTries(final LockerService service, final String lockKey, final int retry) {
+
+ final String lockName = getLockName(service, lockKey);
+ int tries_left = retry;
+ while (tries_left-- > 0) {
+ GlobalLock lock = lock(lockName);
+ if (lock != null) {
+ return lock;
+ }
+ }
+ logger.error(String.format("Failed to acquire lock %s for service %s after %d retry", lockKey, service, retry));
+ throw new LockFailedException();
+ }
+
+ private GlobalLock lock(final String lockName) throws LockFailedException {
+
+ final Handle h = dbi.open();
+ final MySqlGlobalLockerDao dao = h.attach(MySqlGlobalLockerDao.class);
+
+ final boolean obtained = dao.lock(lockName, timeout);
+ if (obtained) {
+ return new GlobalLock() {
+ @Override
+ public void release() {
+ try {
+ dao.releaseLock(lockName);
+ }
+ finally {
+ if (h != null) {
+ h.close();
+ }
+ }
+ }
+ };
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Boolean isFree(final LockerService service, final String lockKey) {
+
+ final String lockName = getLockName(service, lockKey);
+ final Handle h = dbi.open();
+ try {
+ final MySqlGlobalLockerDao dao = h.attach(MySqlGlobalLockerDao.class);
+ return dao.isFree(lockName);
+ } finally {
+ if (h != null) {
+ h.close();
+ }
+ }
+ }
+
+ private String getLockName(final LockerService service, final String lockKey) {
+ StringBuilder tmp = new StringBuilder()
+ .append(service.toString())
+ .append("-")
+ .append(lockKey);
+ return tmp.toString();
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLockerDao.java b/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLockerDao.java
new file mode 100644
index 0000000..14a02d4
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globalLocker/MySqlGlobalLockerDao.java
@@ -0,0 +1,46 @@
+/*
+ * 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.util.globalLocker;
+
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@RegisterMapper(MySqlGlobalLockerDao.LockMapper.class)
+public interface MySqlGlobalLockerDao {
+
+ @SqlQuery("Select GET_LOCK(:lockName, :timeout);")
+ public Boolean lock(@Bind("lockName") final String lockName, @Bind("timeout") final long timeout);
+
+ @SqlQuery("Select RELEASE_LOCK(:lockName);")
+ public Boolean releaseLock(@Bind("lockName") final String lockName);
+
+ @SqlQuery("Select IS_FREE_LOCK(:lockName);")
+ public Boolean isFree(@Bind("lockName") final String lockName);
+
+ class LockMapper implements ResultSetMapper<Boolean> {
+ @Override
+ public Boolean map(int index, ResultSet r, StatementContext ctx) throws SQLException {
+ return (r.getByte(1) == 1);
+ }
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/glue/BusModule.java b/util/src/main/java/com/ning/billing/util/glue/BusModule.java
new file mode 100644
index 0000000..d6f7a37
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/glue/BusModule.java
@@ -0,0 +1,34 @@
+/*
+ * 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.util.glue;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.bus.InMemoryBus;
+
+public class BusModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ bind(BusService.class).to(DefaultBusService.class);
+ bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+
+ }
+
+}
diff --git a/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java b/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java
index 10651be..a598275 100644
--- a/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java
+++ b/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java
@@ -18,8 +18,6 @@ package com.ning.billing.util.glue;
import com.google.inject.AbstractModule;
import com.ning.billing.util.api.TagDefinitionUserApi;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.clock.DefaultClock;
import com.ning.billing.util.tag.api.DefaultTagDefinitionUserApi;
import com.ning.billing.util.tag.dao.DefaultTagDefinitionDao;
import com.ning.billing.util.tag.dao.TagDefinitionDao;
@@ -28,13 +26,16 @@ import com.ning.billing.util.tag.dao.TagStoreSqlDao;
public class TagStoreModule extends AbstractModule
{
- @Override
- protected void configure()
- {
+ protected void installDaos() {
bind(TagDefinitionSqlDao.class).toProvider(TagDescriptionDaoProvider.class).asEagerSingleton();
bind(TagDefinitionDao.class).to(DefaultTagDefinitionDao.class).asEagerSingleton();
bind(TagStoreSqlDao.class).toProvider(TagStoreDaoProvider.class).asEagerSingleton();
+ }
+
+ @Override
+ protected void configure()
+ {
+ installDaos();
bind(TagDefinitionUserApi.class).to(DefaultTagDefinitionUserApi.class).asEagerSingleton();
}
-
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java b/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
index 818d831..2f511ec 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
@@ -49,13 +49,13 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
//
@SqlQuery
@Mapper(NotificationSqlMapper.class)
- public List<Notification> getReadyNotifications(@Bind("now") Date now, @Bind("max") int max);
+ public List<Notification> getReadyNotifications(@Bind("now") Date now, @Bind("max") int max, @Bind("queue_name") String queueName);
@SqlUpdate
- public int claimNotification(@Bind("owner") String owner, @Bind("next_available") Date nextAvailable, @Bind("notification_id") String eventId, @Bind("now") Date now);
+ public int claimNotification(@Bind("owner") String owner, @Bind("next_available") Date nextAvailable, @Bind("id") long id, @Bind("now") Date now);
@SqlUpdate
- public void clearNotification(@Bind("notification_id") String eventId, @Bind("owner") String owner);
+ public void clearNotification(@Bind("id") long id, @Bind("owner") String owner);
@SqlUpdate
public void insertNotification(@Bind(binder = NotificationSqlDaoBinder.class) Notification evt);
@@ -71,12 +71,13 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
@Override
public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, Notification evt) {
- stmt.bind("notification_id", evt.getId().toString());
+ stmt.bind("notification_id", evt.getUUID().toString());
stmt.bind("created_dt", getDate(new DateTime()));
stmt.bind("notification_key", evt.getNotificationKey());
stmt.bind("effective_dt", getDate(evt.getEffectiveDate()));
+ stmt.bind("queue_name", evt.getQueueName());
stmt.bind("processing_available_dt", getDate(evt.getNextAvailableDate()));
- stmt.bind("processing_owner", (String) null);
+ stmt.bind("processing_owner", evt.getOwner());
stmt.bind("processing_state", NotificationLifecycleState.AVAILABLE.toString());
}
}
@@ -93,14 +94,16 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
public Notification map(int index, ResultSet r, StatementContext ctx)
throws SQLException {
- final UUID id = UUID.fromString(r.getString("notification_id"));
+ final long id = r.getLong("id");
+ final UUID uuid = UUID.fromString(r.getString("notification_id"));
final String notificationKey = r.getString("notification_key");
+ final String queueName = r.getString("queue_name");
final DateTime effectiveDate = getDate(r, "effective_dt");
final DateTime nextAvailableDate = getDate(r, "processing_available_dt");
final String processingOwner = r.getString("processing_owner");
final NotificationLifecycleState processingState = NotificationLifecycleState.valueOf(r.getString("processing_state"));
- return new DefaultNotification(id, processingOwner, nextAvailableDate,
+ return new DefaultNotification(id, uuid, processingOwner, queueName, nextAvailableDate,
processingState, notificationKey, effectiveDate);
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java
index 2946e13..26e6c4e 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java
@@ -22,33 +22,41 @@ import org.joda.time.DateTime;
public class DefaultNotification implements Notification {
- private final UUID id;
+ private final long id;
+ private final UUID uuid;
private final String owner;
+ private final String queueName;
private final DateTime nextAvailableDate;
private final NotificationLifecycleState lifecycleState;
private final String notificationKey;
private final DateTime effectiveDate;
- public DefaultNotification(UUID id, String owner, DateTime nextAvailableDate,
+ public DefaultNotification(long id, UUID uuid, String owner, String queueName, DateTime nextAvailableDate,
NotificationLifecycleState lifecycleState,
String notificationKey, DateTime effectiveDate) {
super();
this.id = id;
+ this.uuid = uuid;
this.owner = owner;
+ this.queueName = queueName;
this.nextAvailableDate = nextAvailableDate;
this.lifecycleState = lifecycleState;
this.notificationKey = notificationKey;
this.effectiveDate = effectiveDate;
}
- public DefaultNotification(String notificationKey, DateTime effectiveDate) {
- this(UUID.randomUUID(), null, null, NotificationLifecycleState.AVAILABLE, notificationKey, effectiveDate);
+ @Override
+ public long getId() {
+ return id;
}
+ public DefaultNotification(String queueName, String notificationKey, DateTime effectiveDate) {
+ this(-1L, UUID.randomUUID(), null, queueName, null, NotificationLifecycleState.AVAILABLE, notificationKey, effectiveDate);
+ }
@Override
- public UUID getId() {
- return id;
+ public UUID getUUID() {
+ return uuid;
}
@Override
@@ -94,4 +102,10 @@ public class DefaultNotification implements Notification {
public DateTime getEffectiveDate() {
return effectiveDate;
}
+
+ @Override
+ public String getQueueName() {
+ return queueName;
+ }
+
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
index 16d28b2..3c19a2b 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
@@ -17,15 +17,10 @@
package com.ning.billing.util.notificationq;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Date;
import java.util.List;
-
import org.joda.time.DateTime;
-import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.IDBI;
-import org.skife.jdbi.v2.Transaction;
-import org.skife.jdbi.v2.TransactionStatus;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import com.ning.billing.util.clock.Clock;
@@ -37,48 +32,45 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
protected final NotificationSqlDao dao;
public DefaultNotificationQueue(final IDBI dbi, final Clock clock, final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config) {
+
super(clock, svcName, queueName, handler, config);
this.dao = dbi.onDemand(NotificationSqlDao.class);
}
@Override
- protected void doProcessEvents(int sequenceId) {
+ protected void doProcessEvents(final int sequenceId) {
+
+ logDebug("ENTER doProcessEvents");
List<Notification> notifications = getReadyNotifications(sequenceId);
- for (Notification cur : notifications) {
+ if (notifications.size() == 0) {
+ logDebug("EXIT doProcessEvents");
+ return;
+ }
+
+ logDebug("START processing %d events at time %s", notifications.size(), clock.getUTCNow().toDate());
+
+ for (final Notification cur : notifications) {
nbProcessedEvents.incrementAndGet();
+ logDebug("handling notification %s, key = %s for time %s",
+ cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate());
handler.handleReadyNotification(cur.getNotificationKey());
+ clearNotification(cur);
+ logDebug("done handling notification %s, key = %s for time %s",
+ cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate());
}
- // If anything happens before we get to clear those notifications, somebody else will pick them up
- clearNotifications(notifications);
}
@Override
public void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao,
final DateTime futureNotificationTime, final NotificationKey notificationKey) {
NotificationSqlDao transactionalNotificationDao = transactionalDao.become(NotificationSqlDao.class);
- Notification notification = new DefaultNotification(notificationKey.toString(), futureNotificationTime);
+ Notification notification = new DefaultNotification(getFullQName(), notificationKey.toString(), futureNotificationTime);
transactionalNotificationDao.insertNotification(notification);
}
- private void clearNotifications(final Collection<Notification> cleared) {
-
- log.debug(String.format("NotificationQueue %s clearEventsReady START cleared size = %d",
- getFullQName(),
- cleared.size()));
-
- dao.inTransaction(new Transaction<Void, NotificationSqlDao>() {
-
- @Override
- public Void inTransaction(NotificationSqlDao transactional,
- TransactionStatus status) throws Exception {
- for (Notification cur : cleared) {
- transactional.clearNotification(cur.getId().toString(), hostname);
- log.debug(String.format("NotificationQueue %s cleared events %s", getFullQName(), cur.getId()));
- }
- return null;
- }
- });
+ private void clearNotification(final Notification cleared) {
+ dao.clearNotification(cleared.getId(), hostname);
}
private List<Notification> getReadyNotifications(final int seqId) {
@@ -86,35 +78,34 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
final Date now = clock.getUTCNow().toDate();
final Date nextAvailable = clock.getUTCNow().plus(config.getDaoClaimTimeMs()).toDate();
- log.debug(String.format("NotificationQueue %s getEventsReady START effectiveNow = %s", getFullQName(), now));
-
- List<Notification> result = dao.inTransaction(new Transaction<List<Notification>, NotificationSqlDao>() {
-
- @Override
- public List<Notification> inTransaction(NotificationSqlDao transactionalDao,
- TransactionStatus status) throws Exception {
-
- List<Notification> claimedNotifications = new ArrayList<Notification>();
- List<Notification> input = transactionalDao.getReadyNotifications(now, config.getDaoMaxReadyEvents());
- for (Notification cur : input) {
- final boolean claimed = (transactionalDao.claimNotification(hostname, nextAvailable, cur.getId().toString(), now) == 1);
- if (claimed) {
- claimedNotifications.add(cur);
- transactionalDao.insertClaimedHistory(seqId, hostname, now, cur.getId().toString());
- }
- }
- return claimedNotifications;
+ List<Notification> input = dao.getReadyNotifications(now, config.getDaoMaxReadyEvents(), getFullQName());
+
+ List<Notification> claimedNotifications = new ArrayList<Notification>();
+ for (Notification cur : input) {
+ logDebug("about to claim notification %s, key = %s for time %s",
+ cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate());
+ final boolean claimed = (dao.claimNotification(hostname, nextAvailable, cur.getId(), now) == 1);
+ logDebug("claimed notification %s, key = %s for time %s result = %s",
+ cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate(), Boolean.valueOf(claimed));
+ if (claimed) {
+ claimedNotifications.add(cur);
+ dao.insertClaimedHistory(seqId, hostname, now, cur.getUUID().toString());
}
- });
+ }
- for (Notification cur : result) {
- log.debug(String.format("NotificationQueue %sclaimed events %s",
- getFullQName(), cur.getId()));
+ for (Notification cur : claimedNotifications) {
if (cur.getOwner() != null && !cur.getOwner().equals(hostname)) {
log.warn(String.format("NotificationQueue %s stealing notification %s from %s",
getFullQName(), cur, cur.getOwner()));
}
}
- return result;
+ return claimedNotifications;
+ }
+
+ private void logDebug(String format, Object...args) {
+ if (log.isDebugEnabled()) {
+ String realDebug = String.format(format, args);
+ log.debug(String.format("Thread %d [queue = %s] %s", Thread.currentThread().getId(), getFullQName(), realDebug));
+ }
}
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java
index fe18ead..91e7110 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java
@@ -17,7 +17,6 @@
package com.ning.billing.util.notificationq;
import org.skife.jdbi.v2.IDBI;
-
import com.google.inject.Inject;
import com.ning.billing.util.clock.Clock;
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/Notification.java b/util/src/main/java/com/ning/billing/util/notificationq/Notification.java
index 651469b..d59098b 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/Notification.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/Notification.java
@@ -23,9 +23,15 @@ import org.joda.time.DateTime;
public interface Notification extends NotificationLifecycle {
- public UUID getId();
+ public long getId();
+
+ public UUID getUUID();
public String getNotificationKey();
public DateTime getEffectiveDate();
+
+ public String getQueueName();
+
+
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationError.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationError.java
new file mode 100644
index 0000000..4e771ba
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationError.java
@@ -0,0 +1,38 @@
+/*
+ * 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.util.notificationq;
+
+public class NotificationError extends Error {
+
+ private static final long serialVersionUID = 131398536;
+
+ public NotificationError() {
+ super();
+ }
+
+ public NotificationError(String msg, Throwable arg1) {
+ super(msg, arg1);
+ }
+
+ public NotificationError(String msg) {
+ super(msg);
+ }
+
+ public NotificationError(Throwable msg) {
+ super(msg);
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
index 23f0de0..4ea38f7 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
@@ -42,18 +42,17 @@ public interface NotificationQueue {
public void processReadyNotification();
/**
- * Stops the queue.
+ * Stops the queue. Blocks until queue is completely stopped.
*
* @see NotificationQueueHandler.completedQueueStop to be notified when the notification thread exited
*/
public void stopQueue();
/**
- * Starts the queue.
+ * Starts the queue. Blocks until queue has completely started.
*
* @see NotificationQueueHandler.completedQueueStart to be notified when the notification thread started
*/
public void startQueue();
-
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
index 6eaf33f..9a42d2e 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
@@ -17,34 +17,28 @@
package com.ning.billing.util.notificationq;
import java.lang.Thread.UncaughtExceptionHandler;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.DBI;
-import org.skife.jdbi.v2.Transaction;
-import org.skife.jdbi.v2.TransactionStatus;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ning.billing.util.Hostname;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
-import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
public abstract class NotificationQueueBase implements NotificationQueue {
protected final static Logger log = LoggerFactory.getLogger(NotificationQueueBase.class);
+ private static final long MAX_NOTIFICATION_THREAD_WAIT_MS = 10000; // 10 secs
+ private static final long NOTIFICATION_THREAD_WAIT_INCREMENT_MS = 1000; // 1 sec
+ private static final long NANO_TO_MS = (1000 * 1000);
+
protected static final String NOTIFICATION_THREAD_PREFIX = "Notification-";
protected final long STOP_WAIT_TIMEOUT_MS = 60000;
@@ -63,7 +57,10 @@ public abstract class NotificationQueueBase implements NotificationQueue {
// Use this object's monitor for synchronization (no need for volatile)
protected boolean isProcessingEvents;
-
+
+ private boolean startedComplete = false;
+ private boolean stoppedComplete = false;
+
// Package visibility on purpose
NotificationQueueBase(final Clock clock, final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config) {
this.clock = clock;
@@ -92,14 +89,14 @@ public abstract class NotificationQueueBase implements NotificationQueue {
@Override
public void processReadyNotification() {
- // STEPH to be implemented
+ doProcessEvents(sequenceId.incrementAndGet());
}
@Override
public void stopQueue() {
if (config.isNotificationProcessingOff()) {
- handler.completedQueueStop();
+ completedQueueStop();
return;
}
@@ -113,7 +110,7 @@ public abstract class NotificationQueueBase implements NotificationQueue {
log.warn("NotificationQueue got interrupted exception when stopping notifications", e);
}
}
-
+ waitForNotificationStopCompletion();
}
@Override
@@ -125,7 +122,7 @@ public abstract class NotificationQueueBase implements NotificationQueue {
if (config.isNotificationProcessingOff()) {
log.warn(String.format("KILLBILL NOTIFICATION PROCESSING FOR SVC %s IS OFF !!!", getFullQName()));
- handler.completedQueueStart();
+ completedQueueStart();
return;
}
final NotificationQueueBase notificationQueue = this;
@@ -139,7 +136,7 @@ public abstract class NotificationQueueBase implements NotificationQueue {
Thread.currentThread().getId()));
// Thread is now started, notify the listener
- handler.completedQueueStart();
+ completedQueueStart();
try {
while (true) {
@@ -171,7 +168,7 @@ public abstract class NotificationQueueBase implements NotificationQueue {
// Just to make it really obvious in the log
e.printStackTrace();
} finally {
- handler.completedQueueStop();
+ completedQueueStop();
log.info(String.format("NotificationQueue thread %s [%d] exited...",
Thread.currentThread().getName(),
Thread.currentThread().getId()));
@@ -182,8 +179,59 @@ public abstract class NotificationQueueBase implements NotificationQueue {
Thread.sleep(config.getNotificationSleepTimeMs());
}
});
+ waitForNotificationStartCompletion();
+ }
+
+ private void completedQueueStop() {
+ synchronized (this) {
+ stoppedComplete = true;
+ this.notifyAll();
+ }
+ }
+
+ private void completedQueueStart() {
+ synchronized (this) {
+ startedComplete = true;
+ this.notifyAll();
+ }
}
+ private void waitForNotificationStartCompletion() {
+ waitForNotificationEventCompletion(true);
+ }
+
+ private void waitForNotificationStopCompletion() {
+ waitForNotificationEventCompletion(false);
+ }
+
+ private void waitForNotificationEventCompletion(boolean startEvent) {
+
+ long ini = System.nanoTime();
+ synchronized(this) {
+ do {
+ if ((startEvent ? startedComplete : stoppedComplete)) {
+ break;
+ }
+ try {
+ this.wait(NOTIFICATION_THREAD_WAIT_INCREMENT_MS);
+ } catch (InterruptedException e ) {
+ Thread.currentThread().interrupt();
+ throw new NotificationError(e);
+ }
+ } while (!(startEvent ? startedComplete : stoppedComplete) &&
+ (System.nanoTime() - ini) / NANO_TO_MS < MAX_NOTIFICATION_THREAD_WAIT_MS);
+
+ if (!(startEvent ? startedComplete : stoppedComplete)) {
+ log.error("Could not {} notification thread in {} msec !!!",
+ (startEvent ? "start" : "stop"),
+ MAX_NOTIFICATION_THREAD_WAIT_MS);
+ throw new NotificationError("Failed to start service!!");
+ }
+ log.info("Notification thread has been {} in {} ms",
+ (startEvent ? "started" : "stopped"),
+ (System.nanoTime() - ini) / NANO_TO_MS);
+ }
+ }
protected String getFullQName() {
return svcName + ":" + queueName;
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java
index a18906b..4fb17b5 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java
@@ -16,33 +16,22 @@
package com.ning.billing.util.notificationq;
-import java.util.NoSuchElementException;
-
public interface NotificationQueueService {
public interface NotificationQueueHandler {
/**
- * Called when the Notification thread has been started
- */
- public void completedQueueStart();
-
- /**
* Called for each notification ready
*
- * @param key the notification key associated to that notification entry
+ * @param notificationKey the notification key associated to that notification entry
*/
public void handleReadyNotification(String notificationKey);
- /**
- * Called right before the Notification thread is about to exit
- */
- public void completedQueueStop();
- }
+ }
- public static final class NotficationQueueAlreadyExists extends Exception {
+ public static final class NotificationQueueAlreadyExists extends Exception {
private static final long serialVersionUID = 1541281L;
- public NotficationQueueAlreadyExists(String msg) {
+ public NotificationQueueAlreadyExists(String msg) {
super(msg);
}
}
@@ -65,11 +54,11 @@ public interface NotificationQueueService {
*
* @return a new NotificationQueue
*
- * @throws NotficationQueueAlreadyExists is the queue associated with that service and name already exits
+ * @throws com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists is the queue associated with that service and name already exits
*
*/
NotificationQueue createNotificationQueue(final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config)
- throws NotficationQueueAlreadyExists;
+ throws NotificationQueueAlreadyExists;
/**
* Retrieves an already created NotificationQueue by service and name if it exists
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java
index a4dc64e..98a10b1 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java
@@ -43,7 +43,7 @@ public abstract class NotificationQueueServiceBase implements NotificationQueueS
@Override
public NotificationQueue createNotificationQueue(String svcName,
String queueName, NotificationQueueHandler handler,
- NotificationConfig config) throws NotficationQueueAlreadyExists {
+ NotificationConfig config) throws NotificationQueueAlreadyExists {
if (svcName == null || queueName == null || handler == null || config == null) {
throw new RuntimeException("Need to specify all parameters");
}
@@ -53,7 +53,7 @@ public abstract class NotificationQueueServiceBase implements NotificationQueueS
synchronized(queues) {
result = queues.get(compositeName);
if (result != null) {
- throw new NotficationQueueAlreadyExists(String.format("Queue for svc %s and name %s already exist",
+ throw new NotificationQueueAlreadyExists(String.format("Queue for svc %s and name %s already exist",
svcName, queueName));
}
result = createNotificationQueueInternal(svcName, queueName, handler, config);
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagStoreSqlDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagStoreSqlDao.java
index 33dcdb0..bf4ed62 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagStoreSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagStoreSqlDao.java
@@ -16,38 +16,23 @@
package com.ning.billing.util.tag.dao;
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.sql.ResultSet;
-import java.sql.SQLException;
+
import java.util.List;
-import java.util.UUID;
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.SQLStatement;
-import org.skife.jdbi.v2.StatementContext;
+
import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.Binder;
-import org.skife.jdbi.v2.sqlobject.BinderFactory;
-import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
import org.skife.jdbi.v2.sqlobject.SqlBatch;
import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+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.util.entity.EntityCollectionDao;
-import com.ning.billing.util.tag.DescriptiveTag;
-import com.ning.billing.util.tag.DefaultTagDefinition;
import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.TagDefinition;
@ExternalizedSqlViaStringTemplate3
@RegisterMapper(TagMapper.class)
-public interface TagStoreSqlDao extends EntityCollectionDao<Tag> {
+public interface TagStoreSqlDao extends EntityCollectionDao<Tag>, Transactional<TagStoreSqlDao> {
@Override
- @SqlBatch
- public void save(@Bind("objectId") final String objectId,
+ @SqlBatch(transactional=false)
+ public void batchSaveFromTransaction(@Bind("objectId") final String objectId,
@Bind("objectType") final String objectType,
@TagBinder final List<Tag> entities);
}
\ No newline at end of file
diff --git a/util/src/main/resources/com/ning/billing/util/customfield/dao/FieldStoreDao.sql.stg b/util/src/main/resources/com/ning/billing/util/customfield/dao/FieldStoreDao.sql.stg
index 883f61b..9d3e96e 100644
--- a/util/src/main/resources/com/ning/billing/util/customfield/dao/FieldStoreDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/customfield/dao/FieldStoreDao.sql.stg
@@ -1,6 +1,6 @@
group FieldStoreDao;
-save() ::= <<
+batchSaveFromTransaction() ::= <<
INSERT INTO custom_fields(id, object_id, object_type, field_name, field_value)
VALUES (:id, :objectId, :objectType, :fieldName, :fieldValue)
ON DUPLICATE KEY UPDATE
diff --git a/util/src/main/resources/com/ning/billing/util/ddl.sql b/util/src/main/resources/com/ning/billing/util/ddl.sql
index a65e0cd..a0ef302 100644
--- a/util/src/main/resources/com/ning/billing/util/ddl.sql
+++ b/util/src/main/resources/com/ning/billing/util/ddl.sql
@@ -10,6 +10,7 @@ CREATE TABLE custom_fields (
CREATE INDEX custom_fields_object_id_object_type ON custom_fields(object_id, object_type);
CREATE UNIQUE INDEX custom_fields_unique ON custom_fields(object_id, object_type, field_name);
+DROP TABLE IF EXISTS tag_descriptions;
DROP TABLE IF EXISTS tag_definitions;
CREATE TABLE tag_definitions (
id char(36) NOT NULL,
@@ -41,14 +42,14 @@ CREATE TABLE notifications (
created_dt datetime NOT NULL,
notification_key varchar(256) NOT NULL,
effective_dt datetime NOT NULL,
- processing_owner char(36) DEFAULT NULL,
+ queue_name char(64) NOT NULL,
+ processing_owner char(50) DEFAULT NULL,
processing_available_dt datetime DEFAULT NULL,
processing_state varchar(14) DEFAULT 'AVAILABLE',
PRIMARY KEY(id)
) ENGINE=innodb;
-CREATE INDEX `idx_comp_where` ON notifications (`effective_dt`,`processing_state`,`processing_owner`,`processing_available_dt`);
-CREATE INDEX `idx_update` ON notifications (`notification_id`,`processing_state`,`processing_owner`,`processing_available_dt`);
-CREATE INDEX `idx_update1` ON notifications (`notification_id`,`processing_owner`);
+CREATE INDEX `idx_comp_where` ON notifications (`effective_dt`, `queue_name`, `processing_state`,`processing_owner`,`processing_available_dt`);
+CREATE INDEX `idx_update` ON notifications (`processing_state`,`processing_owner`,`processing_available_dt`);
CREATE INDEX `idx_get_ready` ON notifications (`effective_dt`,`created_dt`,`id`);
DROP TABLE IF EXISTS claimed_notifications;
diff --git a/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
index 5a44431..7a7ecab 100644
--- a/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
@@ -2,48 +2,49 @@ group NotificationSqlDao;
getReadyNotifications(now, max) ::= <<
select
- notification_id
- , notification_key
+ id
+ , notification_id
+ , notification_key
, created_dt
, effective_dt
+ , queue_name
, processing_owner
, processing_available_dt
, processing_state
from notifications
where
effective_dt \<= :now
+ and queue_name = :queue_name
and processing_state != 'PROCESSED'
and (processing_owner IS NULL OR processing_available_dt \<= :now)
order by
effective_dt asc
, created_dt asc
- , id asc
+ , id
limit :max
;
>>
-claimNotification(owner, next_available, notification_id, now) ::= <<
+claimNotification(owner, next_available, id, now) ::= <<
update notifications
set
processing_owner = :owner
, processing_available_dt = :next_available
, processing_state = 'IN_PROCESSING'
where
- notification_id = :notification_id
+ id = :id
and processing_state != 'PROCESSED'
and (processing_owner IS NULL OR processing_available_dt \<= :now)
;
>>
-clearNotification(notification_id, owner) ::= <<
+clearNotification(id, owner) ::= <<
update notifications
set
- processing_owner = NULL
- , processing_state = 'PROCESSED'
+ processing_state = 'PROCESSED'
where
- notification_id = :notification_id
- and processing_owner = :owner
+ id = :id
;
>>
@@ -53,6 +54,7 @@ insertNotification() ::= <<
, notification_key
, created_dt
, effective_dt
+ , queue_name
, processing_owner
, processing_available_dt
, processing_state
@@ -61,6 +63,7 @@ insertNotification() ::= <<
, :notification_key
, :created_dt
, :effective_dt
+ , :queue_name
, :processing_owner
, :processing_available_dt
, :processing_state
diff --git a/util/src/main/resources/com/ning/billing/util/tag/dao/TagStoreSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/tag/dao/TagStoreSqlDao.sql.stg
index 2d78f48..9d7ce5c 100644
--- a/util/src/main/resources/com/ning/billing/util/tag/dao/TagStoreSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/tag/dao/TagStoreSqlDao.sql.stg
@@ -1,6 +1,6 @@
group TagStoreDao;
-save() ::= <<
+batchSaveFromTransaction() ::= <<
INSERT INTO tags(id, tag_definition_name, object_id, object_type, added_date, added_by)
VALUES (:id, :tagDefinitionName, :objectId, :objectType, :addedDate, :addedBy)
ON DUPLICATE KEY UPDATE
diff --git a/util/src/test/java/com/ning/billing/dbi/DBIProvider.java b/util/src/test/java/com/ning/billing/dbi/DBIProvider.java
index a4a7b61..d83c033 100644
--- a/util/src/test/java/com/ning/billing/dbi/DBIProvider.java
+++ b/util/src/test/java/com/ning/billing/dbi/DBIProvider.java
@@ -25,13 +25,14 @@ import com.ning.jdbi.metrics.MetricsTimingCollector;
import com.ning.jdbi.metrics.SqlJdbiGroupStrategy;
import com.yammer.metrics.core.MetricsRegistry;
import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.TimingCollector;
import org.skife.jdbi.v2.logging.Log4JLog;
import org.skife.jdbi.v2.tweak.SQLLog;
import java.util.concurrent.TimeUnit;
-public class DBIProvider implements Provider<DBI>
+public class DBIProvider implements Provider<IDBI>
{
private final MetricsRegistry metricsRegistry;
private final DbiConfig config;
@@ -44,7 +45,7 @@ public class DBIProvider implements Provider<DBI>
}
@Override
- public DBI get()
+ public IDBI get()
{
final BoneCPConfig dbConfig = new BoneCPConfig();
dbConfig.setJdbcUrl(config.getJdbcUrl());
@@ -54,7 +55,7 @@ public class DBIProvider implements Provider<DBI>
dbConfig.setMaxConnectionsPerPartition(config.getMaxActive());
dbConfig.setConnectionTimeout(config.getConnectionTimeout().getPeriod(), config.getConnectionTimeout().getUnit());
dbConfig.setPartitionCount(1);
- dbConfig.setDefaultTransactionIsolation("READ_COMMITTED");
+ dbConfig.setDefaultTransactionIsolation("REPEATABLE_READ");
dbConfig.setDisableJMX(false);
final BoneCPDataSource ds = new BoneCPDataSource(dbConfig);
diff --git a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
index be53073..1e949a2 100644
--- a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
+++ b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
@@ -114,7 +114,7 @@ public class MysqlTestingHelper
}
}
- public DBI getDBI()
+ public IDBI getDBI()
{
final String dbiString = "jdbc:mysql://localhost:" + port + "/" + DB_NAME + "?createDatabaseIfNotExist=true";
return new DBI(dbiString, USERNAME, PASSWORD);
diff --git a/util/src/test/java/com/ning/billing/util/clock/ClockMock.java b/util/src/test/java/com/ning/billing/util/clock/ClockMock.java
index 7698697..72fd8f4 100644
--- a/util/src/test/java/com/ning/billing/util/clock/ClockMock.java
+++ b/util/src/test/java/com/ning/billing/util/clock/ClockMock.java
@@ -19,6 +19,8 @@ package com.ning.billing.util.clock;
import com.ning.billing.catalog.api.Duration;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
@@ -26,6 +28,8 @@ import java.util.List;
// STEPH should really be in tests but not accessible from other sub modules
public class ClockMock extends DefaultClock {
+ private static final Logger log = LoggerFactory.getLogger(ClockMock.class);
+
private enum DeltaType {
DELTA_NONE,
DELTA_DURATION,
@@ -54,35 +58,47 @@ public class ClockMock extends DefaultClock {
return getNow(DateTimeZone.UTC);
}
+ private void logClockAdjustement(DateTime prev, DateTime next) {
+ log.info(String.format(" ************ ADJUSTING CLOCK FROM %s to %s ********************", prev, next));
+ }
+
public synchronized void setDeltaFromReality(Duration delta, long epsilon) {
+ DateTime prev = getUTCNow();
deltaType = DeltaType.DELTA_DURATION;
deltaFromRealityDuration = new ArrayList<Duration>();
deltaFromRealityDuration.add(delta);
deltaFromRealitDurationEpsilon = epsilon;
deltaFromRealityMs = 0;
+ logClockAdjustement(prev, getUTCNow());
}
public synchronized void addDeltaFromReality(Duration delta) {
+ DateTime prev = getUTCNow();
if (deltaType != DeltaType.DELTA_DURATION) {
throw new RuntimeException("ClockMock should be set with type DELTA_DURATION");
}
deltaFromRealityDuration.add(delta);
+ logClockAdjustement(prev, getUTCNow());
}
public synchronized void setDeltaFromReality(long delta) {
+ DateTime prev = getUTCNow();
deltaType = DeltaType.DELTA_ABS;
deltaFromRealityDuration = null;
deltaFromRealitDurationEpsilon = 0;
deltaFromRealityMs = delta;
+ logClockAdjustement(prev, getUTCNow());
}
public synchronized void addDeltaFromReality(long delta) {
+ DateTime prev = getUTCNow();
if (deltaType != DeltaType.DELTA_ABS) {
throw new RuntimeException("ClockMock should be set with type DELTA_ABS");
}
deltaFromRealityDuration = null;
deltaFromRealitDurationEpsilon = 0;
deltaFromRealityMs += delta;
+ logClockAdjustement(prev, getUTCNow());
}
public synchronized void resetDeltaFromReality() {
diff --git a/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java b/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
index 3b8016f..4512a5d 100644
--- a/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
+++ b/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
@@ -20,21 +20,15 @@ import java.io.IOException;
import java.util.UUID;
import org.apache.commons.io.IOUtils;
import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.AfterClass;
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.dbi.MysqlTestingHelper;
-import com.ning.billing.util.customfield.DefaultFieldStore;
-import com.ning.billing.util.customfield.FieldStore;
import com.ning.billing.util.customfield.dao.FieldStoreDao;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-import com.ning.billing.util.eventbus.EventBusService;
-import com.ning.billing.util.glue.EventBusModule;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
@@ -70,32 +64,48 @@ public class TestFieldStore {
@Test
public void testFieldStore() {
- UUID id = UUID.randomUUID();
- String objectType = "Test widget";
+ final UUID id = UUID.randomUUID();
+ final String objectType = "Test widget";
- FieldStore fieldStore = new DefaultFieldStore(id, objectType);
+ final FieldStore fieldStore1 = new DefaultFieldStore(id, objectType);
String fieldName = "TestField1";
String fieldValue = "Kitty Hawk";
- fieldStore.setValue(fieldName, fieldValue);
+ fieldStore1.setValue(fieldName, fieldValue);
FieldStoreDao fieldStoreDao = dbi.onDemand(FieldStoreDao.class);
- fieldStoreDao.save(id.toString(), objectType, fieldStore.getEntityList());
+ fieldStoreDao.inTransaction(new Transaction<Void, FieldStoreDao>() {
+ @Override
+ public Void inTransaction(FieldStoreDao transactional,
+ TransactionStatus status) throws Exception {
+ transactional.batchSaveFromTransaction(id.toString(), objectType, fieldStore1.getEntityList());
+ return null;
+ }
+ });
- fieldStore = DefaultFieldStore.create(id, objectType);
- fieldStore.add(fieldStoreDao.load(id.toString(), objectType));
- assertEquals(fieldStore.getValue(fieldName), fieldValue);
+ final FieldStore fieldStore2 = DefaultFieldStore.create(id, objectType);
+ fieldStore2.add(fieldStoreDao.load(id.toString(), objectType));
- fieldValue = "Cape Canaveral";
- fieldStore.setValue(fieldName, fieldValue);
- assertEquals(fieldStore.getValue(fieldName), fieldValue);
- fieldStoreDao.save(id.toString(), objectType, fieldStore.getEntityList());
-
- fieldStore = DefaultFieldStore.create(id, objectType);
- assertEquals(fieldStore.getValue(fieldName), null);
- fieldStore.add(fieldStoreDao.load(id.toString(), objectType));
+ assertEquals(fieldStore2.getValue(fieldName), fieldValue);
- assertEquals(fieldStore.getValue(fieldName), fieldValue);
+ fieldValue = "Cape Canaveral";
+ fieldStore2.setValue(fieldName, fieldValue);
+ assertEquals(fieldStore2.getValue(fieldName), fieldValue);
+ fieldStoreDao.inTransaction(new Transaction<Void, FieldStoreDao>() {
+ @Override
+ public Void inTransaction(FieldStoreDao transactional,
+ TransactionStatus status) throws Exception {
+ transactional.batchSaveFromTransaction(id.toString(), objectType, fieldStore2.getEntityList());
+ return null;
+ }
+ });
+
+
+ final FieldStore fieldStore3 = DefaultFieldStore.create(id, objectType);
+ assertEquals(fieldStore3.getValue(fieldName), null);
+ fieldStore3.add(fieldStoreDao.load(id.toString(), objectType));
+
+ assertEquals(fieldStore3.getValue(fieldName), fieldValue);
}
}
diff --git a/util/src/test/java/com/ning/billing/util/globalLocker/MockGlobalLocker.java b/util/src/test/java/com/ning/billing/util/globalLocker/MockGlobalLocker.java
new file mode 100644
index 0000000..18336df
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/globalLocker/MockGlobalLocker.java
@@ -0,0 +1,35 @@
+/*
+ * 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.util.globalLocker;
+
+public class MockGlobalLocker implements GlobalLocker {
+
+ @Override
+ public GlobalLock lockWithNumberOfTries(LockerService service,
+ String lockKey, int retry) {
+ return new GlobalLock() {
+ @Override
+ public void release() {
+ }
+ };
+ }
+
+ @Override
+ public Boolean isFree(LockerService service, String lockKey) {
+ return Boolean.TRUE;
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/util/globalLocker/TestMysqlGlobalLocker.java b/util/src/test/java/com/ning/billing/util/globalLocker/TestMysqlGlobalLocker.java
new file mode 100644
index 0000000..b797522
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/globalLocker/TestMysqlGlobalLocker.java
@@ -0,0 +1,115 @@
+/*
+ * 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.util.globalLocker;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.TransactionCallback;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.util.globalLocker.GlobalLocker.LockerService;
+
+@Guice(modules=TestMysqlGlobalLocker.TestMysqlGlobalLockerModule.class)
+public class TestMysqlGlobalLocker {
+
+ @Inject
+ private IDBI dbi;
+
+ @Inject
+ private MysqlTestingHelper helper;
+
+ @BeforeClass(alwaysRun=true)
+ public void setup() throws IOException {
+ helper.startMysql();
+ createSimpleTable(dbi);
+ }
+
+ @AfterClass(alwaysRun=true)
+ public void tearDown() {
+ helper.stopMysql();
+ }
+
+ // Used as a manual test to validate the simple DAO by stepping through that locking is done and release correctly
+ @Test(groups= "slow", enabled = true)
+ public void testSimpleLocking() {
+
+ final String lockName = UUID.randomUUID().toString();
+
+ GlobalLocker locker = new MySqlGlobalLocker(dbi);
+ GlobalLock lock = locker.lockWithNumberOfTries(LockerService.INVOICE, lockName, 3);
+
+ dbi.inTransaction(new TransactionCallback<Void>() {
+ @Override
+ public Void inTransaction(Handle conn, TransactionStatus status)
+ throws Exception {
+ conn.execute("insert into dummy (dummy_id) values ('" + UUID.randomUUID().toString() + "')");
+ return null;
+ }
+ });
+ Assert.assertEquals(locker.isFree(LockerService.INVOICE, lockName), Boolean.FALSE);
+
+ boolean gotException = false;
+ try {
+ locker.lockWithNumberOfTries(LockerService.INVOICE, lockName, 1);
+ } catch (LockFailedException e) {
+ gotException = true;
+ }
+ Assert.assertTrue(gotException);
+
+ lock.release();
+
+ Assert.assertEquals(locker.isFree(LockerService.INVOICE, lockName), Boolean.TRUE);
+ }
+
+ private void createSimpleTable(IDBI dbi) {
+ dbi.inTransaction(new TransactionCallback<Void>() {
+
+ @Override
+ public Void inTransaction(Handle h, TransactionStatus status)
+ throws Exception {
+ h.execute("create table dummy " +
+ "(id int(11) unsigned NOT NULL AUTO_INCREMENT, " +
+ "dummy_id char(36) NOT NULL, " +
+ "PRIMARY KEY(id)" +
+ ") ENGINE=innodb;");
+ return null;
+ }
+ });
+ }
+
+ public final static class TestMysqlGlobalLockerModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ MysqlTestingHelper helper = new MysqlTestingHelper();
+ bind(MysqlTestingHelper.class).toInstance(helper);
+ final IDBI dbi = helper.getDBI();
+ bind(IDBI.class).toInstance(dbi);
+ }
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java b/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
index 89dfd76..cb01e00 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
@@ -16,49 +16,42 @@
package com.ning.billing.util.notificationq.dao;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
-
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
-import org.skife.config.ConfigurationObjectFactory;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.tweak.HandleCallback;
import org.testng.Assert;
-import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterSuite;
-import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
-
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
-import com.ning.billing.dbi.DBIProvider;
-import com.ning.billing.dbi.DbiConfig;
import com.ning.billing.dbi.MysqlTestingHelper;
import com.ning.billing.util.notificationq.DefaultNotification;
import com.ning.billing.util.notificationq.Notification;
import com.ning.billing.util.notificationq.NotificationLifecycle.NotificationLifecycleState;
import com.ning.billing.util.notificationq.dao.NotificationSqlDao.NotificationSqlMapper;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
@Guice(modules = TestNotificationSqlDao.TestNotificationSqlDaoModule.class)
public class TestNotificationSqlDao {
private static AtomicInteger sequenceId = new AtomicInteger();
@Inject
- private DBI dbi;
+ private IDBI dbi;
@Inject
MysqlTestingHelper helper;
@@ -110,12 +103,12 @@ public class TestNotificationSqlDao {
String notificationKey = UUID.randomUUID().toString();
DateTime effDt = new DateTime();
- Notification notif = new DefaultNotification(notificationKey, effDt);
+ Notification notif = new DefaultNotification("testBasic",notificationKey, effDt);
dao.insertNotification(notif);
Thread.sleep(1000);
DateTime now = new DateTime();
- List<Notification> notifications = dao.getReadyNotifications(now.toDate(), 3);
+ List<Notification> notifications = dao.getReadyNotifications(now.toDate(), 3, "testBasic");
assertNotNull(notifications);
assertEquals(notifications.size(), 1);
@@ -127,23 +120,23 @@ public class TestNotificationSqlDao {
assertEquals(notification.getNextAvailableDate(), null);
DateTime nextAvailable = now.plusMinutes(5);
- int res = dao.claimNotification(ownerId, nextAvailable.toDate(), notification.getId().toString(), now.toDate());
+ int res = dao.claimNotification(ownerId, nextAvailable.toDate(), notification.getId(), now.toDate());
assertEquals(res, 1);
- dao.insertClaimedHistory(sequenceId.incrementAndGet(), ownerId, now.toDate(), notification.getId().toString());
+ dao.insertClaimedHistory(sequenceId.incrementAndGet(), ownerId, now.toDate(), notification.getUUID().toString());
- notification = fetchNotification(notification.getId().toString());
+ notification = fetchNotification(notification.getUUID().toString());
assertEquals(notification.getNotificationKey(), notificationKey);
validateDate(notification.getEffectiveDate(), effDt);
assertEquals(notification.getOwner().toString(), ownerId);
assertEquals(notification.getProcessingState(), NotificationLifecycleState.IN_PROCESSING);
validateDate(notification.getNextAvailableDate(), nextAvailable);
- dao.clearNotification(notification.getId().toString(), ownerId);
+ dao.clearNotification(notification.getId(), ownerId);
- notification = fetchNotification(notification.getId().toString());
+ notification = fetchNotification(notification.getUUID().toString());
assertEquals(notification.getNotificationKey(), notificationKey);
validateDate(notification.getEffectiveDate(), effDt);
- assertEquals(notification.getOwner(), null);
+ //assertEquals(notification.getOwner(), null);
assertEquals(notification.getProcessingState(), NotificationLifecycleState.PROCESSED);
validateDate(notification.getNextAvailableDate(), nextAvailable);
@@ -155,10 +148,12 @@ public class TestNotificationSqlDao {
@Override
public Notification withHandle(Handle handle) throws Exception {
Notification res = handle.createQuery(" select" +
- " notification_id" +
+ " id " +
+ ", notification_id" +
", notification_key" +
", created_dt" +
", effective_dt" +
+ ", queue_name" +
", processing_owner" +
", processing_available_dt" +
", processing_state" +
@@ -199,8 +194,8 @@ public class TestNotificationSqlDao {
final MysqlTestingHelper helper = new MysqlTestingHelper();
bind(MysqlTestingHelper.class).toInstance(helper);
- DBI dbi = helper.getDBI();
- bind(DBI.class).toInstance(dbi);
+ IDBI dbi = helper.getDBI();
+ bind(IDBI.class).toInstance(dbi);
/*
bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton();
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
index b141310..e1da366 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
@@ -23,7 +23,6 @@ import java.util.List;
import java.util.TreeSet;
import org.joda.time.DateTime;
-import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import com.ning.billing.util.clock.Clock;
@@ -33,7 +32,7 @@ import com.ning.billing.util.notificationq.NotificationQueueService.Notification
public class MockNotificationQueue extends NotificationQueueBase implements NotificationQueue {
- private TreeSet<Notification> notifications;
+ private final TreeSet<Notification> notifications;
public MockNotificationQueue(final Clock clock, final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config) {
super(clock, svcName, queueName, handler, config);
@@ -53,7 +52,7 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
public void recordFutureNotificationFromTransaction(
Transmogrifier transactionalDao, DateTime futureNotificationTime,
NotificationKey notificationKey) {
- Notification notification = new DefaultNotification(notificationKey.toString(), futureNotificationTime);
+ Notification notification = new DefaultNotification("MockQueue", notificationKey.toString(), futureNotificationTime);
synchronized(notifications) {
notifications.add(notification);
}
@@ -76,7 +75,7 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
}
for (Notification cur : readyNotifications) {
handler.handleReadyNotification(cur.getNotificationKey());
- DefaultNotification processedNotification = new DefaultNotification(cur.getId(), hostname, clock.getUTCNow().plus(config.getDaoClaimTimeMs()), NotificationLifecycleState.PROCESSED, cur.getNotificationKey(), cur.getEffectiveDate());
+ DefaultNotification processedNotification = new DefaultNotification(-1L, cur.getUUID(), hostname, "MockQueue", clock.getUTCNow().plus(config.getDaoClaimTimeMs()), NotificationLifecycleState.PROCESSED, cur.getNotificationKey(), cur.getEffectiveDate());
oldNotifications.add(cur);
processedNotifications.add(processedNotification);
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java b/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
index 2e3bb3c..eac2d78 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
@@ -16,8 +16,9 @@
package com.ning.billing.util.notificationq;
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
import java.io.IOException;
import java.sql.SQLException;
@@ -25,21 +26,19 @@ import java.util.Collection;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
-import org.skife.config.ConfigurationObjectFactory;
-import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.Transaction;
import org.skife.jdbi.v2.TransactionStatus;
import org.skife.jdbi.v2.tweak.HandleCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-
-import org.testng.annotations.AfterSuite;
-import org.testng.annotations.AfterTest;
-import org.testng.annotations.BeforeClass;
+import org.testng.Assert;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Guice;
@@ -49,8 +48,8 @@ import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
-import com.ning.billing.dbi.DBIProvider;
-import com.ning.billing.dbi.DbiConfig;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
import com.ning.billing.dbi.MysqlTestingHelper;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.ClockMock;
@@ -59,370 +58,367 @@ import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
@Guice(modules = TestNotificationQueue.TestNotificationQueueModule.class)
public class TestNotificationQueue {
-
- private final static Logger log = LoggerFactory.getLogger(TestNotificationQueue.class);
-
- @Inject
- private DBI dbi;
-
+ Logger log = LoggerFactory.getLogger(TestNotificationQueue.class);
@Inject
- MysqlTestingHelper helper;
-
- @Inject
- private Clock clock;
-
- private DummySqlTest dao;
-
- // private NotificationQueue queue;
-
- private void startMysql() throws IOException, ClassNotFoundException, SQLException {
- final String ddl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
- final String testDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl_test.sql"));
- helper.startMysql();
- helper.initDb(ddl);
- helper.initDb(testDdl);
- }
-
- @BeforeSuite(alwaysRun = true)
- public void setup() throws Exception {
- startMysql();
- dao = dbi.onDemand(DummySqlTest.class);
- }
-
- @BeforeTest
- public void beforeTest() {
- dbi.withHandle(new HandleCallback<Void>() {
-
- @Override
- public Void withHandle(Handle handle) throws Exception {
- handle.execute("delete from notifications");
- handle.execute("delete from claimed_notifications");
- handle.execute("delete from dummy");
- return null;
- }
- });
- // Reset time to real value
- ((ClockMock) clock).resetDeltaFromReality();
- }
-
-
-
- /**
- * Verify that we can call start/stop on a disabled queue and that both start/stop callbacks are called
- *
- * @throws InterruptedException
- */
- @Test
- public void testSimpleQueueDisabled() throws InterruptedException {
-
- final TestStartStop testStartStop = new TestStartStop(false, false);
- DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "dead",
- new NotificationQueueHandler() {
- @Override
- public void handleReadyNotification(String notificationKey) {
- }
- @Override
- public void completedQueueStop() {
- testStartStop.stopped();
- }
- @Override
- public void completedQueueStart() {
- testStartStop.started();
- }
- },
- getNotificationConfig(true, 100, 1, 10000));
-
- executeTest(testStartStop, queue, new WithTest() {
+ private IDBI dbi;
+
+ @Inject
+ MysqlTestingHelper helper;
+
+ @Inject
+ private Clock clock;
+
+ private DummySqlTest dao;
+
+ private int eventsReceived;
+
+ // private NotificationQueue queue;
+
+ private void startMysql() throws IOException, ClassNotFoundException, SQLException {
+ final String ddl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+ final String testDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl_test.sql"));
+ helper.startMysql();
+ helper.initDb(ddl);
+ helper.initDb(testDdl);
+ }
+
+ @BeforeSuite(alwaysRun = true)
+ public void setup() throws Exception {
+ startMysql();
+ dao = dbi.onDemand(DummySqlTest.class);
+ }
+
+ @BeforeTest
+ public void beforeTest() {
+ dbi.withHandle(new HandleCallback<Void>() {
+
+ @Override
+ public Void withHandle(Handle handle) throws Exception {
+ handle.execute("delete from notifications");
+ handle.execute("delete from claimed_notifications");
+ handle.execute("delete from dummy");
+ return null;
+ }
+ });
+ // Reset time to real value
+ ((ClockMock) clock).resetDeltaFromReality();
+ }
+
+
+
+ /**
+ * Test that we can post a notification in the future from a transaction and get the notification
+ * callback with the correct key when the time is ready
+ * @throws Exception
+ */
+ @Test(groups={"fast"}, enabled = true)
+ public void testSimpleNotification() throws Exception {
+
+ final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
+
+ final DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "foo",
+ new NotificationQueueHandler() {
+ @Override
+ public void handleReadyNotification(String notificationKey) {
+ synchronized (expectedNotifications) {
+ log.info("Handler received key: " + notificationKey);
+
+ expectedNotifications.put(notificationKey.toString(), Boolean.TRUE);
+ expectedNotifications.notify();
+ }
+ }
+ },
+ getNotificationConfig(false, 100, 1, 10000));
+
+
+ queue.startQueue();
+
+ final UUID key = UUID.randomUUID();
+ final DummyObject obj = new DummyObject("foo", key);
+ final DateTime now = new DateTime();
+ final DateTime readyTime = now.plusMillis(2000);
+ final NotificationKey notificationKey = new NotificationKey() {
+ @Override
+ public String toString() {
+ return key.toString();
+ }
+ };
+ expectedNotifications.put(notificationKey.toString(), Boolean.FALSE);
+
+
+ // Insert dummy to be processed in 2 sec'
+ dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+ @Override
+ public Void inTransaction(DummySqlTest transactional,
+ TransactionStatus status) throws Exception {
+
+ transactional.insertDummy(obj);
+ queue.recordFutureNotificationFromTransaction(transactional,
+ readyTime, notificationKey);
+ log.info("Posted key: " + notificationKey);
+
+ return null;
+ }
+ });
+
+ // Move time in the future after the notification effectiveDate
+ ((ClockMock) clock).setDeltaFromReality(3000);
+
+ // Notification should have kicked but give it at least a sec' for thread scheduling
+ await().atMost(1, MINUTES).until(new Callable<Boolean>() {
@Override
- public void test(final DefaultNotificationQueue readyQueue) throws InterruptedException {
- // Do nothing
+ public Boolean call() throws Exception {
+ return expectedNotifications.get(notificationKey.toString());
}
});
- assertTrue(true);
- }
-
- /**
- * Test that we can post a notification in the future from a transaction and get the notification
- * callback with the correct key when the time is ready
- *
- * @throws InterruptedException
- */
- @Test
- public void testSimpleNotification() throws InterruptedException {
-
- final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
-
- final TestStartStop testStartStop = new TestStartStop(false, false);
- DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "foo",
- new NotificationQueueHandler() {
- @Override
- public void handleReadyNotification(String notificationKey) {
- synchronized (expectedNotifications) {
- expectedNotifications.put(notificationKey, Boolean.TRUE);
- expectedNotifications.notify();
- }
- }
- @Override
- public void completedQueueStop() {
- testStartStop.stopped();
- }
- @Override
- public void completedQueueStart() {
- testStartStop.started();
- }
- },
- getNotificationConfig(false, 100, 1, 10000));
-
-
- executeTest(testStartStop, queue, new WithTest() {
- @Override
- public void test(final DefaultNotificationQueue readyQueue) throws InterruptedException {
-
- final UUID key = UUID.randomUUID();
- final DummyObject obj = new DummyObject("foo", key);
- final DateTime now = new DateTime();
- final DateTime readyTime = now.plusMillis(2000);
- final NotificationKey notificationKey = new NotificationKey() {
- @Override
- public String toString() {
- return key.toString();
- }
- };
- expectedNotifications.put(notificationKey.toString(), Boolean.FALSE);
-
-
- // Insert dummy to be processed in 2 sec'
- dao.inTransaction(new Transaction<Void, DummySqlTest>() {
- @Override
- public Void inTransaction(DummySqlTest transactional,
- TransactionStatus status) throws Exception {
-
- transactional.insertDummy(obj);
- readyQueue.recordFutureNotificationFromTransaction(transactional,
- readyTime, notificationKey);
- return null;
- }
- });
-
- // Move time in the future after the notification effectiveDate
- ((ClockMock) clock).setDeltaFromReality(3000);
-
- // Notification should have kicked but give it at least a sec' for thread scheduling
- int nbTry = 1;
- boolean success = false;
- do {
- synchronized(expectedNotifications) {
- if (expectedNotifications.get(notificationKey.toString())) {
- success = true;
- break;
- }
- expectedNotifications.wait(1000);
- }
- } while (nbTry-- > 0);
- assertEquals(success, true);
- }
- });
- }
-
- @Test
- public void testManyNotifications() throws InterruptedException {
- final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
-
- final TestStartStop testStartStop = new TestStartStop(false, false);
- DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "many",
- new NotificationQueueHandler() {
- @Override
- public void handleReadyNotification(String notificationKey) {
- synchronized (expectedNotifications) {
- expectedNotifications.put(notificationKey, Boolean.TRUE);
- expectedNotifications.notify();
- }
- }
- @Override
- public void completedQueueStop() {
- testStartStop.stopped();
- }
- @Override
- public void completedQueueStart() {
- testStartStop.started();
- }
- },
- getNotificationConfig(false, 100, 10, 10000));
-
-
- executeTest(testStartStop, queue, new WithTest() {
- @Override
- public void test(final DefaultNotificationQueue readyQueue) throws InterruptedException {
-
- final DateTime now = clock.getUTCNow();
- final int MAX_NOTIFICATIONS = 100;
- for (int i = 0; i < MAX_NOTIFICATIONS; i++) {
-
- final int nextReadyTimeIncrementMs = 1000;
-
- final UUID key = UUID.randomUUID();
- final DummyObject obj = new DummyObject("foo", key);
- final int currentIteration = i;
-
- final NotificationKey notificationKey = new NotificationKey() {
- @Override
- public String toString() {
- return key.toString();
- }
- };
- expectedNotifications.put(notificationKey.toString(), Boolean.FALSE);
-
- dao.inTransaction(new Transaction<Void, DummySqlTest>() {
- @Override
- public Void inTransaction(DummySqlTest transactional,
- TransactionStatus status) throws Exception {
-
- transactional.insertDummy(obj);
- readyQueue.recordFutureNotificationFromTransaction(transactional,
- now.plus((currentIteration + 1) * nextReadyTimeIncrementMs), notificationKey);
- return null;
- }
- });
-
- // Move time in the future after the notification effectiveDate
- if (i == 0) {
- ((ClockMock) clock).setDeltaFromReality(nextReadyTimeIncrementMs);
- } else {
- ((ClockMock) clock).addDeltaFromReality(nextReadyTimeIncrementMs);
- }
- }
-
- // Wait a little longer since there are a lot of callback that need to happen
- int nbTry = MAX_NOTIFICATIONS + 1;
- boolean success = false;
- do {
- synchronized(expectedNotifications) {
-
- Collection<Boolean> completed = Collections2.filter(expectedNotifications.values(), new Predicate<Boolean>() {
- @Override
- public boolean apply(Boolean input) {
- return input;
- }
- });
-
- if (completed.size() == MAX_NOTIFICATIONS) {
- success = true;
- break;
- }
- //log.debug(String.format("BEFORE WAIT : Got %d notifications at time %s (real time %s)", completed.size(), clock.getUTCNow(), new DateTime()));
- expectedNotifications.wait(1000);
- }
- } while (nbTry-- > 0);
- assertEquals(success, true);
- }
- });
- }
-
- NotificationConfig getNotificationConfig(final boolean off,
- final long sleepTime, final int maxReadyEvents, final long claimTimeMs) {
- return new NotificationConfig() {
+ Assert.assertTrue(expectedNotifications.get(notificationKey.toString()));
+ }
+
+ @Test
+ public void testManyNotifications() throws InterruptedException {
+ final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
+
+ final DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "many",
+ new NotificationQueueHandler() {
+ @Override
+ public void handleReadyNotification(String notificationKey) {
+ synchronized (expectedNotifications) {
+ expectedNotifications.put(notificationKey, Boolean.TRUE);
+ expectedNotifications.notify();
+ }
+ }
+ },
+ getNotificationConfig(false, 100, 10, 10000));
+
+
+ queue.startQueue();
+
+ final DateTime now = clock.getUTCNow();
+ final int MAX_NOTIFICATIONS = 100;
+ for (int i = 0; i < MAX_NOTIFICATIONS; i++) {
+
+ final int nextReadyTimeIncrementMs = 1000;
+
+ final UUID key = UUID.randomUUID();
+ final DummyObject obj = new DummyObject("foo", key);
+ final int currentIteration = i;
+
+ final NotificationKey notificationKey = new NotificationKey() {
+ @Override
+ public String toString() {
+ return key.toString();
+ }
+ };
+ expectedNotifications.put(notificationKey.toString(), Boolean.FALSE);
+
+ dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+ @Override
+ public Void inTransaction(DummySqlTest transactional,
+ TransactionStatus status) throws Exception {
+
+ transactional.insertDummy(obj);
+ queue.recordFutureNotificationFromTransaction(transactional,
+ now.plus((currentIteration + 1) * nextReadyTimeIncrementMs), notificationKey);
+ return null;
+ }
+ });
+
+ // Move time in the future after the notification effectiveDate
+ if (i == 0) {
+ ((ClockMock) clock).setDeltaFromReality(nextReadyTimeIncrementMs);
+ } else {
+ ((ClockMock) clock).addDeltaFromReality(nextReadyTimeIncrementMs);
+ }
+ }
+
+ // Wait a little longer since there are a lot of callback that need to happen
+ int nbTry = MAX_NOTIFICATIONS + 1;
+ boolean success = false;
+ do {
+ synchronized(expectedNotifications) {
+
+ Collection<Boolean> completed = Collections2.filter(expectedNotifications.values(), new Predicate<Boolean>() {
+ @Override
+ public boolean apply(Boolean input) {
+ return input;
+ }
+ });
+
+ if (completed.size() == MAX_NOTIFICATIONS) {
+ success = true;
+ break;
+ }
+ //log.debug(String.format("BEFORE WAIT : Got %d notifications at time %s (real time %s)", completed.size(), clock.getUTCNow(), new DateTime()));
+ expectedNotifications.wait(1000);
+ }
+ } while (nbTry-- > 0);
+ assertEquals(success, true);
+
+ }
+
+ /**
+ * Test that we can post a notification in the future from a transaction and get the notification
+ * callback with the correct key when the time is ready
+ * @throws Exception
+ */
+ @Test(groups={"fast"}, enabled = true)
+ public void testMultipleHandlerNotification() throws Exception {
+
+ final Map<String, Boolean> expectedNotificationsFred = new TreeMap<String, Boolean>();
+ final Map<String, Boolean> expectedNotificationsBarney = new TreeMap<String, Boolean>();
+
+ NotificationQueueService notificationQueueService = new DefaultNotificationQueueService(dbi, clock);
+
+ NotificationConfig config=new NotificationConfig() {
@Override
public boolean isNotificationProcessingOff() {
- return off;
+ return false;
}
@Override
public long getNotificationSleepTimeMs() {
- return sleepTime;
+ return 10;
}
@Override
public int getDaoMaxReadyEvents() {
- return maxReadyEvents;
+ return 1;
}
@Override
public long getDaoClaimTimeMs() {
- return claimTimeMs;
- }
- };
- }
-
- private static class TestStartStop {
- private boolean started;
- private boolean stopped;
-
- public TestStartStop(boolean started, boolean stopped) {
- super();
- this.started = started;
- this.stopped = stopped;
- }
-
- public void started() {
- synchronized(this) {
- started = true;
- notify();
- }
- }
-
- public void stopped() {
- synchronized(this) {
- stopped = true;
- notify();
- }
- }
-
- public boolean waitForStartComplete(int timeoutMs) throws InterruptedException {
- return waitForEventCompletion(timeoutMs, true);
- }
-
- public boolean waitForStopComplete(int timeoutMs) throws InterruptedException {
- return waitForEventCompletion(timeoutMs, false);
- }
-
- private boolean waitForEventCompletion(int timeoutMs, boolean start) throws InterruptedException {
- DateTime init = new DateTime();
- synchronized(this) {
- while (! ((start ? started : stopped))) {
- wait(timeoutMs);
- if (init.plusMillis(timeoutMs).isAfterNow()) {
- break;
- }
- }
+ return 60000;
}
- return (start ? started : stopped);
- }
- }
+ };
- private interface WithTest {
- public void test(DefaultNotificationQueue readyQueue) throws InterruptedException;
- }
- private void executeTest(final TestStartStop testStartStop,
- DefaultNotificationQueue queue, WithTest test) throws InterruptedException{
-
- queue.startQueue();
- boolean started = testStartStop.waitForStartComplete(3000);
- assertEquals(started, true);
-
- test.test(queue);
-
- queue.stopQueue();
- boolean stopped = testStartStop.waitForStopComplete(3000);
- assertEquals(stopped, true);
- }
-
-
- public static class TestNotificationQueueModule extends AbstractModule {
- @Override
- protected void configure() {
-
- bind(Clock.class).to(ClockMock.class);
+ final NotificationQueue queueFred = notificationQueueService.createNotificationQueue("UtilTest", "Fred", new NotificationQueueHandler() {
+ @Override
+ public void handleReadyNotification(String notificationKey) {
+ log.info("Fred received key: " + notificationKey);
+ expectedNotificationsFred.put(notificationKey, Boolean.TRUE);
+ eventsReceived++;
+ }
+ },
+ config);
- final MysqlTestingHelper helper = new MysqlTestingHelper();
- bind(MysqlTestingHelper.class).toInstance(helper);
- DBI dbi = helper.getDBI();
- bind(DBI.class).toInstance(dbi);
- /*
+ final NotificationQueue queueBarney = notificationQueueService.createNotificationQueue("UtilTest", "Barney", new NotificationQueueHandler() {
+ @Override
+ public void handleReadyNotification(String notificationKey) {
+ log.info("Barney received key: " + notificationKey);
+ expectedNotificationsBarney.put(notificationKey, Boolean.TRUE);
+ eventsReceived++;
+ }
+ },
+ config);
+
+ queueFred.startQueue();
+// We don't start Barney so it can never pick up notifications
+
+
+ final UUID key = UUID.randomUUID();
+ final DummyObject obj = new DummyObject("foo", key);
+ final DateTime now = new DateTime();
+ final DateTime readyTime = now.plusMillis(2000);
+ final NotificationKey notificationKeyFred = new NotificationKey() {
+ @Override
+ public String toString() {
+ return "Fred" ;
+ }
+ };
+
+
+ final NotificationKey notificationKeyBarney = new NotificationKey() {
+ @Override
+ public String toString() {
+ return "Barney" ;
+ }
+ };
+
+ expectedNotificationsFred.put(notificationKeyFred.toString(), Boolean.FALSE);
+ expectedNotificationsFred.put(notificationKeyBarney.toString(), Boolean.FALSE);
+
+
+ // Insert dummy to be processed in 2 sec'
+ dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+ @Override
+ public Void inTransaction(DummySqlTest transactional,
+ TransactionStatus status) throws Exception {
+
+ transactional.insertDummy(obj);
+ queueFred.recordFutureNotificationFromTransaction(transactional,
+ readyTime, notificationKeyFred);
+ log.info("posted key: " + notificationKeyFred.toString());
+ queueBarney.recordFutureNotificationFromTransaction(transactional,
+ readyTime, notificationKeyBarney);
+ log.info("posted key: " + notificationKeyBarney.toString());
+
+ return null;
+ }
+ });
+
+ // Move time in the future after the notification effectiveDate
+ ((ClockMock) clock).setDeltaFromReality(3000);
+
+ // Note the timeout is short on this test, but expected behaviour is that it times out.
+ // We are checking that the Fred queue does not pick up the Barney event
+ try {
+ await().atMost(5, TimeUnit.SECONDS).until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return eventsReceived >= 2;
+ }
+ });
+ Assert.fail("There should only have been one event for the queue to pick up - it got more than that");
+ } catch (Exception e) {
+ // expected behavior
+ }
+
+ Assert.assertTrue(expectedNotificationsFred.get(notificationKeyFred.toString()));
+ Assert.assertFalse(expectedNotificationsFred.get(notificationKeyBarney.toString()));
+
+ }
+
+ NotificationConfig getNotificationConfig(final boolean off,
+ final long sleepTime, final int maxReadyEvents, final long claimTimeMs) {
+ return new NotificationConfig() {
+ @Override
+ public boolean isNotificationProcessingOff() {
+ return off;
+ }
+ @Override
+ public long getNotificationSleepTimeMs() {
+ return sleepTime;
+ }
+ @Override
+ public int getDaoMaxReadyEvents() {
+ return maxReadyEvents;
+ }
+ @Override
+ public long getDaoClaimTimeMs() {
+ return claimTimeMs;
+ }
+ };
+ }
+
+
+ public static class TestNotificationQueueModule extends AbstractModule {
+ @Override
+ protected void configure() {
+
+ bind(Clock.class).to(ClockMock.class);
+
+ final MysqlTestingHelper helper = new MysqlTestingHelper();
+ bind(MysqlTestingHelper.class).toInstance(helper);
+ IDBI dbi = helper.getDBI();
+ bind(IDBI.class).toInstance(dbi);
+ IDBI otherDbi = helper.getDBI();
+ bind(IDBI.class).annotatedWith(Names.named("global-lock")).toInstance(otherDbi);
+ /*
bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton();
final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
bind(DbiConfig.class).toInstance(config);
- */
- }
- }
+ */
+ }
+ }
}
diff --git a/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java b/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java
new file mode 100644
index 0000000..f09e851
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java
@@ -0,0 +1,61 @@
+/*
+ * 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.util.tag.dao;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.tag.DefaultTagDefinition;
+import com.ning.billing.util.tag.TagDefinition;
+
+public class MockTagDefinitionDao implements TagDefinitionDao {
+ private final Map<String, TagDefinition> tags = new ConcurrentHashMap<String, TagDefinition>();
+
+ @Override
+ public List<TagDefinition> getTagDefinitions() {
+ return new ArrayList<TagDefinition>(tags.values());
+ }
+
+ @Override
+ public TagDefinition getByName(String definitionName) {
+ return tags.get(definitionName);
+ }
+
+ @Override
+ public TagDefinition create(String definitionName, String description, String createdBy) throws TagDefinitionApiException {
+ TagDefinition tag = new DefaultTagDefinition(UUID.randomUUID(), definitionName, description, createdBy, new DateTime());
+
+ tags.put(definitionName, tag);
+ return tag;
+ }
+
+ @Override
+ public void deleteAllTagsForDefinition(String definitionName) throws TagDefinitionApiException {
+ tags.remove(definitionName);
+ }
+
+ @Override
+ public void deleteTagDefinition(String definitionName) throws TagDefinitionApiException {
+ tags.remove(definitionName);
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
index f764e0c..c913791 100644
--- a/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
+++ b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
@@ -21,6 +21,8 @@ import java.util.List;
import java.util.UUID;
import org.apache.commons.io.IOUtils;
import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.AfterClass;
@@ -53,7 +55,7 @@ public class TestTagStore {
private TagStoreModuleMock module;
private TagStoreSqlDao tagStoreSqlDao;
private TagDefinitionDao tagDefinitionDao;
- private Logger log = LoggerFactory.getLogger(TestTagStore.class);
+ private final Logger log = LoggerFactory.getLogger(TestTagStore.class);
@BeforeClass(alwaysRun = true)
protected void setup() throws IOException {
@@ -87,6 +89,17 @@ public class TestTagStore {
module.stopDb();
}
+ private void saveTags(final TagStoreSqlDao dao, final String objectType, final String accountId, final List<Tag> tagList) {
+ dao.inTransaction(new Transaction<Void, TagStoreSqlDao>() {
+ @Override
+ public Void inTransaction(TagStoreSqlDao transactional,
+ TransactionStatus status) throws Exception {
+ dao.batchSaveFromTransaction(accountId.toString(), objectType, tagList);
+ return null;
+ }
+ });
+ }
+
@Test
public void testTagCreationAndRetrieval() {
UUID accountId = UUID.randomUUID();
@@ -96,7 +109,7 @@ public class TestTagStore {
tagStore.add(tag);
TagStoreSqlDao dao = dbi.onDemand(TagStoreSqlDao.class);
- dao.save(accountId.toString(), ACCOUNT_TYPE, tagStore.getEntityList());
+ saveTags(dao, ACCOUNT_TYPE, accountId.toString(), tagStore.getEntityList());
List<Tag> savedTags = dao.load(accountId.toString(), ACCOUNT_TYPE);
assertEquals(savedTags.size(), 1);
@@ -108,6 +121,7 @@ public class TestTagStore {
assertEquals(savedTag.getId(), tag.getId());
}
+
@Test
public void testControlTagCreation() {
UUID accountId = UUID.randomUUID();
@@ -118,7 +132,7 @@ public class TestTagStore {
assertEquals(tagStore.generateInvoice(), false);
List<Tag> tagList = tagStore.getEntityList();
- tagStoreSqlDao.save(accountId.toString(), ACCOUNT_TYPE, tagList);
+ saveTags(tagStoreSqlDao, ACCOUNT_TYPE, accountId.toString(), tagList);
tagStore.clear();
assertEquals(tagStore.getEntityList().size(), 0);
@@ -148,7 +162,7 @@ public class TestTagStore {
assertEquals(tagStore.generateInvoice(), true);
List<Tag> tagList = tagStore.getEntityList();
- tagStoreSqlDao.save(accountId.toString(), ACCOUNT_TYPE, tagList);
+ saveTags(tagStoreSqlDao, ACCOUNT_TYPE, accountId.toString(), tagList);
tagStore.clear();
assertEquals(tagStore.getEntityList().size(), 0);
@@ -182,7 +196,7 @@ public class TestTagStore {
assertEquals(tagStore.generateInvoice(), false);
List<Tag> tagList = tagStore.getEntityList();
- tagStoreSqlDao.save(accountId.toString(), ACCOUNT_TYPE, tagList);
+ saveTags(tagStoreSqlDao, ACCOUNT_TYPE, accountId.toString(), tagList);
tagStore.clear();
assertEquals(tagStore.getEntityList().size(), 0);
@@ -245,7 +259,8 @@ public class TestTagStore {
Tag tag = new DescriptiveTag(tagDefinition, "test", clock.getUTCNow());
tagStore.add(tag);
- tagStoreSqlDao.save(objectId.toString(), objectType, tagStore.getEntityList());
+ saveTags(tagStoreSqlDao, objectType, objectId.toString(), tagStore.getEntityList());
+
List<Tag> tags = tagStoreSqlDao.load(objectId.toString(), objectType);
assertEquals(tags.size(), 1);
@@ -270,7 +285,8 @@ public class TestTagStore {
Tag tag = new DescriptiveTag(tagDefinition, "test", clock.getUTCNow());
tagStore.add(tag);
- tagStoreSqlDao.save(objectId.toString(), objectType, tagStore.getEntityList());
+ saveTags(tagStoreSqlDao, objectType, objectId.toString(), tagStore.getEntityList());
+
List<Tag> tags = tagStoreSqlDao.load(objectId.toString(), objectType);
assertEquals(tags.size(), 1);