killbill-uncached
Changes
account/pom.xml 13(+12 -1)
analytics/pom.xml 2(+1 -1)
analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java 22(+8 -14)
api/pom.xml 2(+1 -1)
beatrix/pom.xml 2(+1 -1)
catalog/pom.xml 17(+16 -1)
entitlement/pom.xml 23(+22 -1)
entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java 102(+88 -14)
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 30(+25 -5)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java 2(+1 -1)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java 24(+12 -12)
entitlement/src/main/java/com/ning/billing/entitlement/engine/core/ApiEventProcessorBase.java 253(+0 -253)
entitlement/src/main/java/com/ning/billing/entitlement/engine/core/DefaultApiEventProcessor.java 63(+0 -63)
entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java 152(+85 -67)
entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg 79(+7 -72)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccount.java 154(+154 -0)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java 63(+63 -0)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java 141(+141 -0)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java 281(+281 -0)
entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java 113(+71 -42)
entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java 21(+14 -7)
invoice/pom.xml 17(+16 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java 172(+104 -68)
payment/pom.xml 2(+1 -1)
pom.xml 16(+15 -1)
util/pom.xml 17(+15 -2)
util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java 41(+41 -0)
Details
account/pom.xml 13(+12 -1)
diff --git a/account/pom.xml b/account/pom.xml
index 2107a34..44b4543 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.0.21-SNAPSHOT</version>
+ <version>0.1.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-account</artifactId>
@@ -107,6 +107,17 @@
<groups>setup,fast</groups>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
</project>
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 1c7a397..b953707 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
@@ -133,7 +133,9 @@ public class DefaultAccount extends CustomizableEntityBase implements Account {
@Override
public void addTags(List<Tag> tags) {
- this.tags.add(tags);
+ if (tags != null) {
+ this.tags.add(tags);
+ }
}
@Override
@@ -160,4 +162,4 @@ public class DefaultAccount extends CustomizableEntityBase implements Account {
public BigDecimal getBalance() {
return balance;
}
-}
+}
\ No newline at end of file
diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
index a20a519..d6bf995 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
@@ -19,33 +19,47 @@ package com.ning.billing.account.api.user;
import java.util.List;
import java.util.UUID;
import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.AccountData;
import com.ning.billing.account.api.DefaultAccount;
import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.Tag;
public class DefaultAccountUserApi implements com.ning.billing.account.api.AccountUserApi {
private final AccountDao dao;
@Inject
- public DefaultAccountUserApi(AccountDao dao) {
+ public DefaultAccountUserApi(final AccountDao dao) {
this.dao = dao;
}
@Override
- public Account createAccount(AccountData data) {
- Account account = new DefaultAccount(data);
- dao.save(account);
- return account;
+ public Account createAccount(final AccountData data, final List<CustomField> fields, List<Tag> tags) throws AccountApiException {
+ String key = data.getExternalKey();
+ Account existingAccount = dao.getAccountByKey(key);
+
+ if (existingAccount == null) {
+ Account account = new DefaultAccount(data);
+ account.addFields(fields);
+ account.addTags(tags);
+
+ dao.create(account);
+ return account;
+ } else {
+ throw new AccountApiException(ErrorCode.ACCOUNT_ALREADY_EXISTS, key);
+ }
}
@Override
- public Account getAccountByKey(String key) {
+ public Account getAccountByKey(final String key) {
return dao.getAccountByKey(key);
}
@Override
- public Account getAccountById(UUID id) {
+ public Account getAccountById(final UUID id) {
return dao.getById(id.toString());
}
@@ -55,12 +69,12 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
}
@Override
- public UUID getIdFromKey(String externalKey) {
+ public UUID getIdFromKey(final String externalKey) {
return dao.getIdFromKey(externalKey);
}
@Override
- public void saveAccount(Account account) {
- dao.save(account);
+ public void updateAccount(final Account account) {
+ dao.update(account);
}
}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
index 20eb90b..21122e2 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
@@ -17,6 +17,7 @@
package com.ning.billing.account.dao;
import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.user.AccountBuilder;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.util.UuidMapper;
@@ -56,7 +57,11 @@ public interface AccountSqlDao extends EntityDao<Account>, Transactional<Account
@Override
@SqlUpdate
- public void save(@AccountBinder Account account);
+ public void create(@AccountBinder Account account);
+
+ @Override
+ @SqlUpdate
+ public void update(@AccountBinder Account account);
public static class AccountMapper implements ResultSetMapper<Account> {
@Override
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 c108bb4..c4671ea 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
@@ -50,27 +50,10 @@ public class DefaultAccountDao implements AccountDao {
@Override
public Account inTransaction(AccountSqlDao accountSqlDao, TransactionStatus status) throws Exception {
Account account = accountSqlDao.getAccountByKey(key);
-
if (account != null) {
- FieldStoreDao fieldStoreDao = accountSqlDao.become(FieldStoreDao.class);
- List<CustomField> fields = fieldStoreDao.load(account.getId().toString(), account.getObjectName());
-
- account.clearFields();
- if (fields != null) {
- for (CustomField field : fields) {
- account.setFieldValue(field.getName(), field.getValue());
- }
- }
-
- TagStoreDao tagStoreDao = fieldStoreDao.become(TagStoreDao.class);
- List<Tag> tags = tagStoreDao.load(account.getId().toString(), account.getObjectName());
- account.clearTags();
-
- if (tags != null) {
- account.addTags(tags);
- }
+ setCustomFieldsFromWithinTransaction(account, accountSqlDao);
+ setTagsFromWithinTransaction(account, accountSqlDao);
}
-
return account;
}
});
@@ -87,71 +70,97 @@ public class DefaultAccountDao implements AccountDao {
@Override
public Account inTransaction(AccountSqlDao accountSqlDao, TransactionStatus status) throws Exception {
Account account = accountSqlDao.getById(id);
-
if (account != null) {
- FieldStoreDao fieldStoreDao = accountSqlDao.become(FieldStoreDao.class);
- List<CustomField> fields = fieldStoreDao.load(account.getId().toString(), account.getObjectName());
-
- account.clearFields();
- if (fields != null) {
- for (CustomField field : fields) {
- account.setFieldValue(field.getName(), field.getValue());
- }
- }
-
- TagStoreDao tagStoreDao = fieldStoreDao.become(TagStoreDao.class);
- List<Tag> tags = tagStoreDao.load(account.getId().toString(), account.getObjectName());
- account.clearTags();
-
- if (tags != null) {
- account.addTags(tags);
- }
+ setCustomFieldsFromWithinTransaction(account, accountSqlDao);
+ setTagsFromWithinTransaction(account, accountSqlDao);
}
-
return account;
}
});
}
+
@Override
public List<Account> get() {
return accountDao.get();
}
@Override
- public void test() {
- accountDao.test();
+ public void create(final Account account) {
+ final String accountId = account.getId().toString();
+ final String objectType = DefaultAccount.OBJECT_TYPE;
+
+ accountDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
+ @Override
+ public Void inTransaction(AccountSqlDao accountSqlDao, TransactionStatus status) throws Exception {
+ accountSqlDao.create(account);
+
+ FieldStoreDao fieldStoreDao = accountSqlDao.become(FieldStoreDao.class);
+ fieldStoreDao.save(accountId, objectType, account.getFieldList());
+
+ TagStoreDao tagStoreDao = accountSqlDao.become(TagStoreDao.class);
+ tagStoreDao.save(accountId, objectType, account.getTagList());
+
+ AccountCreationNotification creationEvent = new DefaultAccountCreationEvent(account);
+ eventBus.post(creationEvent);
+ return null;
+ }
+ });
}
@Override
- public void save(final Account account) {
+ public void update(final Account account) {
final String accountId = account.getId().toString();
final String objectType = DefaultAccount.OBJECT_TYPE;
accountDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
@Override
- public Void inTransaction(AccountSqlDao accountDao, TransactionStatus status) throws Exception {
- Account currentAccount = accountDao.getById(accountId);
- accountDao.save(account);
+ public Void inTransaction(AccountSqlDao accountSqlDao, TransactionStatus status) throws Exception {
+ Account currentAccount = accountSqlDao.getById(accountId);
- FieldStoreDao fieldStoreDao = accountDao.become(FieldStoreDao.class);
+ accountSqlDao.update(account);
+
+ FieldStoreDao fieldStoreDao = accountSqlDao.become(FieldStoreDao.class);
+ fieldStoreDao.clear(accountId, objectType);
fieldStoreDao.save(accountId, objectType, account.getFieldList());
TagStoreDao tagStoreDao = fieldStoreDao.become(TagStoreDao.class);
+ tagStoreDao.clear(accountId, objectType);
tagStoreDao.save(accountId, objectType, account.getTagList());
- if (currentAccount == null) {
- AccountCreationNotification creationEvent = new DefaultAccountCreationEvent(account);
- eventBus.post(creationEvent);
- } else {
- AccountChangeNotification changeEvent = new DefaultAccountChangeNotification(account.getId(), currentAccount, account);
- if (changeEvent.hasChanges()) {
- eventBus.post(changeEvent);
- }
+ AccountChangeNotification changeEvent = new DefaultAccountChangeNotification(account.getId(), currentAccount, account);
+ if (changeEvent.hasChanges()) {
+ eventBus.post(changeEvent);
}
-
return null;
}
});
}
+
+ @Override
+ public void test() {
+ accountDao.test();
+ }
+
+ private void setCustomFieldsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao) {
+ FieldStoreDao fieldStoreDao = transactionalDao.become(FieldStoreDao.class);
+ List<CustomField> fields = fieldStoreDao.load(account.getId().toString(), account.getObjectName());
+
+ account.clearFields();
+ if (fields != null) {
+ for (CustomField field : fields) {
+ account.setFieldValue(field.getName(), field.getValue());
+ }
+ }
+ }
+
+ private void setTagsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao) {
+ TagStoreDao tagStoreDao = transactionalDao.become(TagStoreDao.class);
+ List<Tag> tags = tagStoreDao.load(account.getId().toString(), account.getObjectName());
+ account.clearTags();
+
+ if (tags != null) {
+ account.addTags(tags);
+ }
+ }
}
diff --git a/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg b/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
index 3e0ebdd..7c0e76e 100644
--- a/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
+++ b/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
@@ -1,13 +1,17 @@
group AccountDaoSql;
-save() ::= <<
+create() ::= <<
INSERT INTO accounts
(id, external_key, email, name, first_name_length, phone, currency, billing_cycle_day, payment_provider_name)
VALUES
- (:id, :externalKey, :email, :name, :firstNameLength, :phone, :currency, :billingCycleDay, :paymentProviderName)
- ON DUPLICATE KEY UPDATE
- external_key = :externalKey, email = :email, name = :name, first_name_length = :firstNameLength,
- phone = :phone, currency = :currency, payment_provider_name = :paymentProviderName;
+ (:id, :externalKey, :email, :name, :firstNameLength, :phone, :currency, :billingCycleDay, :paymentProviderName);
+>>
+
+update() ::= <<
+ UPDATE accounts
+ SET email = :email, name = :name, first_name_length = :firstNameLength, phone = :phone,
+ currency = :currency, billing_cycle_day = :billingCycleDay, payment_provider_name = :paymentProviderName
+ WHERE id = :id;
>>
getAccountByKey() ::= <<
diff --git a/account/src/main/resources/com/ning/billing/account/ddl.sql b/account/src/main/resources/com/ning/billing/account/ddl.sql
index acca205..ccfed72 100644
--- a/account/src/main/resources/com/ning/billing/account/ddl.sql
+++ b/account/src/main/resources/com/ning/billing/account/ddl.sql
@@ -12,42 +12,4 @@ CREATE TABLE accounts (
PRIMARY KEY(id)
) ENGINE=innodb;
CREATE UNIQUE INDEX accounts_external_key ON accounts(external_key);
-CREATE UNIQUE INDEX accounts_email ON accounts(email);
-
-DROP TABLE IF EXISTS custom_fields;
-CREATE TABLE custom_fields (
- id char(36) NOT NULL,
- object_id char(36) NOT NULL,
- object_type varchar(30) NOT NULL,
- field_name varchar(30) NOT NULL,
- field_value varchar(255) NOT NULL,
- PRIMARY KEY(id)
-) ENGINE=innodb;
-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;
-CREATE TABLE tag_descriptions (
- id char(36) NOT NULL,
- name varchar(20) NOT NULL,
- created_by varchar(50) NOT NULL,
- creation_date datetime NOT NULL,
- description varchar(200) NOT NULL,
- generate_invoice boolean DEFAULT false,
- process_payment boolean DEFAULT false,
- PRIMARY KEY(id)
-) ENGINE=innodb;
-CREATE UNIQUE INDEX tag_descriptions_name ON tag_descriptions(name);
-
-DROP TABLE IF EXISTS tags;
-CREATE TABLE tags (
- id char(36) NOT NULL,
- tag_description_id char(36) NOT NULL,
- object_id char(36) NOT NULL,
- object_type varchar(30) NOT NULL,
- date_added datetime NOT NULL,
- added_by varchar(50) NOT NULL,
- PRIMARY KEY(id)
-) ENGINE = innodb;
-CREATE INDEX tags_by_object ON tags(object_id);
-CREATE UNIQUE INDEX tags_unique ON tags(tag_description_id, object_id);
\ No newline at end of file
+CREATE UNIQUE INDEX accounts_email ON accounts(email);
\ No newline at end of file
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 77efe00..b18a41b 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
@@ -38,14 +38,17 @@ public abstract class AccountDaoTestBase {
@BeforeClass(alwaysRun = true)
protected void setup() throws IOException {
- // Healthcheck test to make sure MySQL is setup properly
+ // Health check test to make sure MySQL is setup properly
try {
module = new AccountModuleMock();
final String accountDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
final String invoiceDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+ final String utilDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+
module.startDb();
module.initDb(accountDdl);
module.initDb(invoiceDdl);
+ module.initDb(utilDdl);
final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module);
dbi = injector.getInstance(IDBI.class);
diff --git a/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java b/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java
index 7a82f98..a4b0b98 100644
--- a/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java
+++ b/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java
@@ -21,6 +21,7 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.testng.annotations.Test;
import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.AccountData;
import com.ning.billing.account.api.DefaultAccount;
import com.ning.billing.util.tag.DefaultTagDescription;
@@ -30,7 +31,6 @@ import com.ning.billing.account.api.user.AccountBuilder;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.util.clock.DefaultClock;
import com.ning.billing.util.tag.dao.TagDescriptionDao;
-import com.ning.billing.util.tag.dao.TagDescriptionDao;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
@@ -57,7 +57,7 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
public void testBasic() {
Account a = createTestAccount();
- accountDao.save(a);
+ accountDao.create(a);
String key = a.getExternalKey();
Account r = accountDao.getAccountByKey(key);
@@ -74,14 +74,14 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
}
@Test
- public void testGetById() {
+ public void testGetById() throws AccountApiException {
Account account = createTestAccount();
UUID id = account.getId();
String key = account.getExternalKey();
String name = account.getName();
int firstNameLength = account.getFirstNameLength();
- accountDao.save(account);
+ accountDao.create(account);
account = accountDao.getById(id.toString());
assertNotNull(account);
@@ -99,7 +99,7 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
String fieldValue = "testField1_value";
account.setFieldValue(fieldName, fieldValue);
- accountDao.save(account);
+ accountDao.create(account);
Account thisAccount = accountDao.getAccountByKey(account.getExternalKey());
assertNotNull(thisAccount);
@@ -112,13 +112,13 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
Account account = createTestAccount();
TagDescription description = new DefaultTagDescription("Test Tag", "For testing only", true, true, "Test System", new DateTime());
TagDescriptionDao tagDescriptionDao = dbi.onDemand(TagDescriptionDao.class);
- tagDescriptionDao.save(description);
+ tagDescriptionDao.create(description);
String addedBy = "testTags()";
DateTime dateAdded = new DefaultClock().getUTCNow();
account.addTag(description, addedBy, dateAdded);
assertEquals(account.getTagList().size(), 1);
- accountDao.save(account);
+ accountDao.create(account);
Account thisAccount = accountDao.getById(account.getId().toString());
List<Tag> tagList = thisAccount.getTagList();
@@ -135,16 +135,16 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
@Test
public void testGetIdFromKey() {
Account account = createTestAccount();
- accountDao.save(account);
+ accountDao.create(account);
UUID accountId = accountDao.getIdFromKey(account.getExternalKey());
assertEquals(accountId, account.getId());
}
@Test
- public void testUpdate() {
+ public void testUpdate() throws AccountApiException {
final Account account = createTestAccount();
- accountDao.save(account);
+ accountDao.create(account);
AccountData accountData = new AccountData() {
@Override
@@ -182,7 +182,7 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
};
Account updatedAccount = new DefaultAccount(account.getId(), accountData);
- accountDao.save(updatedAccount);
+ accountDao.update(updatedAccount);
Account savedAccount = accountDao.getAccountByKey(account.getExternalKey());
analytics/pom.xml 2(+1 -1)
diff --git a/analytics/pom.xml b/analytics/pom.xml
index 03e7d72..278348b 100644
--- a/analytics/pom.xml
+++ b/analytics/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.0.21-SNAPSHOT</version>
+ <version>0.1.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-analytics</artifactId>
diff --git a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
index 262e9d9..d623338 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
@@ -18,6 +18,7 @@ package com.ning.billing.analytics;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
+import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.AccountChangeNotification;
import com.ning.billing.account.api.AccountCreationNotification;
import com.ning.billing.entitlement.api.user.SubscriptionTransition;
@@ -35,8 +36,7 @@ public class AnalyticsListener
}
@Subscribe
- public void handleSubscriptionTransitionChange(final SubscriptionTransition event)
- {
+ public void handleSubscriptionTransitionChange(final SubscriptionTransition event) throws AccountApiException {
switch (event.getTransitionType()) {
case MIGRATE_ENTITLEMENT:
// TODO do nothing for now
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java
index 33b8f4d..0384fab 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java
@@ -18,6 +18,7 @@ package com.ning.billing.analytics;
import com.google.inject.Inject;
import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.AccountUserApi;
import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionDao;
import com.ning.billing.catalog.api.Currency;
@@ -46,44 +47,37 @@ public class BusinessSubscriptionTransitionRecorder
this.accountApi = accountApi;
}
- public void subscriptionCreated(final SubscriptionTransition created)
- {
+ public void subscriptionCreated(final SubscriptionTransition created) throws AccountApiException {
final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCreated(created.getNextPlan());
recordTransition(event, created);
}
- public void subscriptionCancelled(final SubscriptionTransition cancelled)
- {
+ public void subscriptionCancelled(final SubscriptionTransition cancelled) throws AccountApiException {
final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(cancelled.getNextPlan());
recordTransition(event, cancelled);
}
- public void subscriptionChanged(final SubscriptionTransition changed)
- {
+ public void subscriptionChanged(final SubscriptionTransition changed) throws AccountApiException {
final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionChanged(changed.getNextPlan());
recordTransition(event, changed);
}
- public void subscriptionPaused(final SubscriptionTransition paused)
- {
+ public void subscriptionPaused(final SubscriptionTransition paused) throws AccountApiException {
final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPaused(paused.getNextPlan());
recordTransition(event, paused);
}
- public void subscriptionResumed(final SubscriptionTransition resumed)
- {
+ public void subscriptionResumed(final SubscriptionTransition resumed) throws AccountApiException {
final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionResumed(resumed.getNextPlan());
recordTransition(event, resumed);
}
- public void subscriptionPhaseChanged(final SubscriptionTransition phaseChanged)
- {
+ public void subscriptionPhaseChanged(final SubscriptionTransition phaseChanged) throws AccountApiException {
final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPhaseChanged(phaseChanged.getNextPlan(), phaseChanged.getNextState());
recordTransition(event, phaseChanged);
}
- public void recordTransition(final BusinessSubscriptionEvent event, final SubscriptionTransition transition)
- {
+ public void recordTransition(final BusinessSubscriptionEvent event, final SubscriptionTransition transition) throws AccountApiException {
Currency currency = null;
String transitionKey = null;
String accountKey = null;
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 3c3de7e..6ec95b9 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
@@ -22,6 +22,7 @@ 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.EventBusModule;
+import com.ning.billing.util.glue.NotificationQueueModule;
import com.ning.billing.util.glue.TagStoreModule;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.IDBI;
@@ -39,6 +40,7 @@ public class AnalyticsTestModule extends AnalyticsModule
install(new EventBusModule());
install(new EntitlementModule());
install(new TagStoreModule());
+ install(new NotificationQueueModule());
// Install the Dao layer
final MysqlTestingHelper helper = new MysqlTestingHelper();
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 03a27d0..c146de7 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
@@ -48,7 +48,9 @@ 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.tag.DefaultTag;
import com.ning.billing.util.tag.DefaultTagDescription;
+import com.ning.billing.util.tag.Tag;
import com.ning.billing.util.tag.dao.TagDescriptionDao;
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
@@ -61,13 +63,17 @@ import org.testng.annotations.Test;
import java.io.IOException;
import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.UUID;
+import static org.testng.Assert.fail;
+
@Guice(modules = AnalyticsTestModule.class)
public class TestAnalyticsService
{
- private static final String KEY = "1234";
- private static final String ACCOUNT_KEY = "pierre-1234";
+ private static final String KEY = "12345";
+ private static final String ACCOUNT_KEY = "pierre-12345";
private static final DefaultTagDescription TAG_ONE = new DefaultTagDescription("batch20", "something", false, false, "pierre", new DateTime(DateTimeZone.UTC));
private static final DefaultTagDescription TAG_TWO = new DefaultTagDescription("awesome", "something", false, false, "pierre", new DateTime(DateTimeZone.UTC));
@@ -106,18 +112,23 @@ public class TestAnalyticsService
// Killbill generic setup
setupBusAndMySQL();
- tagDao.save(TAG_ONE);
- tagDao.save(TAG_TWO);
+ tagDao.create(TAG_ONE);
+ tagDao.create(TAG_TWO);
final MockAccount account = new MockAccount(UUID.randomUUID(), ACCOUNT_KEY, Currency.USD);
- final Account storedAccount = accountApi.createAccount(account);
- storedAccount.addTag(TAG_ONE, "pierre", new DateTime(DateTimeZone.UTC));
- storedAccount.addTag(TAG_TWO, "pierre", new DateTime(DateTimeZone.UTC));
- accountApi.saveAccount(storedAccount);
-
- // Create events for the bus and expected results
- createSubscriptionTransitionEvent(storedAccount);
- createAccountCreationEvent(storedAccount);
+ try {
+ List<Tag> tags = new ArrayList<Tag>();
+ tags.add(new DefaultTag(TAG_ONE, "pierre", new DateTime(DateTimeZone.UTC)));
+ tags.add(new DefaultTag(TAG_TWO, "pierre", new DateTime(DateTimeZone.UTC)));
+
+ final Account storedAccount = accountApi.createAccount(account, null, tags);
+
+ // Create events for the bus and expected results
+ createSubscriptionTransitionEvent(storedAccount);
+ createAccountCreationEvent(storedAccount);
+ } catch (Throwable t) {
+ fail("Initializing accounts failed.", t);
+ }
}
private void setupBusAndMySQL() throws IOException
@@ -127,11 +138,13 @@ public class TestAnalyticsService
final String analyticsDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/analytics/ddl.sql"));
final String accountDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
final String entitlementDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+ final String utilDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
helper.startMysql();
helper.initDb(analyticsDdl);
helper.initDb(accountDdl);
helper.initDb(entitlementDdl);
+ helper.initDb(utilDdl);
}
private void createSubscriptionTransitionEvent(final Account account) throws EntitlementUserApiException
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
index faf4dc8..3b38c8c 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
@@ -109,6 +109,11 @@ public class MockAccount implements Account
}
@Override
+ public void addFields(List<CustomField> fields) {
+ throw new NotImplementedException();
+ }
+
+ @Override
public void clearFields() {
throw new NotImplementedException();
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java b/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
index 4c39456..791d191 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
@@ -21,10 +21,13 @@ import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import java.util.List;
import java.util.UUID;
import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.AccountData;
import com.ning.billing.account.api.AccountUserApi;
import com.ning.billing.account.api.DefaultAccount;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.Tag;
public class MockIAccountUserApi implements AccountUserApi
{
@@ -38,14 +41,14 @@ public class MockIAccountUserApi implements AccountUserApi
}
@Override
- public Account createAccount(final AccountData data)
+ public Account createAccount(final AccountData data, final List<CustomField> fields, final List<Tag> tags)
{
throw new UnsupportedOperationException();
}
@Override
- public void saveAccount(Account account) {
- throw new NotImplementedException();
+ public void updateAccount(final Account account) {
+ throw new UnsupportedOperationException();
}
@Override
@@ -55,8 +58,7 @@ public class MockIAccountUserApi implements AccountUserApi
}
@Override
- public Account getAccountById(final UUID uid)
- {
+ public Account getAccountById(final UUID uid) {
return new DefaultAccount(account);
}
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 7eb9b44..3f11c0e 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
@@ -53,12 +53,7 @@ public class MockPhase implements PlanPhase
return BigDecimal.valueOf(price);
}
- @Override
- public Date getEffectiveDateForExistingSubscriptons()
- {
- return new Date();
- }
- };
+ };
}
@Override
@@ -78,11 +73,6 @@ public class MockPhase implements PlanPhase
return BigDecimal.valueOf(price);
}
- @Override
- public Date getEffectiveDateForExistingSubscriptons()
- {
- return new Date();
- }
};
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java b/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java
index 0e0809c..72ce7ca 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java
@@ -17,10 +17,12 @@
package com.ning.billing.analytics;
import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.Product;
+import java.util.Date;
import java.util.Iterator;
public class MockPlan implements Plan
@@ -51,6 +53,12 @@ public class MockPlan implements Plan
{
return name;
}
+
+ @Override
+ public Date getEffectiveDateForExistingSubscriptons()
+ {
+ return new Date();
+ }
@Override
public Iterator<PlanPhase> getInitialPhaseIterator()
@@ -80,4 +88,14 @@ public class MockPlan implements Plan
public PlanPhase[] getAllPhases() {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public PlanPhase findPhase(String name) throws CatalogApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isRetired() {
+ return false;
+ }
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockProduct.java b/analytics/src/test/java/com/ning/billing/analytics/MockProduct.java
index 27ace04..36fba0d 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockProduct.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockProduct.java
@@ -61,4 +61,9 @@ public class MockProduct implements Product
{
return null;
}
+
+ @Override
+ public boolean isRetired() {
+ return false;
+ }
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
index 5982bcd..6420325 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
@@ -129,6 +129,11 @@ public class MockSubscription implements Subscription
}
@Override
+
+ public List<SubscriptionTransition> getAllTransitions() {
+ throw new UnsupportedOperationException();
+ }
+
public SubscriptionTransition getPendingTransition() {
throw new UnsupportedOperationException();
}
api/pom.xml 2(+1 -1)
diff --git a/api/pom.xml b/api/pom.xml
index a998c67..dce65e7 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.0.21-SNAPSHOT</version>
+ <version>0.1.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-api</artifactId>
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 b34d9a3..a535ada 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
@@ -18,12 +18,14 @@ 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.tag.Tag;
public interface AccountUserApi {
- public Account createAccount(AccountData data);
+ public Account createAccount(AccountData data, List<CustomField> fields, List<Tag> tags) throws AccountApiException;
- public void saveAccount(Account account);
+ public void updateAccount(Account account);
public Account getAccountByKey(String key);
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Catalog.java b/api/src/main/java/com/ning/billing/catalog/api/Catalog.java
index 0fe2e25..3c04a74 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Catalog.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Catalog.java
@@ -16,45 +16,65 @@
package com.ning.billing.catalog.api;
-import java.util.Date;
+import org.joda.time.DateTime;
public interface Catalog {
+ //
+ // Simple getters
+ //
+ public abstract String getCatalogName();
- public abstract Product[] getProducts();
-
- public abstract Plan findPlan(String productName, BillingPeriod term, String priceList) throws CatalogApiException;
+ public abstract Currency[] getSupportedCurrencies(DateTime requestedDate);
- public abstract Plan findPlan(String name) throws CatalogApiException;
+ public abstract Product[] getProducts(DateTime requestedDate);
+
+ public abstract Plan[] getPlans(DateTime requestedDate);
- public abstract Product findProduct(String name) throws CatalogApiException;
+
+ //
+ // Find a plan
+ //
- public abstract PlanPhase findPhase(String name) throws CatalogApiException;
+ public abstract Plan findPlan(String name, DateTime requestedDate) throws CatalogApiException;
+ public abstract Plan findPlan(String productName, BillingPeriod term, String priceListName,
+ DateTime requestedDate) throws CatalogApiException;
- public abstract Currency[] getSupportedCurrencies();
-
- public abstract Plan[] getPlans();
+ public abstract Plan findPlan(String name, DateTime effectiveDate, DateTime subscriptionStartDate) throws CatalogApiException;
+ public abstract Plan findPlan(String productName, BillingPeriod term, String priceListName,
+ DateTime requestedDate, DateTime subscriptionStartDate) throws CatalogApiException;
+
+ //
+ // Find a product
+ //
+ public abstract Product findProduct(String name, DateTime requestedDate) throws CatalogApiException;
+
+ //
+ // Find a phase
+ //
+ public abstract PlanPhase findPhase(String name, DateTime requestedDate, DateTime subscriptionStartDate) throws CatalogApiException;
+
+ //
+ // Rules
+ //
public abstract ActionPolicy planChangePolicy(PlanPhaseSpecifier from,
- PlanSpecifier to) throws CatalogApiException;
+ PlanSpecifier to, DateTime requestedDate) throws CatalogApiException;
public abstract PlanChangeResult planChange(PlanPhaseSpecifier from,
- PlanSpecifier to) throws IllegalPlanChange, CatalogApiException;
+ PlanSpecifier to, DateTime requestedDate) throws CatalogApiException;
- public abstract Date getEffectiveDate();
+ public abstract ActionPolicy planCancelPolicy(PlanPhaseSpecifier planPhase, DateTime requestedDate) throws CatalogApiException;
- public abstract ActionPolicy planCancelPolicy(PlanPhaseSpecifier planPhase) throws CatalogApiException;
+ public abstract PlanAlignmentCreate planCreateAlignment(PlanSpecifier specifier, DateTime requestedDate) throws CatalogApiException;
- public abstract void configureEffectiveDate(Date date);
-
- public abstract String getCatalogName();
-
- public abstract PlanAlignmentCreate planCreateAlignment(PlanSpecifier specifier) throws CatalogApiException;
-
- public abstract BillingAlignment billingAlignment(PlanPhaseSpecifier planPhase) throws CatalogApiException;
+ public abstract BillingAlignment billingAlignment(PlanPhaseSpecifier planPhase, DateTime requestedDate) throws CatalogApiException;
public abstract PlanAlignmentChange planChangeAlignment(PlanPhaseSpecifier from,
- PlanSpecifier to) throws CatalogApiException;
+ PlanSpecifier to, DateTime requestedDate) throws CatalogApiException;
+ public abstract boolean canCreatePlan(PlanSpecifier specifier, DateTime requestedDate) throws CatalogApiException;
+
+
-}
\ No newline at end of file
+}
diff --git a/api/src/main/java/com/ning/billing/catalog/api/CatalogService.java b/api/src/main/java/com/ning/billing/catalog/api/CatalogService.java
index ab70569..c363a87 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/CatalogService.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/CatalogService.java
@@ -20,6 +20,7 @@ import com.ning.billing.lifecycle.KillbillService;
public interface CatalogService extends KillbillService {
- public abstract Catalog getCatalog();
+ public abstract Catalog getFullCatalog();
+ public abstract StaticCatalog getCurrentCatalog();
}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Currency.java b/api/src/main/java/com/ning/billing/catalog/api/Currency.java
index 0e7cd7d..3bef242 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Currency.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Currency.java
@@ -31,7 +31,7 @@ public enum Currency {
// CAD,
// JPY
- public Currency getDefaultCurrency() {
+ public static Currency getDefaultCurrency() {
return Currency.USD;
}
}
diff --git a/api/src/main/java/com/ning/billing/catalog/api/InternationalPrice.java b/api/src/main/java/com/ning/billing/catalog/api/InternationalPrice.java
index 44252fb..2876f1d 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/InternationalPrice.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/InternationalPrice.java
@@ -17,7 +17,6 @@
package com.ning.billing.catalog.api;
import java.math.BigDecimal;
-import java.util.Date;
public interface InternationalPrice {
@@ -26,6 +25,4 @@ public interface InternationalPrice {
public abstract BigDecimal getPrice(Currency currency) throws CatalogApiException;
- public abstract Date getEffectiveDateForExistingSubscriptons();
-
}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Plan.java b/api/src/main/java/com/ning/billing/catalog/api/Plan.java
index a1b6815..c0dbce5 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Plan.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Plan.java
@@ -16,6 +16,7 @@
package com.ning.billing.catalog.api;
+import java.util.Date;
import java.util.Iterator;
public interface Plan {
@@ -26,6 +27,8 @@ public interface Plan {
public abstract String getName();
+ public abstract boolean isRetired();
+
public abstract Iterator<PlanPhase> getInitialPhaseIterator();
public abstract PlanPhase getFinalPhase();
@@ -35,4 +38,8 @@ public interface Plan {
public abstract int getPlansAllowedInBundle();
public abstract PlanPhase[] getAllPhases();
+
+ public abstract Date getEffectiveDateForExistingSubscriptons();
+
+ public abstract PlanPhase findPhase(String name) throws CatalogApiException;
}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/catalog/api/PriceList.java b/api/src/main/java/com/ning/billing/catalog/api/PriceList.java
index 2d7192b..58a98fa 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/PriceList.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/PriceList.java
@@ -21,6 +21,8 @@ public interface PriceList {
public abstract String getName();
+ public abstract boolean isRetired();
+
public abstract Plan findPlan(Product product, BillingPeriod period);
}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Product.java b/api/src/main/java/com/ning/billing/catalog/api/Product.java
index 8483678..e226a0d 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Product.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Product.java
@@ -21,6 +21,8 @@ public interface Product {
public abstract String getName();
+ public abstract boolean isRetired();
+
public abstract Product[] getAvailable();
public abstract Product[] getIncluded();
@@ -29,4 +31,5 @@ public interface Product {
public abstract String getCatalogName();
+
}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java b/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java
new file mode 100644
index 0000000..0db7db5
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java
@@ -0,0 +1,76 @@
+/*
+ * 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.catalog.api;
+
+import java.util.Date;
+
+
+public interface StaticCatalog {
+ //
+ // Simple getters
+ //
+ public abstract String getCatalogName();
+
+ public abstract Date getEffectiveDate();
+
+ public abstract Currency[] getCurrentSupportedCurrencies();
+
+ public abstract Product[] getCurrentProducts();
+
+ public abstract Plan[] getCurrentPlans();
+
+ //
+ // Find a plan
+ //
+ public abstract Plan findCurrentPlan(String productName, BillingPeriod term, String priceList) throws CatalogApiException;
+
+ public abstract Plan findCurrentPlan(String name) throws CatalogApiException;
+
+ //
+ // Find a product
+ //
+ public abstract Product findCurrentProduct(String name) throws CatalogApiException;
+
+ //
+ // Find a phase
+ //
+ public abstract PlanPhase findCurrentPhase(String name) throws CatalogApiException;
+
+ //
+ // Rules
+ //
+ public abstract ActionPolicy planChangePolicy(PlanPhaseSpecifier from,
+ PlanSpecifier to) throws CatalogApiException;
+
+ public abstract PlanChangeResult planChange(PlanPhaseSpecifier from,
+ PlanSpecifier to) throws CatalogApiException;
+
+
+ public abstract ActionPolicy planCancelPolicy(PlanPhaseSpecifier planPhase) throws CatalogApiException;
+
+ public abstract PlanAlignmentCreate planCreateAlignment(PlanSpecifier specifier) throws CatalogApiException;
+
+ public abstract BillingAlignment billingAlignment(PlanPhaseSpecifier planPhase) throws CatalogApiException;
+
+ public abstract PlanAlignmentChange planChangeAlignment(PlanPhaseSpecifier from,
+ PlanSpecifier to) throws CatalogApiException;
+
+ public abstract boolean canCreatePlan(PlanSpecifier specifier) throws CatalogApiException;
+
+
+
+}
\ No newline at end of file
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 086bcd8..d3425bb 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,13 +16,17 @@
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 org.joda.time.DateTime;
-
-import java.math.BigDecimal;
-import java.util.UUID;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.user.Subscription;
public interface BillingEvent extends Comparable<BillingEvent> {
@@ -36,9 +40,9 @@ public interface BillingEvent extends Comparable<BillingEvent> {
/**
*
- * @return the id for the matching subscription
+ * @return the subscription
*/
- public UUID getSubscriptionId();
+ public Subscription getSubscription();
/**
*
@@ -48,30 +52,16 @@ public interface BillingEvent extends Comparable<BillingEvent> {
/**
*
- * @return the name of the plan phase
+ * @return the plan phase
*/
- public String getPlanPhaseName();
+ public PlanPhase getPlanPhase();
/**
*
- * @return the name of the plan
- */
- public String getPlanName();
-
- /**
- *
- * @return the international price for the event
- *
- */
- public InternationalPrice getPrice();
-
- /**
- *
- * @param currency the target currency for invoicing
- * @return the price of the plan phase in the specified currency
+ * @return the plan
*/
- public BigDecimal getPrice(Currency currency);
+ public Plan getPlan();
/**
*
@@ -90,4 +80,32 @@ public interface BillingEvent extends Comparable<BillingEvent> {
* @return the description of the billing event
*/
public String getDescription();
+
+ /**
+ *
+ * @return the fixed price for the phase
+ */
+ public InternationalPrice getFixedPrice();
+
+ /**
+ *
+ * @return the recurring price for the phase
+ */
+ 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 ;
}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/DefaultBillingEvent.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/DefaultBillingEvent.java
new file mode 100644
index 0000000..5f64dc7
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/DefaultBillingEvent.java
@@ -0,0 +1,146 @@
+/*
+ * 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.UUID;
+
+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;
+
+public class DefaultBillingEvent implements BillingEvent {
+ final private int billCycleDay;
+ final private Subscription subscription;
+ final private DateTime effectiveDate;
+ final private PlanPhase planPhase;
+ final private Plan plan;
+ final private InternationalPrice fixedPrice;
+ final private InternationalPrice recurringPrice;
+ final private String description;
+ final private BillingModeType billingModeType;
+ final private BillingPeriod billingPeriod;
+
+ public DefaultBillingEvent(SubscriptionTransition transition, Subscription subscription, int billCycleDay) {
+ this.billCycleDay = billCycleDay;
+ this.subscription = subscription;
+ effectiveDate = transition.getEffectiveTransitionTime();
+ planPhase = transition.getNextPhase();
+ plan = transition.getNextPlan();
+ fixedPrice = transition.getNextPhase().getFixedPrice();
+ recurringPrice = transition.getNextPhase().getRecurringPrice();
+ description = transition.getTransitionType().toString();
+ billingModeType=BillingModeType.IN_ADVANCE;
+ billingPeriod = transition.getNextPhase().getBillingPeriod();
+
+ }
+
+ // Intended for test only
+ public DefaultBillingEvent(Subscription subscription, DateTime effectiveDate, Plan plan, PlanPhase planPhase, InternationalPrice fixedPrice,
+ InternationalPrice recurringPrice, BillingPeriod billingPeriod, int billCycleDay, BillingModeType billingModeType, String description) {
+ this.subscription = subscription;
+ this.effectiveDate = effectiveDate;
+ this.plan = plan;
+ this.planPhase = planPhase;
+ this.fixedPrice = fixedPrice;
+ this.recurringPrice = recurringPrice;
+ this.billingPeriod = billingPeriod;
+ this.billCycleDay = billCycleDay;
+ this.billingModeType = billingModeType;
+ this.description = description;
+ }
+
+ @Override
+ public int compareTo(BillingEvent e1) {
+ if (getSubscription().getId().equals(e1.getSubscription().getId())) {
+ if (getEffectiveDate().equals(e1.getEffectiveDate())) { //ordering with a HashSet fails if we get equality
+ return hashCode() - e1.hashCode();
+ } else {
+ return getEffectiveDate().compareTo(e1.getEffectiveDate());
+ }
+ } else {
+ return getSubscription().getId().compareTo(e1.getSubscription().getId());
+ }
+ }
+
+ @Override
+ public int getBillCycleDay() {
+ return billCycleDay;
+ }
+
+ @Override
+ public Subscription getSubscription() {
+ return subscription;
+ }
+
+ @Override
+ public DateTime getEffectiveDate() {
+ return effectiveDate;
+ }
+
+ @Override
+ public PlanPhase getPlanPhase() {
+ return planPhase;
+ }
+
+ @Override
+ public Plan getPlan() {
+ return plan;
+ }
+
+ @Override
+ public BillingPeriod getBillingPeriod() {
+ return billingPeriod;
+ }
+
+ @Override
+ public BillingModeType getBillingMode() {
+ return billingModeType;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public InternationalPrice getFixedPrice() {
+ return fixedPrice;
+ }
+
+ @Override
+ public InternationalPrice getRecurringPrice() {
+ return recurringPrice;
+ }
+ @Override
+ public BigDecimal getFixedPrice(Currency currency) throws CatalogApiException {
+ return fixedPrice.getPrice(currency);
+ }
+
+ @Override
+ public BigDecimal getRecurringPrice(Currency currency) throws CatalogApiException {
+ return recurringPrice.getPrice(currency);
+ }
+
+}
\ No newline at end of file
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 874a7bc..2b07182 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,29 +16,24 @@
package com.ning.billing.entitlement.api.billing;
-import com.ning.billing.account.api.Account;
-import org.joda.time.DateTime;
-
import java.util.List;
import java.util.SortedSet;
import java.util.UUID;
+import org.joda.time.DateTime;
+
+import com.ning.billing.account.api.Account;
+
public interface EntitlementBillingApi {
- /**
- *
- * @return the list of accounts which have active subscriptions
- */
- public List<Account> getActiveAccounts();
/**
*
- * @param subscriptionId the subscriptionId of interest for a gievn account
- * @return an ordered list of billing event
+ * @param accountId
+ * @return an ordered list of billing event for the given accounts
*
- * Note: The user api allows to get list of subscription bundle / subscriptions for an account
*/
- public SortedSet<BillingEvent> getBillingEventsForSubscription(UUID subscriptionId);
+ public SortedSet<BillingEvent> getBillingEventsForAccount(UUID accountId);
public void setChargedThroughDate(UUID subscriptionId, DateTime ctd);
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java b/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
index 4b56301..d19de5f 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
@@ -67,6 +67,8 @@ public interface Subscription {
public List<SubscriptionTransition> getActiveTransitions();
+ public List<SubscriptionTransition> getAllTransitions();
+
public SubscriptionTransition getPendingTransition();
}
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index f2c6add..0c60808 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -71,7 +71,8 @@ public enum ErrorCode {
/* 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_NULL_PRICE_LIST_NAME(2012,"Price list name was null"),
+ CAT_PRICE_LIST_NOT_FOUND(2013, "Could not find a pricelist with name '%s'"),
/*
* Plans
*/
@@ -81,14 +82,33 @@ public enum ErrorCode {
/*
* Products
*/
- CAT_NO_SUCH_PRODUCT(2030,"Could not find any plans named '%s'"),
-
+ CAT_NO_SUCH_PRODUCT(2030,"Could not find any product named '%s'"),
+ CAT_NULL_PRODUCT_NAME(2031,"Product name was null"),
/*
* Phases
*/
- CAT_NO_SUCH_PHASE(2040,"Could not find any phases named '%s'")
- ;
+ CAT_NO_SUCH_PHASE(2040,"Could not find any phases named '%s'"),
+ CAT_BAD_PHASE_NAME(2041,"Bad phase name '%s'"),
+ /*
+ * Versioned Catalog
+ */
+ CAT_NO_CATALOG_FOR_GIVEN_DATE(2050, "There is no catalog version that applies for the given date '%s'"),
+ CAT_NO_CATALOG_ENTRIES_FOR_GIVEN_DATE(2051, "The are no catalog entries that apply for the given date '%s'"),
+ CAT_CATALOG_NAME_MISMATCH(2052, "The catalog name '%s' does not match the name of the catalog we are trying to add '%s'"),
+ /*
+ * Billing Alignment
+ */
+ CAT_INVALID_BILLING_ALIGNMENT(2060, "Invalid billing alignment '%s'"),
+
+ /*
+ *
+ * Range 3000 : ACCOUNT
+ *
+ */
+ ACCOUNT_ALREADY_EXISTS(3000, "Account already exists for key %s"),
+ ACCOUNT_INVALID_NAME(3001, "An invalid name was specified when creating or updating an account.")
+ ;
private int code;
private String format;
diff --git a/api/src/main/java/com/ning/billing/util/customfield/CustomizableEntity.java b/api/src/main/java/com/ning/billing/util/customfield/CustomizableEntity.java
index 7b8f37a..e21fd83 100644
--- a/api/src/main/java/com/ning/billing/util/customfield/CustomizableEntity.java
+++ b/api/src/main/java/com/ning/billing/util/customfield/CustomizableEntity.java
@@ -26,6 +26,8 @@ public interface CustomizableEntity extends Entity {
public List<CustomField> getFieldList();
+ public void addFields(List<CustomField> fields);
+
public void clearFields();
public String getObjectName();
beatrix/pom.xml 2(+1 -1)
diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index ec79685..32ceef9 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.0.21-SNAPSHOT</version>
+ <version>0.1.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-beatrix</artifactId>
catalog/pom.xml 17(+16 -1)
diff --git a/catalog/pom.xml b/catalog/pom.xml
index 8d5c9c1..1b5b8d6 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.0.21-SNAPSHOT</version>
+ <version>0.1.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-catalog</artifactId>
@@ -43,4 +43,19 @@
<scope>provided</scope>
</dependency>
</dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
</project>
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 813ed2b..7f88f07 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
@@ -20,6 +20,7 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import com.ning.billing.catalog.api.Catalog;
import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.StaticCatalog;
import com.ning.billing.catalog.io.VersionedCatalogLoader;
import com.ning.billing.config.CatalogConfig;
import com.ning.billing.lifecycle.KillbillService;
@@ -30,7 +31,7 @@ public class DefaultCatalogService implements KillbillService, Provider<Catalog>
private static final String CATALOG_SERVICE_NAME = "catalog-service";
- private static Catalog catalog;
+ private static VersionedCatalog catalog;
private final CatalogConfig config;
private boolean isInitialized;
@@ -74,7 +75,7 @@ public class DefaultCatalogService implements KillbillService, Provider<Catalog>
* @see com.ning.billing.catalog.ICatlogService#getCatalog()
*/
@Override
- public Catalog getCatalog() {
+ public Catalog getFullCatalog() {
return catalog;
}
@@ -85,4 +86,9 @@ public class DefaultCatalogService implements KillbillService, Provider<Catalog>
return catalog;
}
+ @Override
+ public StaticCatalog getCurrentCatalog() {
+ return catalog;
+ }
+
}
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 b53dcd3..6990d3c 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
@@ -16,26 +16,25 @@
package com.ning.billing.catalog;
-import com.ning.billing.ErrorCode;
-import com.ning.billing.catalog.api.*;
-import com.ning.billing.util.config.ValidatingConfig;
-import com.ning.billing.util.config.ValidationError;
-import com.ning.billing.util.config.ValidationErrors;
+import java.math.BigDecimal;
+import java.net.URI;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
-import java.math.BigDecimal;
-import java.net.URI;
-import java.util.Date;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.CurrencyValueNull;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.catalog.api.Price;
+import com.ning.billing.util.config.ValidatingConfig;
+import com.ning.billing.util.config.ValidationErrors;
@XmlAccessorType(XmlAccessType.NONE)
public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalog> implements InternationalPrice {
- //TODO MDW Validation - effectiveDateForExistingSubscriptons > catalog effectiveDate
- @XmlElement(required=false)
- private Date effectiveDateForExistingSubscriptons;
-
//TODO: Must have a price point for every configured currency
//TODO: No prices is a zero cost plan
@XmlElement(name="price")
@@ -50,13 +49,7 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
return prices;
}
- /* (non-Javadoc)
- * @see com.ning.billing.catalog.IInternationalPrice#getEffectiveDateForExistingSubscriptons()
- */
- @Override
- public Date getEffectiveDateForExistingSubscriptons() {
- return effectiveDateForExistingSubscriptons;
- }
+
/* (non-Javadoc)
* @see com.ning.billing.catalog.IInternationalPrice#getPrice(com.ning.billing.catalog.api.Currency)
@@ -71,12 +64,6 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
throw new CatalogApiException(ErrorCode.CAT_NO_PRICE_FOR_CURRENCY, currency);
}
-
- protected void setEffectiveDateForExistingSubscriptons(
- Date effectiveDateForExistingSubscriptons) {
- this.effectiveDateForExistingSubscriptons = effectiveDateForExistingSubscriptons;
- }
-
protected DefaultInternationalPrice setPrices(DefaultPrice[] prices) {
this.prices = prices;
return this;
@@ -85,7 +72,7 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
@Override
public ValidationErrors validate(StandaloneCatalog catalog, ValidationErrors errors) {
- Currency[] supportedCurrencies = catalog.getSupportedCurrencies();
+ Currency[] supportedCurrencies = catalog.getCurrentSupportedCurrencies();
for (Price p : prices) {
Currency currency = p.getCurrency();
if(!currencyIsSupported(currency, supportedCurrencies)) {
@@ -99,14 +86,6 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
// No currency => nothing to check, ignore exception
}
}
- if(effectiveDateForExistingSubscriptons != null &&
- catalog.getEffectiveDate().getTime() > effectiveDateForExistingSubscriptons.getTime()) {
- errors.add(new ValidationError(String.format("Price effective date %s is before catalog effective date '%s'",
- effectiveDateForExistingSubscriptons,
- catalog.getEffectiveDate().getTime()),
- catalog.getCatalogURI(), DefaultInternationalPrice.class, ""));
- }
-
return errors;
}
@@ -129,7 +108,7 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
}
private synchronized DefaultPrice[] getZeroPrice(StandaloneCatalog root) {
- Currency[] currencies = root.getSupportedCurrencies();
+ Currency[] currencies = root.getCurrentSupportedCurrencies();
DefaultPrice[] zeroPrice = new DefaultPrice[currencies.length];
for(int i = 0; i < currencies.length; i++) {
zeroPrice[i] = new DefaultPrice();
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java
index 3620162..4972e26 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java
@@ -16,25 +16,42 @@
package com.ning.billing.catalog;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+
+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 com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
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.util.config.ValidatingConfig;
+import com.ning.billing.util.config.ValidationError;
import com.ning.billing.util.config.ValidationErrors;
-import javax.xml.bind.annotation.*;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Iterator;
-
@XmlAccessorType(XmlAccessType.NONE)
public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements Plan {
-
@XmlAttribute(required=true)
@XmlID
private String name;
+
+ @XmlAttribute(required=false)
+ private Boolean retired = false;
+
+ //TODO MDW Validation - effectiveDateForExistingSubscriptons > catalog effectiveDate
+ @XmlElement(required=false)
+ private Date effectiveDateForExistingSubscriptons;
@XmlElement(required=true)
@XmlIDREF
@@ -55,6 +72,12 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
private Integer plansAllowedInBundle = 1;
/* (non-Javadoc)
+ * @see com.ning.billing.catalog.IPlan#getEffectiveDateForExistingSubscriptons()
+ */
+ @Override
+ public Date getEffectiveDateForExistingSubscriptons() {
+ return effectiveDateForExistingSubscriptons;
+ } /* (non-Javadoc)
* @see com.ning.billing.catalog.IPlan#getPhases()
*/
@Override
@@ -77,6 +100,12 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
public String getName() {
return name;
}
+
+ @Override
+ public boolean isRetired() {
+ return retired;
+ }
+
@Override
public DefaultPlanPhase getFinalPhase() {
return finalPhase;
@@ -97,6 +126,17 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
}
@Override
+ public PlanPhase findPhase(String name) throws CatalogApiException {
+ for(PlanPhase pp : getAllPhases()) {
+ if(pp.getName().equals(name)) {
+ return pp;
+ }
+
+ }
+ throw new CatalogApiException(ErrorCode.CAT_NO_SUCH_PHASE, name);
+ }
+
+ @Override
public BillingPeriod getBillingPeriod(){
return finalPhase.getBillingPeriod();
}
@@ -138,9 +178,21 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
@Override
public ValidationErrors validate(StandaloneCatalog catalog, ValidationErrors errors) {
+ if(effectiveDateForExistingSubscriptons != null &&
+ catalog.getEffectiveDate().getTime() > effectiveDateForExistingSubscriptons.getTime()) {
+ errors.add(new ValidationError(String.format("Price effective date %s is before catalog effective date '%s'",
+ effectiveDateForExistingSubscriptons,
+ catalog.getEffectiveDate().getTime()),
+ catalog.getCatalogURI(), DefaultInternationalPrice.class, ""));
+ }
+
return errors;
}
+ protected void setEffectiveDateForExistingSubscriptons(
+ Date effectiveDateForExistingSubscriptons) {
+ this.effectiveDateForExistingSubscriptons = effectiveDateForExistingSubscriptons;
+ }
protected DefaultPlan setName(String name) {
this.name = name;
@@ -166,4 +218,16 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
this.initialPhases = phases;
return this;
}
+
+ public DefaultPlan setRetired(boolean retired) {
+ this.retired = retired;
+ return this;
+ }
+
+ public DefaultPlan setPlansAllowedInBundle(Integer plansAllowedInBundle) {
+ this.plansAllowedInBundle = plansAllowedInBundle;
+ return this;
+ }
+
+
}
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 e086bfe..4afe278 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
@@ -16,6 +16,7 @@
package com.ning.billing.catalog;
+import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.*;
import com.ning.billing.util.config.ValidatingConfig;
import com.ning.billing.util.config.ValidationError;
@@ -51,6 +52,20 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
//Not exposed in XML
private Plan plan;
+
+ public static String phaseName(Plan plan, PlanPhase phase) {
+ return plan.getName() + "-" + phase.getPhaseType().toString().toLowerCase();
+ }
+
+ public static String planName(String phaseName) throws CatalogApiException {
+ for(PhaseType type : PhaseType.values()) {
+ if(phaseName.endsWith(type.toString().toLowerCase())) {
+ return phaseName.substring(0, phaseName.length() - type.toString().length() - 1);
+ }
+ }
+ throw new CatalogApiException(ErrorCode.CAT_BAD_PHASE_NAME, phaseName);
+ }
+
/* (non-Javadoc)
* @see com.ning.billing.catalog.IPlanPhase#getRecurringPrice()
@@ -89,7 +104,7 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
*/
@Override
public String getName() {
- return plan.getName() + "-" + type.toString().toLowerCase();
+ return phaseName(plan,this);
}
/* (non-Javadoc)
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 04916ba..48de5d4 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
@@ -32,6 +32,9 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
@XmlID
private String name;
+ @XmlAttribute(required=false)
+ private Boolean retired = false;
+
@XmlElementWrapper(name="plans", required=true)
@XmlElement(name="plan", required=true)
@XmlIDREF
@@ -47,6 +50,10 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
protected DefaultPlan[] getPlans() {
return plans;
}
+
+ public boolean isRetired() {
+ return retired;
+ }
/* (non-Javadoc)
* @see com.ning.billing.catalog.IPriceList#getName()
@@ -94,6 +101,11 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
}
return count;
}
+
+ public DefaultPriceList setRetired(boolean retired) {
+ this.retired = retired;
+ return this;
+ }
}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceListSet.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceListSet.java
index b876fa9..f0636dd 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceListSet.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceListSet.java
@@ -16,17 +16,19 @@
package com.ning.billing.catalog;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+
+import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.catalog.api.Product;
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.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlElement;
-
@XmlAccessorType(XmlAccessType.NONE)
public class DefaultPriceListSet extends ValidatingConfig<StandaloneCatalog> {
@XmlElement(required=true, name="defaultPriceList")
@@ -47,7 +49,7 @@ public class DefaultPriceListSet extends ValidatingConfig<StandaloneCatalog> {
}
public DefaultPlan getPlanListFrom(String priceListName, Product product,
- BillingPeriod period) {
+ BillingPeriod period) throws CatalogApiException {
DefaultPlan result = null;
DefaultPriceList pl = findPriceListFrom(priceListName);
if(pl != null) {
@@ -60,7 +62,10 @@ public class DefaultPriceListSet extends ValidatingConfig<StandaloneCatalog> {
return defaultPricelist.findPlan(product, period);
}
- public DefaultPriceList findPriceListFrom (String priceListName) {
+ public DefaultPriceList findPriceListFrom (String priceListName) throws CatalogApiException {
+ if(priceListName == null) {
+ throw new CatalogApiException(ErrorCode.CAT_NULL_PRICE_LIST_NAME);
+ }
if (defaultPricelist.getName().equals(priceListName)) {
return defaultPricelist;
}
@@ -69,7 +74,7 @@ public class DefaultPriceListSet extends ValidatingConfig<StandaloneCatalog> {
return pl;
}
}
- return null;
+ throw new CatalogApiException(ErrorCode.CAT_PRICE_LIST_NOT_FOUND, priceListName);
}
@Override
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 79520ce..bf8cfd4 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
@@ -22,6 +22,7 @@ import com.ning.billing.util.config.ValidatingConfig;
import com.ning.billing.util.config.ValidationErrors;
import javax.xml.bind.annotation.*;
+
import java.net.URI;
@XmlAccessorType(XmlAccessType.NONE)
@@ -32,6 +33,9 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
@XmlID
private String name;
+ @XmlAttribute(required=false)
+ private Boolean retired = false;
+
@XmlElement(required=true)
private ProductCategory category;
@@ -53,6 +57,11 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
}
@Override
+ public boolean isRetired() {
+ return retired;
+ }
+
+ @Override
public ProductCategory getCategory() {
return category;
}
@@ -140,4 +149,9 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
return this;
}
+ public DefaultProduct setRetired(boolean retired) {
+ this.retired = retired;
+ return this;
+ }
+
}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/io/VersionedCatalogLoader.java b/catalog/src/main/java/com/ning/billing/catalog/io/VersionedCatalogLoader.java
index 4ad78df..7ef0600 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/io/VersionedCatalogLoader.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/io/VersionedCatalogLoader.java
@@ -60,13 +60,11 @@ public class VersionedCatalogLoader implements ICatalogLoader {
xmlURIs = findXmlReferences(directoryContents, new URL(uriString));
}
- VersionedCatalog result = new VersionedCatalog();
+ VersionedCatalog result = new VersionedCatalog(clock);
for(URI u : xmlURIs) {
StandaloneCatalog catalog = XMLLoader.getObjectFromUri(u, StandaloneCatalog.class);
result.add(catalog);
}
- Date now = clock.getUTCNow().toDate();
- result.configureEffectiveDate(now);
return result;
} catch (Exception e) {
throw new ServiceException("Problem encountered loading catalog", e);
diff --git a/catalog/src/main/java/com/ning/billing/catalog/rules/Case.java b/catalog/src/main/java/com/ning/billing/catalog/rules/Case.java
index 737373e..7ae442f 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/rules/Case.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/rules/Case.java
@@ -16,6 +16,7 @@
package com.ning.billing.catalog.rules;
+
import com.ning.billing.catalog.DefaultPriceList;
import com.ning.billing.catalog.DefaultProduct;
import com.ning.billing.catalog.StandaloneCatalog;
@@ -28,12 +29,15 @@ import com.ning.billing.util.config.ValidationErrors;
public abstract class Case<T> extends ValidatingConfig<StandaloneCatalog> {
- protected DefaultProduct product;
- protected ProductCategory productCategory;
- protected BillingPeriod billingPeriod;
- protected DefaultPriceList priceList;
-
protected abstract T getResult();
+
+ public abstract DefaultProduct getProduct();
+
+ public abstract ProductCategory getProductCategory();
+
+ public abstract BillingPeriod getBillingPeriod();
+
+ public abstract DefaultPriceList getPriceList();
public T getResult(PlanSpecifier planPhase, StandaloneCatalog c) throws CatalogApiException {
if (satisfiesCase(planPhase, c) ) {
@@ -43,10 +47,10 @@ public abstract class Case<T> extends ValidatingConfig<StandaloneCatalog> {
}
protected boolean satisfiesCase(PlanSpecifier planPhase, StandaloneCatalog c) throws CatalogApiException {
- return (product == null || product.equals(c.findProduct(planPhase.getProductName()))) &&
- (productCategory == null || productCategory.equals(planPhase.getProductCategory())) &&
- (billingPeriod == null || billingPeriod.equals(planPhase.getBillingPeriod())) &&
- (priceList == null || priceList.equals(c.getPriceListFromName(planPhase.getPriceListName())));
+ return (getProduct() == null || getProduct().equals(c.findCurrentProduct(planPhase.getProductName()))) &&
+ (getProductCategory() == null || getProductCategory().equals(planPhase.getProductCategory())) &&
+ (getBillingPeriod() == null || getBillingPeriod().equals(planPhase.getBillingPeriod())) &&
+ (getPriceList() == null || getPriceList().equals(c.findCurrentPriceList(planPhase.getPriceListName())));
}
public static <K> K getResult(Case<K>[] cases, PlanSpecifier planSpec, StandaloneCatalog catalog) throws CatalogApiException {
@@ -67,26 +71,11 @@ public abstract class Case<T> extends ValidatingConfig<StandaloneCatalog> {
return errors;
}
- protected Case<T> setProduct(DefaultProduct product) {
- this.product = product;
- return this;
- }
-
- protected Case<T> setProductCategory(ProductCategory productCategory) {
- this.productCategory = productCategory;
- return this;
- }
+ protected abstract Case<T> setProduct(DefaultProduct product);
- protected Case<T> setBillingPeriod(BillingPeriod billingPeriod) {
- this.billingPeriod = billingPeriod;
- return this;
- }
+ protected abstract Case<T> setProductCategory(ProductCategory productCategory);
- protected Case<T> setPriceList(DefaultPriceList priceList) {
- this.priceList = priceList;
- return this;
- }
+ protected abstract Case<T> setBillingPeriod(BillingPeriod billingPeriod);
-
-
+ protected abstract Case<T> setPriceList(DefaultPriceList priceList);
}
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 a340fdc..f101ef1 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
@@ -68,14 +68,14 @@ public abstract class CaseChange<T> extends ValidatingConfig<StandaloneCatalog>
PlanSpecifier to, StandaloneCatalog catalog) throws CatalogApiException {
if(
(phaseType == null || from.getPhaseType() == phaseType) &&
- (fromProduct == null || fromProduct.equals(catalog.findProduct(from.getProductName()))) &&
+ (fromProduct == null || fromProduct.equals(catalog.findCurrentProduct(from.getProductName()))) &&
(fromProductCategory == null || fromProductCategory.equals(from.getProductCategory())) &&
(fromBillingPeriod == null || fromBillingPeriod.equals(from.getBillingPeriod())) &&
- (toProduct == null || toProduct.equals(catalog.findProduct(to.getProductName()))) &&
+ (toProduct == null || toProduct.equals(catalog.findCurrentProduct(to.getProductName()))) &&
(toProductCategory == null || toProductCategory.equals(to.getProductCategory())) &&
(toBillingPeriod == null || toBillingPeriod.equals(to.getBillingPeriod())) &&
- (fromPriceList == null || fromPriceList.equals(catalog.getPriceListFromName(from.getPriceListName()))) &&
- (toPriceList == null || toPriceList.equals(catalog.getPriceListFromName(to.getPriceListName())))
+ (fromPriceList == null || fromPriceList.equals(catalog.findCurrentPriceList(from.getPriceListName()))) &&
+ (toPriceList == null || toPriceList.equals(catalog.findCurrentPriceList(to.getPriceListName())))
) {
return getResult();
}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/rules/CasePhase.java b/catalog/src/main/java/com/ning/billing/catalog/rules/CasePhase.java
index b38c1f8..ebd7561 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/rules/CasePhase.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/rules/CasePhase.java
@@ -31,9 +31,8 @@ public abstract class CasePhase<T> extends CaseStandardNaming<T> {
private PhaseType phaseType;
public T getResult(PlanPhaseSpecifier specifier, StandaloneCatalog c) throws CatalogApiException {
- if ((phaseType == null ||
- specifier.getPhaseType() == null || specifier.getPhaseType() == phaseType) &&
- satisfiesCase(new PlanSpecifier(specifier), c)
+ if ((phaseType == null || specifier.getPhaseType() == phaseType)
+ && satisfiesCase(new PlanSpecifier(specifier), c)
) {
return getResult();
}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/rules/CasePriceList.java b/catalog/src/main/java/com/ning/billing/catalog/rules/CasePriceList.java
index fbb9cab..5063131 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/rules/CasePriceList.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/rules/CasePriceList.java
@@ -25,37 +25,66 @@ import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlIDREF;
public class CasePriceList extends Case<DefaultPriceList> {
+ @XmlElement(required=false, name="fromProduct")
+ @XmlIDREF
+ private DefaultProduct fromProduct;
+
+ @XmlElement(required=false, name="fromProductCategory")
+ private ProductCategory fromProductCategory;
+
+ @XmlElement(required=false, name="fromBillingPeriod")
+ private BillingPeriod fromBillingPeriod;
+
+ @XmlElement(required=false, name="fromPriceList")
+ @XmlIDREF
+ private DefaultPriceList fromPriceList;
- private DefaultPriceList toPriceList;
+ @XmlElement(required=true, name="toPriceList")
+ @XmlIDREF
+ private DefaultPriceList toPriceList;
- @XmlElement(required=false, name="fromProduct")
- @XmlIDREF
- public DefaultProduct getProduct(){
- return product;
- }
+ public DefaultProduct getProduct(){
+ return fromProduct;
+ }
- @XmlElement(required=false, name="fromProductCategory")
- public ProductCategory getProductCategory() {
- return productCategory;
- }
+ public ProductCategory getProductCategory() {
+ return fromProductCategory;
+ }
- @XmlElement(required=false, name="fromBillingPeriod")
- public BillingPeriod getBillingPeriod() {
- return billingPeriod;
- }
-
- @XmlElement(required=false, name="fromPriceList")
- @XmlIDREF
- public DefaultPriceList getPriceList() {
- return priceList;
- }
+ public BillingPeriod getBillingPeriod() {
+ return fromBillingPeriod;
+ }
+
+ public DefaultPriceList getPriceList() {
+ return fromPriceList;
+ }
+
+ protected DefaultPriceList getResult() {
+ return toPriceList;
+ }
+
+ protected CasePriceList setProduct(DefaultProduct product) {
+ this.fromProduct = product;
+ return this;
+ }
+
+ protected CasePriceList setProductCategory(ProductCategory productCategory) {
+ this.fromProductCategory = productCategory;
+ return this;
+ }
+
+ protected CasePriceList setBillingPeriod(BillingPeriod billingPeriod) {
+ this.fromBillingPeriod = billingPeriod;
+ return this;
+ }
+
+ protected CasePriceList setPriceList(DefaultPriceList priceList) {
+ this.fromPriceList = priceList;
+ return this;
+ }
+
+
- @Override
- @XmlElement(required=true, name="toPriceList")
- @XmlIDREF
- protected DefaultPriceList getResult() {
- return toPriceList;
- }
protected CasePriceList setToPriceList(DefaultPriceList toPriceList) {
this.toPriceList = toPriceList;
diff --git a/catalog/src/main/java/com/ning/billing/catalog/rules/CaseStandardNaming.java b/catalog/src/main/java/com/ning/billing/catalog/rules/CaseStandardNaming.java
index cf61fcd..8e161ce 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/rules/CaseStandardNaming.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/rules/CaseStandardNaming.java
@@ -25,27 +25,53 @@ import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlIDREF;
public abstract class CaseStandardNaming<T> extends Case<T> {
+ @XmlElement(required=false, name="product")
+ @XmlIDREF
+ private DefaultProduct product;
+ @XmlElement(required=false, name="productCategory")
+ private ProductCategory productCategory;
+
+ @XmlElement(required=false, name="billingPeriod")
+ private BillingPeriod billingPeriod;
+
+ @XmlElement(required=false, name="priceList")
+ @XmlIDREF
+ private DefaultPriceList priceList;
- @XmlElement(required=false, name="product")
- @XmlIDREF
public DefaultProduct getProduct(){
return product;
}
- @XmlElement(required=false, name="productCategory")
public ProductCategory getProductCategory() {
return productCategory;
}
- @XmlElement(required=false, name="billingPeriod")
public BillingPeriod getBillingPeriod() {
return billingPeriod;
}
- @XmlElement(required=false, name="priceList")
- @XmlIDREF
public DefaultPriceList getPriceList() {
return priceList;
}
+ protected CaseStandardNaming<T> setProduct(DefaultProduct product) {
+ this.product = product;
+ return this;
+ }
+
+ protected CaseStandardNaming<T> setProductCategory(ProductCategory productCategory) {
+ this.productCategory = productCategory;
+ return this;
+ }
+
+ protected CaseStandardNaming<T> setBillingPeriod(BillingPeriod billingPeriod) {
+ this.billingPeriod = billingPeriod;
+ return this;
+ }
+
+ protected CaseStandardNaming<T> setPriceList(DefaultPriceList priceList) {
+ this.priceList = priceList;
+ return this;
+ }
+
}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/rules/PlanRules.java b/catalog/src/main/java/com/ning/billing/catalog/rules/PlanRules.java
index 54aa5c7..c47529a 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/rules/PlanRules.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/rules/PlanRules.java
@@ -16,17 +16,28 @@
package com.ning.billing.catalog.rules;
-import com.ning.billing.catalog.DefaultPriceList;
-import com.ning.billing.catalog.StandaloneCatalog;
-import com.ning.billing.catalog.api.*;
-import com.ning.billing.util.config.ValidatingConfig;
-import com.ning.billing.util.config.ValidationErrors;
-
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.DefaultPriceList;
+import com.ning.billing.catalog.StandaloneCatalog;
+import com.ning.billing.catalog.api.ActionPolicy;
+import com.ning.billing.catalog.api.BillingAlignment;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.IllegalPlanChange;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanAlignmentChange;
+import com.ning.billing.catalog.api.PlanAlignmentCreate;
+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.util.config.ValidatingConfig;
+import com.ning.billing.util.config.ValidationErrors;
+
@XmlAccessorType(XmlAccessType.NONE)
public class PlanRules extends ValidatingConfig<StandaloneCatalog> {
@@ -67,11 +78,13 @@ public class PlanRules extends ValidatingConfig<StandaloneCatalog> {
}
public PlanChangeResult planChange(PlanPhaseSpecifier from, PlanSpecifier to, StandaloneCatalog catalog) throws CatalogApiException {
- DefaultPriceList priceList = catalog.getPriceListFromName(to.getPriceListName());
- if( priceList== null ) {
- priceList = findPriceList(from.toPlanSpecifier(), catalog);
- to = new PlanSpecifier(to.getProductName(), to.getProductCategory(), to.getBillingPeriod(), priceList.getName());
- }
+ DefaultPriceList toPriceList;
+ if( to.getPriceListName() == null ) { // Pricelist may be null because it is unspecified this is the principal use-case
+ toPriceList = findPriceList(from.toPlanSpecifier(), catalog);
+ to = new PlanSpecifier(to.getProductName(), to.getProductCategory(), to.getBillingPeriod(), toPriceList.getName());
+ } else {
+ toPriceList = catalog.findCurrentPriceList(to.getPriceListName());
+ }
ActionPolicy policy = getPlanChangePolicy(from, to, catalog);
if(policy == ActionPolicy.ILLEGAL) {
@@ -80,7 +93,7 @@ public class PlanRules extends ValidatingConfig<StandaloneCatalog> {
PlanAlignmentChange alignment = getPlanChangeAlignment(from, to, catalog);
- return new PlanChangeResult(priceList, policy, alignment);
+ return new PlanChangeResult(toPriceList, policy, alignment);
}
public PlanAlignmentChange getPlanChangeAlignment(PlanPhaseSpecifier from,
@@ -95,6 +108,7 @@ public class PlanRules extends ValidatingConfig<StandaloneCatalog> {
from.getPriceListName().equals(to.getPriceListName())) {
return ActionPolicy.ILLEGAL;
}
+ //Plan toPlan = catalog.findPlan()
return CaseChange.getResult(changeCase, from, to, catalog);
}
@@ -102,7 +116,7 @@ public class PlanRules extends ValidatingConfig<StandaloneCatalog> {
private DefaultPriceList findPriceList(PlanSpecifier specifier, StandaloneCatalog catalog) throws CatalogApiException {
DefaultPriceList result = Case.getResult(priceListCase, specifier, catalog);
if (result == null) {
- result = catalog.getPriceListFromName(specifier.getPriceListName());
+ result = catalog.findCurrentPriceList(specifier.getPriceListName());
}
return result;
}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/StandaloneCatalog.java b/catalog/src/main/java/com/ning/billing/catalog/StandaloneCatalog.java
index 06b24bb..b7f7b79 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/StandaloneCatalog.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/StandaloneCatalog.java
@@ -16,21 +16,39 @@
package com.ning.billing.catalog;
+import java.net.URI;
+import java.util.Collection;
+import java.util.Date;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+
import com.ning.billing.ErrorCode;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.ActionPolicy;
+import com.ning.billing.catalog.api.BillingAlignment;
+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.Plan;
+import com.ning.billing.catalog.api.PlanAlignmentChange;
+import com.ning.billing.catalog.api.PlanAlignmentCreate;
+import com.ning.billing.catalog.api.PlanChangeResult;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.StaticCatalog;
import com.ning.billing.catalog.rules.PlanRules;
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 java.net.URI;
-import java.util.Collection;
-import java.util.Date;
-
@XmlRootElement(name="catalog")
@XmlAccessorType(XmlAccessType.NONE)
-public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> implements Catalog {
+public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> implements StaticCatalog {
@XmlElement(required=true)
private Date effectiveDate;
@@ -80,18 +98,18 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
* @see com.ning.billing.catalog.ICatalog#getProducts()
*/
@Override
- public DefaultProduct[] getProducts() {
+ public DefaultProduct[] getCurrentProducts() {
return products;
}
@Override
- public Currency[] getSupportedCurrencies() {
+ public Currency[] getCurrentSupportedCurrencies() {
return supportedCurrencies;
}
@Override
- public DefaultPlan[] getPlans() {
+ public DefaultPlan[] getCurrentPlans() {
return plans;
}
@@ -103,7 +121,7 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
return planRules;
}
- public DefaultPriceList getPriceListFromName(String priceListName) {
+ public DefaultPriceList findCurrentPriceList(String priceListName) throws CatalogApiException {
return priceLists.findPriceListFrom(priceListName);
}
@@ -111,29 +129,30 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
return this.priceLists;
}
- @Override
- public void configureEffectiveDate(Date date) {
- // Nothing to do here this is a method that is only implemented on VersionedCatalog
- }
-
-
/* (non-Javadoc)
* @see com.ning.billing.catalog.ICatalog#getPlan(java.lang.String, java.lang.String)
*/
@Override
- public DefaultPlan findPlan(String productName, BillingPeriod period, String priceListName) throws CatalogApiException {
- Product product = findProduct(productName);
+ public DefaultPlan findCurrentPlan(String productName, BillingPeriod period, String priceListName) throws CatalogApiException {
+ if (productName == null ) {
+ throw new CatalogApiException(ErrorCode.CAT_NULL_PRODUCT_NAME);
+ }
+ if (priceLists == null) {
+ throw new CatalogApiException(ErrorCode.CAT_PRICE_LIST_NOT_FOUND,priceListName);
+ }
+ Product product = findCurrentProduct(productName);
DefaultPlan result = priceLists.getPlanListFrom(priceListName, product, period);
if ( result == null) {
- throw new CatalogApiException(ErrorCode.CAT_PLAN_NOT_FOUND, productName, period.toString(), priceListName);
+ String periodString = (period == null) ? "NULL" : period.toString();
+ throw new CatalogApiException(ErrorCode.CAT_PLAN_NOT_FOUND, productName, periodString, priceListName);
}
return result;
}
@Override
- public DefaultPlan findPlan(String name) throws CatalogApiException {
- if (name == null) {
- return null;
+ public DefaultPlan findCurrentPlan(String name) throws CatalogApiException {
+ if (name == null || plans == null) {
+ throw new CatalogApiException(ErrorCode.CAT_NO_SUCH_PLAN, name);
}
for(DefaultPlan p : plans) {
if(p.getName().equals(name)) {
@@ -144,7 +163,10 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
}
@Override
- public Product findProduct(String name) throws CatalogApiException {
+ public Product findCurrentProduct(String name) throws CatalogApiException {
+ if (name == null || products == null) {
+ throw new CatalogApiException(ErrorCode.CAT_NO_SUCH_PRODUCT, name);
+ }
for(DefaultProduct p : products) {
if (p.getName().equals(name)) {
return p;
@@ -154,24 +176,18 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
}
@Override
- public DefaultPlanPhase findPhase(String name) throws CatalogApiException {
- for(DefaultPlan p : plans) {
-
- if(p.getFinalPhase().getName().equals(name)) {
- return p.getFinalPhase();
- }
- if (p.getInitialPhases() != null) {
- for(DefaultPlanPhase pp : p.getInitialPhases()) {
- if(pp.getName().equals(name)) {
- return pp;
- }
- }
- }
+ public PlanPhase findCurrentPhase(String name) throws CatalogApiException {
+ if (name == null || plans == null) {
+ throw new CatalogApiException(ErrorCode.CAT_NO_SUCH_PHASE, name);
}
-
- throw new CatalogApiException(ErrorCode.CAT_NO_SUCH_PHASE, name);
+
+ String planName = DefaultPlanPhase.planName(name);
+ Plan plan = findCurrentPlan(planName);
+ return plan.findPhase(name);
}
+
+
//////////////////////////////////////////////////////////////////////////////
//
// RULES
@@ -220,9 +236,9 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
private Collection<? extends ValidationError> validate(StandaloneCatalog catalog,
ValidationErrors errors, ValidatingConfig<StandaloneCatalog>[] configs) {
for(ValidatingConfig<StandaloneCatalog> config: configs) {
-
+ config.validate(catalog, errors);
}
- return null;
+ return errors;
}
@@ -275,6 +291,14 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
return this;
}
-
-
+ @Override
+ public boolean canCreatePlan(PlanSpecifier specifier) throws CatalogApiException {
+ Product product = findCurrentProduct(specifier.getProductName());
+ Plan plan = findCurrentPlan(specifier.getProductName(), specifier.getBillingPeriod(), specifier.getPriceListName());
+ DefaultPriceList priceList = findCurrentPriceList(specifier.getPriceListName());
+
+ return (!product.isRetired()) &&
+ (!plan.isRetired()) &&
+ (!priceList.isRetired());
+ }
}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java b/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java
index bb48b9b..f652347 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java
@@ -28,7 +28,11 @@ import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
-import com.google.inject.Inject;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.ActionPolicy;
import com.ning.billing.catalog.api.BillingAlignment;
import com.ning.billing.catalog.api.BillingPeriod;
@@ -43,40 +47,137 @@ import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.PlanPhaseSpecifier;
import com.ning.billing.catalog.api.PlanSpecifier;
import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.StaticCatalog;
+import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.config.ValidatingConfig;
import com.ning.billing.util.config.ValidationErrors;
@XmlRootElement(name="catalog")
@XmlAccessorType(XmlAccessType.NONE)
-public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implements Catalog {
+public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implements Catalog, StaticCatalog {
+ private static final Logger log = LoggerFactory.getLogger(VersionedCatalog.class);
- private StandaloneCatalog currentCatalog;
+ final private Clock clock;
+ private String catalogName;
@XmlElement(name="catalogVersion", required=true)
private final List<StandaloneCatalog> versions = new ArrayList<StandaloneCatalog>();
-
- @Inject
- public VersionedCatalog() {
+
+ public VersionedCatalog(Clock clock) {
+ this.clock = clock;
StandaloneCatalog baseline = new StandaloneCatalog(new Date(0)); // init with an empty catalog may need to
// populate some empty pieces here to make validation work
- add(baseline);
+ try {
+ add(baseline);
+ } catch (CatalogApiException e) {
+ // This should never happen
+ log.error("This error should never happpen", e);
+ }
}
-
- private StandaloneCatalog versionForDate(Date date) {
- StandaloneCatalog previous = versions.get(0);
- for(StandaloneCatalog c : versions) {
+
+ //
+ // Private methods
+ //
+ private StandaloneCatalog versionForDate(DateTime date) {
+ return versions.get(indexOfVersionForDate(date.toDate()));
+ }
+
+ private List<StandaloneCatalog> versionsBeforeDate(Date date) {
+ List<StandaloneCatalog> result = new ArrayList<StandaloneCatalog>();
+ int index = indexOfVersionForDate(date);
+ for(int i = 0; i <= index; i++) {
+ result.add(versions.get(i));
+ }
+ return result;
+ }
+
+ private int indexOfVersionForDate(Date date) {
+ for(int i = 1; i < versions.size(); i++) {
+ StandaloneCatalog c = versions.get(i);
if(c.getEffectiveDate().getTime() > date.getTime()) {
- return previous;
+ return i - 1;
}
- previous = c;
}
- return versions.get(versions.size() - 1);
+ return versions.size() - 1;
}
+
+ private class PlanRequestWrapper {
+ String name;
+ String productName;
+ BillingPeriod bp;
+ String priceListName;
+
+ public PlanRequestWrapper(String name) {
+ super();
+ this.name = name;
+ }
- public void add(StandaloneCatalog e) {
- if(currentCatalog == null) {
- currentCatalog = e;
+ public PlanRequestWrapper(String productName, BillingPeriod bp,
+ String priceListName) {
+ super();
+ this.productName = productName;
+ this.bp = bp;
+ this.priceListName = priceListName;
+ }
+
+ public Plan findPlan(StandaloneCatalog catalog) throws CatalogApiException {
+ if(name != null) {
+ return catalog.findCurrentPlan(name);
+ } else {
+ return catalog.findCurrentPlan(productName, bp, priceListName);
+ }
+ }
+ }
+
+ private Plan findPlan(PlanRequestWrapper wrapper,
+ DateTime requestedDate,
+ DateTime subscriptionStartDate)
+ throws CatalogApiException {
+ List<StandaloneCatalog> catalogs = versionsBeforeDate(requestedDate.toDate());
+ if(catalogs.size() == 0) {
+ throw new CatalogApiException(ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE, requestedDate.toDate().toString());
+ }
+
+ for(int i = catalogs.size() - 1; i >= 0 ; i--) { // Working backwards to find the latest applicable plan
+ StandaloneCatalog c = catalogs.get(i);
+ Plan plan = null;
+ try {
+ plan = wrapper.findPlan(c);
+ } catch (CatalogApiException e) {
+ if(e.getCode() != ErrorCode.CAT_NO_SUCH_PLAN.getCode()) {
+ throw e;
+ } else {
+ break;
+ }
+ }
+
+ DateTime catalogEffectiveDate = new DateTime(c.getEffectiveDate());
+ if(subscriptionStartDate.isAfter(catalogEffectiveDate)) { // Its a new subscription this plan always applies
+ return plan;
+ } else { //Its an existing subscription
+ if(plan.getEffectiveDateForExistingSubscriptons() != null) { //if it is null any change to this does not apply to existing subscriptions
+ DateTime existingSubscriptionDate = new DateTime(plan.getEffectiveDateForExistingSubscriptons());
+ if(requestedDate.isAfter(existingSubscriptionDate)){ // this plan is now applicable to existing subs
+ return plan;
+ }
+ }
+ }
+ }
+
+ throw new CatalogApiException(ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE, requestedDate.toDate().toString());
+ }
+
+ //
+ // Public methods not exposed in interface
+ //
+ public void add(StandaloneCatalog e) throws CatalogApiException {
+ if(catalogName == null) {
+ catalogName = e.getCatalogName();
+ } else {
+ if(!catalogName.equals(getCatalogName())) {
+ throw new CatalogApiException(ErrorCode.CAT_CATALOG_NAME_MISMATCH,catalogName, e.getCatalogName());
+ }
}
versions.add(e);
Collections.sort(versions,new Comparator<StandaloneCatalog>() {
@@ -86,61 +187,144 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
}
});
}
-
+
public Iterator<StandaloneCatalog> iterator() {
return versions.iterator();
}
-
- @Override
- public void configureEffectiveDate(Date date) {
- currentCatalog = versionForDate(date); //
- }
public int size() {
return versions.size();
}
+ //
+ // Simple getters
+ //
@Override
- public DefaultProduct[] getProducts() {
- return currentCatalog.getProducts();
+ public String getCatalogName() {
+ return catalogName;
}
@Override
- public Currency[] getSupportedCurrencies() {
- return currentCatalog.getSupportedCurrencies();
+ public DefaultProduct[] getProducts(DateTime requestedDate) {
+ return versionForDate(requestedDate).getCurrentProducts();
}
@Override
- public DefaultPlan[] getPlans() {
- return currentCatalog.getPlans();
+ public Currency[] getSupportedCurrencies(DateTime requestedDate) {
+ return versionForDate(requestedDate).getCurrentSupportedCurrencies();
}
@Override
- public Date getEffectiveDate() {
- return currentCatalog.getEffectiveDate();
+ public DefaultPlan[] getPlans(DateTime requestedDate) {
+ return versionForDate(requestedDate).getCurrentPlans();
+ }
+
+ //
+ // Find a plan
+ //
+ @Override
+ public Plan findPlan(String name,
+ DateTime requestedDate)
+ throws CatalogApiException {
+ return versionForDate(requestedDate).findCurrentPlan(name);
+ }
+
+ @Override
+ public Plan findPlan(String productName,
+ BillingPeriod term,
+ String priceListName,
+ DateTime requestedDate)
+ throws CatalogApiException {
+ return versionForDate(requestedDate).findCurrentPlan(productName, term, priceListName);
+ }
+
+ @Override
+ public Plan findPlan(String name,
+ DateTime requestedDate,
+ DateTime subscriptionStartDate)
+ throws CatalogApiException {
+ return findPlan(new PlanRequestWrapper(name), requestedDate, subscriptionStartDate);
+ }
+
+ @Override
+ public Plan findPlan(String productName,
+ BillingPeriod term,
+ String priceListName,
+ DateTime requestedDate,
+ DateTime subscriptionStartDate)
+ throws CatalogApiException {
+ return findPlan(new PlanRequestWrapper(productName, term, priceListName), requestedDate, subscriptionStartDate);
+ }
+
+ //
+ // Find a product
+ //
+ @Override
+ public Product findProduct(String name, DateTime requestedDate) throws CatalogApiException {
+ return versionForDate(requestedDate).findCurrentProduct(name);
+ }
+
+
+ //
+ // Find a phase
+ //
+ @Override
+ public PlanPhase findPhase(String phaseName,
+ DateTime requestedDate,
+ DateTime subscriptionStartDate)
+ throws CatalogApiException {
+ String planName = DefaultPlanPhase.planName(phaseName);
+ Plan plan = findPlan(planName, requestedDate, subscriptionStartDate);
+ return plan.findPhase(phaseName);
+ }
+
+ //
+ // Rules
+ //
+ @Override
+ public ActionPolicy planChangePolicy(PlanPhaseSpecifier from,
+ PlanSpecifier to, DateTime requestedDate) throws CatalogApiException {
+ return versionForDate(requestedDate).planChangePolicy(from, to);
+ }
+
+ @Override
+ public ActionPolicy planCancelPolicy(PlanPhaseSpecifier planPhase, DateTime requestedDate) throws CatalogApiException {
+ return versionForDate(requestedDate).planCancelPolicy(planPhase);
+ }
+
+ @Override
+ public PlanAlignmentChange planChangeAlignment(PlanPhaseSpecifier from,
+ PlanSpecifier to, DateTime requestedDate) throws CatalogApiException {
+ return versionForDate(requestedDate).planChangeAlignment(from, to);
}
@Override
- public Plan findPlan(String productName, BillingPeriod term,
- String planSetName) throws CatalogApiException {
- return currentCatalog.findPlan(productName, term, planSetName);
+ public PlanAlignmentCreate planCreateAlignment(PlanSpecifier specifier, DateTime requestedDate) throws CatalogApiException {
+ return versionForDate(requestedDate).planCreateAlignment(specifier);
}
+
+
@Override
- public DefaultPlan findPlan(String name) throws CatalogApiException {
- return currentCatalog.findPlan(name);
+ public BillingAlignment billingAlignment(PlanPhaseSpecifier planPhase, DateTime requestedDate) throws CatalogApiException {
+ return versionForDate(requestedDate).billingAlignment(planPhase);
}
@Override
- public PlanPhase findPhase(String name) throws CatalogApiException {
- return currentCatalog.findPhase(name);
+ public PlanChangeResult planChange(PlanPhaseSpecifier from, PlanSpecifier to, DateTime requestedDate)
+ throws CatalogApiException {
+ return versionForDate(requestedDate).planChange(from, to);
}
@Override
- public Product findProduct(String name) throws CatalogApiException {
- return currentCatalog.findProduct(name);
+ public boolean canCreatePlan(PlanSpecifier specifier, DateTime requestedDate)
+ throws CatalogApiException {
+ return versionForDate(requestedDate).canCreatePlan(specifier);
}
+ //
+ // VerifiableConfig API
+ //
@Override
public void initialize(StandaloneCatalog catalog, URI sourceURI) {
for(StandaloneCatalog c : versions) {
@@ -153,48 +337,103 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
for(StandaloneCatalog c : versions) {
errors.addAll(c.validate(c, errors));
}
+ //TODO MDW validation - ensure all catalog versions have a single name
+ //TODO MDW validation - ensure effective dates are different (actually do we want this?)
+ //TODO MDW validation - check that all products are there
+ //TODO MDW validation - check that all plans are there
+ //TODO MDW validation - check that all currencies are there
+ //TODO MDW validation - check that all pricelists are there
return errors;
}
-
+
+ //
+ // Static catalog API
+ //
+ @Override
+ public Date getEffectiveDate() {
+ return versionForDate(clock.getUTCNow()).getEffectiveDate();
+ }
+
+ @Override
+ public Currency[] getCurrentSupportedCurrencies() {
+ return versionForDate(clock.getUTCNow()).getCurrentSupportedCurrencies();
+ }
+
+ @Override
+ public Product[] getCurrentProducts() {
+ return versionForDate(clock.getUTCNow()).getCurrentProducts() ;
+ }
+
+ @Override
+ public Plan[] getCurrentPlans() {
+ return versionForDate(clock.getUTCNow()).getCurrentPlans();
+ }
+
+ @Override
+ public Plan findCurrentPlan(String productName, BillingPeriod term,
+ String priceList) throws CatalogApiException {
+ return versionForDate(clock.getUTCNow()).findCurrentPlan(productName, term, priceList);
+ }
+
+ @Override
+ public Plan findCurrentPlan(String name) throws CatalogApiException {
+ return versionForDate(clock.getUTCNow()).findCurrentPlan(name);
+ }
+
+ @Override
+ public Product findCurrentProduct(String name) throws CatalogApiException {
+ return versionForDate(clock.getUTCNow()).findCurrentProduct(name);
+ }
+
+ @Override
+ public PlanPhase findCurrentPhase(String name) throws CatalogApiException {
+ return versionForDate(clock.getUTCNow()).findCurrentPhase(name);
+ }
+
@Override
public ActionPolicy planChangePolicy(PlanPhaseSpecifier from,
PlanSpecifier to) throws CatalogApiException {
- return currentCatalog.planChangePolicy(from, to);
+ return versionForDate(clock.getUTCNow()).planChangePolicy(from, to);
}
@Override
- public ActionPolicy planCancelPolicy(PlanPhaseSpecifier planPhase) throws CatalogApiException {
- return currentCatalog.planCancelPolicy(planPhase);
+ public PlanChangeResult planChange(PlanPhaseSpecifier from, PlanSpecifier to)
+ throws CatalogApiException {
+ return versionForDate(clock.getUTCNow()).planChange(from, to);
}
@Override
- public PlanAlignmentChange planChangeAlignment(PlanPhaseSpecifier from,
- PlanSpecifier to) throws CatalogApiException {
- return currentCatalog.planChangeAlignment(from, to);
+ public ActionPolicy planCancelPolicy(PlanPhaseSpecifier planPhase)
+ throws CatalogApiException {
+ return versionForDate(clock.getUTCNow()).planCancelPolicy(planPhase);
}
@Override
- public PlanAlignmentCreate planCreateAlignment(PlanSpecifier specifier) throws CatalogApiException {
- return currentCatalog.planCreateAlignment(specifier);
+ public PlanAlignmentCreate planCreateAlignment(PlanSpecifier specifier)
+ throws CatalogApiException {
+ return versionForDate(clock.getUTCNow()).planCreateAlignment(specifier);
}
@Override
- public String getCatalogName() {
- return currentCatalog.getCatalogName();
+ public BillingAlignment billingAlignment(PlanPhaseSpecifier planPhase)
+ throws CatalogApiException {
+ return versionForDate(clock.getUTCNow()).billingAlignment(planPhase);
}
@Override
- public BillingAlignment billingAlignment(PlanPhaseSpecifier planPhase) throws CatalogApiException {
- return currentCatalog.billingAlignment(planPhase);
+ public PlanAlignmentChange planChangeAlignment(PlanPhaseSpecifier from,
+ PlanSpecifier to) throws CatalogApiException {
+ return versionForDate(clock.getUTCNow()).planChangeAlignment(from, to);
}
@Override
- public PlanChangeResult planChange(PlanPhaseSpecifier from, PlanSpecifier to)
+ public boolean canCreatePlan(PlanSpecifier specifier)
throws CatalogApiException {
- return currentCatalog.planChange(from, to);
+ return versionForDate(clock.getUTCNow()).canCreatePlan(specifier);
}
+
- //TODO MDW validation - ensure all catalog versions have a single name
- //TODO MDW validation - ensure effective dates are different (actually do we want this?)
+
+
}
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 089c1e4..765d4bd 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
@@ -57,7 +57,7 @@ public class MockCatalog extends StandaloneCatalog {
}
public void populatePlans() {
- DefaultProduct[] products = getProducts();
+ 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());
@@ -67,7 +67,7 @@ public class MockCatalog extends StandaloneCatalog {
}
public void populatePriceLists() {
- DefaultPlan[] plans = getPlans();
+ DefaultPlan[] plans = getCurrentPlans();
DefaultPriceList[] priceList = new DefaultPriceList[plans.length - 1];
for(int i = 1; i < plans.length; i++) {
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 5d1af74..773c58b 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java
@@ -24,19 +24,12 @@ import java.util.Date;
public class MockInternationalPrice extends DefaultInternationalPrice {
MockInternationalPrice() {
- setEffectiveDateForExistingSubscriptons(new Date());
setPrices(new DefaultPrice[] {
new DefaultPrice().setCurrency(Currency.USD).setValue(new BigDecimal(1))
});
}
- MockInternationalPrice(Date effectiveDateForExistingSubscriptions, DefaultPrice[] price) {
- setEffectiveDateForExistingSubscriptons(effectiveDateForExistingSubscriptions);
- setPrices(price);
- }
-
MockInternationalPrice(DefaultPrice... price) {
- setEffectiveDateForExistingSubscriptons(new Date());
setPrices(price);
}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCase.java b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCase.java
index c00f1c4..10229ef 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCase.java
@@ -16,209 +16,275 @@
package com.ning.billing.catalog.rules;
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertNull;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlIDREF;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.ErrorCode;
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 org.testng.annotations.Test;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
-import javax.xml.bind.annotation.XmlElement;
+public class TestCase {
-import static org.testng.AssertJUnit.assertEquals;
-import static org.testng.AssertJUnit.assertNull;
+ protected class CaseResult extends Case<Result> {
+
+ @XmlElement(required=true)
+ private Result policy;
+
+ public CaseResult(DefaultProduct product, ProductCategory productCategory, BillingPeriod billingPeriod, DefaultPriceList priceList,
+ Result policy) {
+ setProduct(product);
+ setProductCategory(productCategory);
+ setBillingPeriod(billingPeriod);
+ setPriceList(priceList);
+ this.policy = policy;
+ }
+
+ @Override
+ protected Result getResult() {
+ return policy;
+ }
+
+ @XmlElement(required=false, name="product")
+ @XmlIDREF
+ protected DefaultProduct product;
+ @XmlElement(required=false, name="productCategory")
+ protected ProductCategory productCategory;
+
+ @XmlElement(required=false, name="billingPeriod")
+ protected BillingPeriod billingPeriod;
+
+ @XmlElement(required=false, name="priceList")
+ @XmlIDREF
+ protected DefaultPriceList priceList;
+
+ public DefaultProduct getProduct(){
+ return product;
+ }
+
+ public ProductCategory getProductCategory() {
+ return productCategory;
+ }
+
+ public BillingPeriod getBillingPeriod() {
+ return billingPeriod;
+ }
+
+ public DefaultPriceList getPriceList() {
+ return priceList;
+ }
+
+ protected CaseResult setProduct(DefaultProduct product) {
+ this.product = product;
+ return this;
+ }
-public class TestCase {
+ protected CaseResult setProductCategory(ProductCategory productCategory) {
+ this.productCategory = productCategory;
+ return this;
+ }
+
+ protected CaseResult setBillingPeriod(BillingPeriod billingPeriod) {
+ this.billingPeriod = billingPeriod;
+ return this;
+ }
+
+ protected CaseResult setPriceList(DefaultPriceList priceList) {
+ this.priceList = priceList;
+ return this;
+ }
+ }
+
+ @Test(enabled=true)
+ public void testBasic() throws CatalogApiException{
+ MockCatalog cat = new MockCatalog();
+
+ DefaultProduct product = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+
+ CaseResult cr = new CaseResult(
+ product,
+ ProductCategory.BASE,
+ BillingPeriod.MONTHLY,
+ priceList,
+ Result.FOO);
+
+ assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), cat);
+ assertionNull(cr, cat.getCurrentProducts()[1].getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), cat);
+ assertionNull(cr, product.getName(), ProductCategory.ADD_ON,BillingPeriod.MONTHLY, priceList.getName(), cat);
+ assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.ANNUAL, priceList.getName(), cat);
+ assertionException(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", cat);
+ }
+
+ @Test(enabled=true)
+ public void testWildCardProduct() throws CatalogApiException{
+ MockCatalog cat = new MockCatalog();
+
+ DefaultProduct product = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+
+ CaseResult cr = new CaseResult(
+ null,
+ ProductCategory.BASE,
+ BillingPeriod.MONTHLY,
+ priceList,
+
+ Result.FOO);
+
+ assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), cat);
+ assertion(Result.FOO, cr, cat.getCurrentProducts()[1].getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), cat);
+ assertionNull(cr, product.getName(), ProductCategory.ADD_ON,BillingPeriod.MONTHLY, priceList.getName(), cat);
+ assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.ANNUAL, priceList.getName(), cat);
+ assertionException(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", cat);
+ }
+
+ @Test(enabled=true)
+ public void testWildCardProductCategory() throws CatalogApiException{
+ MockCatalog cat = new MockCatalog();
+
+ DefaultProduct product = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+
+ CaseResult cr = new CaseResult(
+ product,
+ null,
+ BillingPeriod.MONTHLY,
+ priceList,
+
+ Result.FOO);
+
+ assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), cat);
+ assertionNull(cr, cat.getCurrentProducts()[1].getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), cat);
+ assertion(Result.FOO, cr, product.getName(), ProductCategory.ADD_ON,BillingPeriod.MONTHLY, priceList.getName(), cat);
+ assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.ANNUAL, priceList.getName(), cat);
+ assertionException(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", cat);
+ }
+
+ @Test(enabled=true)
+ public void testWildCardBillingPeriod() throws CatalogApiException{
+ MockCatalog cat = new MockCatalog();
+
+ DefaultProduct product = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+
+ CaseResult cr = new CaseResult(
+ product,
+ ProductCategory.BASE,
+ null,
+ priceList,
+
+ Result.FOO);
+
+ assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), cat);
+ assertionNull(cr, cat.getCurrentProducts()[1].getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), cat);
+ assertionNull(cr, product.getName(), ProductCategory.ADD_ON,BillingPeriod.MONTHLY, priceList.getName(), cat);
+ assertion(Result.FOO,cr, product.getName(), ProductCategory.BASE,BillingPeriod.ANNUAL, priceList.getName(), cat);
+ assertionException(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", cat);
+ }
+
+ @Test(enabled=true)
+ public void testWildCardPriceList() throws CatalogApiException{
+ MockCatalog cat = new MockCatalog();
+
+ DefaultProduct product = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+
+ CaseResult cr = new CaseResult(
+ product,
+ ProductCategory.BASE,
+ BillingPeriod.MONTHLY,
+ null,
+
+ Result.FOO);
+
+ assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), cat);
+ assertionNull(cr, cat.getCurrentProducts()[1].getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), cat);
+ assertionNull(cr, product.getName(), ProductCategory.ADD_ON,BillingPeriod.MONTHLY, priceList.getName(), cat);
+ assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.ANNUAL, priceList.getName(), cat);
+ assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", cat);
+ }
+
+ @Test
+ public void testCaseOrder() throws CatalogApiException {
+ MockCatalog cat = new MockCatalog();
+
+ DefaultProduct product = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
+
+
+ CaseResult cr0 = new CaseResult(
+ product,
+ ProductCategory.BASE,
+ BillingPeriod.MONTHLY,
+ priceList,
+ Result.FOO);
+
+ CaseResult cr1 = new CaseResult(
+ product,
+ ProductCategory.BASE,
+ BillingPeriod.MONTHLY,
+ priceList,
+ Result.BAR);
- protected class CaseResult extends Case<Result> {
-
- @XmlElement(required=true)
- private Result policy;
-
- public CaseResult(DefaultProduct product, ProductCategory productCategory, BillingPeriod billingPeriod, DefaultPriceList priceList,
- Result policy) {
- setProduct(product);
- setProductCategory(productCategory);
- setBillingPeriod(billingPeriod);
- setPriceList(priceList);
- this.policy = policy;
- }
-
- @Override
- protected Result getResult() {
- return policy;
- }
- }
-
- @Test(enabled=true)
- public void testBasic() throws CatalogApiException{
- MockCatalog cat = new MockCatalog();
-
- DefaultProduct product = cat.getProducts()[0];
- DefaultPriceList priceList = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
-
-
- CaseResult cr = new CaseResult(
- product,
- ProductCategory.BASE,
- BillingPeriod.MONTHLY,
- priceList,
- Result.FOO);
-
- assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), cat);
- assertionNull(cr, cat.getProducts()[1].getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), cat);
- assertionNull(cr, product.getName(), ProductCategory.ADD_ON,BillingPeriod.MONTHLY, priceList.getName(), cat);
- assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.ANNUAL, priceList.getName(), cat);
- assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", cat);
- }
-
- @Test(enabled=true)
- public void testWildCardProduct() throws CatalogApiException{
- MockCatalog cat = new MockCatalog();
-
- DefaultProduct product = cat.getProducts()[0];
- DefaultPriceList priceList = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
-
-
- CaseResult cr = new CaseResult(
- null,
- ProductCategory.BASE,
- BillingPeriod.MONTHLY,
- priceList,
-
- Result.FOO);
-
- assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), cat);
- assertion(Result.FOO, cr, cat.getProducts()[1].getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), cat);
- assertionNull(cr, product.getName(), ProductCategory.ADD_ON,BillingPeriod.MONTHLY, priceList.getName(), cat);
- assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.ANNUAL, priceList.getName(), cat);
- assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", cat);
- }
-
- @Test(enabled=true)
- public void testWildCardProductCategory() throws CatalogApiException{
- MockCatalog cat = new MockCatalog();
-
- DefaultProduct product = cat.getProducts()[0];
- DefaultPriceList priceList = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
-
-
- CaseResult cr = new CaseResult(
- product,
- null,
- BillingPeriod.MONTHLY,
- priceList,
-
- Result.FOO);
-
- assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), cat);
- assertionNull(cr, cat.getProducts()[1].getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), cat);
- assertion(Result.FOO, cr, product.getName(), ProductCategory.ADD_ON,BillingPeriod.MONTHLY, priceList.getName(), cat);
- assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.ANNUAL, priceList.getName(), cat);
- assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", cat);
- }
-
- @Test(enabled=true)
- public void testWildCardBillingPeriod() throws CatalogApiException{
- MockCatalog cat = new MockCatalog();
-
- DefaultProduct product = cat.getProducts()[0];
- DefaultPriceList priceList = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
-
-
- CaseResult cr = new CaseResult(
- product,
- ProductCategory.BASE,
- null,
- priceList,
-
- Result.FOO);
-
- assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), cat);
- assertionNull(cr, cat.getProducts()[1].getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), cat);
- assertionNull(cr, product.getName(), ProductCategory.ADD_ON,BillingPeriod.MONTHLY, priceList.getName(), cat);
- assertion(Result.FOO,cr, product.getName(), ProductCategory.BASE,BillingPeriod.ANNUAL, priceList.getName(), cat);
- assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", cat);
- }
-
- @Test(enabled=true)
- public void testWildCardPriceList() throws CatalogApiException{
- MockCatalog cat = new MockCatalog();
-
- DefaultProduct product = cat.getProducts()[0];
- DefaultPriceList priceList = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
-
-
- CaseResult cr = new CaseResult(
- product,
- ProductCategory.BASE,
- BillingPeriod.MONTHLY,
- null,
-
- Result.FOO);
-
- assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), cat);
- assertionNull(cr, cat.getProducts()[1].getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), cat);
- assertionNull(cr, product.getName(), ProductCategory.ADD_ON,BillingPeriod.MONTHLY, priceList.getName(), cat);
- assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.ANNUAL, priceList.getName(), cat);
- assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", cat);
- }
-
- @Test
- public void testCaseOrder() throws CatalogApiException {
- MockCatalog cat = new MockCatalog();
-
- DefaultProduct product = cat.getProducts()[0];
- DefaultPriceList priceList = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
-
-
- CaseResult cr0 = new CaseResult(
- product,
- ProductCategory.BASE,
- BillingPeriod.MONTHLY,
- priceList,
- Result.FOO);
-
- CaseResult cr1 = new CaseResult(
- product,
- ProductCategory.BASE,
- BillingPeriod.MONTHLY,
- priceList,
- Result.BAR);
-
- CaseResult cr2 = new CaseResult(
- product,
- ProductCategory.BASE,
- BillingPeriod.ANNUAL,
- priceList,
- Result.DIPSY);
-
- CaseResult cr3 = new CaseResult(
- product,
- ProductCategory.BASE,
- BillingPeriod.ANNUAL,
- priceList,
- Result.LALA);
-
- Result r1 = Case.getResult(new CaseResult[]{cr0, cr1, cr2,cr3},
- new PlanSpecifier(product.getName(), product.getCategory(), BillingPeriod.MONTHLY, priceList.getName()), cat);
- assertEquals(Result.FOO, r1);
-
- Result r2 = Case.getResult(new CaseResult[]{cr0, cr1, cr2},
- new PlanSpecifier(product.getName(), product.getCategory(), BillingPeriod.ANNUAL, priceList.getName()), cat);
- assertEquals(Result.DIPSY, r2);
- }
-
-
-
-
- protected void assertionNull(CaseResult cr, String productName, ProductCategory productCategory, BillingPeriod bp, String priceListName, StandaloneCatalog cat) throws CatalogApiException{
- assertNull(cr.getResult(new PlanSpecifier(productName, productCategory, bp, priceListName), cat));
- }
-
- protected void assertion(Result result, CaseResult cr, String productName, ProductCategory productCategory, BillingPeriod bp, String priceListName,StandaloneCatalog cat) throws CatalogApiException{
- assertEquals(result, cr.getResult(new PlanSpecifier(productName, productCategory, bp, priceListName), cat));
- }
+ CaseResult cr2 = new CaseResult(
+ product,
+ ProductCategory.BASE,
+ BillingPeriod.ANNUAL,
+ priceList,
+ Result.DIPSY);
+
+ CaseResult cr3 = new CaseResult(
+ product,
+ ProductCategory.BASE,
+ BillingPeriod.ANNUAL,
+ priceList,
+ Result.LALA);
+
+ Result r1 = Case.getResult(new CaseResult[]{cr0, cr1, cr2,cr3},
+ new PlanSpecifier(product.getName(), product.getCategory(), BillingPeriod.MONTHLY, priceList.getName()), cat);
+ assertEquals(Result.FOO, r1);
+
+ Result r2 = Case.getResult(new CaseResult[]{cr0, cr1, cr2},
+ new PlanSpecifier(product.getName(), product.getCategory(), BillingPeriod.ANNUAL, priceList.getName()), cat);
+ assertEquals(Result.DIPSY, r2);
+ }
+
+
+
+
+ protected void assertionNull(CaseResult cr, String productName, ProductCategory productCategory, BillingPeriod bp, String priceListName, StandaloneCatalog cat) throws CatalogApiException{
+ assertNull(cr.getResult(new PlanSpecifier(productName, productCategory, bp, priceListName), cat));
+ }
+
+ protected void assertionException(CaseResult cr, String productName, ProductCategory productCategory, BillingPeriod bp, String priceListName, StandaloneCatalog cat) {
+ try{
+ cr.getResult(new PlanSpecifier(productName, productCategory, bp, priceListName), cat);
+ Assert.fail("Expecting an exception");
+ } catch (CatalogApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.CAT_PRICE_LIST_NOT_FOUND.getCode());
+ }
+ }
+
+ protected void assertion(Result result, CaseResult cr, String productName, ProductCategory productCategory, BillingPeriod bp, String priceListName,StandaloneCatalog cat) throws CatalogApiException{
+ assertEquals(result, cr.getResult(new PlanSpecifier(productName, productCategory, bp, priceListName), cat));
+ }
}
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 af13162..f684503 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
@@ -16,11 +16,14 @@
package com.ning.billing.catalog.rules;
+import com.ning.billing.ErrorCode;
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 org.testng.Assert;
import org.testng.annotations.Test;
@@ -60,13 +63,13 @@ public class TestCaseChange {
}
}
@Test(enabled=true)
- public void testBasic(){
+ public void testBasic() throws CatalogApiException{
MockCatalog cat = new MockCatalog();
- DefaultProduct product1 = cat.getProducts()[0];
- DefaultPriceList priceList1 = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
+ DefaultProduct product1 = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList1 = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
- DefaultProduct product2 = cat.getProducts()[2];
+ DefaultProduct product2 = cat.getCurrentProducts()[2];
DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
@@ -86,14 +89,14 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- cat.getProducts()[1].getName(), product2.getName(),
+ cat.getCurrentProducts()[1].getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- product1.getName(), cat.getProducts()[1].getName(),
+ product1.getName(), cat.getCurrentProducts()[1].getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -127,18 +130,18 @@ public class TestCaseChange {
priceList1.getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- cat.getProducts()[1].getName(), priceList2.getName(),
+ cat.getCurrentProducts()[1].getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- priceList1.getName(), cat.getProducts()[1].getName(),
+ priceList1.getName(), cat.getCurrentProducts()[1].getName(),
PhaseType.EVERGREEN, cat);
assertionNull(cr,
@@ -150,13 +153,13 @@ public class TestCaseChange {
}
@Test(enabled=true)
- public void testWildcardFromProduct(){
+ public void testWildcardFromProduct()throws CatalogApiException{
MockCatalog cat = new MockCatalog();
- DefaultProduct product1 = cat.getProducts()[0];
- DefaultPriceList priceList1 = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
+ DefaultProduct product1 = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList1 = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
- DefaultProduct product2 = cat.getProducts()[2];
+ DefaultProduct product2 = cat.getCurrentProducts()[2];
DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
@@ -176,7 +179,7 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertion(Result.FOO,cr,
- cat.getProducts()[1].getName(), product2.getName(),
+ cat.getCurrentProducts()[1].getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -197,7 +200,7 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- product1.getName(), cat.getProducts()[1].getName(),
+ product1.getName(), cat.getCurrentProducts()[1].getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.ANNUAL, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -210,18 +213,18 @@ public class TestCaseChange {
priceList1.getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- cat.getProducts()[1].getName(), priceList2.getName(),
+ cat.getCurrentProducts()[1].getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- priceList1.getName(), cat.getProducts()[1].getName(),
+ priceList1.getName(), cat.getCurrentProducts()[1].getName(),
PhaseType.EVERGREEN, cat);
assertionNull(cr,
@@ -233,13 +236,13 @@ public class TestCaseChange {
}
@Test(enabled=true)
- public void testWildcardToProduct(){
+ public void testWildcardToProduct() throws CatalogApiException{
MockCatalog cat = new MockCatalog();
- DefaultProduct product1 = cat.getProducts()[0];
- DefaultPriceList priceList1 = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
+ DefaultProduct product1 = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList1 = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
- DefaultProduct product2 = cat.getProducts()[2];
+ DefaultProduct product2 = cat.getCurrentProducts()[2];
DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
@@ -259,7 +262,7 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- cat.getProducts()[1].getName(), product2.getName(),
+ cat.getCurrentProducts()[1].getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -280,7 +283,7 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertion(Result.FOO, cr,
- product1.getName(), cat.getProducts()[1].getName(),
+ product1.getName(), cat.getCurrentProducts()[1].getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -300,18 +303,18 @@ public class TestCaseChange {
priceList1.getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- cat.getProducts()[1].getName(), priceList2.getName(),
+ cat.getCurrentProducts()[1].getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- priceList1.getName(), cat.getProducts()[1].getName(),
+ priceList1.getName(), cat.getCurrentProducts()[1].getName(),
PhaseType.EVERGREEN, cat);
assertionNull(cr,
@@ -323,13 +326,13 @@ public class TestCaseChange {
}
@Test(enabled=true)
- public void testWildcardFromProductCategory(){
+ public void testWildcardFromProductCategory() throws CatalogApiException{
MockCatalog cat = new MockCatalog();
- DefaultProduct product1 = cat.getProducts()[0];
- DefaultPriceList priceList1 = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
+ DefaultProduct product1 = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList1 = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
- DefaultProduct product2 = cat.getProducts()[2];
+ DefaultProduct product2 = cat.getCurrentProducts()[2];
DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
@@ -349,14 +352,14 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- cat.getProducts()[1].getName(), product2.getName(),
+ cat.getCurrentProducts()[1].getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- product1.getName(), cat.getProducts()[1].getName(),
+ product1.getName(), cat.getCurrentProducts()[1].getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -390,18 +393,18 @@ public class TestCaseChange {
priceList1.getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- cat.getProducts()[1].getName(), priceList2.getName(),
+ cat.getCurrentProducts()[1].getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- priceList1.getName(), cat.getProducts()[1].getName(),
+ priceList1.getName(), cat.getCurrentProducts()[1].getName(),
PhaseType.EVERGREEN, cat);
assertionNull(cr,
@@ -413,13 +416,13 @@ public class TestCaseChange {
}
@Test(enabled=true)
- public void testWildcardToProductCategory(){
+ public void testWildcardToProductCategory() throws CatalogApiException{
MockCatalog cat = new MockCatalog();
- DefaultProduct product1 = cat.getProducts()[0];
- DefaultPriceList priceList1 = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
+ DefaultProduct product1 = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList1 = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
- DefaultProduct product2 = cat.getProducts()[2];
+ DefaultProduct product2 = cat.getCurrentProducts()[2];
DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
@@ -439,14 +442,14 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- cat.getProducts()[1].getName(), product2.getName(),
+ cat.getCurrentProducts()[1].getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- product1.getName(), cat.getProducts()[1].getName(),
+ product1.getName(), cat.getCurrentProducts()[1].getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -480,18 +483,18 @@ public class TestCaseChange {
priceList1.getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- cat.getProducts()[1].getName(), priceList2.getName(),
+ cat.getCurrentProducts()[1].getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- priceList1.getName(), cat.getProducts()[1].getName(),
+ priceList1.getName(), cat.getCurrentProducts()[1].getName(),
PhaseType.EVERGREEN, cat);
assertionNull(cr,
@@ -503,13 +506,13 @@ public class TestCaseChange {
}
@Test(enabled=true)
- public void testWildcardFromBillingPeriod(){
+ public void testWildcardFromBillingPeriod() throws CatalogApiException{
MockCatalog cat = new MockCatalog();
- DefaultProduct product1 = cat.getProducts()[0];
- DefaultPriceList priceList1 = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
+ DefaultProduct product1 = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList1 = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
- DefaultProduct product2 = cat.getProducts()[2];
+ DefaultProduct product2 = cat.getCurrentProducts()[2];
DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
@@ -529,7 +532,7 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- cat.getProducts()[1].getName(), product2.getName(),
+ cat.getCurrentProducts()[1].getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -550,7 +553,7 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- product1.getName(), cat.getProducts()[1].getName(),
+ product1.getName(), cat.getCurrentProducts()[1].getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -570,18 +573,18 @@ public class TestCaseChange {
priceList1.getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- cat.getProducts()[1].getName(), priceList2.getName(),
+ cat.getCurrentProducts()[1].getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- priceList1.getName(), cat.getProducts()[1].getName(),
+ priceList1.getName(), cat.getCurrentProducts()[1].getName(),
PhaseType.EVERGREEN, cat);
assertionNull(cr,
@@ -594,13 +597,13 @@ public class TestCaseChange {
@Test(enabled=true)
- public void testWildCardToBillingPeriod(){
+ public void testWildCardToBillingPeriod() throws CatalogApiException{
MockCatalog cat = new MockCatalog();
- DefaultProduct product1 = cat.getProducts()[0];
- DefaultPriceList priceList1 = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
+ DefaultProduct product1 = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList1 = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
- DefaultProduct product2 = cat.getProducts()[2];
+ DefaultProduct product2 = cat.getCurrentProducts()[2];
DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
@@ -620,7 +623,7 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- cat.getProducts()[1].getName(), product2.getName(),
+ cat.getCurrentProducts()[1].getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -641,7 +644,7 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- product1.getName(), cat.getProducts()[1].getName(),
+ product1.getName(), cat.getCurrentProducts()[1].getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -661,18 +664,18 @@ public class TestCaseChange {
priceList1.getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- cat.getProducts()[1].getName(), priceList2.getName(),
+ cat.getCurrentProducts()[1].getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- priceList1.getName(), cat.getProducts()[1].getName(),
+ priceList1.getName(), cat.getCurrentProducts()[1].getName(),
PhaseType.EVERGREEN, cat);
assertionNull(cr,
@@ -684,13 +687,13 @@ public class TestCaseChange {
}
@Test(enabled=true)
- public void testWildCardFromPriceList(){
+ public void testWildCardFromPriceList() throws CatalogApiException{
MockCatalog cat = new MockCatalog();
- DefaultProduct product1 = cat.getProducts()[0];
- DefaultPriceList priceList1 = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
+ DefaultProduct product1 = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList1 = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
- DefaultProduct product2 = cat.getProducts()[2];
+ DefaultProduct product2 = cat.getCurrentProducts()[2];
DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
@@ -710,7 +713,7 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- cat.getProducts()[1].getName(), product2.getName(),
+ cat.getCurrentProducts()[1].getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -731,7 +734,7 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- product1.getName(), cat.getProducts()[1].getName(),
+ product1.getName(), cat.getCurrentProducts()[1].getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -755,14 +758,14 @@ public class TestCaseChange {
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- cat.getProducts()[1].getName(), priceList2.getName(),
+ cat.getCurrentProducts()[1].getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- priceList1.getName(), cat.getProducts()[1].getName(),
+ priceList1.getName(), cat.getCurrentProducts()[1].getName(),
PhaseType.EVERGREEN, cat);
assertionNull(cr,
@@ -774,13 +777,13 @@ public class TestCaseChange {
}
@Test(enabled=true)
- public void testWildcardToPriceList(){
+ public void testWildcardToPriceList() throws CatalogApiException{
MockCatalog cat = new MockCatalog();
- DefaultProduct product1 = cat.getProducts()[0];
- DefaultPriceList priceList1 = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
+ DefaultProduct product1 = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList1 = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
- DefaultProduct product2 = cat.getProducts()[2];
+ DefaultProduct product2 = cat.getCurrentProducts()[2];
DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
@@ -800,7 +803,7 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- cat.getProducts()[1].getName(), product2.getName(),
+ cat.getCurrentProducts()[1].getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -821,7 +824,7 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- product1.getName(), cat.getProducts()[1].getName(),
+ product1.getName(), cat.getCurrentProducts()[1].getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -841,18 +844,18 @@ public class TestCaseChange {
priceList1.getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- cat.getProducts()[1].getName(), priceList2.getName(),
+ cat.getCurrentProducts()[1].getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
assertion(Result.FOO,cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- priceList1.getName(), cat.getProducts()[1].getName(),
+ priceList1.getName(), cat.getCurrentProducts()[1].getName(),
PhaseType.EVERGREEN, cat);
assertionNull(cr,
@@ -864,13 +867,13 @@ public class TestCaseChange {
}
@Test(enabled=true)
- public void testWildcardPlanPhase(){
+ public void testWildcardPlanPhase() throws CatalogApiException{
MockCatalog cat = new MockCatalog();
- DefaultProduct product1 = cat.getProducts()[0];
- DefaultPriceList priceList1 = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
+ DefaultProduct product1 = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList1 = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
- DefaultProduct product2 = cat.getProducts()[2];
+ DefaultProduct product2 = cat.getCurrentProducts()[2];
DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
@@ -890,7 +893,7 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- cat.getProducts()[1].getName(), product2.getName(),
+ cat.getCurrentProducts()[1].getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -911,7 +914,7 @@ public class TestCaseChange {
PhaseType.EVERGREEN, cat);
assertionNull(cr,
- product1.getName(), cat.getProducts()[1].getName(),
+ product1.getName(), cat.getCurrentProducts()[1].getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
priceList1.getName(), priceList2.getName(),
@@ -931,18 +934,18 @@ public class TestCaseChange {
priceList1.getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- cat.getProducts()[1].getName(), priceList2.getName(),
+ cat.getCurrentProducts()[1].getName(), priceList2.getName(),
PhaseType.EVERGREEN, cat);
- assertionNull(cr,
+ assertionException(cr,
product1.getName(), product2.getName(),
ProductCategory.BASE, ProductCategory.BASE,
BillingPeriod.MONTHLY, BillingPeriod.MONTHLY,
- priceList1.getName(), cat.getProducts()[1].getName(),
+ priceList1.getName(), cat.getCurrentProducts()[1].getName(),
PhaseType.EVERGREEN, cat);
assertion(Result.FOO,cr,
@@ -958,10 +961,10 @@ public class TestCaseChange {
public void testOrder() throws CatalogApiException{
MockCatalog cat = new MockCatalog();
- DefaultProduct product1 = cat.getProducts()[0];
- DefaultPriceList priceList1 = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
+ DefaultProduct product1 = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList1 = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
- DefaultProduct product2 = cat.getProducts()[2];
+ DefaultProduct product2 = cat.getCurrentProducts()[2];
DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[1];
@@ -1033,7 +1036,20 @@ public class TestCaseChange {
Assert.fail("", e);
}
}
-
+ protected void assertionException(CaseChangeResult cr,
+ String fromProductName, String toProductName,
+ ProductCategory fromProductCategory, ProductCategory toProductCategory,
+ BillingPeriod fromBp, BillingPeriod toBp,
+ 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);
+ Assert.fail("Expecting an exception");
+ } catch (CatalogApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.CAT_PRICE_LIST_NOT_FOUND.getCode());
+ }
+ }
protected void assertion(Result result, CaseChangeResult cr,
String fromProductName, String toProductName,
ProductCategory fromProductCategory, ProductCategory toProductCategory,
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 fe505f4..02f7ab5 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
@@ -16,6 +16,7 @@
package com.ning.billing.catalog.rules;
+import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.DefaultPriceList;
import com.ning.billing.catalog.DefaultProduct;
import com.ning.billing.catalog.MockCatalog;
@@ -53,7 +54,7 @@ public class TestCasePhase {
public void testBasic(){
MockCatalog cat = new MockCatalog();
- DefaultProduct product = cat.getProducts()[0];
+ DefaultProduct product = cat.getCurrentProducts()[0];
DefaultPriceList priceList = cat.getPriceLists().getDefaultPricelist();
@@ -66,10 +67,10 @@ public class TestCasePhase {
Result.FOO);
assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
- assertionNull(cr, cat.getProducts()[1].getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
+ assertionNull(cr, cat.getCurrentProducts()[1].getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
assertionNull(cr, product.getName(), ProductCategory.ADD_ON,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.ANNUAL, priceList.getName(), PhaseType.EVERGREEN, cat);
- assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", PhaseType.EVERGREEN, cat);
+ assertionException(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", PhaseType.EVERGREEN, cat);
assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.TRIAL, cat);
}
@@ -77,7 +78,7 @@ public class TestCasePhase {
public void testWildCardProduct(){
MockCatalog cat = new MockCatalog();
- DefaultProduct product = cat.getProducts()[0];
+ DefaultProduct product = cat.getCurrentProducts()[0];
DefaultPriceList priceList = cat.getPriceLists().getDefaultPricelist();
@@ -90,10 +91,10 @@ public class TestCasePhase {
Result.FOO);
assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
- assertion(Result.FOO, cr, cat.getProducts()[1].getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
+ assertion(Result.FOO, cr, cat.getCurrentProducts()[1].getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
assertionNull(cr, product.getName(), ProductCategory.ADD_ON,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.ANNUAL, priceList.getName(), PhaseType.EVERGREEN, cat);
- assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", PhaseType.EVERGREEN, cat);
+ assertionException(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", PhaseType.EVERGREEN, cat);
assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.TRIAL, cat);
}
@@ -101,7 +102,7 @@ public class TestCasePhase {
public void testWildCardProductCategory(){
MockCatalog cat = new MockCatalog();
- DefaultProduct product = cat.getProducts()[0];
+ DefaultProduct product = cat.getCurrentProducts()[0];
DefaultPriceList priceList = cat.getPriceLists().getDefaultPricelist();
@@ -114,10 +115,10 @@ public class TestCasePhase {
Result.FOO);
assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
- assertionNull(cr, cat.getProducts()[1].getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
+ assertionNull(cr, cat.getCurrentProducts()[1].getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
assertion(Result.FOO, cr, product.getName(), ProductCategory.ADD_ON,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.ANNUAL, priceList.getName(), PhaseType.EVERGREEN, cat);
- assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", PhaseType.EVERGREEN, cat);
+ assertionException(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", PhaseType.EVERGREEN, cat);
assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.TRIAL, cat);
}
@@ -125,7 +126,7 @@ public class TestCasePhase {
public void testWildCardBillingPeriod(){
MockCatalog cat = new MockCatalog();
- DefaultProduct product = cat.getProducts()[0];
+ DefaultProduct product = cat.getCurrentProducts()[0];
DefaultPriceList priceList = cat.getPriceLists().getDefaultPricelist();
@@ -138,10 +139,10 @@ public class TestCasePhase {
Result.FOO);
assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
- assertionNull(cr, cat.getProducts()[1].getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
+ assertionNull(cr, cat.getCurrentProducts()[1].getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
assertionNull(cr, product.getName(), ProductCategory.ADD_ON,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
assertion(Result.FOO,cr, product.getName(), ProductCategory.BASE,BillingPeriod.ANNUAL, priceList.getName(), PhaseType.EVERGREEN, cat);
- assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", PhaseType.EVERGREEN, cat);
+ assertionException(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", PhaseType.EVERGREEN, cat);
assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.TRIAL, cat);
}
@@ -149,7 +150,7 @@ public class TestCasePhase {
public void testWildCardPriceList(){
MockCatalog cat = new MockCatalog();
- DefaultProduct product = cat.getProducts()[0];
+ DefaultProduct product = cat.getCurrentProducts()[0];
DefaultPriceList priceList = cat.getPriceLists().getDefaultPricelist();
@@ -162,7 +163,7 @@ public class TestCasePhase {
Result.FOO);
assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
- assertionNull(cr, cat.getProducts()[1].getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
+ assertionNull(cr, cat.getCurrentProducts()[1].getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
assertionNull(cr, product.getName(), ProductCategory.ADD_ON,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.ANNUAL, priceList.getName(), PhaseType.EVERGREEN, cat);
assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, "dipsy", PhaseType.EVERGREEN, cat);
@@ -173,7 +174,7 @@ public class TestCasePhase {
public void testWildCardPhaseType(){
MockCatalog cat = new MockCatalog();
- DefaultProduct product = cat.getProducts()[0];
+ DefaultProduct product = cat.getCurrentProducts()[0];
DefaultPriceList priceList = cat.getPriceLists().getDefaultPricelist();
@@ -186,10 +187,10 @@ public class TestCasePhase {
Result.FOO);
assertion(Result.FOO, cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
- assertionNull(cr, cat.getProducts()[1].getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
+ assertionNull(cr, cat.getCurrentProducts()[1].getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
assertionNull(cr, product.getName(), ProductCategory.ADD_ON,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.EVERGREEN, cat);
assertionNull(cr, product.getName(), ProductCategory.BASE,BillingPeriod.ANNUAL, priceList.getName(), PhaseType.EVERGREEN, cat);
- assertionNull(cr, product.getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, "dipsy", PhaseType.EVERGREEN, cat);
+ assertionException(cr, product.getName(), ProductCategory.BASE, BillingPeriod.MONTHLY, "dipsy", PhaseType.EVERGREEN, cat);
assertion(Result.FOO,cr, product.getName(), ProductCategory.BASE,BillingPeriod.MONTHLY, priceList.getName(), PhaseType.TRIAL, cat);
}
@@ -197,7 +198,7 @@ public class TestCasePhase {
public void testOrder() throws CatalogApiException{
MockCatalog cat = new MockCatalog();
- DefaultProduct product = cat.getProducts()[0];
+ DefaultProduct product = cat.getCurrentProducts()[0];
DefaultPriceList priceList = cat.getPriceLists().getDefaultPricelist();
@@ -262,6 +263,16 @@ public class TestCasePhase {
}
}
+
+ protected void assertionException(CaseResult cr, String productName, ProductCategory productCategory, BillingPeriod bp, String priceListName, PhaseType phaseType, StandaloneCatalog cat){
+ try {
+ Assert.assertNull(cr.getResult(new PlanPhaseSpecifier(productName, productCategory, bp, priceListName, phaseType), cat));
+ Assert.fail("Exception expected");
+ } catch (CatalogApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.CAT_PRICE_LIST_NOT_FOUND.getCode());
+ }
+ }
+
protected void assertion(Result result, CaseResult cr, String productName, ProductCategory productCategory, BillingPeriod bp, String priceListName, PhaseType phaseType, StandaloneCatalog cat){
try {
Assert.assertEquals(result, cr.getResult(new PlanPhaseSpecifier(productName, productCategory, bp, priceListName, phaseType), cat));
diff --git a/catalog/src/test/java/com/ning/billing/catalog/rules/TestLoadRules.java b/catalog/src/test/java/com/ning/billing/catalog/rules/TestLoadRules.java
new file mode 100644
index 0000000..6f1d9e2
--- /dev/null
+++ b/catalog/src/test/java/com/ning/billing/catalog/rules/TestLoadRules.java
@@ -0,0 +1,52 @@
+/*
+ * 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.catalog.rules;
+
+import java.io.File;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.StandaloneCatalog;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanAlignmentCreate;
+import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.util.config.XMLLoader;
+
+public class TestLoadRules {
+
+ @Test
+ public void test() throws Exception {
+ StandaloneCatalog catalog = XMLLoader.getObjectFromUri(new File("src/test/resources/WeaponsHireSmall.xml").toURI(), StandaloneCatalog.class);
+ Assert.assertNotNull(catalog);
+ PlanRules rules = catalog.getPlanRules();
+
+ PlanSpecifier specifier = new PlanSpecifier("Laser-Scope", ProductCategory.ADD_ON , BillingPeriod.MONTHLY,
+ "DEFAULT");
+
+ PlanAlignmentCreate alignment= rules.getPlanCreateAlignment(specifier, catalog);
+ Assert.assertEquals(alignment, PlanAlignmentCreate.START_OF_SUBSCRIPTION);
+
+ PlanSpecifier specifier2 = new PlanSpecifier("Extra-Ammo", ProductCategory.ADD_ON , BillingPeriod.MONTHLY,
+ "DEFAULT");
+
+ PlanAlignmentCreate alignment2 = rules.getPlanCreateAlignment(specifier2, catalog);
+ Assert.assertEquals(alignment2, PlanAlignmentCreate.START_OF_BUNDLE);
+ }
+}
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 e41d506..7ac9cc0 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
@@ -20,6 +20,7 @@ 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
@@ -48,9 +49,9 @@ public class TestPlanRules {
}
@Test
- public void testCannotChangeToSamePlan() {
- DefaultProduct product1 = cat.getProducts()[0];
- DefaultPriceList priceList1 = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
+ public void testCannotChangeToSamePlan() throws CatalogApiException {
+ DefaultProduct product1 = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList1 = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
PlanPhaseSpecifier from = new PlanPhaseSpecifier(product1.getName(), product1.getCategory(), BillingPeriod.MONTHLY, priceList1.getName(), PhaseType.EVERGREEN);
PlanSpecifier to = new PlanSpecifier(product1.getName(), product1.getCategory(), BillingPeriod.MONTHLY, priceList1.getName());
@@ -67,9 +68,9 @@ public class TestPlanRules {
}
@Test
- public void testExistingPriceListIsKept() {
- DefaultProduct product1 = cat.getProducts()[0];
- DefaultPriceList priceList1 = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
+ public void testExistingPriceListIsKept() throws CatalogApiException {
+ DefaultProduct product1 = cat.getCurrentProducts()[0];
+ DefaultPriceList priceList1 = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
PlanPhaseSpecifier from = new PlanPhaseSpecifier(product1.getName(), product1.getCategory(), BillingPeriod.MONTHLY, priceList1.getName(), PhaseType.EVERGREEN);
PlanSpecifier to = new PlanSpecifier(product1.getName(), product1.getCategory(), BillingPeriod.ANNUAL, priceList1.getName());
@@ -92,10 +93,10 @@ public class TestPlanRules {
@Test
- public void testBaseCase() {
- DefaultProduct product1 = cat.getProducts()[0];
- DefaultProduct product2 = cat.getProducts()[1];
- DefaultPriceList priceList1 = cat.getPriceListFromName(PriceListSet.DEFAULT_PRICELIST_NAME);
+ public void testBaseCase() throws CatalogApiException {
+ DefaultProduct product1 = cat.getCurrentProducts()[0];
+ DefaultProduct product2 = cat.getCurrentProducts()[1];
+ DefaultPriceList priceList1 = cat.findCurrentPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
DefaultPriceList priceList2 = cat.getPriceLists().getChildPriceLists()[0];
PlanPhaseSpecifier from = new PlanPhaseSpecifier(product1.getName(), product1.getCategory(), BillingPeriod.MONTHLY, priceList1.getName(), PhaseType.EVERGREEN);
@@ -103,7 +104,7 @@ public class TestPlanRules {
PlanChangeResult result = null;
try {
- result = cat.getPlanRules().planChange(from, to, cat);
+ result = cat.getPlanRules().planChange(from, to, cat);
} catch (IllegalPlanChange e) {
log.info("Correct - cannot change to the same plan:", e);
Assert.fail("We should not have triggered this error");
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestCatalogService.java b/catalog/src/test/java/com/ning/billing/catalog/TestCatalogService.java
index 754b03a..7bb45a6 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestCatalogService.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestCatalogService.java
@@ -35,8 +35,8 @@ public class TestCatalogService {
}, new VersionedCatalogLoader(new DefaultClock()));
service.loadCatalog();
- Assert.assertNotNull(service.getCatalog());
- Assert.assertEquals(service.getCatalog().getCatalogName(), "WeaponsHireSmall");
+ Assert.assertNotNull(service.getFullCatalog());
+ Assert.assertEquals(service.getFullCatalog().getCatalogName(), "WeaponsHireSmall");
}
@Test
@@ -49,7 +49,7 @@ public class TestCatalogService {
}, new VersionedCatalogLoader(new DefaultClock()));
service.loadCatalog();
- Assert.assertNotNull(service.getCatalog());
- Assert.assertEquals(service.getCatalog().getCatalogName(), "Firearms");
+ Assert.assertNotNull(service.getFullCatalog());
+ Assert.assertEquals(service.getFullCatalog().getCatalogName(), "Firearms");
}
}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestInternationalPrice.java b/catalog/src/test/java/com/ning/billing/catalog/TestInternationalPrice.java
index 0f75459..3e6a755 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestInternationalPrice.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestInternationalPrice.java
@@ -67,7 +67,7 @@ public class TestInternationalPrice {
StandaloneCatalog c = new MockCatalog();
c.setSupportedCurrencies(new Currency[]{Currency.GBP, Currency.EUR, Currency.USD, Currency.BRL, Currency.MXN});
c.initialize(c, new URI("foo://bar"));
- Assert.assertEquals(c.getPlans()[0].getFinalPhase().getRecurringPrice().getPrice(Currency.GBP), new BigDecimal(0));
+ Assert.assertEquals(c.getCurrentPlans()[0].getFinalPhase().getRecurringPrice().getPrice(Currency.GBP), new BigDecimal(0));
}
@Test
@@ -88,16 +88,7 @@ public class TestInternationalPrice {
errors.log(log);
Assert.assertEquals(errors.size(), 3);
}
- @Test
- public void testDateValidation(){
- StandaloneCatalog c = new MockCatalog();
- c.setSupportedCurrencies(new Currency[]{Currency.GBP, Currency.EUR, Currency.USD, Currency.BRL, Currency.MXN});
- DefaultInternationalPrice p1 = new MockInternationalPrice();
- p1.setEffectiveDateForExistingSubscriptons(new Date((new Date().getTime()) - (1000 * 60 * 60 * 24)));
- ValidationErrors errors = p1.validate(c, new ValidationErrors());
- Assert.assertEquals(errors.size(), 1);
- errors.log(log);
- }
+
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java b/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java
new file mode 100644
index 0000000..00dd1b6
--- /dev/null
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java
@@ -0,0 +1,43 @@
+/*
+ * 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.catalog;
+
+import java.util.Date;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.config.ValidationErrors;
+
+public class TestPlan {
+ private static final Logger log = LoggerFactory.getLogger(TestPlan.class);
+ @Test
+ public void testDateValidation() {
+
+ StandaloneCatalog c = new MockCatalog();
+ c.setSupportedCurrencies(new Currency[]{Currency.GBP, Currency.EUR, Currency.USD, Currency.BRL, Currency.MXN});
+ DefaultPlan p1 = new MockPlan();
+ p1.setEffectiveDateForExistingSubscriptons(new Date((new Date().getTime()) - (1000 * 60 * 60 * 24)));
+ ValidationErrors errors = p1.validate(c, new ValidationErrors());
+ Assert.assertEquals(errors.size(), 1);
+ errors.log(log);
+
+ }
+}
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 4bfcf30..d778c4b 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
@@ -17,6 +17,8 @@
package com.ning.billing.catalog;
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.util.config.ValidationErrors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,5 +46,37 @@ public class TestPlanPhase {
errors = pp.validate(new MockCatalog(), new ValidationErrors());
errors.log(log);
Assert.assertEquals(errors.size(), 1);
-}
+ }
+
+ @Test
+ public void testPhaseNames() throws CatalogApiException {
+ String planName = "Foo";
+ String planNameExt = planName + "-";
+
+ DefaultPlan p = new MockPlan().setName(planName);
+ DefaultPlanPhase ppDiscount = new MockPlanPhase().setPhaseType(PhaseType.DISCOUNT).setPlan(p);
+ DefaultPlanPhase ppTrial = new MockPlanPhase().setPhaseType(PhaseType.TRIAL).setPlan(p);
+ DefaultPlanPhase ppEvergreen = new MockPlanPhase().setPhaseType(PhaseType.EVERGREEN).setPlan(p);
+ DefaultPlanPhase ppFixedterm = new MockPlanPhase().setPhaseType(PhaseType.FIXEDTERM).setPlan(p);
+
+ String ppnDiscount = DefaultPlanPhase.phaseName(p, ppDiscount);
+ String ppnTrial = DefaultPlanPhase.phaseName(p, ppTrial);
+ String ppnEvergreen = DefaultPlanPhase.phaseName(p, ppEvergreen);
+ String ppnFixedterm = DefaultPlanPhase.phaseName(p, ppFixedterm);
+
+ Assert.assertEquals(ppnTrial, planNameExt + "trial");
+ Assert.assertEquals(ppnEvergreen, planNameExt + "evergreen");
+ Assert.assertEquals(ppnFixedterm, planNameExt + "fixedterm");
+ Assert.assertEquals(ppnDiscount, planNameExt + "discount");
+
+
+ Assert.assertEquals(DefaultPlanPhase.planName(ppnDiscount),planName);
+ Assert.assertEquals(DefaultPlanPhase.planName(ppnTrial),planName);
+ Assert.assertEquals(DefaultPlanPhase.planName(ppnEvergreen), planName);
+ Assert.assertEquals(DefaultPlanPhase.planName(ppnFixedterm), planName);
+
+
+
+
+ }
}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestPriceListSet.java b/catalog/src/test/java/com/ning/billing/catalog/TestPriceListSet.java
index 09024ce..3cb8d2f 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestPriceListSet.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestPriceListSet.java
@@ -17,6 +17,7 @@
package com.ning.billing.catalog;
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.PriceListSet;
import com.ning.billing.catalog.api.ProductCategory;
@@ -30,7 +31,7 @@ import static com.ning.billing.catalog.api.PhaseType.EVERGREEN;
public class TestPriceListSet {
@Test(enabled=true)
- public void testOverriding() {
+ public void testOverriding() throws CatalogApiException {
DefaultProduct foo = new DefaultProduct("Foo", ProductCategory.BASE);
DefaultProduct bar = new DefaultProduct("Bar", ProductCategory.BASE);
DefaultPlan[] defaultPlans = new DefaultPlan[]{
@@ -55,7 +56,7 @@ public class TestPriceListSet {
Assert.assertEquals(set.getPlanListFrom("child", foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
}
- public void testForNullBillingPeriod() {
+ public void testForNullBillingPeriod() throws CatalogApiException {
DefaultProduct foo = new DefaultProduct("Foo", ProductCategory.BASE);
DefaultProduct bar = new DefaultProduct("Bar", ProductCategory.BASE);
DefaultPlan[] defaultPlans = new DefaultPlan[]{
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestStandaloneCatalog.java b/catalog/src/test/java/com/ning/billing/catalog/TestStandaloneCatalog.java
new file mode 100644
index 0000000..fd5d8fb
--- /dev/null
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestStandaloneCatalog.java
@@ -0,0 +1,52 @@
+/*
+ * 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.catalog;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
+
+public class TestStandaloneCatalog {
+
+ @Test
+ public void testFindPhase() throws CatalogApiException{
+ DefaultPlanPhase phaseTrial1 = new MockPlanPhase().setPhaseType(PhaseType.TRIAL);
+ DefaultPlanPhase phaseTrial2 = new MockPlanPhase().setPhaseType(PhaseType.TRIAL);
+ DefaultPlanPhase phaseDiscount1 = new MockPlanPhase().setPhaseType(PhaseType.DISCOUNT);
+ DefaultPlanPhase phaseDiscount2 = new MockPlanPhase().setPhaseType(PhaseType.DISCOUNT);
+
+ DefaultPlan plan1 = new MockPlan().setName("TestPlan1").setFinalPhase(phaseDiscount1).setInitialPhases(new DefaultPlanPhase[]{phaseTrial1});
+ DefaultPlan plan2 = new MockPlan().setName("TestPlan2").setFinalPhase(phaseDiscount2).setInitialPhases(new DefaultPlanPhase[]{phaseTrial2});
+ phaseTrial1.setPlan(plan1);
+ phaseTrial2.setPlan(plan2);
+ phaseDiscount1.setPlan(plan1);
+ phaseDiscount2.setPlan(plan2);
+
+ StandaloneCatalog cat = new MockCatalog().setPlans(new DefaultPlan[]{plan1, plan2});
+
+ Assert.assertEquals(cat.findCurrentPhase("TestPlan1-discount"), phaseDiscount1);
+ Assert.assertEquals(cat.findCurrentPhase("TestPlan2-discount"), phaseDiscount2);
+ Assert.assertEquals(cat.findCurrentPhase("TestPlan1-trial"), phaseTrial1);
+ Assert.assertEquals(cat.findCurrentPhase("TestPlan2-trial"), phaseTrial2);
+
+
+ }
+
+
+}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java b/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java
index bc07e3e..ddd623e 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java
@@ -15,56 +15,88 @@
*/
package com.ning.billing.catalog;
-import com.google.common.io.Resources;
-import com.ning.billing.catalog.api.InvalidConfigException;
-import com.ning.billing.catalog.io.VersionedCatalogLoader;
-import com.ning.billing.lifecycle.KillbillService.ServiceException;
-import com.ning.billing.util.clock.DefaultClock;
-import org.joda.time.DateTime;
-import org.testng.annotations.Test;
-import org.xml.sax.SAXException;
+import static org.testng.AssertJUnit.assertEquals;
-import javax.xml.bind.JAXBException;
-import javax.xml.transform.TransformerException;
import java.io.IOException;
+import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.Date;
-import static org.testng.AssertJUnit.assertEquals;
+import javax.xml.bind.JAXBException;
+import javax.xml.transform.TransformerException;
+
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.xml.sax.SAXException;
+
+import com.google.common.io.Resources;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.InvalidConfigException;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.io.VersionedCatalogLoader;
+import com.ning.billing.lifecycle.KillbillService.ServiceException;
+import com.ning.billing.util.clock.DefaultClock;
public class TestVersionedCatalog {
+ private static final Logger log = LoggerFactory.getLogger(TestVersionedCatalog.class);
private final VersionedCatalogLoader loader = new VersionedCatalogLoader(new DefaultClock());
+ private VersionedCatalog vc;
+
+ @BeforeClass(groups={"setup"})
+ public void setUp() throws ServiceException {
+ vc = loader.load(Resources.getResource("versionedCatalog").toString());
+ }
@Test(enabled=true)
- public void testAddCatalog() throws MalformedURLException, IOException, SAXException, InvalidConfigException, JAXBException, TransformerException, URISyntaxException, ServiceException {
- VersionedCatalog vc = loader.load(Resources.getResource("versionedCatalog").toString());
+ public void testAddCatalog() throws MalformedURLException, IOException, SAXException, InvalidConfigException, JAXBException, TransformerException, URISyntaxException, ServiceException, CatalogApiException {
vc.add(new StandaloneCatalog(new Date()));
assertEquals(5, vc.size());
}
+
@Test(enabled=true)
- public void testApplyEffectiveDate() throws MalformedURLException, IOException, SAXException, InvalidConfigException, JAXBException, TransformerException, URISyntaxException, ServiceException {
- VersionedCatalog vc = loader.load(Resources.getResource("versionedCatalog").toString());
- Date d = new Date(1L);
- vc.configureEffectiveDate(d);
- assertEquals(new Date(0), vc.getEffectiveDate()); // Start at the begining of time
+ public void testFindPlanWithDates() throws Exception {
+ DateTime dt0= new DateTime("2010-01-01T00:00:00+00:00");
+ DateTime dt1 = new DateTime("2011-01-01T00:01:00+00:00");
+ DateTime dt2 = new DateTime("2011-02-02T00:01:00+00:00");
+ DateTime dt214 = new DateTime("2011-02-14T00:01:00+00:00");
+ DateTime dt3 = new DateTime("2011-03-03T00:01:00+00:00");
- DateTime dt = new DateTime("2011-01-01T00:00:00+00:00");
- d = new Date(dt.getMillis() + 1000);
- vc.configureEffectiveDate(d);
- assertEquals(dt.toDate(),vc.getEffectiveDate());
+ // New subscription
+ try {
+ vc.findPlan("pistol-monthly", dt0, dt0);
+ Assert.fail("Exception should have been thrown there are no plans for this date");
+ } catch (CatalogApiException e) {
+ // Expected behaviour
+ log.error("Expected exception", e);
+
+ }
+ Plan newSubPlan1 = vc.findPlan("pistol-monthly", dt1, dt1);
+ Plan newSubPlan2 = vc.findPlan("pistol-monthly", dt2, dt2);
+ Plan newSubPlan214 = vc.findPlan("pistol-monthly", dt214, dt214);
+ Plan newSubPlan3 = vc.findPlan("pistol-monthly", dt3, dt3);
- dt = new DateTime("2011-02-02T00:00:00+00:00");
- d = new Date(dt.getMillis() + 1000);
- vc.configureEffectiveDate(d);
- assertEquals(dt.toDate(),vc.getEffectiveDate());
+ Assert.assertEquals(newSubPlan1.getAllPhases()[1].getRecurringPrice().getPrice(Currency.USD), new BigDecimal("1.0"));
+ Assert.assertEquals(newSubPlan2.getAllPhases()[1].getRecurringPrice().getPrice(Currency.USD), new BigDecimal("2.0"));
+ Assert.assertEquals(newSubPlan214.getAllPhases()[1].getRecurringPrice().getPrice(Currency.USD), new BigDecimal("2.0"));
+ Assert.assertEquals(newSubPlan3.getAllPhases()[1].getRecurringPrice().getPrice(Currency.USD), new BigDecimal("3.0"));
- dt = new DateTime("2011-03-03T00:00:00+00:00");
- d = new Date(dt.getMillis() + 1000);
- vc.configureEffectiveDate(d);
- assertEquals(dt.toDate(),vc.getEffectiveDate());
+ // Existing subscription
- }
+ Plan exSubPlan2 = vc.findPlan("pistol-monthly", dt2, dt1);
+ Plan exSubPlan214 = vc.findPlan("pistol-monthly", dt214, dt1);
+ Plan exSubPlan3 = vc.findPlan("pistol-monthly", dt3, dt1);
+
+ Assert.assertEquals(exSubPlan2.getAllPhases()[1].getRecurringPrice().getPrice(Currency.USD), new BigDecimal("1.0"));
+ Assert.assertEquals(exSubPlan214.getAllPhases()[1].getRecurringPrice().getPrice(Currency.USD), new BigDecimal("2.0"));
+ Assert.assertEquals(exSubPlan3.getAllPhases()[1].getRecurringPrice().getPrice(Currency.USD), new BigDecimal("2.0"));
+
+ }
}
diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml
index a29e929..c21aac1 100644
--- a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml
+++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml
@@ -57,6 +57,15 @@
<alignment>START_OF_SUBSCRIPTION</alignment>
</changeAlignmentCase>
</changeAlignment>
+ <createAlignment>
+ <createAlignmentCase>
+ <product>Laser-Scope</product>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </createAlignmentCase>
+ <createAlignmentCase>
+ <alignment>START_OF_BUNDLE</alignment>
+ </createAlignmentCase>
+ </createAlignment>
</rules>
@@ -80,9 +89,9 @@
</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>
+ <price><currency>GBP</currency><value>1.0</value></price>
+ <price><currency>EUR</currency><value>1.0</value></price>
+ <price><currency>USD</currency><value>1.0</value></price>
</recurringPrice>
</finalPhase>
</plan>
diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml
index 135a89e..0e650f1 100644
--- a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml
+++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-2.xml
@@ -57,10 +57,20 @@
<alignment>START_OF_SUBSCRIPTION</alignment>
</changeAlignmentCase>
</changeAlignment>
+ <createAlignment>
+ <createAlignmentCase>
+ <product>Laser-Scope</product>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </createAlignmentCase>
+ <createAlignmentCase>
+ <alignment>START_OF_BUNDLE</alignment>
+ </createAlignmentCase>
+ </createAlignment>
</rules>
<plans>
<plan name="pistol-monthly">
+ <effectiveDateForExistingSubscriptons>2011-02-14T00:00:00+00:00</effectiveDateForExistingSubscriptons>
<product>Pistol</product>
<initialPhases>
<phase type="TRIAL">
@@ -79,9 +89,9 @@
</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>
+ <price><currency>GBP</currency><value>2.0</value></price>
+ <price><currency>EUR</currency><value>2.0</value></price>
+ <price><currency>USD</currency><value>2.0</value></price>
</recurringPrice>
</finalPhase>
</plan>
diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml
index 747f54b..f7ae066 100644
--- a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml
+++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml
@@ -57,6 +57,15 @@
<alignment>START_OF_SUBSCRIPTION</alignment>
</changeAlignmentCase>
</changeAlignment>
+ <createAlignment>
+ <createAlignmentCase>
+ <product>Laser-Scope</product>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </createAlignmentCase>
+ <createAlignmentCase>
+ <alignment>START_OF_BUNDLE</alignment>
+ </createAlignmentCase>
+ </createAlignment>
</rules>
@@ -80,9 +89,9 @@
</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>
+ <price><currency>GBP</currency><value>3.0</value></price>
+ <price><currency>EUR</currency><value>3.0</value></price>
+ <price><currency>USD</currency><value>3.0</value></price>
</recurringPrice>
</finalPhase>
</plan>
diff --git a/catalog/src/test/resources/WeaponsHireSmall.xml b/catalog/src/test/resources/WeaponsHireSmall.xml
index 0a3ed52..21a9f08 100644
--- a/catalog/src/test/resources/WeaponsHireSmall.xml
+++ b/catalog/src/test/resources/WeaponsHireSmall.xml
@@ -37,6 +37,9 @@
<product name="Laser-Scope">
<category>ADD_ON</category>
</product>
+ <product name="Extra-Ammo">
+ <category>ADD_ON</category>
+ </product>
</products>
<rules>
@@ -52,11 +55,20 @@
<policy>IMMEDIATE</policy>
</changePolicyCase>
</changePolicy>
- <changeAlignment>
- <changeAlignmentCase>
- <alignment>START_OF_SUBSCRIPTION</alignment>
- </changeAlignmentCase>
- </changeAlignment>
+ <changeAlignment>
+ <changeAlignmentCase>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </changeAlignmentCase>
+ </changeAlignment>
+ <createAlignment>
+ <createAlignmentCase>
+ <product>Laser-Scope</product>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </createAlignmentCase>
+ <createAlignmentCase>
+ <alignment>START_OF_BUNDLE</alignment>
+ </createAlignmentCase>
+ </createAlignment>
</rules>
<plans>
@@ -136,6 +148,34 @@
</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="extra-ammo-monthly">
+ <product>Extra-Ammo</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>
</plans>
<priceLists>
<defaultPriceList name="DEFAULT">
entitlement/pom.xml 23(+22 -1)
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 7e97852..f0d0aae 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.0.21-SNAPSHOT</version>
+ <version>0.1.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-entitlement</artifactId>
@@ -61,6 +61,16 @@
<artifactId>killbill-util</artifactId>
</dependency>
<dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-account</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-account</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
@@ -108,6 +118,17 @@
<groups>setup,fast</groups>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
<profiles>
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/alignment/MigrationPlanAligner.java b/entitlement/src/main/java/com/ning/billing/entitlement/alignment/MigrationPlanAligner.java
index 4290f73..5730399 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/alignment/MigrationPlanAligner.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/alignment/MigrationPlanAligner.java
@@ -48,11 +48,11 @@ public class MigrationPlanAligner {
try {
TimedMigration [] events = null;
- Plan plan0 = catalogService.getCatalog().findPlan(input[0].getPlanPhaseSpecifer().getProductName(),
- input[0].getPlanPhaseSpecifer().getBillingPeriod(), input[0].getPlanPhaseSpecifer().getPriceListName());
+ Plan plan0 = catalogService.getFullCatalog().findPlan(input[0].getPlanPhaseSpecifer().getProductName(),
+ input[0].getPlanPhaseSpecifer().getBillingPeriod(), input[0].getPlanPhaseSpecifer().getPriceListName(), now);
- Plan plan1 = (input.length > 1) ? catalogService.getCatalog().findPlan(input[1].getPlanPhaseSpecifer().getProductName(),
- input[1].getPlanPhaseSpecifer().getBillingPeriod(), input[1].getPlanPhaseSpecifer().getPriceListName()) :
+ Plan plan1 = (input.length > 1) ? catalogService.getFullCatalog().findPlan(input[1].getPlanPhaseSpecifer().getProductName(),
+ input[1].getPlanPhaseSpecifer().getBillingPeriod(), input[1].getPlanPhaseSpecifer().getPriceListName(), now) :
null;
DateTime migrationStartDate = now;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/alignment/PlanAligner.java b/entitlement/src/main/java/com/ning/billing/entitlement/alignment/PlanAligner.java
index 541c57c..7553e0d 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/alignment/PlanAligner.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/alignment/PlanAligner.java
@@ -22,7 +22,6 @@ import com.ning.billing.catalog.api.*;
import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.SubscriptionData;
import com.ning.billing.entitlement.api.user.SubscriptionTransition;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
import com.ning.billing.entitlement.exceptions.EntitlementError;
import com.ning.billing.util.clock.DefaultClock;
import org.joda.time.DateTime;
@@ -63,11 +62,11 @@ public class PlanAligner {
* @throws CatalogApiException
* @throws EntitlementUserApiException
*/
- public TimedPhase [] getCurrentAndNextTimedPhaseOnCreate(SubscriptionData subscription,
- Plan plan, PhaseType initialPhase, String priceList, DateTime effectiveDate)
+ public TimedPhase [] getCurrentAndNextTimedPhaseOnCreate(final SubscriptionData subscription,
+ final Plan plan, final PhaseType initialPhase, final String priceList, final DateTime requestedDate, final DateTime effectiveDate)
throws CatalogApiException, EntitlementUserApiException {
List<TimedPhase> timedPhases = getTimedPhaseOnCreate(subscription.getStartDate(),
- subscription.getBundleStartDate(), plan, initialPhase, priceList);
+ subscription.getBundleStartDate(), plan, initialPhase, priceList, requestedDate);
TimedPhase [] result = new TimedPhase[2];
result[0] = getTimedPhase(timedPhases, effectiveDate, WhichPhase.CURRENT);
result[1] = getTimedPhase(timedPhases, effectiveDate, WhichPhase.NEXT);
@@ -86,10 +85,10 @@ public class PlanAligner {
* @throws CatalogApiException
* @throws EntitlementUserApiException
*/
- public TimedPhase getCurrentTimedPhaseOnChange(SubscriptionData subscription,
- Plan plan, String priceList, DateTime effectiveDate)
+ public TimedPhase getCurrentTimedPhaseOnChange(final SubscriptionData subscription,
+ final Plan plan, final String priceList, final DateTime requestedDate, final DateTime effectiveDate)
throws CatalogApiException, EntitlementUserApiException {
- return getTimedPhaseOnChange(subscription, plan, priceList, effectiveDate, WhichPhase.CURRENT);
+ return getTimedPhaseOnChange(subscription, plan, priceList, requestedDate, effectiveDate, WhichPhase.CURRENT);
}
/**
@@ -103,10 +102,10 @@ public class PlanAligner {
* @throws CatalogApiException
* @throws EntitlementUserApiException
*/
- public TimedPhase getNextTimedPhaseOnChange(SubscriptionData subscription,
- Plan plan, String priceList, DateTime effectiveDate)
+ public TimedPhase getNextTimedPhaseOnChange(final SubscriptionData subscription,
+ final Plan plan, final String priceList, final DateTime requestedDate, final DateTime effectiveDate)
throws CatalogApiException, EntitlementUserApiException {
- return getTimedPhaseOnChange(subscription, plan, priceList, effectiveDate, WhichPhase.NEXT);
+ return getTimedPhaseOnChange(subscription, plan, priceList, requestedDate, effectiveDate, WhichPhase.NEXT);
}
@@ -117,7 +116,7 @@ public class PlanAligner {
* @param effectiveDate the date at which we look to compute that event. effective needs to be after last Plan change or initial Plan
* @return The PhaseEvent at the correct point in time
*/
- public TimedPhase getNextTimedPhase(SubscriptionData subscription, DateTime effectiveDate) {
+ public TimedPhase getNextTimedPhase(final SubscriptionData subscription, final DateTime requestedDate, final DateTime effectiveDate) {
try {
SubscriptionTransition lastPlanTransition = subscription.getInitialTransitionForCurrentPlan();
@@ -133,7 +132,8 @@ public class PlanAligner {
subscription.getBundleStartDate(),
lastPlanTransition.getNextPlan(),
lastPlanTransition.getNextPhase().getPhaseType(),
- lastPlanTransition.getNextPriceList());
+ lastPlanTransition.getNextPriceList(),
+ requestedDate);
return getTimedPhase(timedPhases, effectiveDate, WhichPhase.NEXT);
// If we went through Plan changes, borrow the logics for changePlan alignement
case CHANGE:
@@ -144,7 +144,9 @@ public class PlanAligner {
lastPlanTransition.getPreviousPriceList(),
lastPlanTransition.getNextPlan(),
lastPlanTransition.getNextPriceList(),
- effectiveDate, WhichPhase.NEXT);
+ requestedDate,
+ effectiveDate,
+ WhichPhase.NEXT);
default:
throw new EntitlementError(String.format("Unexpectd initial transition %s for current plan %s on subscription %s",
lastPlanTransition.getTransitionType(), subscription.getCurrentPlan(), subscription.getId()));
@@ -154,12 +156,13 @@ public class PlanAligner {
}
}
+
private List<TimedPhase> getTimedPhaseOnCreate(DateTime subscriptionStartDate,
DateTime bundleStartDate,
- Plan plan, PhaseType initialPhase, String priceList)
+ Plan plan, PhaseType initialPhase, String priceList, DateTime requestedDate)
throws CatalogApiException, EntitlementUserApiException {
- Catalog catalog = catalogService.getCatalog();
+ Catalog catalog = catalogService.getFullCatalog();
PlanSpecifier planSpecifier = new PlanSpecifier(plan.getProduct().getName(),
plan.getProduct().getCategory(),
@@ -167,8 +170,7 @@ public class PlanAligner {
priceList);
DateTime planStartDate = null;
- PlanAlignmentCreate alignement = catalog.planCreateAlignment(planSpecifier);
-
+ PlanAlignmentCreate alignement = catalog.planCreateAlignment(planSpecifier, requestedDate);
switch(alignement) {
case START_OF_SUBSCRIPTION:
planStartDate = subscriptionStartDate;
@@ -182,9 +184,8 @@ public class PlanAligner {
return getPhaseAlignments(plan, initialPhase, planStartDate);
}
-
private TimedPhase getTimedPhaseOnChange(SubscriptionData subscription,
- Plan nextPlan, String nextPriceList, DateTime effectiveDate, WhichPhase which)
+ Plan nextPlan, String nextPriceList, DateTime requestedDate, DateTime effectiveDate, WhichPhase which)
throws CatalogApiException, EntitlementUserApiException {
return getTimedPhaseOnChange(subscription.getStartDate(),
subscription.getBundleStartDate(),
@@ -193,19 +194,21 @@ public class PlanAligner {
subscription.getCurrentPriceList(),
nextPlan,
nextPriceList,
+ requestedDate,
effectiveDate,
which);
}
+
private TimedPhase getTimedPhaseOnChange(DateTime subscriptionStartDate,
DateTime bundleStartDate,
PlanPhase currentPhase,
Plan currentPlan,
String currentPriceList,
- Plan nextPlan, String priceList, DateTime effectiveDate, WhichPhase which)
+ Plan nextPlan, String priceList, DateTime requestedDate, DateTime effectiveDate, WhichPhase which)
throws CatalogApiException, EntitlementUserApiException {
- Catalog catalog = catalogService.getCatalog();
+ Catalog catalog = catalogService.getFullCatalog();
ProductCategory currentCategory = currentPlan.getProduct().getCategory();
// STEPH tiered ADDON not implemented yet
if (currentCategory != ProductCategory.BASE) {
@@ -226,7 +229,7 @@ public class PlanAligner {
DateTime planStartDate = null;
PlanAlignmentChange alignment = null;
- alignment = catalog.planChangeAlignment(fromPlanPhaseSpecifier, toPlanSpecifier);
+ alignment = catalog.planChangeAlignment(fromPlanPhaseSpecifier, toPlanSpecifier, requestedDate);
switch(alignment) {
case START_OF_SUBSCRIPTION:
planStartDate = subscriptionStartDate;
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 22fdd40..fd130a9 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,37 +16,111 @@
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;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.catalog.api.BillingAlignment;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.Product;
+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;
import com.ning.billing.entitlement.engine.dao.EntitlementDao;
-import org.joda.time.DateTime;
-
-import java.util.List;
-import java.util.SortedSet;
-import java.util.UUID;
public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
-
+ private Logger log = LoggerFactory.getLogger(DefaultEntitlementBillingApi.class);
+
private final EntitlementDao dao;
+ private final AccountUserApi accountApi;
+ private final CatalogService catalogService;
@Inject
- public DefaultEntitlementBillingApi(EntitlementDao dao) {
+ public DefaultEntitlementBillingApi(EntitlementDao dao, AccountUserApi accountApi, CatalogService catalogService) {
super();
this.dao = dao;
+ this.accountApi = accountApi;
+ this.catalogService = catalogService;
}
@Override
- public List<Account> getActiveAccounts() {
- return null;
- }
+ public SortedSet<BillingEvent> getBillingEventsForAccount(
+ UUID accountId) {
+
+ List<SubscriptionBundle> bundles = dao.getSubscriptionBundleForAccount(accountId);
+ List<Subscription> subscriptions = new ArrayList<Subscription>();
+ for (SubscriptionBundle bundle: bundles) {
+ subscriptions.addAll(dao.getSubscriptions(bundle.getId()));
+ }
- @Override
- public SortedSet<BillingEvent> getBillingEventsForSubscription(
- UUID subscriptionId) {
- return null;
+ SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
+ for (Subscription subscription: subscriptions) {
+ for (SubscriptionTransition transition : subscription.getAllTransitions()) {
+ try {
+ result.add(new DefaultBillingEvent(transition, subscription, calculateBCD(transition, accountId)));
+ } catch (CatalogApiException e) {
+ log.error("Failing to identify catalog components while creating BillingEvent from transition: " +
+ transition.getId().toString(), e);
+ }
+ }
+ }
+ return result;
+ }
+
+ private int calculateBCD(SubscriptionTransition transition, UUID accountId) throws CatalogApiException {
+ Catalog catalog = catalogService.getFullCatalog();
+ Plan plan = transition.getNextPlan();
+ Product product = plan.getProduct();
+ PlanPhase phase = transition.getNextPhase();
+
+ BillingAlignment alignment = catalog.billingAlignment(
+ new PlanPhaseSpecifier(product.getName(),
+ product.getCategory(),
+ phase.getBillingPeriod(),
+ transition.getNextPriceList(),
+ phase.getPhaseType()),
+ transition.getRequestedTransitionTime());
+ int result = 0;
+ Account account = accountApi.getAccountById(accountId);
+ switch (alignment) {
+ case ACCOUNT :
+ result = account.getBillCycleDay();
+ break;
+ case BUNDLE :
+ SubscriptionBundle bundle = dao.getSubscriptionBundleFromId(transition.getBundleId());
+ //TODO result = bundle.getStartDate().toDateTime(account.getTimeZone()).getDayOfMonth();
+ result = bundle.getStartDate().getDayOfMonth();
+ break;
+ case SUBSCRIPTION :
+ Subscription subscription = dao.getSubscriptionFromId(transition.getSubscriptionId());
+ //TODO result = subscription.getStartDate().toDateTime(account.getTimeZone()).getDayOfMonth();
+ result = subscription.getStartDate().getDayOfMonth();
+ break;
+ }
+ if(result == 0) {
+ throw new CatalogApiException(ErrorCode.CAT_INVALID_BILLING_ALIGNMENT, alignment.toString());
+ }
+ return result;
+
}
+
@Override
public void setChargedThroughDate(UUID subscriptionId, DateTime ctd) {
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 c2f5878..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
@@ -18,30 +18,50 @@ 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.EventNotifier;
+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;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+
import java.util.UUID;
public class DefaultEntitlementTestApi implements EntitlementTestApi {
private final static Logger log = LoggerFactory.getLogger(DefaultEntitlementTestApi.class);
- private final EventNotifier apiEventProcessor;
private final EntitlementConfig config;
+ private final NotificationQueueService notificationQueueService;
@Inject
- public DefaultEntitlementTestApi(EventNotifier apiEventProcessor, EntitlementConfig config) {
- this.apiEventProcessor = apiEventProcessor;
+ public DefaultEntitlementTestApi(NotificationQueueService notificationQueueService, EntitlementConfig config) {
this.config = config;
+ this.notificationQueueService = notificationQueueService;
}
@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");
- apiEventProcessor.processAllReadyEvents(subscriptionsIds, recursive, oneEventOnly);
+ NotificationQueue queue = getNotificationQueue();
+ queue.processReadyNotification();
+ }
+ }
+
+ private NotificationQueue getNotificationQueue() {
+ try {
+ NotificationQueue subscritionEventQueue = notificationQueueService.getNotificationQueue(Engine.ENTITLEMENT_SERVICE_NAME,
+ Engine.NOTIFICATION_QUEUE_NAME);
+ return subscritionEventQueue;
+ } catch (NoSuchNotificationQueue e) {
+ throw new RuntimeException(e);
}
}
}
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 51010db..deedb6d 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
@@ -107,7 +107,7 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
requestedDate = (requestedDate == null) ? now : requestedDate;
DateTime effectiveDate = requestedDate;
- Plan plan = catalogService.getCatalog().findPlan(spec.getProductName(), spec.getBillingPeriod(), realPriceList);
+ Plan plan = catalogService.getFullCatalog().findPlan(spec.getProductName(), spec.getBillingPeriod(), realPriceList, requestedDate);
PlanPhase phase = plan.getAllPhases()[0];
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
index 548675b..1b95f6b 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
@@ -60,7 +60,7 @@ public class SubscriptionApiService {
SubscriptionData subscription = new SubscriptionData(builder, this, clock);
- TimedPhase [] curAndNextPhases = planAligner.getCurrentAndNextTimedPhaseOnCreate(subscription, plan, initialPhase, realPriceList, effectiveDate);
+ TimedPhase [] curAndNextPhases = planAligner.getCurrentAndNextTimedPhaseOnCreate(subscription, plan, initialPhase, realPriceList, requestedDate, effectiveDate);
ApiEventCreate creationEvent = new ApiEventCreate(new ApiEventBuilder()
.setSubscriptionId(subscription.getId())
.setEventPlan(plan.getName())
@@ -81,7 +81,7 @@ public class SubscriptionApiService {
events.add(nextPhaseEvent);
}
dao.createSubscription(subscription, events);
- subscription.rebuildTransitions(events, catalogService.getCatalog());
+ subscription.rebuildTransitions(events, catalogService.getFullCatalog());
return subscription;
} catch (CatalogApiException e) {
throw new EntitlementUserApiException(e);
@@ -112,7 +112,7 @@ public class SubscriptionApiService {
subscription.getCurrentPhase().getPhaseType());
ActionPolicy policy = null;
- policy = catalogService.getCatalog().planCancelPolicy(planPhase);
+ policy = catalogService.getFullCatalog().planCancelPolicy(planPhase, requestedDate);
DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy, requestedDate);
EntitlementEvent cancelEvent = new ApiEventCancel(new ApiEventBuilder()
@@ -123,7 +123,7 @@ public class SubscriptionApiService {
.setRequestedDate(now));
dao.cancelSubscription(subscription.getId(), cancelEvent);
- subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId()), catalogService.getCatalog());
+ subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId()), catalogService.getFullCatalog());
} catch (CatalogApiException e) {
throw new EntitlementUserApiException(e);
}
@@ -148,7 +148,7 @@ public class SubscriptionApiService {
List<EntitlementEvent> uncancelEvents = new ArrayList<EntitlementEvent>();
uncancelEvents.add(uncancelEvent);
- TimedPhase nextTimedPhase = planAligner.getNextTimedPhase(subscription, now);
+ TimedPhase nextTimedPhase = planAligner.getNextTimedPhase(subscription, now, now);
PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
PhaseEventData.getNextPhaseEvent(nextTimedPhase.getPhase().getName(), subscription, now, nextTimedPhase.getStartPhase()) :
null;
@@ -156,7 +156,7 @@ public class SubscriptionApiService {
uncancelEvents.add(nextPhaseEvent);
}
dao.uncancelSubscription(subscription.getId(), uncancelEvents);
- subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId()), catalogService.getCatalog());
+ subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId()), catalogService.getFullCatalog());
}
public void changePlan(SubscriptionData subscription, String productName, BillingPeriod term,
@@ -186,7 +186,7 @@ public class SubscriptionApiService {
PlanChangeResult planChangeResult = null;
try {
- Product destProduct = catalogService.getCatalog().findProduct(productName);
+ Product destProduct = catalogService.getFullCatalog().findProduct(productName, requestedDate);
Plan currentPlan = subscription.getCurrentPlan();
PlanPhaseSpecifier fromPlanPhase = new PlanPhaseSpecifier(currentPlan.getProduct().getName(),
currentPlan.getProduct().getCategory(),
@@ -197,7 +197,7 @@ public class SubscriptionApiService {
term,
priceList);
- planChangeResult = catalogService.getCatalog().planChange(fromPlanPhase, toPlanPhase);
+ planChangeResult = catalogService.getFullCatalog().planChange(fromPlanPhase, toPlanPhase, requestedDate);
} catch (CatalogApiException e) {
throw new EntitlementUserApiException(e);
}
@@ -205,10 +205,10 @@ public class SubscriptionApiService {
ActionPolicy policy = planChangeResult.getPolicy();
PriceList newPriceList = planChangeResult.getNewPriceList();
- Plan newPlan = catalogService.getCatalog().findPlan(productName, term, newPriceList.getName());
+ Plan newPlan = catalogService.getFullCatalog().findPlan(productName, term, newPriceList.getName(), requestedDate);
DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy, now);
- TimedPhase currentTimedPhase = planAligner.getCurrentTimedPhaseOnChange(subscription, newPlan, newPriceList.getName(), effectiveDate);
+ TimedPhase currentTimedPhase = planAligner.getCurrentTimedPhaseOnChange(subscription, newPlan, newPriceList.getName(), requestedDate, effectiveDate);
EntitlementEvent changeEvent = new ApiEventChange(new ApiEventBuilder()
.setSubscriptionId(subscription.getId())
@@ -220,7 +220,7 @@ public class SubscriptionApiService {
.setEffectiveDate(effectiveDate)
.setRequestedDate(now));
- TimedPhase nextTimedPhase = planAligner.getNextTimedPhaseOnChange(subscription, newPlan, newPriceList.getName(), effectiveDate);
+ TimedPhase nextTimedPhase = planAligner.getNextTimedPhaseOnChange(subscription, newPlan, newPriceList.getName(), requestedDate, effectiveDate);
PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
PhaseEventData.getNextPhaseEvent(nextTimedPhase.getPhase().getName(), subscription, now, nextTimedPhase.getStartPhase()) :
null;
@@ -231,7 +231,7 @@ public class SubscriptionApiService {
}
changeEvents.add(changeEvent);
dao.changePlan(subscription.getId(), changeEvents);
- subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId()), catalogService.getCatalog());
+ subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId()), catalogService.getFullCatalog());
} catch (CatalogApiException e) {
throw new EntitlementUserApiException(e);
}
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 8789537..0cee49d 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
@@ -172,6 +172,18 @@ public class SubscriptionData implements Subscription {
}
@Override
+ public List<SubscriptionTransition> getAllTransitions() {
+ if (transitions == null) {
+ return Collections.emptyList();
+ }
+
+ List<SubscriptionTransition> result = new ArrayList<SubscriptionTransition>();
+ for (SubscriptionTransition cur : transitions) {
+ result.add(cur);
+ }
+ return result;
+ }
+
public SubscriptionTransition getPendingTransition() {
if (transitions == null) {
return null;
@@ -323,11 +335,14 @@ public class SubscriptionData implements Subscription {
String nextPriceList = null;
SubscriptionState previousState = null;
- String previousPlanName = null;
- String previousPhaseName = null;
+ //String previousPlanName = null;
+ //String previousPhaseName = null;
String previousPriceList = null;
transitions = new LinkedList<SubscriptionTransitionData>();
+ Plan previousPlan = null;
+ PlanPhase previousPhase = null;
+
for (final EntitlementEvent cur : events) {
if (!cur.isActive() || cur.getActiveVersion() < activeVersion) {
@@ -383,15 +398,12 @@ public class SubscriptionData implements Subscription {
cur.getType()));
}
- Plan previousPlan = null;
- PlanPhase previousPhase = null;
+
Plan nextPlan = null;
PlanPhase nextPhase = null;
try {
- previousPlan = (previousPlanName != null) ? catalog.findPlan(previousPlanName) : null;
- previousPhase = (previousPhaseName != null) ? catalog.findPhase(previousPhaseName) : null;
- nextPlan = (nextPlanName != null) ? catalog.findPlan(nextPlanName) : null;
- nextPhase = (nextPhaseName != null) ? catalog.findPhase(nextPhaseName) : null;
+ nextPlan = (nextPlanName != null) ? catalog.findPlan(nextPlanName, cur.getRequestedDate(), getStartDate()) : null;
+ nextPhase = (nextPhaseName != null) ? catalog.findPhase(nextPhaseName, cur.getRequestedDate(), getStartDate()) : null;
} catch (CatalogApiException e) {
log.error(String.format("Failed to build transition for subscription %s", id), e);
}
@@ -414,8 +426,8 @@ public class SubscriptionData implements Subscription {
transitions.add(transition);
previousState = nextState;
- previousPlanName = nextPlanName;
- previousPhaseName = nextPhaseName;
+ previousPlan = nextPlan;
+ previousPhase = nextPhase;
previousPriceList = nextPriceList;
}
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionFactory.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionFactory.java
index 207b4b2..cef1b43 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionFactory.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionFactory.java
@@ -44,7 +44,7 @@ public class SubscriptionFactory {
public SubscriptionData createSubscription(SubscriptionBuilder builder, List<EntitlementEvent> events) {
SubscriptionData subscription = new SubscriptionData(builder, apiService, clock);
if (events.size() > 0) {
- subscription.rebuildTransitions(events, catalogService.getCatalog());
+ subscription.rebuildTransitions(events, catalogService.getFullCatalog());
}
return subscription;
}
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 73e488b..cb37037 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
@@ -16,8 +16,10 @@
package com.ning.billing.entitlement.engine.core;
+
import java.util.Iterator;
import java.util.List;
+import java.util.UUID;
import org.joda.time.DateTime;
import org.slf4j.Logger;
@@ -57,10 +59,16 @@ 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.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.NotificationQueueHandler;
public class Engine implements EventListener, EntitlementService {
- private static final String ENTITLEMENT_SERVICE_NAME = "entitlement-service";
+ 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
@@ -70,7 +78,6 @@ public class Engine implements EventListener, EntitlementService {
private final Clock clock;
private final EntitlementDao dao;
- private final EventNotifier apiEventProcessor;
private final PlanAligner planAligner;
private final EntitlementUserApi userApi;
private final EntitlementBillingApi billingApi;
@@ -78,28 +85,31 @@ public class Engine implements EventListener, EntitlementService {
private final EntitlementMigrationApi migrationApi;
private final AddonUtils addonUtils;
private final EventBus 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, EventNotifier apiEventProcessor,
- PlanAligner planAligner, EntitlementConfig config, DefaultEntitlementUserApi userApi,
+ public Engine(Clock clock, EntitlementDao dao, PlanAligner planAligner,
+ EntitlementConfig config, DefaultEntitlementUserApi userApi,
DefaultEntitlementBillingApi billingApi, DefaultEntitlementTestApi testApi,
- DefaultEntitlementMigrationApi migrationApi, AddonUtils addonUtils, EventBus eventBus) {
-
+ DefaultEntitlementMigrationApi migrationApi, AddonUtils addonUtils, EventBus eventBus,
+ NotificationQueueService notificationQueueService) {
super();
this.clock = clock;
this.dao = dao;
- this.apiEventProcessor = apiEventProcessor;
this.planAligner = planAligner;
this.userApi = userApi;
this.testApi = testApi;
this.billingApi = billingApi;
this.migrationApi = migrationApi;
this.addonUtils = addonUtils;
+ this.config = config;
this.eventBus = eventBus;
-
- this.startedNotificationThread = false;
+ this.notificationQueueService = notificationQueueService;
}
@Override
@@ -107,20 +117,75 @@ public class Engine implements EventListener, EntitlementService {
return ENTITLEMENT_SERVICE_NAME;
}
-
@LifecycleHandlerType(LifecycleLevel.INIT_SERVICE)
public void initialize() {
+
+ try {
+ this.stoppedNotificationThread = false;
+ this.startedNotificationThread = false;
+ subscritionEventQueue = notificationQueueService.createNotificationQueue(ENTITLEMENT_SERVICE_NAME,
+ NOTIFICATION_QUEUE_NAME,
+ new NotificationQueueHandler() {
+ @Override
+ public void handleReadyNotification(String notificationKey) {
+ EntitlementEvent event = dao.getEventById(UUID.fromString(notificationKey));
+ if (event == null) {
+ log.warn("Failed to extract event for notification key {}", notificationKey);
+ } else {
+ 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
+ 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 (NotficationQueueAlreadyExists e) {
+ throw new RuntimeException(e);
+ }
}
@LifecycleHandlerType(LifecycleLevel.START_SERVICE)
public void start() {
- apiEventProcessor.startNotifications(this);
+ subscritionEventQueue.startQueue();
waitForNotificationStartCompletion();
}
@LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
public void stop() {
- apiEventProcessor.stopNotifications();
+ if (subscritionEventQueue != null) {
+ subscritionEventQueue.stopQueue();
+ waitForNotificationStopCompletion();
+ }
startedNotificationThread = false;
}
@@ -148,6 +213,9 @@ public class Engine implements EventListener, EntitlementService {
@Override
public void processEventReady(EntitlementEvent event) {
+ if (!event.isActive()) {
+ return;
+ }
SubscriptionData subscription = (SubscriptionData) dao.getSubscriptionFromId(event.getSubscriptionId());
if (subscription == null) {
log.warn("Failed to retrieve subscription for id %s", event.getSubscriptionId());
@@ -169,23 +237,20 @@ public class Engine implements EventListener, EntitlementService {
}
}
- //
- // We want to ensure the notification thread is indeed started when we return from start()
- //
- @Override
- public void completedNotificationStart() {
- synchronized (this) {
- startedNotificationThread = true;
- this.notifyAll();
- }
+ private void waitForNotificationStartCompletion() {
+ waitForNotificationEventCompletion(true);
}
- private void waitForNotificationStartCompletion() {
+ private void waitForNotificationStopCompletion() {
+ waitForNotificationEventCompletion(false);
+ }
+
+ private void waitForNotificationEventCompletion(boolean startEvent) {
long ini = System.nanoTime();
synchronized(this) {
do {
- if (startedNotificationThread) {
+ if ((startEvent ? startedNotificationThread : stoppedNotificationThread)) {
break;
}
try {
@@ -194,21 +259,25 @@ public class Engine implements EventListener, EntitlementService {
Thread.currentThread().interrupt();
throw new EntitlementError(e);
}
- } while (!startedNotificationThread &&
+ } while (!(startEvent ? startedNotificationThread : stoppedNotificationThread) &&
(System.nanoTime() - ini) / NANO_TO_MS < MAX_NOTIFICATION_THREAD_WAIT_MS);
- if (!startedNotificationThread) {
- log.error("Could not start notification thread in %d msec !!!", 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 started in {} ms", (System.nanoTime() - ini) / NANO_TO_MS);
+ log.info("Notification thread has been {} in {} ms",
+ (startEvent ? "started" : "stopped"),
+ (System.nanoTime() - ini) / NANO_TO_MS);
}
}
private void onPhaseEvent(SubscriptionData subscription) {
try {
DateTime now = clock.getUTCNow();
- TimedPhase nextTimedPhase = planAligner.getNextTimedPhase(subscription, now);
+ TimedPhase nextTimedPhase = planAligner.getNextTimedPhase(subscription, now, now);
PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
PhaseEventData.getNextPhaseEvent(nextTimedPhase.getPhase().getName(), subscription, now, nextTimedPhase.getStartPhase()) :
null;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventListener.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventListener.java
index 7a84c51..e9962d8 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventListener.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventListener.java
@@ -24,5 +24,4 @@ public interface EventListener {
public void processEventReady(EntitlementEvent event);
- public void completedNotificationStart();
}
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 b118110..ea62b84 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
@@ -54,14 +54,12 @@ public interface EntitlementDao {
// Event apis
public void createNextPhaseEvent(UUID subscriptionId, EntitlementEvent nextPhase);
+ public EntitlementEvent getEventById(UUID eventId);
+
public List<EntitlementEvent> getEventsForSubscription(UUID subscriptionId);
public List<EntitlementEvent> getPendingEventsForSubscription(UUID subscriptionId);
- public List<EntitlementEvent> getEventsReady(UUID ownerId, int sequenceId);
-
- public void clearEventsReady(UUID ownerId, Collection<EntitlementEvent> cleared);
-
// Subscription creation, cancellation, changePlan apis
public void createSubscription(SubscriptionData subscription, List<EntitlementEvent> initialEvents);
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 a987fe6..df3ee92 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
@@ -16,9 +16,14 @@
package com.ning.billing.entitlement.engine.dao;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
import com.google.inject.Inject;
import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.config.EntitlementConfig;
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;
@@ -28,20 +33,26 @@ 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;
import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
import com.ning.billing.entitlement.events.user.ApiEvent;
import com.ning.billing.entitlement.events.user.ApiEventType;
import com.ning.billing.entitlement.exceptions.EntitlementError;
-import com.ning.billing.util.Hostname;
import com.ning.billing.util.clock.Clock;
+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 java.util.*;
public class EntitlementSqlDao implements EntitlementDao {
@@ -51,19 +62,17 @@ public class EntitlementSqlDao implements EntitlementDao {
private final SubscriptionSqlDao subscriptionsDao;
private final BundleSqlDao bundlesDao;
private final EventSqlDao eventsDao;
- private final EntitlementConfig config;
- private final String hostname;
private final SubscriptionFactory factory;
+ private final NotificationQueueService notificationQueueService;
@Inject
- public EntitlementSqlDao(DBI dbi, Clock clock, EntitlementConfig config, SubscriptionFactory factory) {
+ public EntitlementSqlDao(DBI dbi, Clock clock, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
this.clock = clock;
- this.config = config;
this.factory = factory;
this.subscriptionsDao = dbi.onDemand(SubscriptionSqlDao.class);
this.eventsDao = dbi.onDemand(EventSqlDao.class);
this.bundlesDao = dbi.onDemand(BundleSqlDao.class);
- this.hostname = Hostname.get();
+ this.notificationQueueService = notificationQueueService;
}
@Override
@@ -140,11 +149,24 @@ public class EntitlementSqlDao implements EntitlementDao {
TransactionStatus status) throws Exception {
cancelNextPhaseEventFromTransaction(subscriptionId, dao);
dao.insertEvent(nextPhase);
+ recordFutureNotificationFromTransaction(dao,
+ nextPhase.getEffectiveDate(),
+ new NotificationKey() {
+ @Override
+ public String toString() {
+ return nextPhase.getId().toString();
+ }
+ });
return null;
}
});
}
+ @Override
+ public EntitlementEvent getEventById(UUID eventId) {
+ return eventsDao.getEventById(eventId.toString());
+ }
+
@Override
public List<EntitlementEvent> getEventsForSubscription(UUID subscriptionId) {
@@ -159,61 +181,6 @@ public class EntitlementSqlDao implements EntitlementDao {
return results;
}
- @Override
- public List<EntitlementEvent> getEventsReady(final UUID ownerId, final int sequenceId) {
-
- final Date now = clock.getUTCNow().toDate();
- final Date nextAvailable = clock.getUTCNow().plus(config.getDaoClaimTimeMs()).toDate();
-
- log.debug(String.format("EntitlementDao getEventsReady START effectiveNow = %s", now));
-
- List<EntitlementEvent> events = eventsDao.inTransaction(new Transaction<List<EntitlementEvent>, EventSqlDao>() {
-
- @Override
- public List<EntitlementEvent> inTransaction(EventSqlDao dao,
- TransactionStatus status) throws Exception {
-
- List<EntitlementEvent> claimedEvents = new ArrayList<EntitlementEvent>();
- List<EntitlementEvent> input = dao.getReadyEvents(now, config.getDaoMaxReadyEvents());
- for (EntitlementEvent cur : input) {
- final boolean claimed = (dao.claimEvent(ownerId.toString(), nextAvailable, cur.getId().toString(), now) == 1);
- if (claimed) {
- claimedEvents.add(cur);
- dao.insertClaimedHistory(sequenceId, ownerId.toString(), hostname, now, cur.getId().toString());
- }
- }
- return claimedEvents;
- }
- });
-
- for (EntitlementEvent cur : events) {
- log.debug(String.format("EntitlementDao %s [host %s] claimed events %s", ownerId, hostname, cur.getId()));
- if (cur.getOwner() != null && !cur.getOwner().equals(ownerId)) {
- log.warn(String.format("EventProcessor %s stealing event %s from %s", ownerId, cur, cur.getOwner()));
- }
- }
- return events;
- }
-
- @Override
- public void clearEventsReady(final UUID ownerId, final Collection<EntitlementEvent> cleared) {
-
- log.debug(String.format("EntitlementDao clearEventsReady START cleared size = %d", cleared.size()));
-
- eventsDao.inTransaction(new Transaction<Void, EventSqlDao>() {
-
- @Override
- public Void inTransaction(EventSqlDao dao,
- TransactionStatus status) throws Exception {
- // STEPH Same here batch would nice
- for (EntitlementEvent cur : cleared) {
- dao.clearEvent(cur.getId().toString(), ownerId.toString());
- log.debug(String.format("EntitlementDao %s [host %s] cleared events %s", ownerId, hostname, cur.getId()));
- }
- return null;
- }
- });
- }
@Override
public void createSubscription(final SubscriptionData subscription,
@@ -228,8 +195,16 @@ public class EntitlementSqlDao implements EntitlementDao {
dao.insertSubscription(subscription);
// STEPH batch as well
EventSqlDao eventsDaoFromSameTranscation = dao.become(EventSqlDao.class);
- for (EntitlementEvent cur : initialEvents) {
+ for (final EntitlementEvent cur : initialEvents) {
eventsDaoFromSameTranscation.insertEvent(cur);
+ recordFutureNotificationFromTransaction(dao,
+ cur.getEffectiveDate(),
+ new NotificationKey() {
+ @Override
+ public String toString() {
+ return cur.getId().toString();
+ }
+ });
}
return null;
}
@@ -247,6 +222,14 @@ public class EntitlementSqlDao implements EntitlementDao {
cancelNextChangeEventFromTransaction(subscriptionId, dao);
cancelNextPhaseEventFromTransaction(subscriptionId, dao);
dao.insertEvent(cancelEvent);
+ recordFutureNotificationFromTransaction(dao,
+ cancelEvent.getEffectiveDate(),
+ new NotificationKey() {
+ @Override
+ public String toString() {
+ return cancelEvent.getId().toString();
+ }
+ });
return null;
}
});
@@ -276,8 +259,16 @@ public class EntitlementSqlDao implements EntitlementDao {
if (existingCancelId != null) {
dao.unactiveEvent(existingCancelId.toString(), now);
- for (EntitlementEvent cur : uncancelEvents) {
+ for (final EntitlementEvent cur : uncancelEvents) {
dao.insertEvent(cur);
+ recordFutureNotificationFromTransaction(dao,
+ cur.getEffectiveDate(),
+ new NotificationKey() {
+ @Override
+ public String toString() {
+ return cur.getId().toString();
+ }
+ });
}
}
return null;
@@ -293,8 +284,16 @@ public class EntitlementSqlDao implements EntitlementDao {
TransactionStatus status) throws Exception {
cancelNextChangeEventFromTransaction(subscriptionId, dao);
cancelNextPhaseEventFromTransaction(subscriptionId, dao);
- for (EntitlementEvent cur : changeEvents) {
+ for (final EntitlementEvent cur : changeEvents) {
dao.insertEvent(cur);
+ recordFutureNotificationFromTransaction(dao,
+ cur.getEffectiveDate(),
+ new NotificationKey() {
+ @Override
+ public String toString() {
+ return cur.getId().toString();
+ }
+ });
}
return null;
}
@@ -367,8 +366,16 @@ public class EntitlementSqlDao implements EntitlementDao {
SubscriptionBundleData bundleData = curBundle.getData();
for (SubscriptionMigrationData curSubscription : curBundle.getSubscriptions()) {
SubscriptionData subData = curSubscription.getData();
- for (EntitlementEvent curEvent : curSubscription.getInitialEvents()) {
+ for (final EntitlementEvent curEvent : curSubscription.getInitialEvents()) {
transEventDao.insertEvent(curEvent);
+ recordFutureNotificationFromTransaction(transEventDao,
+ curEvent.getEffectiveDate(),
+ new NotificationKey() {
+ @Override
+ public String toString() {
+ return curEvent.getId().toString();
+ }
+ });
}
transSubDao.insertSubscription(subData);
}
@@ -379,6 +386,7 @@ public class EntitlementSqlDao implements EntitlementDao {
});
}
+
@Override
public void undoMigration(final UUID accountId) {
@@ -407,4 +415,14 @@ public class EntitlementSqlDao implements EntitlementDao {
transBundleDao.removeBundle(curBundle.getId().toString());
}
}
+
+ private void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao, final DateTime effectiveDate, final NotificationKey notificationKey) {
+ try {
+ NotificationQueue subscritionEventQueue = notificationQueueService.getNotificationQueue(Engine.ENTITLEMENT_SERVICE_NAME,
+ Engine.NOTIFICATION_QUEUE_NAME);
+ subscritionEventQueue.recordFutureNotificationFromTransaction(transactionalDao, effectiveDate, notificationKey);
+ } catch (NoSuchNotificationQueue e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java
index de61217..5f485e5 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EventSqlDao.java
@@ -19,7 +19,6 @@ package com.ning.billing.entitlement.engine.dao;
import com.ning.billing.entitlement.events.EntitlementEvent;
import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
import com.ning.billing.entitlement.events.EventBaseBuilder;
-import com.ning.billing.entitlement.events.EventLifecycle.EventLifecycleState;
import com.ning.billing.entitlement.events.phase.PhaseEvent;
import com.ning.billing.entitlement.events.phase.PhaseEventBuilder;
import com.ning.billing.entitlement.events.phase.PhaseEventData;
@@ -49,43 +48,31 @@ import java.util.UUID;
@ExternalizedSqlViaStringTemplate3()
public interface EventSqlDao extends Transactional<EventSqlDao>, CloseMe, Transmogrifier {
- //
- // APIs for event notifications
- //
@SqlQuery
- @Mapper(IEventSqlMapper.class)
- public List<EntitlementEvent> getReadyEvents(@Bind("now") Date now, @Bind("max") int max);
+ @Mapper(EventSqlMapper.class)
+ public EntitlementEvent getEventById(@Bind("event_id") String eventId);
@SqlUpdate
- public int claimEvent(@Bind("owner") String owner, @Bind("next_available") Date nextAvailable, @Bind("event_id") String eventId, @Bind("now") Date now);
+ public void insertEvent(@Bind(binder = EventSqlDaoBinder.class) EntitlementEvent evt);
@SqlUpdate
public void removeEvents(@Bind("subscription_id") String subscriptionId);
@SqlUpdate
- public void clearEvent(@Bind("event_id") String eventId, @Bind("owner") String owner);
-
- @SqlUpdate
- public void insertEvent(@Bind(binder = IEventSqlDaoBinder.class) EntitlementEvent evt);
-
- @SqlUpdate
- public void insertClaimedHistory(@Bind("sequence_id") int sequenceId, @Bind("owner_id") String ownerId, @Bind("hostname") String hostname, @Bind("claimed_dt") Date clainedDate, @Bind("event_id") String eventId);
-
- @SqlUpdate
public void unactiveEvent(@Bind("event_id")String eventId, @Bind("now") Date now);
@SqlUpdate
public void reactiveEvent(@Bind("event_id")String eventId, @Bind("now") Date now);
@SqlQuery
- @Mapper(IEventSqlMapper.class)
+ @Mapper(EventSqlMapper.class)
public List<EntitlementEvent> getFutureActiveEventForSubscription(@Bind("subscription_id") String subscriptionId, @Bind("now") Date now);
@SqlQuery
- @Mapper(IEventSqlMapper.class)
+ @Mapper(EventSqlMapper.class)
public List<EntitlementEvent> getEventsForSubscription(@Bind("subscription_id") String subscriptionId);
- public static class IEventSqlDaoBinder implements Binder<Bind, EntitlementEvent> {
+ public static class EventSqlDaoBinder implements Binder<Bind, EntitlementEvent> {
private Date getDate(DateTime dateTime) {
return dateTime == null ? null : dateTime.toDate();
@@ -106,13 +93,10 @@ public interface EventSqlDao extends Transactional<EventSqlDao>, CloseMe, Transm
stmt.bind("plist_name", (evt.getType() == EventType.API_USER) ? ((ApiEvent) evt).getPriceList() : null);
stmt.bind("current_version", evt.getActiveVersion());
stmt.bind("is_active", evt.isActive());
- stmt.bind("processing_available_dt", getDate(evt.getNextAvailableDate()));
- stmt.bind("processing_owner", (String) null);
- stmt.bind("processing_state", EventLifecycleState.AVAILABLE.toString());
}
}
- public static class IEventSqlMapper implements ResultSetMapper<EntitlementEvent> {
+ public static class EventSqlMapper implements ResultSetMapper<EntitlementEvent> {
private DateTime getDate(ResultSet r, String fieldName) throws SQLException {
final Timestamp resultStamp = r.getTimestamp(fieldName);
@@ -135,9 +119,6 @@ public interface EventSqlDao extends Transactional<EventSqlDao>, CloseMe, Transm
String priceListName = r.getString("plist_name");
long currentVersion = r.getLong("current_version");
boolean isActive = r.getBoolean("is_active");
- DateTime nextAvailableDate = getDate(r, "processing_available_dt");
- UUID processingOwner = (r.getString("processing_owner") != null) ? UUID.fromString(r.getString("processing_owner")) : null;
- EventLifecycleState processingState = EventLifecycleState.valueOf(r.getString("processing_state"));
EventBaseBuilder<?> base = ((eventType == EventType.PHASE) ?
new PhaseEventBuilder() :
@@ -148,11 +129,7 @@ public interface EventSqlDao extends Transactional<EventSqlDao>, CloseMe, Transm
.setEffectiveDate(effectiveDate)
.setProcessedDate(createdDate)
.setActiveVersion(currentVersion)
- .setActive(isActive)
- .setProcessingOwner(processingOwner)
- .setNextAvailableProcessingTime(nextAvailableDate)
- .setProcessingState(processingState);
-
+ .setActive(isActive);
EntitlementEvent result = null;
if (eventType == EventType.PHASE) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/EntitlementEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/EntitlementEvent.java
index af74752..b7bfece 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/EntitlementEvent.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/EntitlementEvent.java
@@ -21,7 +21,7 @@ import org.joda.time.DateTime;
import java.util.UUID;
-public interface EntitlementEvent extends EventLifecycle, Comparable<EntitlementEvent> {
+public interface EntitlementEvent extends Comparable<EntitlementEvent> {
public enum EventType {
API_USER,
@@ -32,6 +32,16 @@ public interface EntitlementEvent extends EventLifecycle, Comparable<Entitlement
public UUID getId();
+ public long getActiveVersion();
+
+ public void setActiveVersion(long activeVersion);
+
+ public boolean isActive();
+
+ public void deactivate();
+
+ public void reactivate();
+
public DateTime getProcessedDate();
public DateTime getRequestedDate();
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBase.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBase.java
index 93dc9e1..9420fbf 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBase.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBase.java
@@ -30,12 +30,8 @@ public abstract class EventBase implements EntitlementEvent {
private final DateTime effectiveDate;
private final DateTime processedDate;
- // Lifecyle of the event
private long activeVersion;
private boolean isActive;
- private UUID processingOwner;
- private DateTime nextAvailableProcessingTime;
- private EventLifecycleState processingState;
public EventBase(EventBaseBuilder<?> builder) {
this.uuid = builder.getUuid();
@@ -46,31 +42,17 @@ public abstract class EventBase implements EntitlementEvent {
this.activeVersion = builder.getActiveVersion();
this.isActive = builder.isActive();
- this.processingOwner = builder.getProcessingOwner();
- this.nextAvailableProcessingTime = builder.getNextAvailableProcessingTime();
- this.processingState = builder.getProcessingState();
}
public EventBase(UUID subscriptionId, DateTime requestedDate,
DateTime effectiveDate, DateTime processedDate,
long activeVersion, boolean isActive) {
- this(subscriptionId, requestedDate, effectiveDate, processedDate, activeVersion, isActive, null, null, EventLifecycleState.AVAILABLE);
- }
-
- private EventBase(UUID subscriptionId, DateTime requestedDate,
- DateTime effectiveDate, DateTime processedDate,
- long activeVersion, boolean isActive,
- UUID processingOwner, DateTime nextAvailableProcessingTime,
- EventLifecycleState processingState) {
- this(UUID.randomUUID(), subscriptionId, requestedDate, effectiveDate, processedDate, activeVersion, isActive,
- processingOwner, nextAvailableProcessingTime, processingState);
+ this(UUID.randomUUID(), subscriptionId, requestedDate, effectiveDate, processedDate, activeVersion, isActive);
}
public EventBase(UUID id, UUID subscriptionId, DateTime requestedDate,
DateTime effectiveDate, DateTime processedDate,
- long activeVersion, boolean isActive,
- UUID processingOwner, DateTime nextAvailableProcessingTime,
- EventLifecycleState processingState) {
+ long activeVersion, boolean isActive) {
this.uuid = id;
this.subscriptionId = subscriptionId;
this.requestedDate = requestedDate;
@@ -79,10 +61,6 @@ public abstract class EventBase implements EntitlementEvent {
this.activeVersion = activeVersion;
this.isActive = isActive;
- this.processingOwner = processingOwner;
- this.nextAvailableProcessingTime = nextAvailableProcessingTime;
- this.processingState = processingState;
-
}
@@ -138,64 +116,9 @@ public abstract class EventBase implements EntitlementEvent {
}
- @Override
- public UUID getOwner() {
- return processingOwner;
- }
-
- @Override
- public void setOwner(UUID owner) {
- this.processingOwner = owner;
- }
-
- @Override
- public DateTime getNextAvailableDate() {
- return nextAvailableProcessingTime;
- }
-
- @Override
- public void setNextAvailableDate(DateTime dateTime) {
- this.nextAvailableProcessingTime = dateTime;
- }
-
-
- @Override
- public EventLifecycleState getProcessingState() {
- return processingState;
- }
-
- @Override
- public void setProcessingState(EventLifecycleState processingState) {
- this.processingState = processingState;
- }
-
- @Override
- public boolean isAvailableForProcessing(DateTime now) {
-
- // Event got deactivated, will never be processed
- if (!isActive) {
- return false;
- }
-
- switch(processingState) {
- case AVAILABLE:
- break;
- case IN_PROCESSING:
- // Somebody already got the event, not available yet
- if (nextAvailableProcessingTime.isAfter(now)) {
- return false;
- }
- break;
- case PROCESSED:
- return false;
- default:
- throw new EntitlementError(String.format("Unkwnon IEvent processing state %s", processingState));
- }
- return effectiveDate.isBefore(now);
- }
//
- // Really used for unit tesrs only as the sql implementation relies on date first and then event insertion
+ // Really used for unit tests only as the sql implementation relies on date first and then event insertion
//
// Order first by:
// - effectiveDate, followed by processedDate, requestedDate
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBaseBuilder.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBaseBuilder.java
index 104fbef..17f5e15 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBaseBuilder.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/EventBaseBuilder.java
@@ -16,7 +16,6 @@
package com.ning.billing.entitlement.events;
-import com.ning.billing.entitlement.events.EventLifecycle.EventLifecycleState;
import org.joda.time.DateTime;
import java.util.UUID;
@@ -32,15 +31,11 @@ public class EventBaseBuilder<T extends EventBaseBuilder<T>> {
private long activeVersion;
private boolean isActive;
- private UUID processingOwner;
- private DateTime nextAvailableProcessingTime;
- private EventLifecycleState processingState;
public EventBaseBuilder() {
this.uuid = UUID.randomUUID();
this.isActive = true;
- this.processingState = EventLifecycleState.AVAILABLE;
}
public EventBaseBuilder(EventBaseBuilder<?> copy) {
@@ -52,9 +47,6 @@ public class EventBaseBuilder<T extends EventBaseBuilder<T>> {
this.activeVersion = copy.activeVersion;
this.isActive = copy.isActive;
- this.processingOwner = copy.processingOwner;
- this.nextAvailableProcessingTime = copy.nextAvailableProcessingTime;
- this.processingState = copy.processingState;
}
public T setUuid(UUID uuid) {
@@ -92,21 +84,6 @@ public class EventBaseBuilder<T extends EventBaseBuilder<T>> {
return (T) this;
}
- public T setProcessingOwner(UUID processingOwner) {
- this.processingOwner = processingOwner;
- return (T) this;
- }
-
- public T setNextAvailableProcessingTime(DateTime nextAvailableProcessingTime) {
- this.nextAvailableProcessingTime = nextAvailableProcessingTime;
- return (T) this;
- }
-
- public T setProcessingState(EventLifecycleState processingState) {
- this.processingState = processingState;
- return (T) this;
- }
-
public UUID getUuid() {
return uuid;
}
@@ -134,16 +111,4 @@ public class EventBaseBuilder<T extends EventBaseBuilder<T>> {
public boolean isActive() {
return isActive;
}
-
- public UUID getProcessingOwner() {
- return processingOwner;
- }
-
- public DateTime getNextAvailableProcessingTime() {
- return nextAvailableProcessingTime;
- }
-
- public EventLifecycleState getProcessingState() {
- return processingState;
- }
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBase.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBase.java
index 3f033a4..2438ddf 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBase.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBase.java
@@ -58,17 +58,6 @@ public class ApiEventBase extends EventBase implements ApiEvent {
}
- public ApiEventBase(UUID id, UUID subscriptionId, DateTime processed, String eventPlan, String eventPhase,
- String priceList, DateTime requestedDate, ApiEventType eventType, DateTime effectiveDate, long activeVersion,
- boolean isActive, UUID processingOwner, DateTime nextAvailableProcessingTime,EventLifecycleState processingState) {
- super(id, subscriptionId, requestedDate, effectiveDate, processed, activeVersion, isActive, processingOwner, nextAvailableProcessingTime, processingState);
- this.eventType = eventType;
- this.eventPlan = eventPlan;
- this.eventPlanPhase = eventPhase;
- this.eventPriceList = priceList;
- }
-
-
@Override
public ApiEventType getEventType() {
return eventType;
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 1098c15..0135ebf 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
@@ -31,9 +31,7 @@ import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
import com.ning.billing.entitlement.api.user.EntitlementUserApi;
import com.ning.billing.entitlement.api.user.SubscriptionApiService;
import com.ning.billing.entitlement.engine.addon.AddonUtils;
-import com.ning.billing.entitlement.engine.core.DefaultApiEventProcessor;
import com.ning.billing.entitlement.engine.core.Engine;
-import com.ning.billing.entitlement.engine.core.EventNotifier;
import com.ning.billing.entitlement.engine.dao.EntitlementDao;
import com.ning.billing.entitlement.engine.dao.EntitlementSqlDao;
import com.ning.billing.util.clock.Clock;
@@ -54,10 +52,6 @@ public class EntitlementModule extends AbstractModule {
bind(EntitlementConfig.class).toInstance(config);
}
- protected void installApiEventProcessor() {
- bind(EventNotifier.class).to(DefaultApiEventProcessor.class).asEagerSingleton();
- }
-
protected void installEntitlementDao() {
bind(EntitlementDao.class).to(EntitlementSqlDao.class).asEagerSingleton();
}
@@ -79,7 +73,6 @@ public class EntitlementModule extends AbstractModule {
protected void configure() {
installConfig();
installClock();
- installApiEventProcessor();
installEntitlementDao();
installEntitlementCore();
}
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql b/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
index 4540ad8..55ad7f4 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
@@ -14,24 +14,9 @@ CREATE TABLE events (
plist_name varchar(64) DEFAULT NULL,
current_version int(11) DEFAULT 1,
is_active bool DEFAULT 1,
- processing_owner char(36) DEFAULT NULL,
- processing_available_dt datetime DEFAULT NULL,
- processing_state varchar(14) DEFAULT 'AVAILABLE',
PRIMARY KEY(id)
) ENGINE=innodb;
-DROP TABLE IF EXISTS claimed_events;
-CREATE TABLE claimed_events (
- id int(11) unsigned NOT NULL AUTO_INCREMENT,
- sequence_id int(11) unsigned NOT NULL,
- owner_id char(36) NOT NULL,
- hostname varchar(64) NOT NULL,
- claimed_dt datetime NOT NULL,
- event_id char(36) NOT NULL,
- PRIMARY KEY(id)
-) ENGINE=innodb;
-
-
DROP TABLE IF EXISTS subscriptions;
CREATE TABLE subscriptions (
id char(36) NOT NULL,
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
index 31e9eaf..704e2c7 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
@@ -1,8 +1,8 @@
group EventSqlDao;
-getReadyEvents(now, max) ::= <<
- select
- event_id
+getEventById(event_id) ::= <<
+ select
+ event_id
, event_type
, user_type
, created_dt
@@ -14,48 +14,11 @@ getReadyEvents(now, max) ::= <<
, phase_name
, plist_name
, current_version
- , is_active
- , processing_owner
- , processing_available_dt
- , processing_state
- from events
- where
- effective_dt \<= :now
- and is_active = 1
- and processing_state != 'PROCESSED'
- and (processing_owner IS NULL OR processing_available_dt \<= :now)
- order by
- effective_dt asc
- , created_dt asc
- , requested_dt asc
- , id asc
- limit :max
- ;
->>
-
-claimEvent(owner, next_available, event_id, now) ::= <<
- update events
- set
- processing_owner = :owner
- , processing_available_dt = :next_available
- , processing_state = 'IN_PROCESSING'
- where
- event_id = :event_id
- and is_active = 1
- and processing_state != 'PROCESSED'
- and (processing_owner IS NULL OR processing_available_dt \<= :now)
- ;
->>
-
-clearEvent(event_id, owner) ::= <<
- update events
- set
- processing_owner = NULL
- , processing_state = 'PROCESSED'
- where
+ , is_active
+ from events
+ where
event_id = :event_id
- and processing_owner = :owner
- ;
+ ;
>>
insertEvent() ::= <<
@@ -73,9 +36,6 @@ insertEvent() ::= <<
, plist_name
, current_version
, is_active
- , processing_owner
- , processing_available_dt
- , processing_state
) values (
:event_id
, :event_type
@@ -90,9 +50,6 @@ insertEvent() ::= <<
, :plist_name
, :current_version
, :is_active
- , :processing_owner
- , :processing_available_dt
- , :processing_state
);
>>
@@ -103,22 +60,6 @@ removeEvents(subscription_id) ::= <<
;
>>
-insertClaimedHistory(sequence_id, owner_id, hostname, claimed_dt, event_id) ::= <<
- insert into claimed_events (
- sequence_id
- , owner_id
- , hostname
- , claimed_dt
- , event_id
- ) values (
- :sequence_id
- , :owner_id
- , :hostname
- , :claimed_dt
- , :event_id
- );
->>
-
unactiveEvent(event_id, now) ::= <<
update events
set
@@ -154,9 +95,6 @@ getFutureActiveEventForSubscription(subscription_id, now) ::= <<
, plist_name
, current_version
, is_active
- , processing_owner
- , processing_available_dt
- , processing_state
from events
where
subscription_id = :subscription_id
@@ -185,9 +123,6 @@ getEventsForSubscription(subscription_id) ::= <<
, plist_name
, current_version
, is_active
- , processing_owner
- , processing_available_dt
- , processing_state
from events
where
subscription_id = :subscription_id
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccount.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccount.java
new file mode 100644
index 0000000..cc08699
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccount.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.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDescription;
+
+public class BrainDeadAccount implements Account {
+
+ @Override
+ public String getExternalKey() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getName() {
+ throw new UnsupportedOperationException(); }
+
+ @Override
+ public int getFirstNameLength() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getEmail() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPhone() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getBillCycleDay() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Currency getCurrency() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPaymentProviderName() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getFieldValue(String fieldName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setFieldValue(String fieldName, String fieldValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<CustomField> getFieldList() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clearFields() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getObjectName() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public UUID getId() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<Tag> getTagList() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean hasTag(String tagName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addTag(TagDescription description, String addedBy,
+ DateTime dateAdded) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addTags(List<Tag> tags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clearTags() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeTag(TagDescription description) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean generateInvoice() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean processPayment() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public BigDecimal getBalance() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addFields(List<CustomField> fields) {
+ throw new UnsupportedOperationException();
+
+ }
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java
new file mode 100644
index 0000000..94f0ea7
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java
@@ -0,0 +1,63 @@
+/*
+ * 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 com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.Tag;
+
+public class BrainDeadAccountUserApi implements AccountUserApi {
+
+
+ @Override
+ public Account getAccountByKey(String key) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Account getAccountById(UUID accountId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<Account> getAccounts() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public UUID getIdFromKey(String externalKey) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Account createAccount(AccountData data, List<CustomField> fields,
+ List<Tag> tags) throws AccountApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void updateAccount(Account account) {
+ throw new UnsupportedOperationException();
+ }
+
+}
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
new file mode 100644
index 0000000..94fd234
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java
@@ -0,0 +1,141 @@
+/*
+ * 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.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+
+class BrainDeadMockEntitlementDao implements EntitlementDao {
+
+ @Override
+ public List<SubscriptionBundle> getSubscriptionBundleForAccount(
+ UUID accountId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SubscriptionBundle createSubscriptionBundle(
+ SubscriptionBundleData bundle) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Subscription getSubscriptionFromId(UUID subscriptionId) {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public Subscription getBaseSubscription(UUID bundleId) {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public List<Subscription> getSubscriptions(UUID bundleId) {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public List<Subscription> getSubscriptionsForKey(String bundleKey) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void updateSubscription(SubscriptionData subscription) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void createNextPhaseEvent(UUID subscriptionId,
+ EntitlementEvent nextPhase) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<EntitlementEvent> getEventsForSubscription(
+ UUID subscriptionId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<EntitlementEvent> getPendingEventsForSubscription(
+ UUID subscriptionId) {
+ throw new UnsupportedOperationException();
+ }
+
+
+ @Override
+ public void createSubscription(SubscriptionData subscription,
+ List<EntitlementEvent> initialEvents) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void cancelSubscription(UUID subscriptionId,
+ EntitlementEvent cancelEvent) {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public void uncancelSubscription(UUID subscriptionId,
+ List<EntitlementEvent> uncancelEvents) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void changePlan(UUID subscriptionId,
+ List<EntitlementEvent> changeEvents) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void migrate(UUID acountId, AccountMigrationData data) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void undoMigration(UUID accountId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public EntitlementEvent getEventById(UUID eventId) {
+ throw new UnsupportedOperationException();
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..08b05d6
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
@@ -0,0 +1,281 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.catalog.DefaultCatalogService;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Plan;
+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.Subscription;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
+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.DefaultClock;
+
+public class TestDefaultEntitlementBillingApi {
+ private static final UUID zeroId = new UUID(0L,0L);
+ private static final UUID oneId = new UUID(1L,0L);
+ private static final UUID twoId = new UUID(2L,0L);
+
+ private CatalogService catalogService;
+ private ArrayList<SubscriptionBundle> bundles;
+ private ArrayList<Subscription> subscriptions;
+ private ArrayList<SubscriptionTransition> transitions;
+ private BrainDeadMockEntitlementDao dao;
+
+ private Clock clock;
+ private SubscriptionData subscription;
+ private DateTime subscriptionStartDate;
+
+ @BeforeClass(groups={"setup"})
+ public void setup() throws ServiceException {
+ TestApiBase.loadSystemPropertiesFromClasspath("/entitlement.properties");
+ final Injector g = Guice.createInjector(Stage.PRODUCTION, new CatalogModule(), new AbstractModule() {
+ protected void configure() {
+ bind(Clock.class).to(DefaultClock.class).asEagerSingleton();
+ }
+ });
+
+
+ catalogService = g.getInstance(CatalogService.class);
+ clock = g.getInstance(Clock.class);
+
+ ((DefaultCatalogService)catalogService).loadCatalog();
+ }
+
+ @BeforeMethod
+ public void setupEveryTime() {
+ bundles = new ArrayList<SubscriptionBundle>();
+ final SubscriptionBundle bundle = new SubscriptionBundleData( zeroId,"TestKey", oneId, new DateTime().minusDays(4));
+ bundles.add(bundle);
+
+
+ transitions = new ArrayList<SubscriptionTransition>();
+
+
+ subscriptions = new ArrayList<Subscription>();
+
+ SubscriptionBuilder builder = new SubscriptionBuilder();
+ subscriptionStartDate = new DateTime().minusDays(3);
+ builder.setStartDate(subscriptionStartDate);
+ subscription = new SubscriptionData(builder) {
+ public List<SubscriptionTransition> getAllTransitions() {
+ return transitions;
+ }
+ };
+
+ subscriptions.add(subscription);
+
+ dao = new BrainDeadMockEntitlementDao() {
+ public List<SubscriptionBundle> getSubscriptionBundleForAccount(
+ UUID accountId) {
+ return bundles;
+
+ }
+
+ public List<Subscription> getSubscriptions(UUID bundleId) {
+ return subscriptions;
+ }
+
+ public Subscription getSubscriptionFromId(UUID subscriptionId) {
+ return subscription;
+
+ }
+
+ @Override
+ public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
+ return bundle;
+ }
+
+
+ };
+
+ }
+
+ @Test
+ public void testBillingEventsEmpty() {
+ EntitlementDao dao = new BrainDeadMockEntitlementDao() {
+ public List<SubscriptionBundle> getSubscriptionBundleForAccount(
+ UUID accountId) {
+ return new ArrayList<SubscriptionBundle>();
+ }
+
+ };
+ 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
+ public void testBillingEventsNoBillingPeriod() throws CatalogApiException {
+ DateTime now = clock.getUTCNow();
+ DateTime then = now.minusDays(1);
+ Plan nextPlan = catalogService.getFullCatalog().findPlan("shotgun-annual", now);
+ PlanPhase nextPhase = nextPlan.getAllPhases()[0]; // The trial has no billing period
+ String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+ SubscriptionTransition t = new SubscriptionTransitionData(
+ zeroId, oneId, twoId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList);
+ transitions.add(t);
+
+ AccountUserApi accountApi = new BrainDeadAccountUserApi(){
+
+ @Override
+ public Account getAccountById(UUID accountId) {
+ return new BrainDeadAccount(){@Override
+ public int getBillCycleDay() {
+ return 32;
+ }};
+ }} ;
+ DefaultEntitlementBillingApi api = new DefaultEntitlementBillingApi(dao,accountApi,catalogService);
+ SortedSet<BillingEvent> events = api.getBillingEventsForAccount(new UUID(0L,0L));
+ checkFirstEvent(events, nextPlan, 32, oneId, now, nextPhase, ApiEventType.CREATE.toString());
+ }
+
+ @Test
+ public void testBillingEventsAnual() throws CatalogApiException {
+ DateTime now = clock.getUTCNow();
+ DateTime then = now.minusDays(1);
+ Plan nextPlan = catalogService.getFullCatalog().findPlan("shotgun-annual", now);
+ PlanPhase nextPhase = nextPlan.getAllPhases()[1];
+ String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+ SubscriptionTransition t = new SubscriptionTransitionData(
+ zeroId, oneId, twoId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList);
+ transitions.add(t);
+
+ AccountUserApi accountApi = new BrainDeadAccountUserApi(){
+
+ @Override
+ public Account getAccountById(UUID accountId) {
+ return new BrainDeadAccount(){@Override
+ public int getBillCycleDay() {
+ return 1;
+ }};
+ }} ;
+ DefaultEntitlementBillingApi api = new DefaultEntitlementBillingApi(dao,accountApi,catalogService);
+ SortedSet<BillingEvent> events = api.getBillingEventsForAccount(new UUID(0L,0L));
+ checkFirstEvent(events, nextPlan, subscription.getStartDate().getDayOfMonth(), oneId, now, nextPhase, ApiEventType.CREATE.toString());
+ }
+
+ @Test
+ public void testBillingEventsMonthly() throws CatalogApiException {
+ DateTime now = clock.getUTCNow();
+ DateTime then = now.minusDays(1);
+ Plan nextPlan = catalogService.getFullCatalog().findPlan("shotgun-annual", now);
+ PlanPhase nextPhase = nextPlan.getAllPhases()[1];
+ String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+ SubscriptionTransition t = new SubscriptionTransitionData(
+ zeroId, oneId, twoId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList);
+ transitions.add(t);
+
+ AccountUserApi accountApi = new BrainDeadAccountUserApi(){
+
+ @Override
+ public Account getAccountById(UUID accountId) {
+ return new BrainDeadAccount(){@Override
+ public int getBillCycleDay() {
+ return 32;
+ }};
+ }} ;
+ DefaultEntitlementBillingApi api = new DefaultEntitlementBillingApi(dao,accountApi,catalogService);
+ SortedSet<BillingEvent> events = api.getBillingEventsForAccount(new UUID(0L,0L));
+ checkFirstEvent(events, nextPlan, 32, oneId, now, nextPhase, ApiEventType.CREATE.toString());
+ }
+
+ @Test
+ public void testBillingEventsAddOn() throws CatalogApiException {
+ DateTime now = clock.getUTCNow();
+ DateTime then = now.minusDays(1);
+ Plan nextPlan = catalogService.getFullCatalog().findPlan("shotgun-annual", now);
+ PlanPhase nextPhase = nextPlan.getAllPhases()[0];
+ String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+ SubscriptionTransition t = new SubscriptionTransitionData(
+ zeroId, oneId, twoId, EventType.API_USER, ApiEventType.CREATE, then, now, null, null, null, null, SubscriptionState.ACTIVE, nextPlan, nextPhase, nextPriceList);
+ transitions.add(t);
+
+ AccountUserApi accountApi = new BrainDeadAccountUserApi(){
+
+ @Override
+ public Account getAccountById(UUID accountId) {
+ return new BrainDeadAccount(){@Override
+ public int getBillCycleDay() {
+ return 1;
+ }};
+ }} ;
+ DefaultEntitlementBillingApi api = new DefaultEntitlementBillingApi(dao,accountApi,catalogService);
+ SortedSet<BillingEvent> events = api.getBillingEventsForAccount(new UUID(0L,0L));
+ checkFirstEvent(events, nextPlan, bundles.get(0).getStartDate().getDayOfMonth(), oneId, now, nextPhase, ApiEventType.CREATE.toString());
+ }
+
+
+ private void checkFirstEvent(SortedSet<BillingEvent> events, Plan nextPlan,
+ int BCD, UUID id, DateTime time, PlanPhase nextPhase, String desc) throws CatalogApiException {
+ Assert.assertEquals(events.size(), 1);
+ BillingEvent event = events.first();
+ if(nextPhase.getFixedPrice() != null) {
+ Assert.assertEquals(nextPhase.getFixedPrice().getPrice(Currency.USD), event.getFixedPrice().getPrice(Currency.USD));
+ }
+ if(nextPhase.getRecurringPrice() != null) {
+ Assert.assertEquals(nextPhase.getRecurringPrice().getPrice(Currency.USD), event.getRecurringPrice().getPrice(Currency.USD));
+ }
+
+ Assert.assertEquals(BCD, event.getBillCycleDay());
+ Assert.assertEquals(id, event.getSubscription().getId());
+ Assert.assertEquals(time, event.getEffectiveDate());
+ Assert.assertEquals(nextPhase, event.getPlanPhase());
+ Assert.assertEquals(nextPlan, event.getPlan());
+ Assert.assertEquals(nextPhase.getBillingPeriod(), event.getBillingPeriod());
+ Assert.assertEquals(BillingModeType.IN_ADVANCE, event.getBillingMode());
+ Assert.assertEquals(desc, event.getDescription());
+ Assert.assertEquals(nextPhase.getFixedPrice(), event.getFixedPrice());
+ Assert.assertEquals(nextPhase.getRecurringPrice(), event.getRecurringPrice());
+ }
+
+
+}
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 e4cc948..30df1f9 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
@@ -93,7 +93,7 @@ public abstract class TestApiBase {
protected ApiTestListener testListener;
protected SubscriptionBundle bundle;
- public static void loadSystemPropertiesFromClasspath( final String resource )
+ public static void loadSystemPropertiesFromClasspath(final String resource)
{
final URL url = TestApiBase.class.getResource(resource);
assertNotNull(url);
@@ -147,7 +147,7 @@ public abstract class TestApiBase {
accountData = getAccountData();
assertNotNull(accountData);
- catalog = catalogService.getCatalog();
+ catalog = catalogService.getFullCatalog();
assertNotNull(catalog);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
index d7b9bf4..4b251f4 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
@@ -237,7 +237,7 @@ public class TestUserApiAddOn extends TestApiBase {
ProductCategory.ADD_ON,
aoTerm,
aoPriceList);
- PlanAlignmentCreate alignement = catalog.planCreateAlignment(planSpecifier);
+ PlanAlignmentCreate alignement = catalog.planCreateAlignment(planSpecifier, clock.getUTCNow());
assertEquals(alignement, PlanAlignmentCreate.START_OF_BUNDLE);
testAddonCreateInternal(aoProduct, aoTerm, aoPriceList, alignement);
@@ -269,7 +269,7 @@ public class TestUserApiAddOn extends TestApiBase {
ProductCategory.ADD_ON,
aoTerm,
aoPriceList);
- PlanAlignmentCreate alignement = catalog.planCreateAlignment(planSpecifier);
+ PlanAlignmentCreate alignement = catalog.planCreateAlignment(planSpecifier, clock.getUTCNow());
assertEquals(alignement, PlanAlignmentCreate.START_OF_SUBSCRIPTION);
testAddonCreateInternal(aoProduct, aoTerm, aoPriceList, alignement);
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 187c080..87491c7 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
@@ -32,7 +32,7 @@ public class TestUserApiCancelSql extends TestUserApiCancel {
return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
}
- @Test(enabled= true, groups={"stress"})
+ @Test(enabled= false, groups={"stress"})
public void stressTest() {
for (int i = 0; i < MAX_STRESS_ITERATIONS; i++) {
cleanupTest();
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 ab32386..2c17255 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
@@ -198,6 +198,15 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
testListener.pushExpectedEvent(NextEvent.PHASE);
clock.addDeltaFromReality(currentPhase.getDuration());
DateTime futureNow = clock.getUTCNow();
+
+ /*
+ try {
+ Thread.sleep(1000 * 3000);
+ } catch (Exception e) {
+
+ }
+ */
+
assertTrue(futureNow.isAfter(nextExpectedPhaseChange));
assertTrue(testListener.isCompleted(3000));
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
index ff55ca0..d5546d7 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
@@ -23,6 +23,8 @@ import static org.testng.Assert.assertTrue;
import java.util.List;
import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.testng.Assert;
import com.ning.billing.catalog.api.BillingPeriod;
@@ -38,7 +40,7 @@ import com.ning.billing.entitlement.events.phase.PhaseEvent;
import com.ning.billing.util.clock.DefaultClock;
public abstract class TestUserApiCreate extends TestApiBase {
-
+ Logger log = LoggerFactory.getLogger(TestUserApiCreate.class);
public void testCreateWithRequestedDate() {
log.info("Starting testCreateWithRequestedDate");
@@ -68,6 +70,7 @@ public abstract class TestUserApiCreate extends TestApiBase {
assertTrue(testListener.isCompleted(5000));
} catch (EntitlementUserApiException e) {
+ log.error("Unexpected exception",e);
Assert.fail(e.getMessage());
}
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
index 9917228..81ee501 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
@@ -50,17 +50,16 @@ public class TestUserApiError extends TestApiBase {
@Test(enabled=true, groups={"fast"})
public void testCreateSubscriptionBadCatalog() {
// WRONG PRODUTCS
- tCreateSubscriptionInternal(bundle.getId(), null, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.CAT_NO_SUCH_PRODUCT);
+ tCreateSubscriptionInternal(bundle.getId(), null, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.CAT_NULL_PRODUCT_NAME);
tCreateSubscriptionInternal(bundle.getId(), "Whatever", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.CAT_NO_SUCH_PRODUCT);
// TODO: MARTIN TO FIX WITH CORRECT ERROR CODE. RIGHT NOW NPE
// WRONG BILLING PERIOD
- //tCreateSubscriptionInternal(bundle.getId(), "Shotgun", null, IPriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.);
+ tCreateSubscriptionInternal(bundle.getId(), "Shotgun", null, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.CAT_PLAN_NOT_FOUND);
// WRONG PLAN SET
- //tCreateSubscriptionInternal(bundle.getId(), "Shotgun", BillingPeriod.ANNUAL, null, ErrorCode.);
- //tCreateSubscriptionInternal(bundle.getId(), "Shotgun", BillingPeriod.ANNUAL, "Whatever", ErrorCode.);
+ tCreateSubscriptionInternal(bundle.getId(), "Shotgun", BillingPeriod.ANNUAL, "Whatever", ErrorCode.CAT_PRICE_LIST_NOT_FOUND);
}
@@ -118,7 +117,7 @@ public class TestUserApiError extends TestApiBase {
entitlementApi.createSubscription(bundleId,
getProductSpecifier(productName, planSet, term, null),
clock.getUTCNow());
- assertFalse(true);
+ Assert.fail("Exception expected, error code: " + expected);
} catch (EntitlementUserApiException e) {
assertEquals(e.getCode(), expected.getCode());
try {
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDao.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDao.java
index 74ac1e7..3623da2 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDao.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDao.java
@@ -17,5 +17,6 @@
package com.ning.billing.entitlement.engine.dao;
public interface MockEntitlementDao {
+
public void reset();
}
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 7fc8342..86e458f 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
@@ -31,16 +31,26 @@ import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
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;
import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
-import com.ning.billing.entitlement.events.EventLifecycle.EventLifecycleState;
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 {
@@ -52,15 +62,15 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
private final Clock clock;
private final EntitlementConfig config;
private final SubscriptionFactory factory;
-
-
+ private final NotificationQueueService notificationQueueService;
@Inject
- public MockEntitlementDaoMemory(Clock clock, EntitlementConfig config, SubscriptionFactory factory) {
+ public MockEntitlementDaoMemory(Clock clock, EntitlementConfig config, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
super();
this.clock = clock;
this.config = config;
this.factory = factory;
+ this.notificationQueueService = notificationQueueService;
this.bundles = new ArrayList<SubscriptionBundle>();
this.subscriptions = new ArrayList<Subscription>();
this.events = new TreeSet<EntitlementEvent>();
@@ -138,6 +148,14 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
synchronized(events) {
events.addAll(initalEvents);
+ for (final EntitlementEvent cur : initalEvents) {
+ recordFutureNotificationFromTransaction(null, cur.getEffectiveDate(), new NotificationKey() {
+ @Override
+ public String toString() {
+ return cur.getId().toString();
+ }
+ });
+ }
}
Subscription updatedSubscription = buildSubscription(subscription);
subscriptions.add(updatedSubscription);
@@ -174,7 +192,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
List<EntitlementEvent> results = new LinkedList<EntitlementEvent>();
for (EntitlementEvent cur : events) {
if (cur.isActive() &&
- cur.getProcessingState() == EventLifecycleState.AVAILABLE &&
+ cur.getEffectiveDate().isAfter(clock.getUTCNow()) &&
cur.getSubscriptionId().equals(subscriptionId)) {
results.add(cur);
}
@@ -202,39 +220,6 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
- @Override
- public List<EntitlementEvent> getEventsReady(UUID ownerId, int sequenceId) {
- synchronized(events) {
- List<EntitlementEvent> readyList = new LinkedList<EntitlementEvent>();
- for (EntitlementEvent cur : events) {
- if (cur.isAvailableForProcessing(clock.getUTCNow())) {
-
- if (cur.getOwner() != null) {
- log.warn(String.format("EventProcessor %s stealing event %s from %s", ownerId, cur, cur.getOwner()));
- }
- cur.setOwner(ownerId);
- cur.setNextAvailableDate(clock.getUTCNow().plus(config.getDaoClaimTimeMs()));
- cur.setProcessingState(EventLifecycleState.IN_PROCESSING);
- readyList.add(cur);
- }
- }
- Collections.sort(readyList);
- return readyList;
- }
- }
-
- @Override
- public void clearEventsReady(UUID ownerId, Collection<EntitlementEvent> cleared) {
- synchronized(events) {
- for (EntitlementEvent cur : cleared) {
- if (cur.getOwner().equals(ownerId)) {
- cur.setProcessingState(EventLifecycleState.PROCESSED);
- } else {
- log.warn(String.format("EventProcessor %s trying to clear event %s that it does not own", ownerId, cur));
- }
- }
- }
- }
private Subscription buildSubscription(SubscriptionData in) {
return factory.createSubscription(new SubscriptionBuilder(in), getEventsForSubscription(in.getId()));
@@ -272,12 +257,26 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
cancelNextChangeEvent(subscriptionId);
cancelNextPhaseEvent(subscriptionId);
events.addAll(changeEvents);
+ for (final EntitlementEvent cur : changeEvents) {
+ recordFutureNotificationFromTransaction(null, cur.getEffectiveDate(), new NotificationKey() {
+ @Override
+ public String toString() {
+ return cur.getId().toString();
+ }
+ });
+ }
}
}
- private void insertEvent(EntitlementEvent event) {
+ private void insertEvent(final EntitlementEvent event) {
synchronized(events) {
events.add(event);
+ recordFutureNotificationFromTransaction(null, event.getEffectiveDate(), new NotificationKey() {
+ @Override
+ public String toString() {
+ return event.getId().toString();
+ }
+ });
}
}
@@ -298,10 +297,11 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
continue;
}
if (cur.getType() == EventType.PHASE &&
- cur.getProcessingState() == EventLifecycleState.AVAILABLE) {
+ cur.getEffectiveDate().isAfter(clock.getUTCNow())) {
cur.deactivate();
break;
}
+
}
}
}
@@ -319,7 +319,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
if (cur.getType() == EventType.API_USER &&
ApiEventType.CHANGE == ((ApiEvent) cur).getEventType() &&
- cur.getProcessingState() == EventLifecycleState.AVAILABLE) {
+ cur.getEffectiveDate().isAfter(clock.getUTCNow())) {
cur.deactivate();
break;
}
@@ -364,8 +364,15 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
SubscriptionBundleData bundleData = curBundle.getData();
for (SubscriptionMigrationData curSubscription : curBundle.getSubscriptions()) {
SubscriptionData subData = curSubscription.getData();
- for (EntitlementEvent curEvent : curSubscription.getInitialEvents()) {
+ for (final EntitlementEvent curEvent : curSubscription.getInitialEvents()) {
events.add(curEvent);
+ recordFutureNotificationFromTransaction(null, curEvent.getEffectiveDate(), new NotificationKey() {
+ @Override
+ public String toString() {
+ return curEvent.getId().toString();
+ }
+ });
+
}
subscriptions.add(subData);
}
@@ -393,4 +400,26 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
}
+
+ @Override
+ public EntitlementEvent getEventById(UUID eventId) {
+ synchronized(events) {
+ for (EntitlementEvent cur : events) {
+ if (cur.getId().equals(eventId)) {
+ return cur;
+ }
+ }
+ }
+ return null;
+ }
+
+ private void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao, final DateTime effectiveDate, final NotificationKey notificationKey) {
+ try {
+ NotificationQueue subscritionEventQueue = notificationQueueService.getNotificationQueue(Engine.ENTITLEMENT_SERVICE_NAME,
+ Engine.NOTIFICATION_QUEUE_NAME);
+ subscritionEventQueue.recordFutureNotificationFromTransaction(transactionalDao, effectiveDate, notificationKey);
+ } catch (NoSuchNotificationQueue e) {
+ throw new RuntimeException(e);
+ }
+ }
}
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 2ebdea9..503fd1a 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
@@ -20,6 +20,8 @@ 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.Transaction;
import org.skife.jdbi.v2.TransactionStatus;
@@ -32,11 +34,12 @@ public class MockEntitlementDaoSql extends EntitlementSqlDao implements MockEnti
private final ResetSqlDao resetDao;
@Inject
- public MockEntitlementDaoSql(DBI dbi, Clock clock, EntitlementConfig config, SubscriptionFactory factory) {
- super(dbi, clock, config, factory);
+ public MockEntitlementDaoSql(DBI dbi, Clock clock, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
+ super(dbi, clock, factory, notificationQueueService);
this.resetDao = dbi.onDemand(ResetSqlDao.class);
}
+
@Override
public void reset() {
resetDao.inTransaction(new Transaction<Void, ResetSqlDao>() {
@@ -45,9 +48,10 @@ public class MockEntitlementDaoSql extends EntitlementSqlDao implements MockEnti
public Void inTransaction(ResetSqlDao dao, TransactionStatus status)
throws Exception {
resetDao.resetEvents();
- resetDao.resetClaimedEvents();
resetDao.resetSubscriptions();
resetDao.resetBundles();
+ resetDao.resetClaimedNotifications();
+ resetDao.resetNotifications();
return null;
}
});
@@ -58,14 +62,17 @@ public class MockEntitlementDaoSql extends EntitlementSqlDao implements MockEnti
@SqlUpdate("truncate table events")
public void resetEvents();
- @SqlUpdate("truncate table claimed_events")
- public void resetClaimedEvents();
-
@SqlUpdate("truncate table subscriptions")
public void resetSubscriptions();
@SqlUpdate("truncate table bundles")
public void resetBundles();
- }
+ @SqlUpdate("truncate table notifications")
+ public void resetNotifications();
+
+ @SqlUpdate("truncate table claimed_notifications")
+ public void resetClaimedNotifications();
+
+ }
}
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 dc422a6..1555bdb 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
@@ -16,10 +16,12 @@
package com.ning.billing.entitlement.glue;
+import com.ning.billing.account.glue.AccountModuleMock;
import com.ning.billing.catalog.glue.CatalogModule;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.ClockMock;
import com.ning.billing.util.glue.EventBusModule;
+import com.ning.billing.util.glue.NotificationQueueModule;
public class MockEngineModule extends EntitlementModule {
@@ -33,6 +35,7 @@ public class MockEngineModule extends EntitlementModule {
super.configure();
install(new EventBusModule());
install(new CatalogModule());
+ install(new AccountModuleMock());
}
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleMemory.java
index c6ce269..786f1e3 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleMemory.java
@@ -17,16 +17,12 @@
package com.ning.billing.entitlement.glue;
-import com.ning.billing.entitlement.engine.core.EventNotifier;
-import com.ning.billing.entitlement.engine.core.MockApiEventProcessorMemory;
import com.ning.billing.entitlement.engine.dao.EntitlementDao;
import com.ning.billing.entitlement.engine.dao.MockEntitlementDaoMemory;
+import com.ning.billing.util.notificationq.MockNotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService;
public class MockEngineModuleMemory extends MockEngineModule {
- @Override
- protected void installApiEventProcessor() {
- bind(EventNotifier.class).to(MockApiEventProcessorMemory.class).asEagerSingleton();
- }
@Override
protected void installEntitlementDao() {
@@ -34,8 +30,12 @@ public class MockEngineModuleMemory extends MockEngineModule {
}
+ private void installNotificationQueue() {
+ bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
+ }
@Override
protected void configure() {
super.configure();
+ installNotificationQueue();
}
}
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 f1cd237..dbe2938 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
@@ -22,6 +22,8 @@ import com.ning.billing.entitlement.engine.dao.EntitlementDao;
import com.ning.billing.entitlement.engine.dao.MockEntitlementDaoSql;
import com.ning.billing.util.clock.Clock;
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;
@@ -47,6 +49,7 @@ public class MockEngineModuleSql extends MockEngineModule {
@Override
protected void configure() {
installDBI();
+ install(new NotificationQueueModule());
super.configure();
}
}
diff --git a/entitlement/src/test/resources/testInput.xml b/entitlement/src/test/resources/testInput.xml
index 3d36b9d..8a97d57 100644
--- a/entitlement/src/test/resources/testInput.xml
+++ b/entitlement/src/test/resources/testInput.xml
@@ -39,7 +39,7 @@ Use Cases to do:
<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
- <effectiveDate>2011-10-08T00:00:00+00:00</effectiveDate>
+ <effectiveDate>2011-01-01T00:00:00+00:00</effectiveDate>
<catalogName>Firearms</catalogName>
<currencies>
@@ -151,16 +151,16 @@ Use Cases to do:
</createAlignmentCase>
</createAlignment>
<billingAlignment>
- <billingAlignmentCase>
- <alignment>ACCOUNT</alignment>
- </billingAlignmentCase>
+ <billingAlignmentCase>
+ <productCategory>ADD_ON</productCategory>
+ <alignment>BUNDLE</alignment>
+ </billingAlignmentCase>
<billingAlignmentCase>
<billingPeriod>ANNUAL</billingPeriod>
<alignment>SUBSCRIPTION</alignment>
</billingAlignmentCase>
<billingAlignmentCase>
- <productCategory>ADD_ON</productCategory>
- <alignment>BUNDLE</alignment>
+ <alignment>ACCOUNT</alignment>
</billingAlignmentCase>
</billingAlignment>
<priceList>
invoice/pom.xml 17(+16 -1)
diff --git a/invoice/pom.xml b/invoice/pom.xml
index 3d7dc27..8c939ca 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.0.21-SNAPSHOT</version>
+ <version>0.1.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-invoice</artifactId>
@@ -35,6 +35,21 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-entitlement</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-catalog</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-catalog</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
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 1186456..20f33c7 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
@@ -16,6 +16,8 @@
package com.ning.billing.invoice.dao;
+import sun.reflect.generics.reflectiveObjects.NotImplementedException;
+
import com.google.inject.Inject;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceCreationNotification;
@@ -40,7 +42,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
private final static Logger log = LoggerFactory.getLogger(DefaultInvoiceDao.class);
@Inject
- public DefaultInvoiceDao(IDBI dbi, EventBus eventBus) {
+ public DefaultInvoiceDao(final IDBI dbi, final EventBus eventBus) {
this.invoiceDao = dbi.onDemand(InvoiceSqlDao.class);
this.eventBus = eventBus;
}
@@ -54,7 +56,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
public List<Invoice> get() {
return invoiceDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
@Override
- public List<Invoice> inTransaction(InvoiceSqlDao invoiceDao, TransactionStatus status) throws Exception {
+ public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
List<Invoice> invoices = invoiceDao.get();
InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
@@ -72,7 +74,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
public Invoice getById(final String invoiceId) {
return invoiceDao.inTransaction(new Transaction<Invoice, InvoiceSqlDao>() {
@Override
- public Invoice inTransaction(InvoiceSqlDao invoiceDao, TransactionStatus status) throws Exception {
+ public Invoice inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
Invoice invoice = invoiceDao.getById(invoiceId);
if (invoice != null) {
@@ -87,25 +89,24 @@ public class DefaultInvoiceDao implements InvoiceDao {
}
@Override
- public void save(final Invoice invoice) {
+ public void create(final Invoice invoice) {
invoiceDao.inTransaction(new Transaction<Void, InvoiceSqlDao>() {
@Override
- public Void inTransaction(InvoiceSqlDao invoiceDao, TransactionStatus status) throws Exception {
+ public Void inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
Invoice currentInvoice = invoiceDao.getById(invoice.getId().toString());
- invoiceDao.save(invoice);
-
- List<InvoiceItem> invoiceItems = invoice.getItems();
- InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
- invoiceItemDao.save(invoiceItems);
if (currentInvoice == null) {
+ invoiceDao.create(invoice);
+
+ List<InvoiceItem> invoiceItems = invoice.getItems();
+ InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
+ invoiceItemDao.create(invoiceItems);
+
InvoiceCreationNotification event;
event = new DefaultInvoiceCreationNotification(invoice.getId(), invoice.getAccountId(),
invoice.getAmountOutstanding(), invoice.getCurrency(),
invoice.getInvoiceDate());
eventBus.post(event);
- } else {
-
}
return null;
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 b59e4ed..e9306b2 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
@@ -23,7 +23,7 @@ import java.util.UUID;
import com.ning.billing.invoice.api.Invoice;
public interface InvoiceDao {
- void save(Invoice invoice);
+ void create(Invoice invoice);
Invoice getById(final String id);
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 a89749e..6f4e47b 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
@@ -50,10 +50,14 @@ public interface InvoiceItemSqlDao extends EntityDao<InvoiceItem> {
@Override
@SqlUpdate
- void save(@InvoiceItemBinder final InvoiceItem invoiceItem);
+ void create(@InvoiceItemBinder final InvoiceItem invoiceItem);
+
+ @Override
+ @SqlUpdate
+ void update(@InvoiceItemBinder final InvoiceItem invoiceItem);
@SqlBatch
- void save(@InvoiceItemBinder final List<InvoiceItem> items);
+ void create(@InvoiceItemBinder final List<InvoiceItem> items);
@BindingAnnotation(InvoiceItemBinder.InvoiceItemBinderFactory.class)
@Retention(RetentionPolicy.RUNTIME)
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 aa0051a..9ede145 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
@@ -53,7 +53,11 @@ import java.util.UUID;
public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<InvoiceSqlDao>, Transmogrifier, CloseMe {
@Override
@SqlUpdate
- void save(@InvoiceBinder Invoice invoice);
+ void create(@InvoiceBinder Invoice invoice);
+
+ @Override
+ @SqlUpdate
+ void update(@InvoiceBinder Invoice invoice);
@SqlQuery
List<Invoice> getInvoicesByAccount(@Bind("accountId") final String accountId);
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 797f587..97f82e0 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
@@ -126,6 +126,10 @@ public class DefaultInvoice implements Invoice {
@Override
public boolean isDueForPayment(final DateTime targetDate, final int numberOfDays) {
+ if (getTotalAmount().compareTo(BigDecimal.ZERO) == 0) {
+ return false;
+ }
+
if (lastPaymentAttempt == null) {
return true;
}
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 54ca894..3b9311e 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,22 +16,29 @@
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.UUID;
+
+import org.joda.time.DateTime;
+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;
import com.ning.billing.entitlement.api.billing.BillingEvent;
import com.ning.billing.entitlement.api.billing.BillingModeType;
import com.ning.billing.invoice.api.BillingEventSet;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceItem;
-import org.joda.time.DateTime;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.UUID;
public class DefaultInvoiceGenerator implements InvoiceGenerator {
+ 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);}
@@ -96,7 +103,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
BillingEvent thisEvent = events.get(i);
BillingEvent nextEvent = events.get(i + 1);
- if (thisEvent.getSubscriptionId() == nextEvent.getSubscriptionId()) {
+ if (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) {
processEvents(invoiceId, thisEvent, nextEvent, items, targetDate, targetCurrency);
} else {
processEvent(invoiceId, thisEvent, items, targetDate, targetCurrency);
@@ -112,26 +119,40 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
}
private void processEvent(UUID invoiceId, BillingEvent event, List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) {
- BigDecimal rate = event.getPrice(targetCurrency);
- BigDecimal invoiceItemAmount = calculateInvoiceItemAmount(event, targetDate, rate);
- BillingMode billingMode = getBillingMode(event.getBillingMode());
- DateTime billThroughDate = billingMode.calculateEffectiveEndDate(event.getEffectiveDate(), targetDate, event.getBillCycleDay(), event.getBillingPeriod());
-
- addInvoiceItem(invoiceId, items, event, billThroughDate, invoiceItemAmount, rate, 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());
+
+ addInvoiceItem(invoiceId, items, event, billThroughDate, invoiceItemAmount, rate, targetCurrency);
+ } catch (CatalogApiException e) {
+ 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) {
- BigDecimal rate = firstEvent.getPrice(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());
-
- addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, invoiceItemAmount, rate, targetCurrency);
+ //TODO: Jeff getPrice() -> getRecurringPrice()
+ 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());
+
+ addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, invoiceItemAmount, rate, targetCurrency);
+ } catch (CatalogApiException e) {
+ 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.getSubscriptionId(), event.getEffectiveDate(), billThroughDate, event.getDescription(), amount, rate, currency);
+ DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, event.getSubscription().getId(), event.getEffectiveDate(), billThroughDate, event.getDescription(), amount, rate, currency);
items.add(item);
}
}
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 490d075..4dc783f 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
@@ -25,12 +25,16 @@ getInvoiceItemsBySubscription() ::= <<
WHERE subscription_id = :subscriptionId;
>>
-save() ::= <<
+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)
- ON DUPLICATE KEY UPDATE
- start_date = :startDate, end_date = :endDate, description = :description,
- amount = :amount, rate = :rate, currency = :currency''
+ VALUES(:id, :invoiceId, :subscriptionId, :startDate, :endDate, :description, :amount, :rate, :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
+ WHERE id = :id;
>>
test() ::= <<
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 0323040..8766e18 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
@@ -52,11 +52,15 @@ getById() ::= <<
GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency;
>>
-save() ::= <<
+create() ::= <<
INSERT INTO invoices(id, account_id, invoice_date, target_date, currency)
- VALUES (:id, :accountId, :invoiceDate, :targetDate, :currency)
- ON DUPLICATE KEY UPDATE
- invoice_date = :invoiceDate, target_date = :targetDate, currency = :currency;
+ VALUES (:id, :accountId, :invoiceDate, :targetDate, :currency);
+>>
+
+update() ::= <<
+ UPDATE invoices
+ SET account_id = :accountId, invoice_date = :invoiceDate, target_date = :targetDate, currency = :currency
+ WHERE id = :id;
>>
notifySuccessfulPayment() ::= <<
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 0666ada..3fe72ed 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
@@ -45,7 +45,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
Invoice invoice = new DefaultInvoice(accountId, new DefaultClock().getUTCNow(), Currency.USD);
DateTime invoiceDate = invoice.getInvoiceDate();
- invoiceDao.save(invoice);
+ invoiceDao.create(invoice);
List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId.toString());
assertNotNull(invoices);
@@ -68,7 +68,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
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);
- invoiceDao.save(invoice);
+ invoiceDao.create(invoice);
Invoice savedInvoice = invoiceDao.getById(invoiceId.toString());
assertNotNull(savedInvoice);
@@ -105,12 +105,12 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
DateTime paymentAttemptDate = new DateTime(2011, 6, 24, 12, 14, 36, 0);
BigDecimal paymentAmount = new BigDecimal("14.0");
- invoiceDao.save(invoice);
+ invoiceDao.create(invoice);
invoiceDao.notifySuccessfulPayment(invoice.getId().toString(), paymentAmount, Currency.USD.toString(), paymentId, paymentAttemptDate.toDate());
invoice = invoiceDao.getById(invoice.getId().toString());
-// assertEquals(invoice.getAmountPaid().compareTo(paymentAmount), 0);
-// assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
+ assertEquals(invoice.getAmountPaid().compareTo(paymentAmount), 0);
+ assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
}
@Test
@@ -121,11 +121,11 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
DateTime paymentAttemptDate = new DateTime(2011, 6, 24, 12, 14, 36, 0);
- invoiceDao.save(invoice);
+ invoiceDao.create(invoice);
invoiceDao.notifyFailedPayment(invoice.getId().toString(), UUID.randomUUID().toString(), paymentAttemptDate.toDate());
invoice = invoiceDao.getById(invoice.getId().toString());
-// assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
+ assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
}
@Test
@@ -140,7 +140,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
UUID accountId = UUID.randomUUID();
Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
- invoiceDao.save(invoice);
+ invoiceDao.create(invoice);
invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
assertEquals(invoices.size(), existingInvoiceCount);
}
@@ -163,7 +163,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, targetDate, endDate, "test", amount, rate, Currency.USD);
invoice.add(item);
- invoiceDao.save(invoice);
+ invoiceDao.create(invoice);
// ensure that the number of invoices for payment has increased by 1
int count;
@@ -248,7 +248,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
// create invoice 1 (subscriptions 1-4)
Invoice invoice1 = new DefaultInvoice(accountId, targetDate, Currency.USD);
- invoiceDao.save(invoice1);
+ invoiceDao.create(invoice1);
UUID invoiceId1 = invoice1.getId();
@@ -256,34 +256,34 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
DateTime endDate = startDate.plusMonths(1);
DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoiceId1, subscriptionId1, startDate, endDate, "test A", rate1, rate1, Currency.USD);
- invoiceItemDao.save(item1);
+ invoiceItemDao.create(item1);
DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoiceId1, subscriptionId2, startDate, endDate, "test B", rate2, rate2, Currency.USD);
- invoiceItemDao.save(item2);
+ invoiceItemDao.create(item2);
DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoiceId1, subscriptionId3, startDate, endDate, "test C", rate3, rate3, Currency.USD);
- invoiceItemDao.save(item3);
+ invoiceItemDao.create(item3);
DefaultInvoiceItem item4 = new DefaultInvoiceItem(invoiceId1, subscriptionId4, startDate, endDate, "test D", rate4, rate4, Currency.USD);
- invoiceItemDao.save(item4);
+ invoiceItemDao.create(item4);
// create invoice 2 (subscriptions 1-3)
- DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
- invoiceDao.save(invoice);
+ DefaultInvoice invoice2 = new DefaultInvoice(accountId, targetDate, Currency.USD);
+ invoiceDao.create(invoice2);
- UUID invoiceId2 = invoice.getId();
+ UUID invoiceId2 = invoice2.getId();
startDate = endDate;
endDate = startDate.plusMonths(1);
DefaultInvoiceItem item5 = new DefaultInvoiceItem(invoiceId2, subscriptionId1, startDate, endDate, "test A", rate1, rate1, Currency.USD);
- invoiceItemDao.save(item5);
+ invoiceItemDao.create(item5);
DefaultInvoiceItem item6 = new DefaultInvoiceItem(invoiceId2, subscriptionId2, startDate, endDate, "test B", rate2, rate2, Currency.USD);
- invoiceItemDao.save(item6);
+ invoiceItemDao.create(item6);
DefaultInvoiceItem item7 = new DefaultInvoiceItem(invoiceId2, subscriptionId3, startDate, endDate, "test C", rate3, rate3, Currency.USD);
- invoiceItemDao.save(item7);
+ invoiceItemDao.create(item7);
// check that each subscription returns the correct number of invoices
List<Invoice> items1 = invoiceDao.getInvoicesBySubscription(subscriptionId1.toString());
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 0f30753..fa743ff 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
@@ -46,7 +46,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
BigDecimal rate = new BigDecimal("20.00");
InvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, "test", rate, rate, Currency.USD);
- invoiceItemDao.save(item);
+ invoiceItemDao.create(item);
InvoiceItem thisItem = invoiceItemDao.getById(item.getId().toString());
assertNotNull(thisItem);
@@ -70,7 +70,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);
- invoiceItemDao.save(item);
+ invoiceItemDao.create(item);
}
List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsBySubscription(subscriptionId.toString());
@@ -87,7 +87,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
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);
- invoiceItemDao.save(item);
+ invoiceItemDao.create(item);
}
List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsByInvoice(invoiceId.toString());
@@ -100,7 +100,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
DateTime targetDate = new DateTime(2011, 5, 23, 0, 0, 0, 0);
DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
- invoiceDao.save(invoice);
+ invoiceDao.create(invoice);
UUID invoiceId = invoice.getId();
DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
@@ -108,7 +108,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
UUID subscriptionId = UUID.randomUUID();
DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, startDate.plusMonths(1), "test", rate, rate, Currency.USD);
- invoiceItemDao.save(item);
+ invoiceItemDao.create(item);
List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsByAccount(accountId.toString());
assertEquals(items.size(), 1);
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 d01c470..5d36d55 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,26 +16,33 @@
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.api.BillingPeriod;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
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.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
import com.ning.billing.invoice.api.BillingEventSet;
-import com.ning.billing.invoice.api.DefaultBillingEvent;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceItem;
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 java.math.BigDecimal;
-import java.util.UUID;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
@Test(groups = {"invoicing", "invoiceGenerator"})
public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
@@ -68,14 +75,15 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
public void testWithSingleMonthlyEvent() {
BillingEventSet events = new BillingEventSet();
- UUID subscriptionId = UUID.randomUUID();
+ Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
DateTime startDate = buildDateTime(2011, 9, 1);
- String planName = "World Domination";
- String phaseName = "Build Space Laser";
- BillingEvent event = new DefaultBillingEvent(subscriptionId, startDate, planName, phaseName,
- new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE);
-
+ MockCatalog catalog = new MockCatalog();
+ Plan plan = catalog.getCurrentPlans()[0];
+ PlanPhase phase = plan.getAllPhases()[0];
+
+ BillingEvent event = new DefaultBillingEvent(sub, startDate, plan, phase,
+ new InternationalPriceMock(ZERO),new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
+ 1, BillingModeType.IN_ADVANCE, "Test");
events.add(event);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -93,15 +101,15 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
public void testWithSingleMonthlyEventWithLeadingProRation() {
BillingEventSet events = new BillingEventSet();
- UUID subscriptionId = UUID.randomUUID();
+ Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
DateTime startDate = buildDateTime(2011, 9, 1);
- String planName = "World Domination";
- String phaseName = "Build Space Laser";
+ MockCatalog catalog = new MockCatalog();
+ Plan plan = catalog.getCurrentPlans()[0];
+ PlanPhase phase = plan.getAllPhases()[0];
BigDecimal rate = TEN;
- BillingEvent event = new DefaultBillingEvent(subscriptionId, startDate, planName, phaseName,
- new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
- 15, BillingModeType.IN_ADVANCE);
-
+ BillingEvent event = new DefaultBillingEvent(sub, startDate, plan, phase,
+ new InternationalPriceMock(ZERO), new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
+ 15, BillingModeType.IN_ADVANCE,"Test");
events.add(event);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -123,16 +131,24 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
public void testTwoMonthlySubscriptionsWithAlignedBillingDates() {
BillingEventSet events = new BillingEventSet();
- BillingEvent event1 = new DefaultBillingEvent(UUID.randomUUID(), buildDateTime(2011, 9, 1),
- "World Domination", "Build Space Laser",
- new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE);
+ MockCatalog catalog = new MockCatalog();
+ Plan plan1 = catalog.getCurrentPlans()[0];
+ PlanPhase phase1 = plan1.getAllPhases()[0];
+ Plan plan2 = catalog.getCurrentPlans()[1];
+ PlanPhase phase2 = plan2.getAllPhases()[0];
+
+ 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");
events.add(event1);
- BillingEvent event2 = new DefaultBillingEvent(UUID.randomUUID(), buildDateTime(2011, 10, 1),
- "Groceries", "Pick Up Milk",
- new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE);
+ BillingEvent event2 = new DefaultBillingEvent(sub, buildDateTime(2011, 10, 1),
+ plan2,phase2,
+ new InternationalPriceMock(ZERO), new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
+ 1, BillingModeType.IN_ADVANCE,"Test");
events.add(event2);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -149,17 +165,22 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
public void testOnePlan_TwoMonthlyPhases_ChangeImmediate() {
BillingEventSet events = new BillingEventSet();
- UUID subscriptionId = UUID.randomUUID();
- BillingEvent event1 = new DefaultBillingEvent(subscriptionId, buildDateTime(2011, 9, 1),
- "World Domination", "Build Space Laser",
- new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE);
+ MockCatalog catalog = new MockCatalog();
+ Plan plan1 = catalog.getCurrentPlans()[0];
+ PlanPhase phase1 = plan1.getAllPhases()[0];
+
+
+ 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");
events.add(event1);
- BillingEvent event2 = new DefaultBillingEvent(subscriptionId, buildDateTime(2011, 10, 15),
- "World Domination", "Incinerate James Bond",
- new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
- 15, BillingModeType.IN_ADVANCE);
+ 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");
events.add(event2);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -187,23 +208,28 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
public void testOnePlan_ThreeMonthlyPhases_ChangeEOT() {
BillingEventSet events = new BillingEventSet();
- UUID subscriptionId = UUID.randomUUID();
- BillingEvent event1 = new DefaultBillingEvent(subscriptionId, buildDateTime(2011, 9, 1),
- "World Domination", "Build Space Laser",
- new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE);
+ MockCatalog catalog = new MockCatalog();
+ Plan plan1 = catalog.getCurrentPlans()[0];
+ PlanPhase phase1 = plan1.getAllPhases()[0];
+
+
+ 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");
events.add(event1);
- BillingEvent event2 = new DefaultBillingEvent(subscriptionId, buildDateTime(2011, 10, 1),
- "World Domination", "Incinerate James Bond",
- new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE);
+ 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");
events.add(event2);
- BillingEvent event3 = new DefaultBillingEvent(subscriptionId, buildDateTime(2011, 11, 1),
- "World Domination", "Cackle Gleefully",
- new InternationalPriceMock(THIRTY), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE);
+ 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");
events.add(event3);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -220,18 +246,22 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
public void testSingleEventWithExistingInvoice() {
BillingEventSet events = new BillingEventSet();
- UUID subscriptionId = UUID.randomUUID();
+ 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];
+
BigDecimal rate = FIVE;
- BillingEvent event1 = new DefaultBillingEvent(subscriptionId, startDate,
- "World Domination", "Build Space Laser",
- new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE);
+ BillingEvent event1 = new DefaultBillingEvent(sub, startDate,
+ plan1,phase1,
+ new InternationalPriceMock(ZERO),new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
+ 1, BillingModeType.IN_ADVANCE,"Test");
events.add(event1);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
- InvoiceItem invoiceItem = new DefaultInvoiceItem(UUID.randomUUID(), subscriptionId, startDate, buildDateTime(2012, 1, 1), "",
+ InvoiceItem invoiceItem = new DefaultInvoiceItem(UUID.randomUUID(), sub.getId(), startDate, buildDateTime(2012, 1, 1), "",
rate.multiply(FOUR), rate, Currency.USD);
existingInvoiceItems.add(invoiceItem);
@@ -398,18 +428,24 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
private DefaultBillingEvent createBillingEvent(UUID subscriptionId, DateTime startDate, String planName, String planPhaseName,
BigDecimal rate, int billCycleDay) {
- return new DefaultBillingEvent(subscriptionId, startDate, planName, planPhaseName,
- new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
- billCycleDay, BillingModeType.IN_ADVANCE);
-
+ 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");
}
private DefaultBillingEvent createAnnualBillingEvent(UUID subscriptionId, DateTime startDate, String planName, String planPhaseName,
BigDecimal rate, int billCycleDay) {
- return new DefaultBillingEvent(subscriptionId, startDate, planName, planPhaseName,
- new InternationalPriceMock(rate), BillingPeriod.ANNUAL,
- billCycleDay, BillingModeType.IN_ADVANCE);
-
+ 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");
}
private void testInvoiceGeneration(BillingEventSet events, InvoiceItemList existingInvoiceItems, DateTime targetDate, int expectedNumberOfItems, BigDecimal expectedAmount) {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java b/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java
index 9198ba0..8577544 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java
@@ -16,14 +16,13 @@
package com.ning.billing.invoice.tests;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.catalog.api.InternationalPrice;
-import com.ning.billing.catalog.api.Price;
+import static org.testng.Assert.fail;
import java.math.BigDecimal;
-import java.util.Date;
-import static org.testng.Assert.fail;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.catalog.api.Price;
public class InternationalPriceMock implements InternationalPrice {
private final BigDecimal rate;
@@ -44,8 +43,4 @@ public class InternationalPriceMock implements InternationalPrice {
return rate;
}
- @Override
- public Date getEffectiveDateForExistingSubscriptons() {
- return new Date();
- }
}
payment/pom.xml 2(+1 -1)
diff --git a/payment/pom.xml b/payment/pom.xml
index aca06a2..3d6356e 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.0.21-SNAPSHOT</version>
+ <version>0.1.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-payment</artifactId>
pom.xml 16(+15 -1)
diff --git a/pom.xml b/pom.xml
index f64b324..8c8fbb3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,7 @@
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
<packaging>pom</packaging>
- <version>0.0.21-SNAPSHOT</version>
+ <version>0.1.2-SNAPSHOT</version>
<name>killbill</name>
<description>Library for managing recurring subscriptions and the associated billing</description>
<url>http://github.com/ning/killbill</url>
@@ -61,6 +61,13 @@
</dependency>
<dependency>
<groupId>com.ning.billing</groupId>
+ <artifactId>killbill-account</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
<artifactId>killbill-entitlement</artifactId>
<version>${project.version}</version>
</dependency>
@@ -71,6 +78,13 @@
</dependency>
<dependency>
<groupId>com.ning.billing</groupId>
+ <artifactId>killbill-catalog</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
<artifactId>killbill-util</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
util/pom.xml 17(+15 -2)
diff --git a/util/pom.xml b/util/pom.xml
index 5152ab7..74d243c 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.0.21-SNAPSHOT</version>
+ <version>0.1.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-util</artifactId>
@@ -33,6 +33,10 @@
<artifactId>jdbi</artifactId>
</dependency>
<dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ </dependency>
+ <dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version>
@@ -69,7 +73,16 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
-
+ <dependency>
+ <groupId>org.antlr</groupId>
+ <artifactId>stringtemplate</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.mysql</groupId>
+ <artifactId>management-dbfiles</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
<plugins>
diff --git a/util/src/main/java/com/ning/billing/util/customfield/CustomizableEntityBase.java b/util/src/main/java/com/ning/billing/util/customfield/CustomizableEntityBase.java
index 05d0137..ac184be 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/CustomizableEntityBase.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/CustomizableEntityBase.java
@@ -23,18 +23,18 @@ import com.ning.billing.util.entity.EntityBase;
public abstract class CustomizableEntityBase extends EntityBase implements CustomizableEntity {
protected final FieldStore fields;
- public CustomizableEntityBase(UUID id) {
+ public CustomizableEntityBase(final UUID id) {
super(id);
fields = DefaultFieldStore.create(getId(), getObjectName());
}
@Override
- public String getFieldValue(String fieldName) {
+ public String getFieldValue(final String fieldName) {
return fields.getValue(fieldName);
}
@Override
- public void setFieldValue(String fieldName, String fieldValue) {
+ public void setFieldValue(final String fieldName, final String fieldValue) {
fields.setValue(fieldName, fieldValue);
}
@@ -44,6 +44,13 @@ public abstract class CustomizableEntityBase extends EntityBase implements Custo
}
@Override
+ public void addFields(final List<CustomField> fields) {
+ if (fields != null) {
+ this.fields.add(fields);
+ }
+ }
+
+ @Override
public void clearFields() {
fields.clear();
}
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 8a7a8c5..8134203 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
@@ -31,6 +31,10 @@ public interface EntityCollectionDao<T extends Entity> {
@Bind("objectType") final String objectType,
@BindBean final List<T> entities);
+ @SqlUpdate
+ public void clear(@Bind("objectId") final String objectId,
+ @Bind("objectType") final String objectType);
+
@SqlQuery
public List<T> load(@Bind("objectId") final String objectId,
@Bind("objectType") final String objectType);
diff --git a/util/src/main/java/com/ning/billing/util/entity/EntityDao.java b/util/src/main/java/com/ning/billing/util/entity/EntityDao.java
index 1ee4fc1..3b5dd46 100644
--- a/util/src/main/java/com/ning/billing/util/entity/EntityDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/EntityDao.java
@@ -26,7 +26,10 @@ import java.util.List;
public interface EntityDao<T extends Entity> {
@SqlUpdate
- public void save(@BindBean T entity);
+ public void create(@BindBean T entity);
+
+ @SqlUpdate
+ public void update(@BindBean T entity);
@SqlQuery
public T getById(@Bind("id") final String id);
diff --git a/util/src/main/java/com/ning/billing/util/glue/NotificationQueueModule.java b/util/src/main/java/com/ning/billing/util/glue/NotificationQueueModule.java
new file mode 100644
index 0000000..f0babf9
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/glue/NotificationQueueModule.java
@@ -0,0 +1,29 @@
+/*
+ * 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.notificationq.DefaultNotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+
+public class NotificationQueueModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ bind(NotificationQueueService.class).to(DefaultNotificationQueueService.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
new file mode 100644
index 0000000..818d831
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
@@ -0,0 +1,108 @@
+/*
+ * 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.dao;
+
+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;
+import org.joda.time.DateTimeZone;
+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.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
+import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.util.notificationq.DefaultNotification;
+import com.ning.billing.util.notificationq.Notification;
+import com.ning.billing.util.notificationq.NotificationLifecycle.NotificationLifecycleState;
+
+@ExternalizedSqlViaStringTemplate3()
+public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, CloseMe {
+
+ //
+ // APIs for event notifications
+ //
+ @SqlQuery
+ @Mapper(NotificationSqlMapper.class)
+ public List<Notification> getReadyNotifications(@Bind("now") Date now, @Bind("max") int max);
+
+ @SqlUpdate
+ public int claimNotification(@Bind("owner") String owner, @Bind("next_available") Date nextAvailable, @Bind("notification_id") String eventId, @Bind("now") Date now);
+
+ @SqlUpdate
+ public void clearNotification(@Bind("notification_id") String eventId, @Bind("owner") String owner);
+
+ @SqlUpdate
+ public void insertNotification(@Bind(binder = NotificationSqlDaoBinder.class) Notification evt);
+
+ @SqlUpdate
+ public void insertClaimedHistory(@Bind("sequence_id") int sequenceId, @Bind("owner") String owner, @Bind("claimed_dt") Date clainedDate, @Bind("notification_id") String notificationId);
+
+ public static class NotificationSqlDaoBinder implements Binder<Bind, Notification> {
+
+ private Date getDate(DateTime dateTime) {
+ return dateTime == null ? null : dateTime.toDate();
+ }
+
+ @Override
+ public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, Notification evt) {
+ stmt.bind("notification_id", evt.getId().toString());
+ stmt.bind("created_dt", getDate(new DateTime()));
+ stmt.bind("notification_key", evt.getNotificationKey());
+ stmt.bind("effective_dt", getDate(evt.getEffectiveDate()));
+ stmt.bind("processing_available_dt", getDate(evt.getNextAvailableDate()));
+ stmt.bind("processing_owner", (String) null);
+ stmt.bind("processing_state", NotificationLifecycleState.AVAILABLE.toString());
+ }
+ }
+
+
+ public static class NotificationSqlMapper implements ResultSetMapper<Notification> {
+
+ private DateTime getDate(ResultSet r, String fieldName) throws SQLException {
+ final Timestamp resultStamp = r.getTimestamp(fieldName);
+ return r.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
+ }
+
+ @Override
+ public Notification map(int index, ResultSet r, StatementContext ctx)
+ throws SQLException {
+
+ final UUID id = UUID.fromString(r.getString("notification_id"));
+ final String notificationKey = r.getString("notification_key");
+ 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,
+ 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
new file mode 100644
index 0000000..2946e13
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+public class DefaultNotification implements Notification {
+
+ private final UUID id;
+ private final String owner;
+ private final DateTime nextAvailableDate;
+ private final NotificationLifecycleState lifecycleState;
+ private final String notificationKey;
+ private final DateTime effectiveDate;
+
+
+ public DefaultNotification(UUID id, String owner, DateTime nextAvailableDate,
+ NotificationLifecycleState lifecycleState,
+ String notificationKey, DateTime effectiveDate) {
+ super();
+ this.id = id;
+ this.owner = owner;
+ 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 UUID getId() {
+ return id;
+ }
+
+ @Override
+ public String getOwner() {
+ return owner;
+ }
+
+ @Override
+ public DateTime getNextAvailableDate() {
+ return nextAvailableDate;
+ }
+
+ @Override
+ public NotificationLifecycleState getProcessingState() {
+ return lifecycleState;
+ }
+
+ @Override
+ public boolean isAvailableForProcessing(DateTime now) {
+ switch(lifecycleState) {
+ case AVAILABLE:
+ break;
+ case IN_PROCESSING:
+ // Somebody already got the event, not available yet
+ if (nextAvailableDate.isAfter(now)) {
+ return false;
+ }
+ break;
+ case PROCESSED:
+ return false;
+ default:
+ throw new RuntimeException(String.format("Unkwnon IEvent processing state %s", lifecycleState));
+ }
+ return effectiveDate.isBefore(now);
+ }
+
+ @Override
+ public String getNotificationKey() {
+ return notificationKey;
+ }
+
+ @Override
+ public DateTime getEffectiveDate() {
+ return effectiveDate;
+ }
+}
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
new file mode 100644
index 0000000..80f7385
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
@@ -0,0 +1,119 @@
+/*
+ * 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;
+
+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.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
+
+public class DefaultNotificationQueue extends NotificationQueueBase {
+
+ protected final NotificationSqlDao dao;
+
+ public DefaultNotificationQueue(final DBI 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) {
+ List<Notification> notifications = getReadyNotifications(sequenceId);
+ for (Notification cur : notifications) {
+ nbProcessedEvents.incrementAndGet();
+ handler.handleReadyNotification(cur.getNotificationKey());
+ }
+ // 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);
+ 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 List<Notification> getReadyNotifications(final int seqId) {
+
+ 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;
+ }
+ });
+
+ for (Notification cur : result) {
+ log.debug(String.format("NotificationQueue %sclaimed events %s",
+ getFullQName(), cur.getId()));
+ 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;
+ }
+}
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
new file mode 100644
index 0000000..5181113
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import org.skife.jdbi.v2.DBI;
+
+import com.google.inject.Inject;
+import com.ning.billing.util.clock.Clock;
+
+public class DefaultNotificationQueueService extends NotificationQueueServiceBase {
+
+ private final DBI dbi;
+
+ @Inject
+ public DefaultNotificationQueueService(final DBI dbi, final Clock clock) {
+ super(clock);
+ this.dbi = dbi;
+ }
+
+ @Override
+ protected NotificationQueue createNotificationQueueInternal(String svcName,
+ String queueName, NotificationQueueHandler handler,
+ NotificationConfig config) {
+ return new DefaultNotificationQueue(dbi, clock, svcName, queueName, handler, config);
+ }
+
+}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationKey.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationKey.java
new file mode 100644
index 0000000..bfe4aa9
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationKey.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ *
+ * The notification key associated with a given notification
+ */
+public interface NotificationKey {
+
+ @Override
+ public String toString();
+}
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
new file mode 100644
index 0000000..23f0de0
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+
+public interface NotificationQueue {
+
+ /**
+ *
+ * Record from within a transaction the need to be called back when the notification is ready
+ *
+ * @param transactionalDao the transactionalDao
+ * @param futureNotificationTime the time at which the notification is ready
+ * @param notificationKey the key for that notification
+ */
+ public void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao,
+ final DateTime futureNotificationTime, final NotificationKey notificationKey);
+
+ /**
+ * This is only valid when the queue has been configured with isNotificationProcessingOff is true
+ * In which case, it will callback users for all the ready notifications.
+ *
+ */
+ public void processReadyNotification();
+
+ /**
+ * Stops the queue.
+ *
+ * @see NotificationQueueHandler.completedQueueStop to be notified when the notification thread exited
+ */
+ public void stopQueue();
+
+ /**
+ * Starts the queue.
+ *
+ * @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
new file mode 100644
index 0000000..cefd1eb
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
@@ -0,0 +1,193 @@
+/*
+ * 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;
+
+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);
+
+ protected static final String NOTIFICATION_THREAD_PREFIX = "Notification-";
+ protected final long STOP_WAIT_TIMEOUT_MS = 60000;
+
+ protected final String svcName;
+ protected final String queueName;
+ protected final NotificationQueueHandler handler;
+ protected final NotificationConfig config;
+
+ protected final Executor executor;
+ protected final Clock clock;
+ protected final String hostname;
+
+ protected static final AtomicInteger sequenceId = new AtomicInteger();
+
+ protected AtomicLong nbProcessedEvents;
+
+ // Use this object's monitor for synchronization (no need for volatile)
+ protected boolean isProcessingEvents;
+
+ // Package visibility on purpose
+ NotificationQueueBase(final Clock clock, final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config) {
+ this.clock = clock;
+ this.svcName = svcName;
+ this.queueName = queueName;
+ this.handler = handler;
+ this.config = config;
+ this.hostname = Hostname.get();
+
+ this.executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread th = new Thread(r);
+ th.setName(NOTIFICATION_THREAD_PREFIX + svcName + "-" + queueName);
+ th.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ log.error("Uncaught exception for thread " + t.getName(), e);
+ }
+ });
+ return th;
+ }
+ });
+ }
+
+
+ @Override
+ public void processReadyNotification() {
+ doProcessEvents(sequenceId.incrementAndGet());
+ }
+
+
+ @Override
+ public void stopQueue() {
+ if (config.isNotificationProcessingOff()) {
+ handler.completedQueueStop();
+ return;
+ }
+
+ synchronized(this) {
+ isProcessingEvents = false;
+ try {
+ log.info("NotificationQueue requested to stop");
+ wait(STOP_WAIT_TIMEOUT_MS);
+ log.info("NotificationQueue requested should have exited");
+ } catch (InterruptedException e) {
+ log.warn("NotificationQueue got interrupted exception when stopping notifications", e);
+ }
+ }
+
+ }
+
+ @Override
+ public void startQueue() {
+
+ this.isProcessingEvents = true;
+ this.nbProcessedEvents = new AtomicLong();
+
+
+ if (config.isNotificationProcessingOff()) {
+ log.warn(String.format("KILLBILL NOTIFICATION PROCESSING FOR SVC %s IS OFF !!!", getFullQName()));
+ handler.completedQueueStart();
+ return;
+ }
+ final NotificationQueueBase notificationQueue = this;
+
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+
+ log.info(String.format("NotificationQueue thread %s [%d] started",
+ Thread.currentThread().getName(),
+ Thread.currentThread().getId()));
+
+ // Thread is now started, notify the listener
+ handler.completedQueueStart();
+
+ try {
+ while (true) {
+
+ synchronized (notificationQueue) {
+ if (!isProcessingEvents) {
+ log.info(String.format("NotificationQueue has been requested to stop, thread %s [%d] stopping...",
+ Thread.currentThread().getName(),
+ Thread.currentThread().getId()));
+ notificationQueue.notify();
+ break;
+ }
+ }
+
+ // Callback may trigger exceptions in user code so catch anything here and live with it.
+ try {
+ doProcessEvents(sequenceId.getAndIncrement());
+ } catch (Exception e) {
+ log.error(String.format("NotificationQueue thread %s [%d] got an exception..",
+ Thread.currentThread().getName(),
+ Thread.currentThread().getId()), e);
+ }
+ sleepALittle();
+ }
+ } catch (InterruptedException e) {
+ log.warn(Thread.currentThread().getName() + " got interrupted ", e);
+ } catch (Throwable e) {
+ log.error(Thread.currentThread().getName() + " got an exception exiting...", e);
+ // Just to make it really obvious in the log
+ e.printStackTrace();
+ } finally {
+ handler.completedQueueStop();
+ log.info(String.format("NotificationQueue thread %s [%d] exited...",
+ Thread.currentThread().getName(),
+ Thread.currentThread().getId()));
+ }
+ }
+
+ private void sleepALittle() throws InterruptedException {
+ Thread.sleep(config.getNotificationSleepTimeMs());
+ }
+ });
+ }
+
+
+ protected String getFullQName() {
+ return svcName + ":" + queueName;
+ }
+
+ protected abstract void doProcessEvents(int sequenceId);
+}
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
new file mode 100644
index 0000000..a18906b
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java
@@ -0,0 +1,86 @@
+/*
+ * 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;
+
+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
+ */
+ 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 {
+ private static final long serialVersionUID = 1541281L;
+
+ public NotficationQueueAlreadyExists(String msg) {
+ super(msg);
+ }
+ }
+
+ public static final class NoSuchNotificationQueue extends Exception {
+ private static final long serialVersionUID = 1541281L;
+
+ public NoSuchNotificationQueue(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Creates a new NotificationQueue for a given associated with the given service and queueName
+ *
+ * @param svcName the name of the service using that queue
+ * @param queueName a name for that queue (unique per service)
+ * @param handler the handler required for notifying the caller of state change
+ * @param config the notification queue configuration
+ *
+ * @return a new NotificationQueue
+ *
+ * @throws NotficationQueueAlreadyExists 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;
+
+ /**
+ * Retrieves an already created NotificationQueue by service and name if it exists
+ *
+ * @param svcName
+ * @param queueName
+ * @return
+ *
+ * @throws NoSuchNotificationQueue if queue does not exist
+ */
+ NotificationQueue getNotificationQueue(final String svcName, final String queueName)
+ throws NoSuchNotificationQueue;
+
+}
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
new file mode 100644
index 0000000..a4dc64e
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java
@@ -0,0 +1,90 @@
+/*
+ * 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;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.util.clock.Clock;
+
+public abstract class NotificationQueueServiceBase implements NotificationQueueService {
+
+ protected final Logger log = LoggerFactory.getLogger(DefaultNotificationQueueService.class);
+
+ protected final Clock clock;
+
+ private final Map<String, NotificationQueue> queues;
+
+ @Inject
+ public NotificationQueueServiceBase(final Clock clock) {
+
+ this.clock = clock;
+ this.queues = new TreeMap<String, NotificationQueue>();
+ }
+
+ @Override
+ public NotificationQueue createNotificationQueue(String svcName,
+ String queueName, NotificationQueueHandler handler,
+ NotificationConfig config) throws NotficationQueueAlreadyExists {
+ if (svcName == null || queueName == null || handler == null || config == null) {
+ throw new RuntimeException("Need to specify all parameters");
+ }
+
+ String compositeName = getCompositeName(svcName, queueName);
+ NotificationQueue result = null;
+ synchronized(queues) {
+ result = queues.get(compositeName);
+ if (result != null) {
+ throw new NotficationQueueAlreadyExists(String.format("Queue for svc %s and name %s already exist",
+ svcName, queueName));
+ }
+ result = createNotificationQueueInternal(svcName, queueName, handler, config);
+ queues.put(compositeName, result);
+ }
+ return result;
+ }
+
+ @Override
+ public NotificationQueue getNotificationQueue(String svcName,
+ String queueName) throws NoSuchNotificationQueue {
+
+ NotificationQueue result = null;
+ String compositeName = getCompositeName(svcName, queueName);
+ synchronized(queues) {
+ result = queues.get(compositeName);
+ if (result == null) {
+ throw new NoSuchNotificationQueue(String.format("Queue for svc %s and name %s does not exist",
+ svcName, queueName));
+ }
+ }
+ return result;
+ }
+
+
+ protected abstract NotificationQueue createNotificationQueueInternal(String svcName,
+ String queueName, NotificationQueueHandler handler,
+ NotificationConfig config);
+
+
+ private String getCompositeName(String svcName, String queueName) {
+ return svcName + ":" + queueName;
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagDescriptionDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagDescriptionDao.java
index 4938d49..6bc029f 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagDescriptionDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagDescriptionDao.java
@@ -43,7 +43,11 @@ import org.skife.jdbi.v2.tweak.ResultSetMapper;
public interface TagDescriptionDao extends EntityDao<TagDescription> {
@Override
@SqlUpdate
- public void save(@TagDescriptionBinder TagDescription entity);
+ public void create(@TagDescriptionBinder TagDescription entity);
+
+ @Override
+ @SqlUpdate
+ public void update(@TagDescriptionBinder TagDescription entity);
public class TagDescriptionMapper implements ResultSetMapper<TagDescription> {
@Override
diff --git a/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java b/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java
index 6a3062e..5d49fb2 100644
--- a/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java
+++ b/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java
@@ -20,12 +20,12 @@ import java.util.UUID;
import com.ning.billing.util.entity.EntityCollectionBase;
public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagStore {
- public DefaultTagStore(UUID objectId, String objectType) {
+ public DefaultTagStore(final UUID objectId, final String objectType) {
super(objectId, objectType);
}
@Override
- public String getEntityKey(Tag entity) {
+ public String getEntityKey(final Tag entity) {
return entity.getName();
}
@@ -58,14 +58,14 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
}
@Override
- public void remove(String tagName) {
+ public void remove(final String tagName) {
entities.remove(entities.get(tagName));
}
@Override
- public boolean containsTag(String tagName) {
+ public boolean containsTag(final String tagName) {
for (Tag tag : entities.values()) {
- if (tag.getName() == tagName) {
+ if (tag.getName().equals(tagName)) {
return true;
}
}
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 c995694..57163a9 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
@@ -13,6 +13,11 @@ load() ::= <<
WHERE object_id = :objectId AND object_type = :objectType;
>>
+clear() ::= <<
+ DELETE FROM custom_fields
+ WHERE object_id = :objectId AND object_type = :objectType;
+>>
+
test() ::= <<
SELECT 1 FROM custom_fields;
>>
diff --git a/util/src/main/resources/com/ning/billing/util/ddl.sql b/util/src/main/resources/com/ning/billing/util/ddl.sql
new file mode 100644
index 0000000..30471c7
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/ddl.sql
@@ -0,0 +1,64 @@
+DROP TABLE IF EXISTS custom_fields;
+CREATE TABLE custom_fields (
+ id char(36) NOT NULL,
+ object_id char(36) NOT NULL,
+ object_type varchar(30) NOT NULL,
+ field_name varchar(30) NOT NULL,
+ field_value varchar(255) NOT NULL,
+ PRIMARY KEY(id)
+) ENGINE=innodb;
+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;
+CREATE TABLE tag_descriptions (
+ id char(36) NOT NULL,
+ name varchar(20) NOT NULL,
+ created_by varchar(50) NOT NULL,
+ creation_date datetime NOT NULL,
+ description varchar(200) NOT NULL,
+ generate_invoice boolean DEFAULT false,
+ process_payment boolean DEFAULT false,
+ PRIMARY KEY(id)
+) ENGINE=innodb;
+CREATE UNIQUE INDEX tag_descriptions_name ON tag_descriptions(name);
+
+DROP TABLE IF EXISTS tags;
+CREATE TABLE tags (
+ id char(36) NOT NULL,
+ tag_description_id char(36) NOT NULL,
+ object_id char(36) NOT NULL,
+ object_type varchar(30) NOT NULL,
+ date_added datetime NOT NULL,
+ added_by varchar(50) NOT NULL,
+ PRIMARY KEY(id)
+) ENGINE = innodb;
+CREATE INDEX tags_by_object ON tags(object_id);
+CREATE UNIQUE INDEX tags_unique ON tags(tag_description_id, object_id);
+
+DROP TABLE IF EXISTS notifications;
+CREATE TABLE notifications (
+ id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ notification_id char(36) NOT NULL,
+ created_dt datetime NOT NULL,
+ notification_key varchar(256) NOT NULL,
+ effective_dt datetime NOT NULL,
+ processing_owner char(36) 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_get_ready` ON notifications (`effective_dt`,`created_dt`,`id`);
+
+DROP TABLE IF EXISTS claimed_notifications;
+CREATE TABLE claimed_notifications (
+ id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ sequence_id int(11) unsigned NOT NULL,
+ owner_id varchar(64) NOT NULL,
+ claimed_dt datetime NOT NULL,
+ notification_id char(36) NOT NULL,
+ PRIMARY KEY(id)
+) ENGINE=innodb;
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
new file mode 100644
index 0000000..5a44431
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
@@ -0,0 +1,83 @@
+group NotificationSqlDao;
+
+getReadyNotifications(now, max) ::= <<
+ select
+ notification_id
+ , notification_key
+ , created_dt
+ , effective_dt
+ , processing_owner
+ , processing_available_dt
+ , processing_state
+ from notifications
+ where
+ effective_dt \<= :now
+ and processing_state != 'PROCESSED'
+ and (processing_owner IS NULL OR processing_available_dt \<= :now)
+ order by
+ effective_dt asc
+ , created_dt asc
+ , id asc
+ limit :max
+ ;
+>>
+
+
+claimNotification(owner, next_available, notification_id, now) ::= <<
+ update notifications
+ set
+ processing_owner = :owner
+ , processing_available_dt = :next_available
+ , processing_state = 'IN_PROCESSING'
+ where
+ notification_id = :notification_id
+ and processing_state != 'PROCESSED'
+ and (processing_owner IS NULL OR processing_available_dt \<= :now)
+ ;
+>>
+
+clearNotification(notification_id, owner) ::= <<
+ update notifications
+ set
+ processing_owner = NULL
+ , processing_state = 'PROCESSED'
+ where
+ notification_id = :notification_id
+ and processing_owner = :owner
+ ;
+>>
+
+insertNotification() ::= <<
+ insert into notifications (
+ notification_id
+ , notification_key
+ , created_dt
+ , effective_dt
+ , processing_owner
+ , processing_available_dt
+ , processing_state
+ ) values (
+ :notification_id
+ , :notification_key
+ , :created_dt
+ , :effective_dt
+ , :processing_owner
+ , :processing_available_dt
+ , :processing_state
+ );
+>>
+
+
+insertClaimedHistory(sequence_id, owner, hostname, claimed_dt, notification_id) ::= <<
+ insert into claimed_notifications (
+ sequence_id
+ , owner_id
+ , claimed_dt
+ , notification_id
+ ) values (
+ :sequence_id
+ , :owner
+ , :claimed_dt
+ , :notification_id
+ );
+>>
diff --git a/util/src/main/resources/com/ning/billing/util/tag/dao/TagDescriptionDao.sql.stg b/util/src/main/resources/com/ning/billing/util/tag/dao/TagDescriptionDao.sql.stg
index bdde261..c71d063 100644
--- a/util/src/main/resources/com/ning/billing/util/tag/dao/TagDescriptionDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/tag/dao/TagDescriptionDao.sql.stg
@@ -1,12 +1,15 @@
group TagDescriptionDao;
-save() ::= <<
+create() ::= <<
INSERT INTO tag_descriptions(id, name, created_by, creation_date, description, generate_invoice, process_payment)
- VALUES(:id, :name, :createdBy, :creationDate, :description, :generateInvoice, :processPayment)
- ON DUPLICATE KEY UPDATE
- name = :name, created_by = :createdBy, creation_date = :creationDate,
- description := description, generate_invoice = :generateInvoice,
- process_payment = :processPayment
+ VALUES(:id, :name, :createdBy, :creationDate, :description, :generateInvoice, :processPayment);
+>>
+
+update() ::= <<
+ UPDATE tag_descriptions
+ SET name = :name, created_by = :createdBy, creation_date = :creationDate,
+ description = :description, generate_invoice = :generateInvoice, process_payment = :processPayment)
+ WHERE id = :id;
>>
load() ::= <<
diff --git a/util/src/main/resources/com/ning/billing/util/tag/dao/TagStoreDao.sql.stg b/util/src/main/resources/com/ning/billing/util/tag/dao/TagStoreDao.sql.stg
index 3b53b2e..e72555d 100644
--- a/util/src/main/resources/com/ning/billing/util/tag/dao/TagStoreDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/tag/dao/TagStoreDao.sql.stg
@@ -17,6 +17,11 @@ load() ::= <<
WHERE t.object_id = :objectId AND t.object_type = :objectType;
>>
+clear() ::= <<
+ DELETE FROM tags
+ WHERE object_id = :objectId AND object_type = :objectType;
+>>
+
test() ::= <<
SELECT 1 FROM tags;
>>
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 0fb473d..7698697 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
@@ -76,6 +76,15 @@ public class ClockMock extends DefaultClock {
deltaFromRealityMs = delta;
}
+ public synchronized void addDeltaFromReality(long delta) {
+ if (deltaType != DeltaType.DELTA_ABS) {
+ throw new RuntimeException("ClockMock should be set with type DELTA_ABS");
+ }
+ deltaFromRealityDuration = null;
+ deltaFromRealitDurationEpsilon = 0;
+ deltaFromRealityMs += delta;
+ }
+
public synchronized void resetDeltaFromReality() {
deltaType = DeltaType.DELTA_NONE;
deltaFromRealityDuration = null;
diff --git a/util/src/test/java/com/ning/billing/util/eventbus/TestEventBus.java b/util/src/test/java/com/ning/billing/util/eventbus/TestEventBus.java
index 47691be..4b0f4a2 100644
--- a/util/src/test/java/com/ning/billing/util/eventbus/TestEventBus.java
+++ b/util/src/test/java/com/ning/billing/util/eventbus/TestEventBus.java
@@ -71,7 +71,7 @@ public class TestEventBus {
@Subscribe
public synchronized void processEvent(MyEvent event) {
gotEvents++;
- log.info("Got event {} {}", event.name, event.value);
+ //log.debug("Got event {} {}", event.name, event.value);
}
public synchronized boolean waitForCompletion(long timeoutMs) {
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
new file mode 100644
index 0000000..89dfd76
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
@@ -0,0 +1,212 @@
+/*
+ * 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.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.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;
+
+@Guice(modules = TestNotificationSqlDao.TestNotificationSqlDaoModule.class)
+public class TestNotificationSqlDao {
+
+ private static AtomicInteger sequenceId = new AtomicInteger();
+
+ @Inject
+ private DBI dbi;
+
+ @Inject
+ MysqlTestingHelper helper;
+
+ private NotificationSqlDao dao;
+
+ private void startMysql() throws IOException, ClassNotFoundException, SQLException {
+
+
+ final String ddl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+ helper.startMysql();
+ helper.initDb(ddl);
+ }
+
+ @BeforeSuite(alwaysRun = true)
+ public void setup() {
+ try {
+ startMysql();
+ dao = dbi.onDemand(NotificationSqlDao.class);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @AfterSuite(alwaysRun = true)
+ public void stopMysql()
+ {
+ helper.stopMysql();
+ }
+
+
+ @BeforeTest
+ public void cleanupDb() {
+ dbi.withHandle(new HandleCallback<Void>() {
+
+ @Override
+ public Void withHandle(Handle handle) throws Exception {
+ handle.execute("delete from notifications");
+ handle.execute("delete from claimed_notifications");
+ return null;
+ }
+ });
+ }
+
+ @Test
+ public void testBasic() throws InterruptedException {
+
+ final String ownerId = UUID.randomUUID().toString();
+
+ String notificationKey = UUID.randomUUID().toString();
+ DateTime effDt = new DateTime();
+ Notification notif = new DefaultNotification(notificationKey, effDt);
+ dao.insertNotification(notif);
+
+ Thread.sleep(1000);
+ DateTime now = new DateTime();
+ List<Notification> notifications = dao.getReadyNotifications(now.toDate(), 3);
+ assertNotNull(notifications);
+ assertEquals(notifications.size(), 1);
+
+ Notification notification = notifications.get(0);
+ assertEquals(notification.getNotificationKey(), notificationKey);
+ validateDate(notification.getEffectiveDate(), effDt);
+ assertEquals(notification.getOwner(), null);
+ assertEquals(notification.getProcessingState(), NotificationLifecycleState.AVAILABLE);
+ assertEquals(notification.getNextAvailableDate(), null);
+
+ DateTime nextAvailable = now.plusMinutes(5);
+ int res = dao.claimNotification(ownerId, nextAvailable.toDate(), notification.getId().toString(), now.toDate());
+ assertEquals(res, 1);
+ dao.insertClaimedHistory(sequenceId.incrementAndGet(), ownerId, now.toDate(), notification.getId().toString());
+
+ notification = fetchNotification(notification.getId().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);
+
+ notification = fetchNotification(notification.getId().toString());
+ assertEquals(notification.getNotificationKey(), notificationKey);
+ validateDate(notification.getEffectiveDate(), effDt);
+ assertEquals(notification.getOwner(), null);
+ assertEquals(notification.getProcessingState(), NotificationLifecycleState.PROCESSED);
+ validateDate(notification.getNextAvailableDate(), nextAvailable);
+
+ }
+
+ private Notification fetchNotification(final String notificationId) {
+ Notification res = dbi.withHandle(new HandleCallback<Notification>() {
+
+ @Override
+ public Notification withHandle(Handle handle) throws Exception {
+ Notification res = handle.createQuery(" select" +
+ " notification_id" +
+ ", notification_key" +
+ ", created_dt" +
+ ", effective_dt" +
+ ", processing_owner" +
+ ", processing_available_dt" +
+ ", processing_state" +
+ " from notifications " +
+ " where " +
+ " notification_id = '" + notificationId + "';")
+ .map(new NotificationSqlMapper())
+ .first();
+ return res;
+ }
+ });
+ return res;
+ }
+
+ private void validateDate(DateTime input, DateTime expected) {
+ if (input == null && expected != null) {
+ Assert.fail("Got input date null");
+ }
+ if (input != null && expected == null) {
+ Assert.fail("Was expecting null date");
+ }
+ expected = truncateAndUTC(expected);
+ input = truncateAndUTC(input);
+ Assert.assertEquals(input, expected);
+ }
+
+ private DateTime truncateAndUTC(DateTime input) {
+ if (input == null) {
+ return null;
+ }
+ DateTime result = input.minus(input.getMillisOfSecond());
+ return result.toDateTime(DateTimeZone.UTC);
+ }
+
+ public static class TestNotificationSqlDaoModule extends AbstractModule {
+ @Override
+ protected void configure() {
+
+ final MysqlTestingHelper helper = new MysqlTestingHelper();
+ bind(MysqlTestingHelper.class).toInstance(helper);
+ DBI dbi = helper.getDBI();
+ bind(DBI.class).toInstance(dbi);
+
+ /*
+ 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/notificationq/DummyObject.java b/util/src/test/java/com/ning/billing/util/notificationq/DummyObject.java
new file mode 100644
index 0000000..9495001
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/notificationq/DummyObject.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;
+
+import java.util.UUID;
+
+public class DummyObject {
+ private final String value;
+ private final UUID key;
+
+ public DummyObject(String value, UUID key) {
+ super();
+ this.value = value;
+ this.key = key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public UUID getKey() {
+ return key;
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/DummySqlTest.java b/util/src/test/java/com/ning/billing/util/notificationq/DummySqlTest.java
new file mode 100644
index 0000000..83b1b20
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/notificationq/DummySqlTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+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.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
+import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+
+@ExternalizedSqlViaStringTemplate3()
+public interface DummySqlTest extends Transactional<DummySqlTest>, Transmogrifier, CloseMe {
+
+ @SqlUpdate
+ public void insertDummy(@Bind(binder = DummySqlTestBinder.class) DummyObject dummy);
+
+ @SqlQuery
+ @Mapper(DummySqlTestMapper.class)
+ public DummyObject getDummyFromId(@Bind("dummy_id") String dummyId);
+
+ public static class DummySqlTestBinder implements Binder<Bind, DummyObject> {
+ @Override
+ public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, DummyObject dummy) {
+ stmt.bind("dummy_id", dummy.getKey().toString());
+ stmt.bind("value", dummy.getValue());
+ }
+ }
+
+ public static class DummySqlTestMapper implements ResultSetMapper<DummyObject> {
+ @Override
+ public DummyObject map(int index, ResultSet r, StatementContext ctx)
+ throws SQLException {
+ final UUID key = UUID.fromString(r.getString("dummy_id"));
+ final String value = r.getString("value");
+ return new DummyObject(value, key);
+ }
+ }
+}
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
new file mode 100644
index 0000000..7ee2e10
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
@@ -0,0 +1,91 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TreeSet;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.notificationq.NotificationLifecycle.NotificationLifecycleState;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+
+public class MockNotificationQueue extends NotificationQueueBase implements NotificationQueue {
+
+
+ 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);
+ notifications = new TreeSet<Notification>(new Comparator<Notification>() {
+ @Override
+ public int compare(Notification o1, Notification o2) {
+ if (o1.getEffectiveDate().equals(o2.getEffectiveDate())) {
+ return o1.getNotificationKey().compareTo(o2.getNotificationKey());
+ } else {
+ return o1.getEffectiveDate().compareTo(o2.getEffectiveDate());
+ }
+ }
+ });
+ }
+
+ @Override
+ public void recordFutureNotificationFromTransaction(
+ Transmogrifier transactionalDao, DateTime futureNotificationTime,
+ NotificationKey notificationKey) {
+ Notification notification = new DefaultNotification(notificationKey.toString(), futureNotificationTime);
+ synchronized(notifications) {
+ notifications.add(notification);
+ }
+ }
+
+ @Override
+ protected void doProcessEvents(int sequenceId) {
+
+ List<Notification> processedNotifications = new ArrayList<Notification>();
+ List<Notification> oldNotifications = new ArrayList<Notification>();
+
+ List<Notification> readyNotifications = new ArrayList<Notification>();
+ synchronized(notifications) {
+ Iterator<Notification> it = notifications.iterator();
+ while (it.hasNext()) {
+ Notification cur = it.next();
+ if (cur.isAvailableForProcessing(clock.getUTCNow())) {
+ readyNotifications.add(cur);
+ }
+ }
+ 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());
+ oldNotifications.add(cur);
+ processedNotifications.add(processedNotification);
+
+ }
+ if (oldNotifications.size() > 0) {
+ notifications.removeAll(oldNotifications);
+ }
+ if (processedNotifications.size() > 0) {
+ notifications.addAll(processedNotifications);
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000..2e3bb3c
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
@@ -0,0 +1,428 @@
+/*
+ * 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;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+
+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.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.annotations.BeforeSuite;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+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.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+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;
+
+ @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() {
+ @Override
+ public void test(final DefaultNotificationQueue readyQueue) throws InterruptedException {
+ // Do nothing
+ }
+ });
+ 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() {
+ @Override
+ public boolean isNotificationProcessingOff() {
+ return off;
+ }
+ @Override
+ public long getNotificationSleepTimeMs() {
+ return sleepTime;
+ }
+ @Override
+ public int getDaoMaxReadyEvents() {
+ return maxReadyEvents;
+ }
+ @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 (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 MysqlTestingHelper helper = new MysqlTestingHelper();
+ bind(MysqlTestingHelper.class).toInstance(helper);
+ DBI dbi = helper.getDBI();
+ bind(DBI.class).toInstance(dbi);
+ /*
+ 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/resources/com/ning/billing/util/ddl_test.sql b/util/src/test/resources/com/ning/billing/util/ddl_test.sql
new file mode 100644
index 0000000..50de498
--- /dev/null
+++ b/util/src/test/resources/com/ning/billing/util/ddl_test.sql
@@ -0,0 +1,6 @@
+DROP TABLE IF EXISTS dummy;
+CREATE TABLE dummy (
+ dummy_id char(36) NOT NULL,
+ value varchar(256) NOT NULL,
+ PRIMARY KEY(dummy_id)
+) ENGINE = innodb;
diff --git a/util/src/test/resources/com/ning/billing/util/notificationq/DummySqlTest.sql.stg b/util/src/test/resources/com/ning/billing/util/notificationq/DummySqlTest.sql.stg
new file mode 100644
index 0000000..9a2e6e2
--- /dev/null
+++ b/util/src/test/resources/com/ning/billing/util/notificationq/DummySqlTest.sql.stg
@@ -0,0 +1,21 @@
+group DummySqlTest;
+
+insertDummy() ::= <<
+ insert into dummy (
+ dummy_id
+ , value
+ ) values (
+ :dummy_id
+ , :value
+ );
+>>
+
+getDummyFromId(dummy_id) ::= <<
+ select
+ dummy_id
+ , value
+ from dummy
+ where
+ dummy_id = :dummy_id
+ ;
+>>
\ No newline at end of file