killbill-memoizeit
Changes
account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java 408(+404 -4)
analytics/pom.xml 23(+23 -0)
analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java 43(+26 -17)
analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java 20(+17 -3)
api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementRepairException.java 41(+41 -0)
beatrix/pom.xml 39(+39 -0)
beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java 142(+142 -0)
beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/MockPaymentInfoReceiver.java 22(+11 -11)
beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/PaymentTestModule.java 74(+74 -0)
beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestNotifyInvoicePaymentApi.java 37(+25 -12)
beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestPaymentInvoiceIntegration.java 33(+16 -17)
beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestPaymentProvider.java 16(+9 -7)
beatrix/src/test/resources/catalogSample.xml 585(+395 -190)
bin/clean-and-install 22(+22 -0)
bin/cleanAndInstall 23(+23 -0)
bin/db-helper 156(+156 -0)
bin/start-server 99(+99 -0)
catalog/pom.xml 6(+6 -0)
doc/api.html 91(+91 -0)
doc/css/bootstrap.min.css 356(+356 -0)
doc/css/killbill.css 3(+3 -0)
doc/css/prettify.css 118(+118 -0)
doc/design.html 100(+100 -0)
doc/js/bootstrap-dropdown.js 55(+55 -0)
doc/js/prettify.js 28(+28 -0)
doc/setup.html 97(+97 -0)
doc/user.html 102(+102 -0)
entitlement/pom.xml 54(+24 -30)
entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultChargeThruApi.java 55(+55 -0)
entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java 222(+0 -222)
entitlement/src/main/java/com/ning/billing/entitlement/api/migration/AccountMigrationData.java 2(+1 -1)
entitlement/src/main/java/com/ning/billing/entitlement/api/migration/DefaultEntitlementMigrationApi.java 26(+14 -12)
entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultDeletedEvent.java 42(+42 -0)
entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultEntitlementTimelineApi.java 465(+465 -0)
entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultRepairEntitlementEvent.java 118(+118 -0)
entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultSubscriptionTimeline.java 288(+288 -0)
entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementLifecycleDao.java 28(+28 -0)
entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionApiService.java 36(+36 -0)
entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionFactory.java 56(+56 -0)
entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionDataRepair.java 210(+210 -0)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java 138(+93 -45)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionApiService.java 119(+77 -42)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionEvent.java 371(+371 -0)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionFactory.java 13(+8 -5)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionStatusDryRun.java 75(+75 -0)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java 34(+29 -5)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java 362(+224 -138)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java 114(+79 -35)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java 2(+1 -1)
entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EntitlementNotificationKey.java 91(+91 -0)
entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/AuditedEntitlementDao.java 431(+237 -194)
entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementEventSqlDao.java 70(+47 -23)
entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/RepairEntitlementDao.java 249(+249 -0)
entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java 43(+26 -17)
entitlement/src/main/java/com/ning/billing/entitlement/glue/DefaultEntitlementModule.java 77(+63 -14)
entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg 83(+49 -34)
entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EntitlementEventSqlDao.sql.stg 104(+69 -35)
entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg 92(+67 -25)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java 269(+0 -269)
entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationMemory.java 8(+4 -4)
entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestApiBaseRepair.java 222(+222 -0)
entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithAO.java 782(+782 -0)
entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java 464(+464 -0)
entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java 11(+6 -5)
entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java 152(+92 -60)
entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java 23(+8 -15)
entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java 7(+4 -3)
entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateMemory.java 13(+7 -6)
entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java 42(+26 -16)
entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java 22(+11 -11)
entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementNotificationKey.java 45(+45 -0)
entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java 26(+19 -7)
entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java 11(+6 -5)
entitlement/src/test/resources/testInput.xml 515(+351 -164)
index.html 120(+120 -0)
invoice/pom.xml 42(+13 -29)
invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java 44(+20 -24)
invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java 3(+2 -1)
invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java 277(+277 -0)
invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java 31(+31 -0)
invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java 126(+126 -0)
invoice/src/main/java/com/ning/billing/invoice/template/translator/DefaultInvoiceTranslator.java 136(+136 -0)
invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg 27(+25 -2)
invoice/src/main/resources/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.sql.stg 27(+25 -2)
invoice/src/test/java/com/ning/billing/invoice/api/migration/MockModuleNoEntitlement.java 37(+20 -17)
invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java 54(+31 -23)
invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java 96(+42 -54)
invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java 253(+182 -71)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java 9(+5 -4)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java 7(+4 -3)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java 9(+5 -4)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java 9(+5 -4)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java 5(+3 -2)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java 9(+5 -4)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java 7(+4 -3)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java 9(+5 -4)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java 14(+8 -6)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java 9(+5 -4)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java 3(+2 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java 9(+5 -4)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java 7(+4 -3)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java 9(+5 -4)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java 9(+5 -4)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java 9(+5 -4)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java 13(+7 -6)
jaxrs/pom.xml 97(+97 -0)
jaxrs/src/test/resources/log4j.xml 36(+36 -0)
junction/pom.xml 114(+114 -0)
junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java 102(+102 -0)
junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingEntitlementUserApi.java 138(+138 -0)
junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java 227(+227 -0)
junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscriptionBundle.java 67(+67 -0)
junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java 108(+108 -0)
junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java 267(+267 -0)
junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingApi.java 130(+130 -0)
junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingEvent.java 61(+41 -20)
junction/src/test/java/com/ning/billing/junction/plumbing/billing/MockSubscription.java 202(+202 -0)
junction/src/test/java/com/ning/billing/junction/plumbing/billing/MockSubscriptionEvent.java 333(+333 -0)
junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java 734(+734 -0)
junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultBillingEvent.java 12(+9 -3)
junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultEntitlementBillingApi.java 459(+459 -0)
junction/src/test/resources/log4j.xml 40(+40 -0)
overdue/pom.xml 118(+118 -0)
overdue/src/main/java/com/ning/billing/ovedue/notification/DefaultOverdueCheckNotifier.java 105(+105 -0)
overdue/src/main/java/com/ning/billing/ovedue/notification/DefaultOverdueCheckPoster.java 77(+77 -0)
overdue/src/main/java/com/ning/billing/overdue/calculator/BillingStateCalculatorBundle.java 108(+108 -0)
overdue/src/test/java/com/ning/billing/overdue/calculator/TestBillingStateCalculator.java 102(+102 -0)
overdue/src/test/java/com/ning/billing/overdue/calculator/TestBillingStateCalculatorBundle.java 144(+144 -0)
overdue/src/test/java/com/ning/billing/overdue/notification/MockOverdueCheckNotifier.java 36(+36 -0)
overdue/src/test/java/com/ning/billing/overdue/notification/TestOverdueCheckNotifier.java 205(+205 -0)
overdue/src/test/resources/log4j.xml 40(+40 -0)
overdue/src/test/resources/OverdueConfig.xml 20(+20 -0)
overdue/src/test/resources/OverdueConfigSchema.xsd 106(+106 -0)
payment/pom.xml 29(+14 -15)
pom.xml 108(+92 -16)
server/pom.xml 434(+434 -0)
server/src/main/jetty-config/etc/webdefault.xml 186(+186 -0)
server/src/main/jetty-config/ning-jetty-conf.xml 122(+122 -0)
server/src/main/resources/catalog-demo.xml 641(+641 -0)
server/src/main/resources/logback.xml 13(+13 -0)
server/src/main/webapp/WEB-INF/web.xml 35(+35 -0)
server/src/test/resources/catalog-weapons.xml 641(+641 -0)
server/src/test/resources/log4j.xml 39(+39 -0)
util/pom.xml 24(+24 -0)
util/src/main/java/com/ning/billing/util/entity/collection/dao/EntityCollectionSqlDao.java 40(+28 -12)
util/src/main/java/com/ning/billing/util/entity/collection/dao/UpdatableEntityCollectionSqlDao.java 51(+51 -0)
util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java 1(+1 -0)
util/src/main/java/com/ning/billing/util/template/translation/DefaultCatalogTranslator.java 36(+36 -0)
util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldAuditSqlDao.sql.stg 17(+0 -17)
util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldHistorySqlDao.sql.stg 18(+0 -18)
util/src/main/resources/com/ning/billing/util/ddl.sql 167(+106 -61)
util/src/main/resources/com/ning/billing/util/email/templates/HtmlInvoiceTemplate.mustache 96(+96 -0)
util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg 88(+49 -39)
util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_EN_US.properties 2(+2 -0)
util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_FR_CA.properties 1(+1 -0)
Details
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 a22491b..54cfd9c 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
@@ -21,13 +21,12 @@ import java.util.UUID;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.dao.ObjectType;
import com.ning.billing.util.entity.ExtendedEntityBase;
-import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import com.ning.billing.catalog.api.Currency;
-
-import javax.annotation.Nullable;
+import com.ning.billing.junction.api.BlockingState;
public class DefaultAccount extends ExtendedEntityBase implements Account {
private final String externalKey;
@@ -47,65 +46,31 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
private final String country;
private final String postalCode;
private final String phone;
- private final String updatedBy;
- private final DateTime updatedDate;
-
- //intended for creation and migration
- public DefaultAccount(final String createdBy, final DateTime createdDate,
- final String updatedBy, final DateTime updatedDate,
- final AccountData data) {
- this(UUID.randomUUID(), createdBy, createdDate, updatedBy, updatedDate, data);
- }
+ private final boolean isMigrated;
+ private final boolean isNotifiedForInvoices;
public DefaultAccount(final AccountData data) {
- this(UUID.randomUUID(), null, null, null, null, data);
- }
-
- public DefaultAccount(final UUID id, final AccountData data) {
- this(id, null, null, null, null, data);
+ this(UUID.randomUUID(), data);
}
/**
* This call is used to update an existing account
- *
+ *
* @param id UUID id of the existing account to update
* @param data AccountData new data for the existing account
*/
- public DefaultAccount(final UUID id, @Nullable final String createdBy, @Nullable final DateTime createdDate,
- @Nullable final String updatedBy, @Nullable final DateTime updatedDate,
- final AccountData data) {
+ public DefaultAccount(final UUID id, final AccountData data) {
this(id, data.getExternalKey(), data.getEmail(), data.getName(), data.getFirstNameLength(),
data.getCurrency(), data.getBillCycleDay(), data.getPaymentProviderName(),
data.getTimeZone(), data.getLocale(),
data.getAddress1(), data.getAddress2(), data.getCompanyName(),
data.getCity(), data.getStateOrProvince(), data.getCountry(),
- data.getPostalCode(), data.getPhone(), createdBy, createdDate,
- updatedBy, updatedDate);
+ data.getPostalCode(), data.getPhone(), data.isMigrated(), data.isNotifiedForInvoices());
}
- /**
+ /*
* This call is used for testing and update from an existing account
- * @param id
- * @param externalKey
- * @param email
- * @param name
- * @param firstNameLength
- * @param currency
- * @param billCycleDay
- * @param paymentProviderName
- * @param timeZone
- * @param locale
- * @param address1
- * @param address2
- * @param companyName
- * @param city
- * @param stateOrProvince
- * @param country
- * @param postalCode
- * @param phone
- * @param createdDate
- * @param updatedDate
- */
+ */
public DefaultAccount(final UUID id, final String externalKey, final String email,
final String name, final int firstNameLength,
final Currency currency, final int billCycleDay, final String paymentProviderName,
@@ -113,10 +78,8 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
final String address1, final String address2, final String companyName,
final String city, final String stateOrProvince, final String country,
final String postalCode, final String phone,
- final String createdBy, final DateTime createdDate,
- final String updatedBy, final DateTime updatedDate) {
-
- super(id, createdBy, createdDate);
+ final boolean isMigrated, final boolean isNotifiedForInvoices) {
+ super(id);
this.externalKey = externalKey;
this.email = email;
this.name = name;
@@ -134,8 +97,8 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
this.postalCode = postalCode;
this.country = country;
this.phone = phone;
- this.updatedBy = updatedBy;
- this.updatedDate = updatedDate;
+ this.isMigrated = isMigrated;
+ this.isNotifiedForInvoices = isNotifiedForInvoices;
}
@Override
@@ -154,8 +117,8 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
}
@Override
- public String getObjectName() {
- return ObjectType;
+ public ObjectType getObjectType() {
+ return ObjectType.ACCOUNT;
}
@Override
@@ -239,13 +202,13 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
}
@Override
- public String getUpdatedBy() {
- return updatedBy;
+ public boolean isMigrated() {
+ return this.isMigrated;
}
@Override
- public DateTime getUpdatedDate() {
- return updatedDate;
+ public boolean isNotifiedForInvoices() {
+ return isNotifiedForInvoices;
}
@Override
@@ -255,7 +218,7 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
@Override
public MutableAccountData toMutableAccountData() {
- return new MutableAccountData(this);
+ return new DefaultMutableAccountData(this);
}
@Override
@@ -277,8 +240,13 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
", stateOrProvince=" + stateOrProvince +
", postalCode=" + postalCode +
", country=" + country +
- ", tags=" + tags +
- ", fields=" + fields +
- "]";
+ ", tags=" + tagStore +
+ ", fields=" + fields +
+ "]";
+ }
+
+ @Override
+ public BlockingState getBlockingState() {
+ throw new UnsupportedOperationException();
}
}
\ No newline at end of file
diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultAccountEmail.java b/account/src/main/java/com/ning/billing/account/api/DefaultAccountEmail.java
new file mode 100644
index 0000000..d511b26
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultAccountEmail.java
@@ -0,0 +1,69 @@
+/*
+ * 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.account.api;
+
+import com.ning.billing.util.entity.UpdatableEntityBase;
+
+import java.util.UUID;
+
+public class DefaultAccountEmail extends UpdatableEntityBase implements AccountEmail {
+ private final UUID accountId;
+ private final String email;
+
+ public DefaultAccountEmail(UUID accountId, String email) {
+ super();
+ this.accountId = accountId;
+ this.email = email;
+ }
+
+ public DefaultAccountEmail(AccountEmail source, String newEmail) {
+ this(source.getId(), source.getAccountId(), newEmail);
+ }
+
+ public DefaultAccountEmail(UUID id, UUID accountId, String email) {
+ super(id);
+ this.accountId = accountId;
+ this.email = email;
+ }
+
+ @Override
+ public UUID getAccountId() {
+ return accountId;
+ }
+
+ @Override
+ public String getEmail() {
+ return email;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ DefaultAccountEmail that = (DefaultAccountEmail) o;
+
+ if (!id.equals(that.id)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+}
diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultAccountService.java b/account/src/main/java/com/ning/billing/account/api/DefaultAccountService.java
index ca2e213..af3d2a7 100644
--- a/account/src/main/java/com/ning/billing/account/api/DefaultAccountService.java
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultAccountService.java
@@ -16,32 +16,17 @@
package com.ning.billing.account.api;
-import com.google.inject.Inject;
-import com.ning.billing.lifecycle.LifecycleHandlerType;
-import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
public class DefaultAccountService implements AccountService {
private static final String ACCOUNT_SERVICE_NAME = "account-service";
- private final AccountUserApi accountApi;
-
- @Inject
- public DefaultAccountService(AccountUserApi api) {
- this.accountApi = api;
- }
-
- @Override
+ @Override
public String getName() {
return ACCOUNT_SERVICE_NAME;
}
- @Override
- public AccountUserApi getAccountUserApi() {
- return accountApi;
- }
-
- @LifecycleHandlerType(LifecycleLevel.INIT_SERVICE)
- public void initialize() {
- }
+// @LifecycleHandlerType(LifecycleLevel.INIT_SERVICE)
+// public void initialize() {
+// }
}
diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultChangedField.java b/account/src/main/java/com/ning/billing/account/api/DefaultChangedField.java
index c97a894..c979fc6 100644
--- a/account/src/main/java/com/ning/billing/account/api/DefaultChangedField.java
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultChangedField.java
@@ -16,22 +16,36 @@
package com.ning.billing.account.api;
-import com.ning.billing.util.clock.DefaultClock;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
import org.joda.time.DateTime;
public class DefaultChangedField implements ChangedField {
+
private final String fieldName;
private final String oldValue;
private final String newValue;
private final DateTime changeDate;
- public DefaultChangedField(String fieldName, String oldValue, String newValue) {
- this.changeDate = new DefaultClock().getUTCNow();
+ @JsonCreator
+ public DefaultChangedField(@JsonProperty("fieldName") String fieldName,
+ @JsonProperty("oldValue") String oldValue,
+ @JsonProperty("newValue") String newValue,
+ @JsonProperty("changeDate") DateTime changeDate) {
+ this.changeDate = changeDate;
this.fieldName = fieldName;
this.oldValue = oldValue;
this.newValue = newValue;
}
+ public DefaultChangedField(String fieldName,
+ String oldValue,
+ String newValue) {
+ this(fieldName, oldValue, newValue, new DateTime());
+ }
+
+
@Override
public String getFieldName() {
return fieldName;
@@ -51,4 +65,52 @@ public class DefaultChangedField implements ChangedField {
public DateTime getChangeDate() {
return changeDate;
}
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((changeDate == null) ? 0 : changeDate.hashCode());
+ result = prime * result
+ + ((fieldName == null) ? 0 : fieldName.hashCode());
+ result = prime * result
+ + ((newValue == null) ? 0 : newValue.hashCode());
+ result = prime * result
+ + ((oldValue == null) ? 0 : oldValue.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DefaultChangedField other = (DefaultChangedField) obj;
+ if (changeDate == null) {
+ if (other.changeDate != null)
+ return false;
+ } else if (changeDate.compareTo(other.changeDate) != 0)
+ return false;
+ if (fieldName == null) {
+ if (other.fieldName != null)
+ return false;
+ } else if (!fieldName.equals(other.fieldName))
+ return false;
+ if (newValue == null) {
+ if (other.newValue != null)
+ return false;
+ } else if (!newValue.equals(other.newValue))
+ return false;
+ if (oldValue == null) {
+ if (other.oldValue != null)
+ return false;
+ } else if (!oldValue.equals(other.oldValue))
+ return false;
+ return true;
+ }
+
}
diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultMutableAccountData.java b/account/src/main/java/com/ning/billing/account/api/DefaultMutableAccountData.java
new file mode 100644
index 0000000..0f72d41
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultMutableAccountData.java
@@ -0,0 +1,361 @@
+/*
+ * 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.account.api;
+
+import org.joda.time.DateTimeZone;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.tag.TagStore;
+
+public class DefaultMutableAccountData implements MutableAccountData {
+ private String externalKey;
+ private String email;
+ private String name;
+ private int firstNameLength;
+ private Currency currency;
+ private int billCycleDay;
+ private String paymentProviderName;
+ private DateTimeZone timeZone;
+ private String locale;
+ private String address1;
+ private String address2;
+ private String companyName;
+ private String city;
+ private String stateOrProvince;
+ private String country;
+ private String postalCode;
+ private String phone;
+ private boolean isMigrated;
+ private boolean isNotifiedForInvoices;
+
+ public DefaultMutableAccountData(String externalKey, String email, String name,
+ int firstNameLength, Currency currency, int billCycleDay,
+ String paymentProviderName, TagStore tags, DateTimeZone timeZone,
+ String locale, String address1, String address2,
+ String companyName, String city, String stateOrProvince,
+ String country, String postalCode, String phone,
+ boolean isMigrated, boolean isNotifiedForInvoices) {
+ super();
+ this.externalKey = externalKey;
+ this.email = email;
+ this.name = name;
+ this.firstNameLength = firstNameLength;
+ this.currency = currency;
+ this.billCycleDay = billCycleDay;
+ this.paymentProviderName = paymentProviderName;
+ this.timeZone = timeZone;
+ this.locale = locale;
+ this.address1 = address1;
+ this.address2 = address2;
+ this.companyName = companyName;
+ this.city = city;
+ this.stateOrProvince = stateOrProvince;
+ this.country = country;
+ this.postalCode = postalCode;
+ this.phone = phone;
+ this.isMigrated = isMigrated;
+ this.isNotifiedForInvoices = isNotifiedForInvoices;
+ }
+
+ public DefaultMutableAccountData(AccountData accountData) {
+ super();
+ this.externalKey = accountData.getExternalKey();
+ this.email = accountData.getEmail();
+ this.name = accountData.getName();
+ this.firstNameLength = accountData.getFirstNameLength();
+ this.currency = accountData.getCurrency();
+ this.billCycleDay = accountData.getBillCycleDay();
+ this.paymentProviderName = accountData.getPaymentProviderName();
+ this.timeZone = accountData.getTimeZone();
+ this.locale = accountData.getLocale();
+ this.address1 = accountData.getAddress1();
+ this.address2 = accountData.getAddress2();
+ this.companyName = accountData.getCompanyName();
+ this.city = accountData.getCity();
+ this.stateOrProvince = accountData.getStateOrProvince();
+ this.country = accountData.getCountry();
+ this.postalCode = accountData.getPostalCode();
+ this.phone = accountData.getPhone();
+ this.isMigrated = accountData.isMigrated();
+ this.isNotifiedForInvoices = accountData.isNotifiedForInvoices();
+ }
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getExternalKey()
+ */
+ @Override
+ public String getExternalKey() {
+ return externalKey;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getEmail()
+ */
+ @Override
+ public String getEmail() {
+ return email;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getName()
+ */
+ @Override
+ public String getName() {
+ return name;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getFirstNameLength()
+ */
+ @Override
+ public int getFirstNameLength() {
+ return firstNameLength;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getCurrency()
+ */
+ @Override
+ public Currency getCurrency() {
+ return currency;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getBillCycleDay()
+ */
+ @Override
+ public int getBillCycleDay() {
+ return billCycleDay;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getPaymentProviderName()
+ */
+ @Override
+ public String getPaymentProviderName() {
+ return paymentProviderName;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getTimeZone()
+ */
+ @Override
+ public DateTimeZone getTimeZone() {
+ return timeZone;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getLocale()
+ */
+ @Override
+ public String getLocale() {
+ return locale;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getAddress1()
+ */
+ @Override
+ public String getAddress1() {
+ return address1;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getAddress2()
+ */
+ @Override
+ public String getAddress2() {
+ return address2;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getCompanyName()
+ */
+ @Override
+ public String getCompanyName() {
+ return companyName;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getCity()
+ */
+ @Override
+ public String getCity() {
+ return city;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getStateOrProvince()
+ */
+ @Override
+ public String getStateOrProvince() {
+ return stateOrProvince;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getCountry()
+ */
+ @Override
+ public String getCountry() {
+ return country;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getPostalCode()
+ */
+ @Override
+ public String getPostalCode() {
+ return postalCode;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getPhone()
+ */
+ @Override
+ public String getPhone() {
+ return phone;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#isMigrated()
+ */
+ @Override
+ public boolean isMigrated() {
+ return isMigrated;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#getSendInvoiceEmails()
+ */
+ @Override
+ public boolean isNotifiedForInvoices() {
+ return isNotifiedForInvoices;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setExternalKey(java.lang.String)
+ */
+ @Override
+ public void setExternalKey(String externalKey) {
+ this.externalKey = externalKey;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setEmail(java.lang.String)
+ */
+ @Override
+ public void setEmail(String email) {
+ this.email = email;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setName(java.lang.String)
+ */
+ @Override
+ public void setName(String name) {
+ this.name = name;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setFirstNameLength(int)
+ */
+ @Override
+ public void setFirstNameLength(int firstNameLength) {
+ this.firstNameLength = firstNameLength;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setCurrency(com.ning.billing.catalog.api.Currency)
+ */
+ @Override
+ public void setCurrency(Currency currency) {
+ this.currency = currency;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setBillCycleDay(int)
+ */
+ @Override
+ public void setBillCycleDay(int billCycleDay) {
+ this.billCycleDay = billCycleDay;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setPaymentProviderName(java.lang.String)
+ */
+ @Override
+ public void setPaymentProviderName(String paymentProviderName) {
+ this.paymentProviderName = paymentProviderName;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setTimeZone(org.joda.time.DateTimeZone)
+ */
+ @Override
+ public void setTimeZone(DateTimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setLocale(java.lang.String)
+ */
+ @Override
+ public void setLocale(String locale) {
+ this.locale = locale;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setAddress1(java.lang.String)
+ */
+ @Override
+ public void setAddress1(String address1) {
+ this.address1 = address1;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setAddress2(java.lang.String)
+ */
+ @Override
+ public void setAddress2(String address2) {
+ this.address2 = address2;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setCompanyName(java.lang.String)
+ */
+ @Override
+ public void setCompanyName(String companyName) {
+ this.companyName = companyName;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setCity(java.lang.String)
+ */
+ @Override
+ public void setCity(String city) {
+ this.city = city;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setStateOrProvince(java.lang.String)
+ */
+ @Override
+ public void setStateOrProvince(String stateOrProvince) {
+ this.stateOrProvince = stateOrProvince;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setCountry(java.lang.String)
+ */
+ @Override
+ public void setCountry(String country) {
+ this.country = country;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setPostalCode(java.lang.String)
+ */
+ @Override
+ public void setPostalCode(String postalCode) {
+ this.postalCode = postalCode;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.account.api.MutableAccountData#setPhone(java.lang.String)
+ */
+ @Override
+ public void setPhone(String phone) {
+ this.phone = phone;
+ }
+
+ @Override
+ public void setIsMigrated(boolean isMigrated) {
+ this.isMigrated = isMigrated;
+ }
+
+ @Override
+ public void setIsNotifiedForInvoices(boolean isNotifiedForInvoices) {
+ this.isNotifiedForInvoices = isNotifiedForInvoices;
+ }
+
+
+}
diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java
index 91d9b82..5dabd40 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java
@@ -17,21 +17,50 @@
package com.ning.billing.account.api.user;
import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.AccountCreationNotification;
+import com.ning.billing.account.api.AccountCreationEvent;
import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.DefaultAccount;
+import com.ning.billing.catalog.api.Currency;
import java.util.UUID;
-public class DefaultAccountCreationEvent implements AccountCreationNotification {
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTimeZone;
+
+public class DefaultAccountCreationEvent implements AccountCreationEvent {
+
+ private final UUID userToken;
private final UUID id;
private final AccountData data;
- public DefaultAccountCreationEvent(Account data) {
- this.id = data.getId();
+ @JsonCreator
+ public DefaultAccountCreationEvent(@JsonProperty("data") DefaultAccountData data,
+ @JsonProperty("userToken") UUID userToken,
+ @JsonProperty("id") UUID id) {
+ this.id = id;
+ this.userToken = userToken;
this.data = data;
}
+
+ public DefaultAccountCreationEvent(Account data, UUID userToken) {
+ this.id = data.getId();
+ this.data = new DefaultAccountData(data);
+ this.userToken = userToken;
+ }
+
+ @JsonIgnore
+ @Override
+ public BusEventType getBusEventType() {
+ return BusEventType.ACCOUNT_CREATE;
+ }
@Override
+ public UUID getUserToken() {
+ return userToken;
+ }
+ @Override
public UUID getId() {
return id;
}
@@ -40,4 +69,375 @@ public class DefaultAccountCreationEvent implements AccountCreationNotification
public AccountData getData() {
return data;
}
+
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((data == null) ? 0 : data.hashCode());
+ result = prime * result + ((id == null) ? 0 : id.hashCode());
+ result = prime * result
+ + ((userToken == null) ? 0 : userToken.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DefaultAccountCreationEvent other = (DefaultAccountCreationEvent) obj;
+ if (data == null) {
+ if (other.data != null)
+ return false;
+ } else if (!data.equals(other.data))
+ return false;
+ if (id == null) {
+ if (other.id != null)
+ return false;
+ } else if (!id.equals(other.id))
+ return false;
+ if (userToken == null) {
+ if (other.userToken != null)
+ return false;
+ } else if (!userToken.equals(other.userToken))
+ return false;
+ return true;
+ }
+
+
+ public static class DefaultAccountData implements AccountData {
+
+ private final String externalKey;
+ private final String name;
+ private final Integer firstNameLength;
+ private final String email;
+ private final Integer billCycleDay;
+ private final String currency;
+ private final String paymentProviderName;
+ private final String timeZone;
+ private final String locale;
+ private final String address1;
+ private final String address2;
+ private final String companyName;
+ private final String city;
+ private final String stateOrProvince;
+ private final String postalCode;
+ private final String country;
+ private final String phone;
+ private final boolean isMigrated;
+ private final boolean isNotifiedForInvoices;
+
+
+ public DefaultAccountData(Account d) {
+ this(d.getExternalKey() != null ? d.getExternalKey().toString() : null,
+ d.getName(),
+ d.getFirstNameLength(),
+ d.getEmail(),
+ d.getBillCycleDay(),
+ d.getCurrency() != null ? d.getCurrency().name() : null,
+ d.getPaymentProviderName(),
+ d.getTimeZone() != null ? d.getTimeZone().getID() : null,
+ d.getLocale(),
+ d.getAddress1(),
+ d.getAddress2(),
+ d.getCompanyName(),
+ d.getCity(),
+ d.getStateOrProvince(),
+ d.getPostalCode(),
+ d.getCountry(),
+ d.getPhone(),
+ d.isMigrated(),
+ d.isNotifiedForInvoices());
+ }
+
+ @JsonCreator
+ public DefaultAccountData(@JsonProperty("externalKey") String externalKey,
+ @JsonProperty("name") String name,
+ @JsonProperty("firstNameLength") Integer firstNameLength,
+ @JsonProperty("email") String email,
+ @JsonProperty("billCycleDay") Integer billCycleDay,
+ @JsonProperty("currency") String currency,
+ @JsonProperty("paymentProviderName") String paymentProviderName,
+ @JsonProperty("timeZone") String timeZone,
+ @JsonProperty("locale") String locale,
+ @JsonProperty("address1") String address1,
+ @JsonProperty("address2") String address2,
+ @JsonProperty("companyName") String companyName,
+ @JsonProperty("city") String city,
+ @JsonProperty("stateOrProvince") String stateOrProvince,
+ @JsonProperty("postalCode") String postalCode,
+ @JsonProperty("country") String country,
+ @JsonProperty("phone") String phone,
+ @JsonProperty("isMigrated") boolean isMigrated,
+ @JsonProperty("isNotifiedForInvoices") boolean isNotifiedForInvoices) {
+ super();
+ this.externalKey = externalKey;
+ this.name = name;
+ this.firstNameLength = firstNameLength;
+ this.email = email;
+ this.billCycleDay = billCycleDay;
+ this.currency = currency;
+ this.paymentProviderName = paymentProviderName;
+ this.timeZone = timeZone;
+ this.locale = locale;
+ this.address1 = address1;
+ this.address2 = address2;
+ this.companyName = companyName;
+ this.city = city;
+ this.stateOrProvince = stateOrProvince;
+ this.postalCode = postalCode;
+ this.country = country;
+ this.phone = phone;
+ this.isMigrated = isMigrated;
+ this.isNotifiedForInvoices = isNotifiedForInvoices;
+ }
+
+ @Override
+ public String getExternalKey() {
+ return externalKey;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public int getFirstNameLength() {
+ return firstNameLength;
+ }
+
+ @Override
+ public String getEmail() {
+ return email;
+ }
+
+ @Override
+ public int getBillCycleDay() {
+ return billCycleDay;
+ }
+
+ @Override
+ public Currency getCurrency() {
+ return Currency.valueOf(currency);
+ }
+
+ @Override
+ public String getPaymentProviderName() {
+ return paymentProviderName;
+ }
+
+ @JsonIgnore
+ @Override
+ public DateTimeZone getTimeZone() {
+ return DateTimeZone.forID(timeZone);
+ }
+
+ @JsonProperty("timeZone")
+ public String getTimeZoneString() {
+ return timeZone;
+ }
+
+ @Override
+ public String getLocale() {
+ return locale;
+ }
+
+ @Override
+ public String getAddress1() {
+ return address1;
+ }
+
+ @Override
+ public String getAddress2() {
+ return address2;
+ }
+
+ @Override
+ public String getCompanyName() {
+ return companyName;
+ }
+
+ @Override
+ public String getCity() {
+ return city;
+ }
+
+ @Override
+ public String getStateOrProvince() {
+ return stateOrProvince;
+ }
+
+ @Override
+ public String getPostalCode() {
+ return postalCode;
+ }
+
+ @Override
+ public String getCountry() {
+ return country;
+ }
+
+ @Override
+ public String getPhone() {
+ return phone;
+ }
+
+ @Override
+ @JsonIgnore
+ public boolean isMigrated() {
+ return isMigrated;
+ }
+
+ @Override
+ @JsonIgnore
+ public boolean isNotifiedForInvoices() {
+ return isNotifiedForInvoices;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((address1 == null) ? 0 : address1.hashCode());
+ result = prime * result
+ + ((address2 == null) ? 0 : address2.hashCode());
+ result = prime * result
+ + ((billCycleDay == null) ? 0 : billCycleDay.hashCode());
+ result = prime * result + ((city == null) ? 0 : city.hashCode());
+ result = prime * result
+ + ((companyName == null) ? 0 : companyName.hashCode());
+ result = prime * result
+ + ((country == null) ? 0 : country.hashCode());
+ result = prime * result
+ + ((currency == null) ? 0 : currency.hashCode());
+ result = prime * result + ((email == null) ? 0 : email.hashCode());
+ result = prime * result
+ + ((externalKey == null) ? 0 : externalKey.hashCode());
+ result = prime
+ * result
+ + ((firstNameLength == null) ? 0 : firstNameLength
+ .hashCode());
+ result = prime * result
+ + ((locale == null) ? 0 : locale.hashCode());
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ result = prime
+ * result
+ + ((paymentProviderName == null) ? 0 : paymentProviderName
+ .hashCode());
+ result = prime * result + ((phone == null) ? 0 : phone.hashCode());
+ result = prime * result
+ + ((postalCode == null) ? 0 : postalCode.hashCode());
+ result = prime
+ * result
+ + ((stateOrProvince == null) ? 0 : stateOrProvince
+ .hashCode());
+ result = prime * result
+ + ((timeZone == null) ? 0 : timeZone.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DefaultAccountData other = (DefaultAccountData) obj;
+ if (address1 == null) {
+ if (other.address1 != null)
+ return false;
+ } else if (!address1.equals(other.address1))
+ return false;
+ if (address2 == null) {
+ if (other.address2 != null)
+ return false;
+ } else if (!address2.equals(other.address2))
+ return false;
+ if (billCycleDay == null) {
+ if (other.billCycleDay != null)
+ return false;
+ } else if (!billCycleDay.equals(other.billCycleDay))
+ return false;
+ if (city == null) {
+ if (other.city != null)
+ return false;
+ } else if (!city.equals(other.city))
+ return false;
+ if (companyName == null) {
+ if (other.companyName != null)
+ return false;
+ } else if (!companyName.equals(other.companyName))
+ return false;
+ if (country == null) {
+ if (other.country != null)
+ return false;
+ } else if (!country.equals(other.country))
+ return false;
+ if (currency == null) {
+ if (other.currency != null)
+ return false;
+ } else if (!currency.equals(other.currency))
+ return false;
+ if (email == null) {
+ if (other.email != null)
+ return false;
+ } else if (!email.equals(other.email))
+ return false;
+ if (externalKey == null) {
+ if (other.externalKey != null)
+ return false;
+ } else if (!externalKey.equals(other.externalKey))
+ return false;
+ if (firstNameLength == null) {
+ if (other.firstNameLength != null)
+ return false;
+ } else if (!firstNameLength.equals(other.firstNameLength))
+ return false;
+ if (locale == null) {
+ if (other.locale != null)
+ return false;
+ } else if (!locale.equals(other.locale))
+ return false;
+ if (name == null) {
+ if (other.name != null)
+ return false;
+ } else if (!name.equals(other.name))
+ return false;
+ if (paymentProviderName == null) {
+ if (other.paymentProviderName != null)
+ return false;
+ } else if (!paymentProviderName.equals(other.paymentProviderName))
+ return false;
+ if (phone == null) {
+ if (other.phone != null)
+ return false;
+ } else if (!phone.equals(other.phone))
+ return false;
+ if (postalCode == null) {
+ if (other.postalCode != null)
+ return false;
+ } else if (!postalCode.equals(other.postalCode))
+ return false;
+ if (stateOrProvince == null) {
+ if (other.stateOrProvince != null)
+ return false;
+ } else if (!stateOrProvince.equals(other.stateOrProvince))
+ return false;
+ if (timeZone == null) {
+ if (other.timeZone != null)
+ return false;
+ } else if (!timeZone.equals(other.timeZone))
+ return false;
+ return true;
+ }
+ }
}
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 f1c58fb..c28e25b 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,41 +19,46 @@ package com.ning.billing.account.api.user;
import java.util.List;
import java.util.UUID;
+import com.ning.billing.account.dao.AccountEmailDao;
+import org.joda.time.DateTime;
+
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.AccountEmail;
+import com.ning.billing.account.api.AccountUserApi;
import com.ning.billing.account.api.DefaultAccount;
import com.ning.billing.account.api.MigrationAccountData;
-import com.ning.billing.account.api.MutableAccountData;
import com.ning.billing.account.dao.AccountDao;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.CallContextFactory;
import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.entity.EntityPersistenceException;
-import com.ning.billing.util.tag.Tag;
-import org.joda.time.DateTime;
+import com.ning.billing.util.tag.TagDefinition;
-public class DefaultAccountUserApi implements com.ning.billing.account.api.AccountUserApi {
+public class DefaultAccountUserApi implements AccountUserApi {
private final CallContextFactory factory;
- private final AccountDao dao;
+ private final AccountDao accountDao;
+ private final AccountEmailDao accountEmailDao;
@Inject
- public DefaultAccountUserApi(final CallContextFactory factory, final AccountDao dao) {
+ public DefaultAccountUserApi(final CallContextFactory factory, final AccountDao accountDao, final AccountEmailDao accountEmailDao) {
this.factory = factory;
- this.dao = dao;
+ this.accountDao = accountDao;
+ this.accountEmailDao = accountEmailDao;
}
@Override
public Account createAccount(final AccountData data, final List<CustomField> fields,
- final List<Tag> tags, final CallContext context) throws AccountApiException {
+ final List<TagDefinition> tagDefinitions, final CallContext context) throws AccountApiException {
Account account = new DefaultAccount(data);
account.setFields(fields);
- account.addTags(tags);
+ account.addTagsFromDefinitions(tagDefinitions);
try {
- dao.create(account, context);
+ accountDao.create(account, context);
} catch (EntityPersistenceException e) {
throw new AccountApiException(e, ErrorCode.ACCOUNT_CREATION_FAILED);
}
@@ -62,29 +67,37 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
}
@Override
- public Account getAccountByKey(final String key) {
- return dao.getAccountByKey(key);
+ public Account getAccountByKey(final String key) throws AccountApiException {
+ Account account = accountDao.getAccountByKey(key);
+ if(account == null) {
+ throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, key);
+ }
+ return account;
}
@Override
- public Account getAccountById(final UUID id) {
- return dao.getById(id.toString());
+ public Account getAccountById(final UUID id) throws AccountApiException {
+ Account account = accountDao.getById(id);
+ if(account == null) {
+ throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, id);
+ }
+ return account;
}
@Override
public List<Account> getAccounts() {
- return dao.get();
+ return accountDao.get();
}
@Override
public UUID getIdFromKey(final String externalKey) throws AccountApiException {
- return dao.getIdFromKey(externalKey);
+ return accountDao.getIdFromKey(externalKey);
}
@Override
public void updateAccount(final Account account, final CallContext context) throws AccountApiException {
try {
- dao.update(account, context);
+ accountDao.update(account, context);
} catch (EntityPersistenceException e) {
throw new AccountApiException(e, ErrorCode.ACCOUNT_UPDATE_FAILED);
}
@@ -96,36 +109,34 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
Account account = new DefaultAccount(accountId, accountData);
try {
- dao.update(account, context);
+ accountDao.update(account, context);
} catch (EntityPersistenceException e) {
- throw new AccountApiException(e, ErrorCode.ACCOUNT_UPDATE_FAILED);
+ throw new AccountApiException(e, e.getCode(), e.getMessage());
}
-
}
@Override
public void updateAccount(final String externalKey, final AccountData accountData, final CallContext context) throws AccountApiException {
UUID accountId = getIdFromKey(externalKey);
- if(accountId == null) {
+ if (accountId == null) {
throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, externalKey);
}
-
updateAccount(accountId, accountData, context);
}
@Override
public Account migrateAccount(final MigrationAccountData data, final List<CustomField> fields,
- final List<Tag> tags, final CallContext context)
+ final List<TagDefinition> tagDefinitions, final CallContext context)
throws AccountApiException {
DateTime createdDate = data.getCreatedDate() == null ? context.getCreatedDate() : data.getCreatedDate();
DateTime updatedDate = data.getUpdatedDate() == null ? context.getUpdatedDate() : data.getUpdatedDate();
CallContext migrationContext = factory.toMigrationCallContext(context, createdDate, updatedDate);
Account account = new DefaultAccount(data);
account.setFields(fields);
- account.addTags(tags);
+ account.addTagsFromDefinitions(tagDefinitions);
try {
- dao.create(account, migrationContext);
+ accountDao.create(account, migrationContext);
} catch (EntityPersistenceException e) {
throw new AccountApiException(e, ErrorCode.ACCOUNT_CREATION_FAILED);
}
@@ -133,5 +144,13 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
return account;
}
+ @Override
+ public List<AccountEmail> getEmails(final UUID accountId) {
+ return accountEmailDao.getEmails(accountId);
+ }
+ @Override
+ public void saveEmails(final UUID accountId, final List<AccountEmail> newEmails, final CallContext context) {
+ accountEmailDao.saveEmails(accountId, newEmails, context);
+ }
}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountBinder.java b/account/src/main/java/com/ning/billing/account/dao/AccountBinder.java
index a4a00a8..57b61c2 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountBinder.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountBinder.java
@@ -60,6 +60,8 @@ public @interface AccountBinder {
q.bind("country", account.getCountry());
q.bind("postalCode", account.getPostalCode());
q.bind("phone", account.getPhone());
+ q.bind("migrated", account.isMigrated());
+ q.bind("isNotifiedForInvoices", account.isNotifiedForInvoices());
}
};
}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
index 4be1058..67474ab 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
@@ -16,11 +16,13 @@
package com.ning.billing.account.dao;
+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.AccountEmail;
import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.entity.UpdatableEntityDao;
+import com.ning.billing.util.entity.dao.UpdatableEntityDao;
public interface AccountDao extends UpdatableEntityDao<Account> {
public Account getAccountByKey(String key);
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailBinder.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailBinder.java
new file mode 100644
index 0000000..bd71364
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailBinder.java
@@ -0,0 +1,48 @@
+/*
+ * 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.account.dao;
+
+import com.ning.billing.account.api.AccountEmail;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(AccountEmailBinder.AccountEmailBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface AccountEmailBinder {
+ public static class AccountEmailBinderFactory implements BinderFactory {
+ @Override
+ public Binder<AccountEmailBinder, AccountEmail> build(Annotation annotation) {
+ return new Binder<AccountEmailBinder, AccountEmail>() {
+ @Override
+ public void bind(@SuppressWarnings("rawtypes") SQLStatement q, AccountEmailBinder bind, AccountEmail accountEmail) {
+ q.bind("id", accountEmail.getId().toString());
+ q.bind("accountId", accountEmail.getAccountId().toString());
+ q.bind("email", accountEmail.getEmail());
+ }
+ };
+ }
+ }
+}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailHistoryBinder.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailHistoryBinder.java
new file mode 100644
index 0000000..01344b3
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailHistoryBinder.java
@@ -0,0 +1,53 @@
+/*
+ * 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.account.dao;
+
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.util.dao.EntityHistory;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(AccountEmailHistoryBinder.AccountEmailHistoryBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface AccountEmailHistoryBinder {
+ public static class AccountEmailHistoryBinderFactory implements BinderFactory {
+ @Override
+ public Binder<AccountEmailHistoryBinder, EntityHistory<AccountEmail>> build(Annotation annotation) {
+ return new Binder<AccountEmailHistoryBinder, EntityHistory<AccountEmail>>() {
+ @Override
+ public void bind(SQLStatement q, AccountEmailHistoryBinder bind, EntityHistory<AccountEmail> history) {
+ q.bind("recordId", history.getValue());
+ q.bind("changeType", history.getChangeType().toString());
+
+ AccountEmail accountEmail = history.getEntity();
+ q.bind("id", accountEmail.getId().toString());
+ q.bind("accountId", accountEmail.getAccountId().toString());
+ q.bind("email", accountEmail.getEmail());
+ }
+ };
+ }
+ }
+}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailMapper.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailMapper.java
new file mode 100644
index 0000000..9a46344
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailMapper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.account.dao;
+
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.account.api.DefaultAccountEmail;
+import com.ning.billing.util.dao.MapperBase;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+public class AccountEmailMapper extends MapperBase implements ResultSetMapper<AccountEmail> {
+ @Override
+ public AccountEmail map(int index, ResultSet result, StatementContext context) throws SQLException {
+ UUID id = UUID.fromString(result.getString("id"));
+ UUID accountId = UUID.fromString(result.getString("account_id"));
+ String email = result.getString("email");
+
+ return new DefaultAccountEmail(id, accountId, email);
+ }
+}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java
new file mode 100644
index 0000000..15f405e
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java
@@ -0,0 +1,99 @@
+/*
+ * 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.account.dao;
+
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.ObjectTypeBinder;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import java.util.List;
+
+@ExternalizedSqlViaStringTemplate3
+@RegisterMapper(AccountEmailMapper.class)
+public interface AccountEmailSqlDao extends UpdatableEntityCollectionSqlDao<AccountEmail>, Transactional<AccountEmailSqlDao>, Transmogrifier {
+ @Override
+ @SqlBatch(transactional=false)
+ public void insertFromTransaction(@Bind("objectId") final String objectId,
+ @ObjectTypeBinder final ObjectType objectType,
+ @AccountEmailBinder final List<AccountEmail> entities,
+ @CallContextBinder final CallContext context);
+
+ @Override
+ @SqlBatch(transactional=false)
+ public void updateFromTransaction(@Bind("objectId") final String objectId,
+ @ObjectTypeBinder final ObjectType objectType,
+ @AccountEmailBinder final List<AccountEmail> entities,
+ @CallContextBinder final CallContext context);
+
+ @Override
+ @SqlBatch(transactional=false)
+ public void deleteFromTransaction(@Bind("objectId") final String objectId,
+ @ObjectTypeBinder final ObjectType objectType,
+ @AccountEmailBinder final List<AccountEmail> entities,
+ @CallContextBinder final CallContext context);
+
+ @Override
+ @SqlBatch(transactional=false)
+ public void addHistoryFromTransaction(@Bind("objectId") final String objectId,
+ @ObjectTypeBinder final ObjectType objectType,
+ @AccountEmailHistoryBinder final List<EntityHistory<AccountEmail>> entities,
+ @CallContextBinder final CallContext context);
+// @Override
+// @SqlUpdate
+// public void create(@AccountEmailBinder final AccountEmail accountEmail,
+// @CallContextBinder final CallContext context);
+
+// @SqlBatch(transactional = false)
+// public void create(@AccountEmailBinder final List<AccountEmail> accountEmailList,
+// @CallContextBinder final CallContext context);
+
+// @Override
+// @SqlUpdate
+// public void update(@AccountEmailBinder final AccountEmail accountEmail,
+// @CallContextBinder final CallContext context);
+//
+// @SqlBatch(transactional = false)
+// public void update(@AccountEmailBinder final List<AccountEmail> accountEmailList,
+// @CallContextBinder final CallContext context);
+//
+// @SqlUpdate
+// public void delete(@AccountEmailBinder final AccountEmail accountEmail,
+// @CallContextBinder final CallContext context);
+//
+// @SqlBatch(transactional = false)
+// public void delete(@AccountEmailBinder final List<AccountEmail> accountEmailList,
+// @CallContextBinder final CallContext context);
+//
+// @SqlBatch(transactional=false)
+// public void insertAccountEmailHistoryFromTransaction(@Bind("historyRecordId") final List<String> historyRecordIdList,
+// @AccountEmailBinder final List<AccountEmail> accountEmail,
+// @ChangeTypeBinder final ChangeType changeType,
+// @CallContextBinder final CallContext context);
+//
+// @SqlQuery
+// public List<AccountEmail> getByAccountId(@Bind("accountId") final String accountId);
+}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountHistoryBinder.java b/account/src/main/java/com/ning/billing/account/dao/AccountHistoryBinder.java
new file mode 100644
index 0000000..66cba44
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountHistoryBinder.java
@@ -0,0 +1,74 @@
+/*
+ * 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.account.dao;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.dao.EntityHistory;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(AccountHistoryBinder.AccountHistoryBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface AccountHistoryBinder {
+ public static class AccountHistoryBinderFactory implements BinderFactory {
+ @Override
+ public Binder<AccountHistoryBinder, EntityHistory<Account>> build(Annotation annotation) {
+ return new Binder<AccountHistoryBinder, EntityHistory<Account>>() {
+ @Override
+ public void bind(SQLStatement q, AccountHistoryBinder bind, EntityHistory<Account> history) {
+ q.bind("recordId", history.getValue());
+ q.bind("changeType", history.getChangeType().toString());
+
+ Account account = history.getEntity();
+ q.bind("id", account.getId().toString());
+ q.bind("externalKey", account.getExternalKey());
+ q.bind("email", account.getEmail());
+ q.bind("name", account.getName());
+ q.bind("firstNameLength", account.getFirstNameLength());
+ Currency currency = account.getCurrency();
+ q.bind("currency", (currency == null) ? null : currency.toString());
+ q.bind("billingCycleDay", account.getBillCycleDay());
+ q.bind("paymentProviderName", account.getPaymentProviderName());
+ DateTimeZone timeZone = account.getTimeZone();
+ q.bind("timeZone", (timeZone == null) ? null : timeZone.toString());
+ q.bind("locale", account.getLocale());
+ q.bind("address1", account.getAddress1());
+ q.bind("address2", account.getAddress2());
+ q.bind("companyName", account.getCompanyName());
+ q.bind("city", account.getCity());
+ q.bind("stateOrProvince", account.getStateOrProvince());
+ q.bind("country", account.getCountry());
+ q.bind("postalCode", account.getPostalCode());
+ q.bind("phone", account.getPhone());
+ q.bind("migrated", account.isMigrated());
+ q.bind("isNotifiedForInvoices", account.isNotifiedForInvoices());
+ }
+ };
+ }
+ }
+}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountMapper.java b/account/src/main/java/com/ning/billing/account/dao/AccountMapper.java
new file mode 100644
index 0000000..1a62c19
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountMapper.java
@@ -0,0 +1,69 @@
+/*
+ * 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.account.dao;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.DefaultAccount;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.dao.MapperBase;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+public class AccountMapper extends MapperBase implements ResultSetMapper<Account> {
+ @Override
+ public Account map(int index, ResultSet result, StatementContext context) throws SQLException {
+ UUID id = UUID.fromString(result.getString("id"));
+ String externalKey = result.getString("external_key");
+ String email = result.getString("email");
+ String name = result.getString("name");
+ int firstNameLength = result.getInt("first_name_length");
+ int billingCycleDay = result.getInt("billing_cycle_day");
+
+ String currencyString = result.getString("currency");
+ Currency currency = (currencyString == null) ? null : Currency.valueOf(currencyString);
+
+ String paymentProviderName = result.getString("payment_provider_name");
+
+ String timeZoneId = result.getString("time_zone");
+ DateTimeZone timeZone = (timeZoneId == null) ? null : DateTimeZone.forID(timeZoneId);
+
+ String locale = result.getString("locale");
+
+ String address1 = result.getString("address1");
+ String address2 = result.getString("address2");
+ String companyName = result.getString("company_name");
+ String city = result.getString("city");
+ String stateOrProvince = result.getString("state_or_province");
+ String postalCode = result.getString("postal_code");
+ String country = result.getString("country");
+ String phone = result.getString("phone");
+
+ Boolean isMigrated = result.getBoolean("migrated");
+ Boolean isNotifiedForInvoices = result.getBoolean("is_notified_for_invoices");
+
+ return new DefaultAccount(id, externalKey, email, name,firstNameLength, currency,
+ billingCycleDay, paymentProviderName, timeZone, locale,
+ address1, address2, companyName, city, stateOrProvince, country, postalCode, phone,
+ isMigrated, isNotifiedForInvoices);
+ }
+}
\ No newline at end of file
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 8c09af4..8ed05ee 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
@@ -16,17 +16,17 @@
package com.ning.billing.account.dao;
-import java.sql.ResultSet;
-import java.sql.SQLException;
import java.util.UUID;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.util.ChangeType;
+import com.ning.billing.util.dao.AuditSqlDao;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.dao.MapperBase;
-import com.ning.billing.util.entity.UpdatableEntityDao;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.skife.jdbi.v2.StatementContext;
+import com.ning.billing.util.dao.ChangeTypeBinder;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.UuidMapper;
+import com.ning.billing.util.entity.dao.UpdatableEntitySqlDao;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
@@ -34,16 +34,10 @@ import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
-import org.skife.jdbi.v2.tweak.ResultSetMapper;
-
-import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.user.AccountBuilder;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.util.UuidMapper;
@ExternalizedSqlViaStringTemplate3
-@RegisterMapper({UuidMapper.class, AccountSqlDao.AccountMapper.class})
-public interface AccountSqlDao extends UpdatableEntityDao<Account>, Transactional<AccountSqlDao>, Transmogrifier {
+@RegisterMapper({UuidMapper.class, AccountMapper.class})
+public interface AccountSqlDao extends UpdatableEntitySqlDao<Account>, Transactional<AccountSqlDao>, Transmogrifier {
@SqlQuery
public Account getAccountByKey(@Bind("externalKey") final String key);
@@ -58,53 +52,8 @@ public interface AccountSqlDao extends UpdatableEntityDao<Account>, Transactiona
@SqlUpdate
public void update(@AccountBinder Account account, @CallContextBinder final CallContext context);
- public static class AccountMapper extends MapperBase implements ResultSetMapper<Account> {
- @Override
- public Account map(int index, ResultSet result, StatementContext context) throws SQLException {
- UUID id = UUID.fromString(result.getString("id"));
- String externalKey = result.getString("external_key");
- String email = result.getString("email");
- String name = result.getString("name");
- int firstNameLength = result.getInt("first_name_length");
- int billingCycleDay = result.getInt("billing_cycle_day");
-
- String currencyString = result.getString("currency");
- Currency currency = (currencyString == null) ? null : Currency.valueOf(currencyString);
-
- String paymentProviderName = result.getString("payment_provider_name");
-
- String timeZoneId = result.getString("time_zone");
- DateTimeZone timeZone = (timeZoneId == null) ? null : DateTimeZone.forID(timeZoneId);
-
- String locale = result.getString("locale");
-
- String address1 = result.getString("address1");
- String address2 = result.getString("address2");
- String companyName = result.getString("company_name");
- String city = result.getString("city");
- String stateOrProvince = result.getString("state_or_province");
- String postalCode = result.getString("postal_code");
- String country = result.getString("country");
- String phone = result.getString("phone");
-
- String createdBy = result.getString("created_by");
- DateTime createdDate = getDate(result, "created_date");
- String updatedBy = result.getString("updated_by");
- DateTime updatedDate = getDate(result, "updated_date");
-
- return new AccountBuilder(id).externalKey(externalKey).email(email)
- .name(name).firstNameLength(firstNameLength)
- .phone(phone).currency(currency)
- .billingCycleDay(billingCycleDay)
- .paymentProviderName(paymentProviderName)
- .timeZone(timeZone).locale(locale)
- .address1(address1).address2(address2)
- .companyName(companyName)
- .city(city).stateOrProvince(stateOrProvince)
- .postalCode(postalCode).country(country)
- .createdBy(createdBy).createdDate(createdDate)
- .updatedBy(updatedBy).updatedDate(updatedDate)
- .build();
- }
- }
+ @Override
+ @SqlUpdate
+ public void insertHistoryFromTransaction(@AccountHistoryBinder final EntityHistory<Account> account,
+ @CallContextBinder final CallContext context);
}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AuditedAccountDao.java b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountDao.java
index 2d06666..6d52d10 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AuditedAccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountDao.java
@@ -21,28 +21,37 @@ import java.util.List;
import java.util.UUID;
import com.ning.billing.util.ChangeType;
-import com.ning.billing.util.audit.dao.AuditSqlDao;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.customfield.dao.CustomFieldDao;
+import com.ning.billing.util.dao.EntityAudit;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.TableName;
import com.ning.billing.util.entity.EntityPersistenceException;
import com.ning.billing.util.tag.dao.TagDao;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.Transaction;
import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import com.google.inject.Inject;
import com.ning.billing.ErrorCode;
import com.ning.billing.account.api.Account;
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.account.api.user.DefaultAccountChangeNotification;
+import com.ning.billing.account.api.AccountChangeEvent;
+import com.ning.billing.account.api.AccountCreationEvent;
+import com.ning.billing.account.api.user.DefaultAccountChangeEvent;
import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.customfield.dao.CustomFieldSqlDao;
import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
import com.ning.billing.util.tag.Tag;
public class AuditedAccountDao implements AccountDao {
+ private final static Logger log = LoggerFactory.getLogger(AuditedAccountDao.class);
+
private final AccountSqlDao accountSqlDao;
private final TagDao tagDao;
private final CustomFieldDao customFieldDao;
@@ -80,11 +89,11 @@ public class AuditedAccountDao implements AccountDao {
}
@Override
- public Account getById(final String id) {
+ public Account getById(final UUID id) {
return accountSqlDao.inTransaction(new Transaction<Account, AccountSqlDao>() {
@Override
public Account inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws Exception {
- Account account = accountSqlDao.getById(id);
+ Account account = accountSqlDao.getById(id.toString());
if (account != null) {
setCustomFieldsFromWithinTransaction(account, accountSqlDao);
setTagsFromWithinTransaction(account, accountSqlDao);
@@ -122,20 +131,25 @@ public class AuditedAccountDao implements AccountDao {
throw new AccountApiException(ErrorCode.ACCOUNT_ALREADY_EXISTS, key);
}
transactionalDao.create(account, context);
- UUID historyId = UUID.randomUUID();
- AccountHistorySqlDao historyDao = accountSqlDao.become(AccountHistorySqlDao.class);
- historyDao.insertAccountHistoryFromTransaction(account, historyId.toString(),
- ChangeType.INSERT.toString(), context);
+ // insert history
+ Long recordId = accountSqlDao.getRecordId(account.getId().toString());
+ EntityHistory<Account> history = new EntityHistory<Account>(account.getId(), recordId, account, ChangeType.INSERT);
+ accountSqlDao.insertHistoryFromTransaction(history, context);
- AuditSqlDao auditDao = accountSqlDao.become(AuditSqlDao.class);
- auditDao.insertAuditFromTransaction("account_history", historyId.toString(),
- ChangeType.INSERT, context);
+ // insert audit
+ Long historyRecordId = accountSqlDao.getHistoryRecordId(recordId);
+ EntityAudit audit = new EntityAudit(TableName.ACCOUNT_HISTORY, historyRecordId, ChangeType.INSERT);
+ accountSqlDao.insertAuditFromTransaction(audit, context);
saveTagsFromWithinTransaction(account, transactionalDao, context);
saveCustomFieldsFromWithinTransaction(account, transactionalDao, context);
- AccountCreationNotification creationEvent = new DefaultAccountCreationEvent(account);
- eventBus.post(creationEvent);
+ AccountCreationEvent creationEvent = new DefaultAccountCreationEvent(account, context.getUserToken());
+ try {
+ eventBus.postFromTransaction(creationEvent, transactionalDao);
+ } catch (EventBusException e) {
+ log.warn("Failed to post account creation event for account " + account.getId(), e);
+ }
return null;
}
});
@@ -155,9 +169,9 @@ public class AuditedAccountDao implements AccountDao {
try {
accountSqlDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
@Override
- public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws EntityPersistenceException, Bus.EventBusException {
+ public Void inTransaction(final AccountSqlDao transactional, final TransactionStatus status) throws EntityPersistenceException, Bus.EventBusException {
String accountId = account.getId().toString();
- Account currentAccount = accountSqlDao.getById(accountId);
+ Account currentAccount = transactional.getById(accountId);
if (currentAccount == null) {
throw new EntityPersistenceException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, accountId);
}
@@ -167,22 +181,26 @@ public class AuditedAccountDao implements AccountDao {
throw new EntityPersistenceException(ErrorCode.ACCOUNT_CANNOT_CHANGE_EXTERNAL_KEY, currentKey);
}
- accountSqlDao.update(account, context);
+ transactional.update(account, context);
- UUID historyId = UUID.randomUUID();
- AccountHistorySqlDao historyDao = accountSqlDao.become(AccountHistorySqlDao.class);
- historyDao.insertAccountHistoryFromTransaction(account, historyId.toString(), ChangeType.UPDATE.toString(), context);
+ Long recordId = accountSqlDao.getRecordId(account.getId().toString());
+ EntityHistory<Account> history = new EntityHistory<Account>(account.getId(), recordId, account, ChangeType.INSERT);
+ accountSqlDao.insertHistoryFromTransaction(history, context);
- AuditSqlDao auditDao = accountSqlDao.become(AuditSqlDao.class);
- auditDao.insertAuditFromTransaction("account_history" ,historyId.toString(),
- ChangeType.INSERT, context);
+ Long historyRecordId = accountSqlDao.getHistoryRecordId(recordId);
+ EntityAudit audit = new EntityAudit(TableName.ACCOUNT_HISTORY, historyRecordId, ChangeType.INSERT);
+ accountSqlDao.insertAuditFromTransaction(audit, context);
- saveTagsFromWithinTransaction(account, accountSqlDao, context);
- saveCustomFieldsFromWithinTransaction(account, accountSqlDao, context);
+ saveTagsFromWithinTransaction(account, transactional, context);
+ saveCustomFieldsFromWithinTransaction(account, transactional, context);
- AccountChangeNotification changeEvent = new DefaultAccountChangeNotification(account.getId(), currentAccount, account);
+ AccountChangeEvent changeEvent = new DefaultAccountChangeEvent(account.getId(), context.getUserToken(), currentAccount, account);
if (changeEvent.hasChanges()) {
- eventBus.post(changeEvent);
+ try {
+ eventBus.postFromTransaction(changeEvent, transactional);
+ } catch (EventBusException e) {
+ log.warn("Failed to post account change event for account " + account.getId(), e);
+ }
}
return null;
}
@@ -203,7 +221,7 @@ public class AuditedAccountDao implements AccountDao {
private void setCustomFieldsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao) {
CustomFieldSqlDao customFieldSqlDao = transactionalDao.become(CustomFieldSqlDao.class);
- List<CustomField> fields = customFieldSqlDao.load(account.getId().toString(), account.getObjectName());
+ List<CustomField> fields = customFieldSqlDao.load(account.getId().toString(), account.getObjectType());
account.clearFields();
if (fields != null) {
@@ -212,7 +230,7 @@ public class AuditedAccountDao implements AccountDao {
}
private void setTagsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao) {
- List<Tag> tags = tagDao.loadTagsFromTransaction(transactionalDao, account.getId(), Account.ObjectType);
+ List<Tag> tags = tagDao.loadEntitiesFromTransaction(transactionalDao, account.getId(), ObjectType.ACCOUNT);
account.clearTags();
if (tags != null) {
@@ -222,11 +240,11 @@ public class AuditedAccountDao implements AccountDao {
private void saveTagsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao,
final CallContext context) {
- tagDao.saveTagsFromTransaction(transactionalDao, account.getId(), account.getObjectName(), account.getTagList(), context);
+ tagDao.saveEntitiesFromTransaction(transactionalDao, account.getId(), account.getObjectType(), account.getTagList(), context);
}
private void saveCustomFieldsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao,
final CallContext context) {
- customFieldDao.saveFields(transactionalDao, account.getId(), account.getObjectName(), account.getFieldList(), context);
+ customFieldDao.saveEntitiesFromTransaction(transactionalDao, account.getId(), account.getObjectType(), account.getFieldList(), context);
}
}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AuditedAccountEmailDao.java b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountEmailDao.java
new file mode 100644
index 0000000..e51c194
--- /dev/null
+++ b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountEmailDao.java
@@ -0,0 +1,134 @@
+/*
+ * 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.account.dao;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.dao.AuditedCollectionDaoBase;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.TableName;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+import java.util.List;
+import java.util.UUID;
+
+public class AuditedAccountEmailDao extends AuditedCollectionDaoBase<AccountEmail> implements AccountEmailDao {
+ private final AccountEmailSqlDao accountEmailSqlDao;
+
+ @Inject
+ public AuditedAccountEmailDao(IDBI dbi) {
+ this.accountEmailSqlDao = dbi.onDemand(AccountEmailSqlDao.class);
+ }
+
+ @Override
+ public List<AccountEmail> getEmails(final UUID accountId) {
+ return super.loadEntities(accountId, ObjectType.ACCOUNT_EMAIL);
+ }
+
+ @Override
+ public void saveEmails(final UUID accountId, final List<AccountEmail> emails, final CallContext context) {
+ super.saveEntitiesFromTransaction(accountEmailSqlDao, accountId, ObjectType.ACCOUNT_EMAIL, emails, context);
+ }
+
+ public void test() {
+ accountEmailSqlDao.test();
+ }
+
+// @Override
+// public List<AccountEmail> getEmails(final UUID accountId) {
+// return accountEmailSqlDao.load(accountId.toString(), null);
+// //return accountEmailSqlDao.getByAccountId(accountId.toString());
+// }
+
+// @Override
+// public void saveEmails(final UUID accountId, final List<AccountEmail> emails, final CallContext context) {
+// final List<AccountEmail> existingEmails = accountEmailSqlDao.getByAccountId(accountId.toString());
+// final List<AccountEmail> updatedEmails = new ArrayList<AccountEmail>();
+//
+// Iterator<AccountEmail> existingEmailIterator = existingEmails.iterator();
+// while (existingEmailIterator.hasNext()) {
+// AccountEmail existingEmail = existingEmailIterator.next();
+//
+// Iterator<AccountEmail> newEmailIterator = emails.iterator();
+// while (newEmailIterator.hasNext()) {
+// AccountEmail newEmail = newEmailIterator.next();
+// if (newEmail.getId().equals(existingEmail.getId())) {
+// // check equality; if not equal, add to updated
+// if (!newEmail.equals(existingEmail)) {
+// updatedEmails.add(newEmail);
+// }
+//
+// // remove from both
+// newEmailIterator.remove();
+// existingEmailIterator.remove();
+// }
+// }
+// }
+//
+// // remaining emails in newEmail are inserts; remaining emails in existingEmail are deletes
+// accountEmailSqlDao.inTransaction(new Transaction<Void, AccountEmailSqlDao>() {
+// @Override
+// public Void inTransaction(AccountEmailSqlDao dao, TransactionStatus transactionStatus) throws Exception {
+// dao.create(emails, context);
+// dao.update(updatedEmails, context);
+// dao.delete(existingEmails, context);
+//
+// List<String> insertHistoryIdList = getIdList(emails.size());
+// List<String> updateHistoryIdList = getIdList(updatedEmails.size());
+// List<String> deleteHistoryIdList = getIdList(existingEmails.size());
+//
+// // insert histories
+// dao.insertAccountEmailHistoryFromTransaction(insertHistoryIdList, emails, ChangeType.INSERT, context);
+// dao.insertAccountEmailHistoryFromTransaction(updateHistoryIdList, updatedEmails, ChangeType.UPDATE, context);
+// dao.insertAccountEmailHistoryFromTransaction(deleteHistoryIdList, existingEmails, ChangeType.DELETE, context);
+//
+// // insert audits
+// auditSqlDao.insertAuditFromTransaction(TableName.ACCOUNT_EMAIL_HISTORY, insertHistoryIdList, ChangeType.INSERT, context);
+// auditSqlDao.insertAuditFromTransaction(TableName.ACCOUNT_EMAIL_HISTORY, updateHistoryIdList, ChangeType.UPDATE, context);
+// auditSqlDao.insertAuditFromTransaction(TableName.ACCOUNT_EMAIL_HISTORY, deleteHistoryIdList, ChangeType.DELETE, context);
+//
+// return null;
+// }
+// });
+// }
+//
+// private List<String> getIdList(int size) {
+// List<String> results = new ArrayList<String>();
+// for (int i = 0; i < size; i++) {
+// results.add(UUID.randomUUID().toString());
+// }
+// return results;
+// }
+
+ @Override
+ protected TableName getTableName() {
+ return TableName.ACCOUNT_EMAIL_HISTORY;
+ }
+
+ @Override
+ protected UpdatableEntityCollectionSqlDao<AccountEmail> transmogrifyDao(Transmogrifier transactionalDao) {
+ return transactionalDao.become(AccountEmailSqlDao.class);
+ }
+
+ @Override
+ protected UpdatableEntityCollectionSqlDao<AccountEmail> getSqlDao() {
+ return accountEmailSqlDao;
+ }
+}
diff --git a/account/src/main/java/com/ning/billing/account/glue/AccountModule.java b/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
index 60e0a14..956d878 100644
--- a/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
+++ b/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
@@ -16,7 +16,6 @@
package com.ning.billing.account.glue;
-import org.skife.config.ConfigurationObjectFactory;
import com.google.inject.AbstractModule;
import com.ning.billing.account.api.AccountService;
@@ -24,21 +23,22 @@ import com.ning.billing.account.api.AccountUserApi;
import com.ning.billing.account.api.DefaultAccountService;
import com.ning.billing.account.api.user.DefaultAccountUserApi;
import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.account.dao.AccountEmailDao;
import com.ning.billing.account.dao.AuditedAccountDao;
+import com.ning.billing.account.dao.AuditedAccountEmailDao;
+import com.ning.billing.util.glue.RealImplementation;
public class AccountModule extends AbstractModule {
-
private void installConfig() {
- final AccountConfig config = new ConfigurationObjectFactory(System.getProperties()).build(AccountConfig.class);
- bind(AccountConfig.class).toInstance(config);
}
protected void installAccountDao() {
+ bind(AccountEmailDao.class).to(AuditedAccountEmailDao.class).asEagerSingleton();
bind(AccountDao.class).to(AuditedAccountDao.class).asEagerSingleton();
}
protected void installAccountUserApi() {
- bind(AccountUserApi.class).to(DefaultAccountUserApi.class).asEagerSingleton();
+ bind(AccountUserApi.class).annotatedWith(RealImplementation.class).to(DefaultAccountUserApi.class).asEagerSingleton();
}
private void installAccountService() {
diff --git a/account/src/main/resources/com/ning/billing/account/dao/AccountEmailSqlDao.sql.stg b/account/src/main/resources/com/ning/billing/account/dao/AccountEmailSqlDao.sql.stg
new file mode 100644
index 0000000..3f981a4
--- /dev/null
+++ b/account/src/main/resources/com/ning/billing/account/dao/AccountEmailSqlDao.sql.stg
@@ -0,0 +1,85 @@
+group account_emails;
+
+fields(prefix) ::= <<
+ <prefix>id,
+ <prefix>account_id,
+ <prefix>email,
+ <prefix>created_by,
+ <prefix>created_date,
+ <prefix>updated_by,
+ <prefix>updated_date
+>>
+
+insertFromTransaction() ::= <<
+ INSERT INTO account_emails(<fields()>)
+ VALUES (:id, :accountId, :email, :userName, :createdDate, :userName, :updatedDate);
+>>
+
+updateFromTransaction() ::= <<
+ UPDATE account_emails
+ SET email = :email, updated_by = :userName, updated_date = :updatedDate
+ WHERE id = :id;
+>>
+
+deleteFromTransaction() ::= <<
+ DELETE FROM account_emails
+ WHERE id = :id;
+>>
+
+addHistoryFromTransaction() ::= <<
+ INSERT INTO account_email_history(record_id, id, account_id, email, change_type, updated_by, date)
+ VALUES (:recordId, :id, :accountId, :email, :changeType, :userName, :updatedDate);
+>>
+
+load() ::= <<
+ SELECT <fields()> FROM account_emails WHERE account_id = :objectId;
+>>
+
+getRecordIds() ::= <<
+ SELECT record_id, id
+ FROM account_emails
+ WHERE account_id = :objectId;
+>>
+
+getMaxHistoryRecordId() ::= <<
+ SELECT MAX(history_record_id)
+ FROM account_email_history;
+>>
+
+getHistoryRecordIds() ::= <<
+ SELECT history_record_id, record_id
+ FROM account_email_history
+ WHERE history_record_id > :maxHistoryRecordId;
+>>
+
+getById() ::= <<
+ SELECT <fields()> FROM account_emails WHERE id = :id;
+>>
+
+get() ::= <<
+ SELECT <fields()> FROM account_emails;
+>>
+
+getByAccountId() ::= <<
+ SELECT <fields()> FROM account_emails WHERE account_id = :accountId;
+>>
+
+auditFields(prefix) ::= <<
+ <prefix>table_name,
+ <prefix>record_id,
+ <prefix>change_type,
+ <prefix>change_date,
+ <prefix>changed_by,
+ <prefix>reason_code,
+ <prefix>comments,
+ <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+ INSERT INTO audit_log(<auditFields()>)
+ VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
+test() ::= <<
+ SELECT 1 FROM account_emails;
+>>
\ No newline at end of file
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 eda3949..fda1503 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
@@ -19,6 +19,8 @@ accountFields(prefix) ::= <<
<prefix>country,
<prefix>postal_code,
<prefix>phone,
+ <prefix>migrated,
+ <prefix>is_notified_for_invoices,
<prefix>created_by,
<prefix>created_date,
<prefix>updated_by,
@@ -32,7 +34,7 @@ create() ::= <<
(:id, :externalKey, :email, :name, :firstNameLength, :currency, :billingCycleDay,
:paymentProviderName, :timeZone, :locale,
:address1, :address2, :companyName, :city, :stateOrProvince, :country, :postalCode, :phone,
- :userName, :createdDate, :userName, :updatedDate);
+ :migrated, :isNotifiedForInvoices, :userName, :createdDate, :userName, :updatedDate);
>>
update() ::= <<
@@ -42,10 +44,58 @@ update() ::= <<
time_zone = :timeZone, locale = :locale,
address1 = :address1, address2 = :address2, company_name = :companyName, city = :city, state_or_province = :stateOrProvince,
country = :country, postal_code = :postalCode, phone = :phone,
- updated_date = :updatedDate, updated_by = :userName
+ is_notified_for_invoices = :isNotifiedForInvoices, updated_date = :updatedDate, updated_by = :userName
WHERE id = :id;
>>
+historyFields() ::= <<
+ record_id,
+ id,
+ external_key,
+ email,
+ name,
+ first_name_length,
+ currency,
+ billing_cycle_day,
+ payment_provider_name,
+ time_zone,
+ locale,
+ address1,
+ address2,
+ company_name,
+ city,
+ state_or_province,
+ country,
+ postal_code,
+ phone,
+ migrated,
+ is_notified_for_invoices,
+ change_type,
+ updated_by,
+ date
+>>
+
+getRecordId() ::= <<
+ SELECT record_id
+ FROM accounts
+ WHERE id = :id;
+>>
+
+getHistoryRecordId() ::= <<
+ SELECT MAX(history_record_id)
+ FROM account_history
+ WHERE record_id = :recordId;
+>>
+
+insertHistoryFromTransaction() ::= <<
+ INSERT INTO account_history(<historyFields()>)
+ VALUES
+ (:recordId, :id, :externalKey, :email, :name, :firstNameLength, :currency,
+ :billingCycleDay, :paymentProviderName, :timeZone, :locale,
+ :address1, :address2, :companyName, :city, :stateOrProvince,
+ :country, :postalCode, :phone, :migrated, :isNotifiedForInvoices, :changeType, :userName, :createdDate);
+>>
+
getAccountByKey() ::= <<
select <accountFields()>
from accounts
@@ -69,6 +119,22 @@ getIdFromKey() ::= <<
WHERE external_key = :externalKey;
>>
+auditFields(prefix) ::= <<
+ <prefix>table_name,
+ <prefix>record_id,
+ <prefix>change_type,
+ <prefix>change_date,
+ <prefix>changed_by,
+ <prefix>reason_code,
+ <prefix>comments,
+ <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+ INSERT INTO audit_log(<auditFields()>)
+ VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
test() ::= <<
SELECT 1 FROM accounts;
>>
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 ff9a41c..5fe7c90 100644
--- a/account/src/main/resources/com/ning/billing/account/ddl.sql
+++ b/account/src/main/resources/com/ning/billing/account/ddl.sql
@@ -1,5 +1,6 @@
DROP TABLE IF EXISTS accounts;
CREATE TABLE accounts (
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
id char(36) NOT NULL,
external_key varchar(128) NULL,
email varchar(50) NOT NULL,
@@ -19,18 +20,21 @@ CREATE TABLE accounts (
postal_code varchar(11) DEFAULT NULL,
phone varchar(25) DEFAULT NULL,
migrated bool DEFAULT false,
+ is_notified_for_invoices boolean NOT NULL,
created_date datetime NOT NULL,
created_by varchar(50) NOT NULL,
updated_date datetime DEFAULT NULL,
updated_by varchar(50) DEFAULT NULL,
- PRIMARY KEY(id)
+ PRIMARY KEY(record_id)
) ENGINE=innodb;
+CREATE UNIQUE INDEX accounts_id ON accounts(id);
CREATE UNIQUE INDEX accounts_external_key ON accounts(external_key);
CREATE UNIQUE INDEX accounts_email ON accounts(email);
DROP TABLE IF EXISTS account_history;
CREATE TABLE account_history (
- history_id char(36) NOT NULL,
+ history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ record_id int(11) unsigned NOT NULL,
id char(36) NOT NULL,
external_key varchar(128) NULL,
email varchar(50) NOT NULL,
@@ -49,8 +53,41 @@ CREATE TABLE account_history (
country varchar(50) DEFAULT NULL,
postal_code varchar(11) DEFAULT NULL,
phone varchar(25) DEFAULT NULL,
+ migrated bool DEFAULT false,
+ is_notified_for_invoices boolean NOT NULL,
+ change_type char(6) NOT NULL,
+ updated_by varchar(50) NOT NULL,
+ date datetime NOT NULL,
+ PRIMARY KEY(history_record_id)
+) ENGINE=innodb;
+CREATE INDEX account_history_record_id ON account_history(record_id);
+
+DROP TABLE IF EXISTS account_emails;
+CREATE TABLE account_emails (
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ id char(36) NOT NULL,
+ account_id char(36) NOT NULL,
+ email varchar(50) NOT NULL,
+ created_by varchar(50) NOT NULL,
+ created_date datetime NOT NULL,
+ updated_by varchar(50) NOT NULL,
+ updated_date datetime NOT NULL,
+ PRIMARY KEY(record_id)
+) ENGINE=innodb;
+CREATE UNIQUE INDEX account_email_id ON account_emails(id);
+CREATE INDEX account_email_account_id ON account_emails(account_id);
+CREATE UNIQUE INDEX account_email_account_id_email ON account_emails(account_id, email);
+
+DROP TABLE IF EXISTS account_email_history;
+CREATE TABLE account_email_history (
+ history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ record_id int(11) unsigned NOT NULL,
+ id char(36) NOT NULL,
+ account_id char(36) NOT NULL,
+ email varchar(50) NOT NULL,
change_type char(6) NOT NULL,
updated_by varchar(50) NOT NULL,
- date datetime
+ date datetime NOT NULL,
+ PRIMARY KEY(history_record_id)
) ENGINE=innodb;
-CREATE INDEX account_id ON account_history(id);
\ No newline at end of file
+CREATE INDEX account_email_record_id ON account_email_history(record_id);
\ No newline at end of file
diff --git a/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
index d2fea9e..f916278 100644
--- a/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
+++ b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
@@ -22,11 +22,11 @@ import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.tag.TagDefinition;
import org.joda.time.DateTimeZone;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.tag.Tag;
public class MockAccountUserApi implements AccountUserApi {
private final CopyOnWriteArrayList<Account> accounts = new CopyOnWriteArrayList<Account>();
@@ -51,16 +51,16 @@ public class MockAccountUserApi implements AccountUserApi {
final String phone) {
Account result = new DefaultAccount(id, externalKey, email, name,
- firstNameLength, currency, billCycleDay, paymentProviderName,
- timeZone, locale, address1, address2, companyName, city,
- stateOrProvince, country, postalCode, phone, null, null, null, null);
+ firstNameLength, currency, billCycleDay, paymentProviderName,
+ timeZone, locale, address1, address2, companyName, city,
+ stateOrProvince, country, postalCode, phone, false, false);
accounts.add(result);
return result;
}
@Override
public Account createAccount(final AccountData data, final List<CustomField> fields,
- final List<Tag> tags, final CallContext context) throws AccountApiException {
+ final List<TagDefinition> tagDefinitions, final CallContext context) throws AccountApiException {
Account result = new DefaultAccount(data);
accounts.add(result);
return result;
@@ -102,13 +102,23 @@ public class MockAccountUserApi implements AccountUserApi {
}
@Override
+ public List<AccountEmail> getEmails(UUID accountId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public void updateAccount(final Account account, final CallContext context) {
throw new UnsupportedOperationException();
}
@Override
public Account migrateAccount(final MigrationAccountData data,
- final List<CustomField> fields, final List<Tag> tags, final CallContext context)
+ final List<CustomField> fields, final List<TagDefinition> tagDefinitions, final CallContext context)
throws AccountApiException {
Account result = new DefaultAccount(data);
accounts.add(result);
diff --git a/account/src/test/java/com/ning/billing/account/api/user/TestEventJson.java b/account/src/test/java/com/ning/billing/account/api/user/TestEventJson.java
new file mode 100644
index 0000000..6518355
--- /dev/null
+++ b/account/src/test/java/com/ning/billing/account/api/user/TestEventJson.java
@@ -0,0 +1,71 @@
+/*
+ * 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.account.api.user;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.AccountChangeEvent;
+import com.ning.billing.account.api.ChangedField;
+import com.ning.billing.account.api.DefaultChangedField;
+import com.ning.billing.account.api.user.DefaultAccountCreationEvent.DefaultAccountData;
+
+public class TestEventJson {
+
+ private ObjectMapper mapper = new ObjectMapper();
+
+ @BeforeTest(groups= {"fast"})
+ public void setup() {
+ mapper = new ObjectMapper();
+ mapper.disable(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS);
+ }
+
+ @Test(groups= {"fast"})
+ public void testDefaultAccountChangeEvent() throws Exception {
+
+ List<ChangedField> changes = new ArrayList<ChangedField>();
+ changes.add(new DefaultChangedField("fieldXX", "valueX", "valueXXX"));
+ changes.add(new DefaultChangedField("fieldYY", "valueY", "valueYYY"));
+ AccountChangeEvent e = new DefaultAccountChangeEvent(UUID.randomUUID(), changes, UUID.randomUUID());
+
+ String json = mapper.writeValueAsString(e);
+
+ Class<?> claz = Class.forName("com.ning.billing.account.api.user.DefaultAccountChangeEvent");
+ Object obj = mapper.readValue(json, claz);
+ Assert.assertTrue(obj.equals(e));
+ }
+
+ @Test(groups= {"fast"})
+ public void testAccountCreationEvent() throws Exception {
+
+ DefaultAccountData data = new DefaultAccountData("dsfdsf", "bobo", 3, "bobo@yahoo.com", 12, "USD", "paypal",
+ "UTC", "US", "21 avenue", "", "Gling", "San Franciso", "CA", "94110", "USA", "4126789887", false, false);
+ DefaultAccountCreationEvent e = new DefaultAccountCreationEvent(data, UUID.randomUUID(), UUID.randomUUID());
+
+ String json = mapper.writeValueAsString(e);
+ Class<?> claz = Class.forName(DefaultAccountCreationEvent.class.getName());
+ Object obj = mapper.readValue(json, claz);
+ Assert.assertTrue(obj.equals(e));
+
+ }
+}
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 3a51bcc..9eb462f 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
@@ -20,11 +20,19 @@ import static org.testng.Assert.fail;
import java.io.IOException;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.InMemoryBus;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.CallOrigin;
import com.ning.billing.util.callcontext.UserType;
import com.ning.billing.util.callcontext.DefaultCallContextFactory;
import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.customfield.dao.AuditedCustomFieldDao;
+import com.ning.billing.util.customfield.dao.CustomFieldDao;
+import com.ning.billing.util.tag.dao.AuditedTagDao;
+import com.ning.billing.util.tag.dao.TagDao;
import org.apache.commons.io.IOUtils;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.IDBI;
@@ -33,17 +41,15 @@ import org.skife.jdbi.v2.TransactionStatus;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
-import com.ning.billing.account.glue.AccountModuleWithEmbeddedDb;
import com.ning.billing.util.bus.DefaultBusService;
import com.ning.billing.util.bus.BusService;
import org.testng.annotations.BeforeMethod;
public abstract class AccountDaoTestBase {
- protected AccountModuleWithEmbeddedDb module;
+ private final MysqlTestingHelper helper = new MysqlTestingHelper();
+
protected AccountDao accountDao;
+ protected AccountEmailDao accountEmailDao;
protected IDBI dbi;
protected CallContext context;
@@ -52,26 +58,30 @@ public abstract class AccountDaoTestBase {
protected void setup() throws IOException {
// Health check test to make sure MySQL is setup properly
try {
- module = new AccountModuleWithEmbeddedDb();
final String accountDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
final String utilDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
- module.startDb();
- module.initDb(accountDdl);
- module.initDb(utilDdl);
+ helper.startMysql();
+ helper.initDb(accountDdl);
+ helper.initDb(utilDdl);
- final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module);
- dbi = injector.getInstance(IDBI.class);
+ dbi = helper.getDBI();
- accountDao = injector.getInstance(AccountDao.class);
- accountDao.test();
+ Bus bus = new InMemoryBus();
+ BusService busService = new DefaultBusService(bus);
+ ((DefaultBusService) busService).startBus();
- Clock clock = injector.getInstance(Clock.class);
- context = new DefaultCallContextFactory(clock).createCallContext("Vizzini", CallOrigin.TEST, UserType.TEST);
+ TagDao tagDao = new AuditedTagDao(dbi);
+ CustomFieldDao customFieldDao = new AuditedCustomFieldDao(dbi);
+ accountDao = new AuditedAccountDao(dbi, bus, tagDao, customFieldDao);
+ accountDao.test();
- BusService busService = injector.getInstance(BusService.class);
- ((DefaultBusService) busService).startBus();
+ accountEmailDao = new AuditedAccountEmailDao(dbi);
+ accountEmailDao.test();
+
+ Clock clock = new ClockMock();
+ context = new DefaultCallContextFactory(clock).createCallContext("Account Dao Tests", CallOrigin.TEST, UserType.TEST);
}
catch (Throwable t) {
fail(t.toString());
@@ -81,7 +91,7 @@ public abstract class AccountDaoTestBase {
@AfterClass(alwaysRun = true)
public void stopMysql()
{
- module.stopDb();
+ helper.stopMysql();
}
@BeforeMethod(alwaysRun = true)
@@ -91,6 +101,8 @@ public abstract class AccountDaoTestBase {
public Void inTransaction(Handle h, TransactionStatus status) throws Exception {
h.execute("truncate table accounts");
h.execute("truncate table notifications");
+ h.execute("truncate table bus_events");
+ h.execute("truncate table claimed_bus_events");
h.execute("truncate table claimed_notifications");
h.execute("truncate table tag_definitions");
h.execute("truncate table tags");
diff --git a/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
index 645124b..907447d 100644
--- a/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
+++ b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
@@ -24,9 +24,8 @@ import java.util.concurrent.ConcurrentHashMap;
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.AccountChangeNotification;
-import com.ning.billing.account.api.user.DefaultAccountChangeNotification;
+import com.ning.billing.account.api.AccountChangeEvent;
+import com.ning.billing.account.api.user.DefaultAccountChangeEvent;
import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.bus.Bus;
@@ -34,7 +33,7 @@ import com.ning.billing.util.bus.Bus.EventBusException;
public class MockAccountDao implements AccountDao {
private final Bus eventBus;
- private final Map<String, Account> accounts = new ConcurrentHashMap<String, Account>();
+ private final Map<UUID, Account> accounts = new ConcurrentHashMap<UUID, Account>();
@Inject
public MockAccountDao(Bus eventBus) {
@@ -43,10 +42,10 @@ public class MockAccountDao implements AccountDao {
@Override
public void create(Account account, CallContext context) {
- accounts.put(account.getId().toString(), account);
+ accounts.put(account.getId(), account);
try {
- eventBus.post(new DefaultAccountCreationEvent(account));
+ eventBus.post(new DefaultAccountCreationEvent(account, null));
}
catch (EventBusException ex) {
throw new RuntimeException(ex);
@@ -54,7 +53,7 @@ public class MockAccountDao implements AccountDao {
}
@Override
- public Account getById(String id) {
+ public Account getById(UUID id) {
return accounts.get(id);
}
@@ -85,9 +84,9 @@ public class MockAccountDao implements AccountDao {
@Override
public void update(Account account, CallContext context) {
- Account currentAccount = accounts.put(account.getId().toString(), account);
+ Account currentAccount = accounts.put(account.getId(), account);
- AccountChangeNotification changeEvent = new DefaultAccountChangeNotification(account.getId(), currentAccount, account);
+ AccountChangeEvent changeEvent = new DefaultAccountChangeEvent(account.getId(), null, currentAccount, account);
if (changeEvent.hasChanges()) {
try {
eventBus.post(changeEvent);
diff --git a/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java
index 74fe321..8700cfa 100644
--- a/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java
+++ b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java
@@ -17,8 +17,12 @@
package com.ning.billing.account.glue;
import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.account.dao.AccountEmailDao;
import com.ning.billing.account.dao.MockAccountDao;
+import com.ning.billing.mock.BrainDeadProxyFactory;
import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.customfield.dao.CustomFieldDao;
import com.ning.billing.util.glue.CallContextModule;
import com.ning.billing.util.glue.FieldStoreModule;
import com.ning.billing.util.tag.MockTagStoreModuleMemory;
@@ -27,6 +31,8 @@ public class AccountModuleWithMocks extends AccountModule {
@Override
protected void installAccountDao() {
bind(MockAccountDao.class).asEagerSingleton();
+ AccountEmailDao accountEmailDao = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountEmailDao.class);
+ bind(AccountEmailDao.class).toInstance(accountEmailDao);
bind(AccountDao.class).to(MockAccountDao.class);
}
@@ -35,7 +41,5 @@ public class AccountModuleWithMocks extends AccountModule {
super.configure();
install(new MockClockModule());
install(new CallContextModule());
- install(new MockTagStoreModuleMemory());
- install(new FieldStoreModule());
}
}
analytics/pom.xml 23(+23 -0)
diff --git a/analytics/pom.xml b/analytics/pom.xml
index b98db69..1a60b63 100644
--- a/analytics/pom.xml
+++ b/analytics/pom.xml
@@ -65,11 +65,34 @@
<dependency>
<groupId>com.ning.billing</groupId>
<artifactId>killbill-catalog</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-catalog</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-entitlement</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.ning.billing</groupId>
<artifactId>killbill-entitlement</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-junction</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-junction</artifactId>
+ <type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
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 3c3bab3..bf6f3eb 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/AnalyticsListener.java
@@ -19,12 +19,13 @@ 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;
-import com.ning.billing.invoice.api.InvoiceCreationNotification;
-import com.ning.billing.payment.api.PaymentError;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.account.api.AccountChangeEvent;
+import com.ning.billing.account.api.AccountCreationEvent;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
public class AnalyticsListener {
private final BusinessSubscriptionTransitionRecorder bstRecorder;
@@ -37,7 +38,7 @@ public class AnalyticsListener {
}
@Subscribe
- public void handleSubscriptionTransitionChange(final SubscriptionTransition event) throws AccountApiException {
+ public void handleSubscriptionTransitionChange(final SubscriptionEvent event) throws AccountApiException, EntitlementUserApiException {
switch (event.getTransitionType()) {
// A susbcription enters either through migration or as newly created subscription
case MIGRATE_ENTITLEMENT:
@@ -66,12 +67,12 @@ public class AnalyticsListener {
}
@Subscribe
- public void handleAccountCreation(final AccountCreationNotification event) {
+ public void handleAccountCreation(final AccountCreationEvent event) {
bacRecorder.accountCreated(event.getData());
}
@Subscribe
- public void handleAccountChange(final AccountChangeNotification event) {
+ public void handleAccountChange(final AccountChangeEvent event) {
if (!event.hasChanges()) {
return;
}
@@ -80,17 +81,17 @@ public class AnalyticsListener {
}
@Subscribe
- public void handleInvoice(final InvoiceCreationNotification event) {
+ public void handleInvoice(final InvoiceCreationEvent event) {
bacRecorder.accountUpdated(event.getAccountId());
}
@Subscribe
- public void handlePaymentInfo(final PaymentInfo paymentInfo) {
+ public void handlePaymentInfo(final PaymentInfoEvent paymentInfo) {
bacRecorder.accountUpdated(paymentInfo);
}
@Subscribe
- public void handlePaymentError(final PaymentError paymentError) {
+ public void handlePaymentError(final PaymentErrorEvent paymentError) {
// TODO - we can't tie the error back to an account yet
}
}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
index 6832a2b..e692eb7 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
@@ -16,8 +16,18 @@
package com.ning.billing.analytics;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+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.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.ChangedField;
@@ -25,17 +35,10 @@ import com.ning.billing.analytics.dao.BusinessAccountDao;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceUserApi;
import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentApiException;
import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentInfoEvent;
import com.ning.billing.util.tag.Tag;
-import org.joda.time.DateTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
public class BusinessAccountRecorder {
private static final Logger log = LoggerFactory.getLogger(BusinessAccountRecorder.class);
@@ -54,11 +57,16 @@ public class BusinessAccountRecorder {
}
public void accountCreated(final AccountData data) {
- final Account account = accountApi.getAccountByKey(data.getExternalKey());
- final BusinessAccount bac = createBusinessAccountFromAccount(account);
+ Account account;
+ try {
+ account = accountApi.getAccountByKey(data.getExternalKey());
+ final BusinessAccount bac = createBusinessAccountFromAccount(account);
- log.info("ACCOUNT CREATION " + bac);
- dao.createAccount(bac);
+ log.info("ACCOUNT CREATION " + bac);
+ dao.createAccount(bac);
+ } catch (AccountApiException e) {
+ log.warn("Error encountered creating BusinessAccount",e);
+ }
}
/**
@@ -77,18 +85,20 @@ public class BusinessAccountRecorder {
*
* @param paymentInfo payment object (from the payment plugin)
*/
- public void accountUpdated(final PaymentInfo paymentInfo) {
- final PaymentAttempt paymentAttempt = paymentApi.getPaymentAttemptForPaymentId(paymentInfo.getPaymentId());
- if (paymentAttempt == null) {
- return;
- }
+ public void accountUpdated(final PaymentInfoEvent paymentInfo) {
+ try {
+ final PaymentAttempt paymentAttempt = paymentApi.getPaymentAttemptForPaymentId(paymentInfo.getId());
+ if (paymentAttempt == null) {
+ return;
+ }
- final Account account = accountApi.getAccountById(paymentAttempt.getAccountId());
- if (account == null) {
- return;
+ final Account account = accountApi.getAccountById(paymentAttempt.getAccountId());
+ accountUpdated(account.getId());
+ } catch (AccountApiException e) {
+ log.warn("Error encountered creating BusinessAccount",e);
+ } catch (PaymentApiException e) {
+ log.warn("Error encountered creating BusinessAccount",e);
}
-
- accountUpdated(account.getId());
}
/**
@@ -97,23 +107,28 @@ public class BusinessAccountRecorder {
* @param accountId account id associated with the created invoice
*/
public void accountUpdated(final UUID accountId) {
- final Account account = accountApi.getAccountById(accountId);
+ try {
+ final Account account = accountApi.getAccountById(accountId);
- if (account == null) {
- log.warn("Couldn't find account {}", accountId);
- return;
- }
+ if (account == null) {
+ log.warn("Couldn't find account {}", accountId);
+ return;
+ }
- BusinessAccount bac = dao.getAccount(account.getExternalKey());
- if (bac == null) {
- bac = createBusinessAccountFromAccount(account);
- log.info("ACCOUNT CREATION " + bac);
- dao.createAccount(bac);
- } else {
- updateBusinessAccountFromAccount(account, bac);
- log.info("ACCOUNT UPDATE " + bac);
- dao.saveAccount(bac);
+ BusinessAccount bac = dao.getAccount(account.getExternalKey());
+ if (bac == null) {
+ bac = createBusinessAccountFromAccount(account);
+ log.info("ACCOUNT CREATION " + bac);
+ dao.createAccount(bac);
+ } else {
+ updateBusinessAccountFromAccount(account, bac);
+ log.info("ACCOUNT UPDATE " + bac);
+ dao.saveAccount(bac);
+ }
+ } catch (AccountApiException e) {
+ log.warn("Error encountered creating BusinessAccount",e);
}
+
}
private BusinessAccount createBusinessAccountFromAccount(final Account account) {
@@ -161,30 +176,25 @@ public class BusinessAccountRecorder {
}
// Retrieve payments information for these invoices
- DateTime lastPaymentDate = null;
- final List<PaymentInfo> payments = paymentApi.getPaymentInfo(invoiceIds);
- if (payments != null) {
- for (final PaymentInfo payment : payments) {
- // Use the last payment method/type/country as the default one for the account
- if (lastPaymentDate == null || payment.getCreatedDate().isAfter(lastPaymentDate)) {
- lastPaymentDate = payment.getCreatedDate();
-
- lastPaymentStatus = payment.getStatus();
- paymentMethod = payment.getPaymentMethod();
- creditCardType = payment.getCardType();
- billingAddressCountry = payment.getCardCountry();
- }
- }
+ try {
+ final PaymentInfoEvent payment = paymentApi.getLastPaymentInfo(invoiceIds);
+
+ lastPaymentStatus = payment.getStatus();
+ paymentMethod = payment.getPaymentMethod();
+ creditCardType = payment.getCardType();
+ billingAddressCountry = payment.getCardCountry();
+
+ bac.setLastPaymentStatus(lastPaymentStatus);
+ bac.setPaymentMethod(paymentMethod);
+ bac.setCreditCardType(creditCardType);
+ bac.setBillingAddressCountry(billingAddressCountry);
+ bac.setLastInvoiceDate(lastInvoiceDate);
+ bac.setTotalInvoiceBalance(totalInvoiceBalance);
+
+ bac.setBalance(invoiceUserApi.getAccountBalance(account.getId()));
+ } catch (PaymentApiException ex) {
+ // TODO: handle this exception
}
}
-
- bac.setLastPaymentStatus(lastPaymentStatus);
- bac.setPaymentMethod(paymentMethod);
- bac.setCreditCardType(creditCardType);
- bac.setBillingAddressCountry(billingAddressCountry);
- bac.setLastInvoiceDate(lastInvoiceDate);
- bac.setTotalInvoiceBalance(totalInvoiceBalance);
-
- bac.setBalance(invoiceUserApi.getAccountBalance(account.getId()));
}
}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
index 71e3402..8b00dff 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
@@ -17,6 +17,7 @@
package com.ning.billing.analytics;
import com.ning.billing.analytics.utils.Rounder;
+import com.ning.billing.catalog.api.Catalog;
import com.ning.billing.catalog.api.CatalogApiException;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.Duration;
@@ -87,17 +88,98 @@ public class BusinessSubscription
* want the phase start date).
*
* @param subscription Subscription to use as a model
- * @param currency Account currency
+ * @param currency ACCOUNT currency
*/
- BusinessSubscription(final Subscription subscription, final Currency currency)
+ BusinessSubscription(final Subscription subscription, final Currency currency, Catalog catalog)
{
- this(subscription.getCurrentPriceList(), subscription.getCurrentPlan(), subscription.getCurrentPhase(), currency, subscription.getStartDate(), subscription.getState(), subscription.getId(), subscription.getBundleId());
+ this(subscription.getCurrentPriceList() == null ? null : subscription.getCurrentPriceList().getName(), subscription.getCurrentPlan().getName(), subscription.getCurrentPhase().getName(), currency, subscription.getStartDate(), subscription.getState(), subscription.getId(), subscription.getBundleId(), catalog);
}
+ public BusinessSubscription(final String priceList, final String currentPlan, final String currentPhase, final Currency currency, final DateTime startDate, final SubscriptionState state, final UUID subscriptionId, final UUID bundleId, Catalog catalog) {
+ Plan thePlan = null;
+ PlanPhase thePhase = null;
+ try {
+ thePlan = (currentPlan != null) ? catalog.findPlan(currentPlan, new DateTime(), startDate) : null;
+ thePhase = (currentPhase != null) ? catalog.findPhase(currentPhase, new DateTime(), startDate) : null;
+ } catch (CatalogApiException e) {
+ log.error(String.format("Failed to retrieve Plan from catalog for plan %s, phase ", currentPlan, currentPhase));
+ }
+
+ this.priceList = priceList;
+
+ // Record plan information
+ if (currentPlan != null && thePlan.getProduct() != null) {
+ final Product product = thePlan.getProduct();
+ productName = product.getName();
+ productCategory = product.getCategory();
+ // TODO - we should keep the product type
+ productType = product.getCatalogName();
+ }
+ else {
+ productName = null;
+ productCategory = null;
+ productType = null;
+ }
+
+ // Record phase information
+ if (currentPhase != null) {
+ slug = thePhase.getName();
+
+ if (thePhase.getPhaseType() != null) {
+ phase = thePhase.getPhaseType().toString();
+ }
+ else {
+ phase = null;
+ }
+
+ if (thePhase.getBillingPeriod() != null) {
+ billingPeriod = thePhase.getBillingPeriod().toString();
+ }
+ else {
+ billingPeriod = null;
+ }
+
+ if (thePhase.getRecurringPrice() != null) {
+ //TODO check if this is the right way to handle exception
+ BigDecimal tmpPrice = null;
+ try {
+ tmpPrice = thePhase.getRecurringPrice().getPrice(USD);
+ } catch (CatalogApiException e) {
+ tmpPrice = new BigDecimal(0);
+ }
+ price = tmpPrice;
+ mrr = getMrrFromISubscription(thePhase.getDuration(), price);
+ }
+ else {
+ price = BigDecimal.ZERO;
+ mrr = BigDecimal.ZERO;
+ }
+ }
+ else {
+ slug = null;
+ phase = null;
+ billingPeriod = null;
+ price = BigDecimal.ZERO;
+ mrr = BigDecimal.ZERO;
+ }
+
+ if (currency != null) {
+ this.currency = currency.toString();
+ }
+ else {
+ this.currency = null;
+ }
+
+ this.startDate = startDate;
+ this.state = state;
+ this.subscriptionId = subscriptionId;
+ this.bundleId = bundleId;
+ }
+
public BusinessSubscription(final String priceList, final Plan currentPlan, final PlanPhase currentPhase, final Currency currency, final DateTime startDate, final SubscriptionState state, final UUID subscriptionId, final UUID bundleId)
{
this.priceList = priceList;
-
+
// Record plan information
if (currentPlan != null && currentPlan.getProduct() != null) {
final Product product = currentPlan.getProduct();
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionEvent.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionEvent.java
index 2d45fd5..b1668dc 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionEvent.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionEvent.java
@@ -16,6 +16,13 @@
package com.ning.billing.analytics;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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.Product;
import com.ning.billing.catalog.api.ProductCategory;
@@ -27,6 +34,9 @@ import static com.ning.billing.entitlement.api.user.Subscription.SubscriptionSta
*/
public class BusinessSubscriptionEvent
{
+
+ private static final Logger log = LoggerFactory.getLogger(BusinessSubscriptionEvent.class);
+
private static final String MISC = "MISC";
public enum EventType
@@ -78,44 +88,52 @@ public class BusinessSubscriptionEvent
return eventType;
}
- public static BusinessSubscriptionEvent subscriptionCreated(final Plan plan)
+ public static BusinessSubscriptionEvent subscriptionCreated(final String plan, Catalog catalog, DateTime eventTime, DateTime subscriptionCreationDate)
{
- return eventFromType(EventType.ADD, plan);
+ return eventFromType(EventType.ADD, plan, catalog, eventTime, subscriptionCreationDate);
}
- public static BusinessSubscriptionEvent subscriptionCancelled(final Plan plan)
+ public static BusinessSubscriptionEvent subscriptionCancelled(final String plan, Catalog catalog, DateTime eventTime, DateTime subscriptionCreationDate)
{
- return eventFromType(EventType.CANCEL, plan);
+ return eventFromType(EventType.CANCEL, plan, catalog, eventTime, subscriptionCreationDate);
}
- public static BusinessSubscriptionEvent subscriptionChanged(final Plan plan)
+ public static BusinessSubscriptionEvent subscriptionChanged(final String plan, Catalog catalog, DateTime eventTime, DateTime subscriptionCreationDate)
{
- return eventFromType(EventType.CHANGE, plan);
+ return eventFromType(EventType.CHANGE, plan, catalog, eventTime, subscriptionCreationDate);
}
- public static BusinessSubscriptionEvent subscriptionRecreated(final Plan plan)
+ public static BusinessSubscriptionEvent subscriptionRecreated(final String plan, Catalog catalog, DateTime eventTime, DateTime subscriptionCreationDate)
{
- return eventFromType(EventType.RE_ADD, plan);
+ return eventFromType(EventType.RE_ADD, plan, catalog, eventTime, subscriptionCreationDate);
}
- public static BusinessSubscriptionEvent subscriptionPhaseChanged(final Plan plan, final SubscriptionState state)
+ public static BusinessSubscriptionEvent subscriptionPhaseChanged(final String plan, final SubscriptionState state, Catalog catalog, DateTime eventTime, DateTime subscriptionCreationDate)
{
if (state != null && state.equals(SubscriptionState.CANCELLED)) {
- return eventFromType(EventType.SYSTEM_CANCEL, plan);
+ return eventFromType(EventType.SYSTEM_CANCEL, plan, catalog, eventTime, subscriptionCreationDate);
}
else {
- return eventFromType(EventType.SYSTEM_CHANGE, plan);
+ return eventFromType(EventType.SYSTEM_CHANGE, plan, catalog, eventTime, subscriptionCreationDate);
}
}
- private static BusinessSubscriptionEvent eventFromType(final EventType eventType, final Plan plan)
+ private static BusinessSubscriptionEvent eventFromType(final EventType eventType, final String plan, Catalog catalog, DateTime eventTime, DateTime subscriptionCreationDate)
{
- final ProductCategory category = getTypeFromSubscription(plan);
+ Plan thePlan = null;
+ try {
+ thePlan = catalog.findPlan(plan, eventTime, subscriptionCreationDate);
+ } catch (CatalogApiException e) {
+ log.error(String.format("Failed to retrieve PLan from catalog for %s", plan));
+
+ }
+ final ProductCategory category = getTypeFromSubscription(thePlan);
return new BusinessSubscriptionEvent(eventType, category);
}
private static ProductCategory getTypeFromSubscription(final Plan plan)
{
+
if (plan != null && plan.getProduct() != null) {
final Product product = plan.getProduct();
if (product.getCatalogName() != null && product.getCategory() != null) {
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 09fbc96..0044758 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscriptionTransitionRecorder.java
@@ -21,10 +21,12 @@ 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.CatalogService;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,48 +41,54 @@ public class BusinessSubscriptionTransitionRecorder
private final BusinessSubscriptionTransitionDao dao;
private final EntitlementUserApi entitlementApi;
private final AccountUserApi accountApi;
+ private final CatalogService catalogService;
@Inject
- public BusinessSubscriptionTransitionRecorder(final BusinessSubscriptionTransitionDao dao, final EntitlementUserApi entitlementApi, final AccountUserApi accountApi)
+ public BusinessSubscriptionTransitionRecorder(final BusinessSubscriptionTransitionDao dao, final CatalogService catalogService, final EntitlementUserApi entitlementApi, final AccountUserApi accountApi)
{
this.dao = dao;
+ this.catalogService = catalogService;
this.entitlementApi = entitlementApi;
this.accountApi = accountApi;
}
- public void subscriptionCreated(final SubscriptionTransition created) throws AccountApiException
+
+ public void subscriptionCreated(final SubscriptionEvent created) throws AccountApiException, EntitlementUserApiException
{
- final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCreated(created.getNextPlan());
+ final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCreated(created.getNextPlan(), catalogService.getFullCatalog(), created.getEffectiveTransitionTime(), created.getSubscriptionStartDate());
recordTransition(event, created);
}
- public void subscriptionRecreated(final SubscriptionTransition recreated) throws AccountApiException
+
+ public void subscriptionRecreated(final SubscriptionEvent recreated) throws AccountApiException, EntitlementUserApiException
{
- final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionRecreated(recreated.getNextPlan());
+ final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionRecreated(recreated.getNextPlan(), catalogService.getFullCatalog(), recreated.getEffectiveTransitionTime(), recreated.getSubscriptionStartDate());
recordTransition(event, recreated);
}
- public void subscriptionCancelled(final SubscriptionTransition cancelled) throws AccountApiException
+ public void subscriptionCancelled(final SubscriptionEvent cancelled) throws AccountApiException, EntitlementUserApiException
{
// cancelled.getNextPlan() is null here - need to look at the previous one to create the correct event name
- final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(cancelled.getPreviousPlan());
+ final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(cancelled.getPreviousPlan(), catalogService.getFullCatalog(), cancelled.getEffectiveTransitionTime(), cancelled.getSubscriptionStartDate());
recordTransition(event, cancelled);
}
- public void subscriptionChanged(final SubscriptionTransition changed) throws AccountApiException
+
+ public void subscriptionChanged(final SubscriptionEvent changed) throws AccountApiException, EntitlementUserApiException
{
- final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionChanged(changed.getNextPlan());
+ final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionChanged(changed.getNextPlan(), catalogService.getFullCatalog(), changed.getEffectiveTransitionTime(), changed.getSubscriptionStartDate());
recordTransition(event, changed);
}
- public void subscriptionPhaseChanged(final SubscriptionTransition phaseChanged) throws AccountApiException
+ public void subscriptionPhaseChanged(final SubscriptionEvent phaseChanged) throws AccountApiException, EntitlementUserApiException
{
- final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPhaseChanged(phaseChanged.getNextPlan(), phaseChanged.getNextState());
+ final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionPhaseChanged(phaseChanged.getNextPlan(), phaseChanged.getNextState(), catalogService.getFullCatalog(), phaseChanged.getEffectiveTransitionTime(), phaseChanged.getSubscriptionStartDate());
recordTransition(event, phaseChanged);
}
- public void recordTransition(final BusinessSubscriptionEvent event, final SubscriptionTransition transition) throws AccountApiException
+ public void recordTransition(final BusinessSubscriptionEvent event, final SubscriptionEvent transition)
+ throws AccountApiException, EntitlementUserApiException
{
Currency currency = null;
String transitionKey = null;
@@ -90,13 +98,13 @@ public class BusinessSubscriptionTransitionRecorder
final SubscriptionBundle bundle = entitlementApi.getBundleFromId(transition.getBundleId());
if (bundle != null) {
transitionKey = bundle.getKey();
-
+
final Account account = accountApi.getAccountById(bundle.getAccountId());
if (account != null) {
accountKey = account.getExternalKey();
currency = account.getCurrency();
}
- }
+ }
// The ISubscriptionTransition interface gives us all the prev/next information we need but the start date
// of the previous plan. We need to retrieve it from our own transitions table
@@ -115,7 +123,8 @@ public class BusinessSubscriptionTransitionRecorder
prevSubscription = null;
}
else {
- prevSubscription = new BusinessSubscription(transition.getPreviousPriceList(), transition.getPreviousPlan(), transition.getPreviousPhase(), currency, previousEffectiveTransitionTime, transition.getPreviousState(), transition.getSubscriptionId(), transition.getBundleId());
+
+ prevSubscription = new BusinessSubscription(transition.getPreviousPriceList(), transition.getPreviousPlan(), transition.getPreviousPhase(), currency, previousEffectiveTransitionTime, transition.getPreviousState(), transition.getSubscriptionId(), transition.getBundleId(), catalogService.getFullCatalog());
}
final BusinessSubscription nextSubscription;
@@ -124,7 +133,7 @@ public class BusinessSubscriptionTransitionRecorder
nextSubscription = null;
}
else {
- nextSubscription = new BusinessSubscription(transition.getNextPriceList(), transition.getNextPlan(), transition.getNextPhase(), currency, transition.getEffectiveTransitionTime(), transition.getNextState(), transition.getSubscriptionId(), transition.getBundleId());
+ nextSubscription = new BusinessSubscription(transition.getNextPriceList(), transition.getNextPlan(), transition.getNextPhase(), currency, transition.getEffectiveTransitionTime(), transition.getNextState(), transition.getSubscriptionId(), transition.getBundleId(), catalogService.getFullCatalog());
}
record(transition.getId(), transitionKey, accountKey, transition.getRequestedTransitionTime(), event, prevSubscription, nextSubscription);
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountBinder.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountBinder.java
index 6478536..b9da3fa 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountBinder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountBinder.java
@@ -50,12 +50,12 @@ public @interface BusinessAccountBinder
final DateTime dateTimeNow = new DateTime(DateTimeZone.UTC);
if (account.getCreatedDt() != null) {
- q.bind("created_dt", account.getCreatedDt().getMillis());
+ q.bind("created_date", account.getCreatedDt().getMillis());
}
else {
- q.bind("created_dt", dateTimeNow.getMillis());
+ q.bind("created_date", dateTimeNow.getMillis());
}
- q.bind("updated_dt", dateTimeNow.getMillis());
+ q.bind("updated_date", dateTimeNow.getMillis());
q.bind("account_key", account.getKey());
q.bind("balance", account.getRoundedBalance());
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessAccountDao.sql.stg b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessAccountDao.sql.stg
index 55f03e8..10f27b8 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessAccountDao.sql.stg
+++ b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessAccountDao.sql.stg
@@ -3,8 +3,8 @@ group BusinessAccount;
getAccount(account_key) ::= <<
select
account_key
- , created_dt
- , updated_dt
+ , created_date
+ , updated_date
, balance
, tags
, last_invoice_date
@@ -22,8 +22,8 @@ getAccount(account_key) ::= <<
createAccount() ::= <<
insert into bac(
account_key
- , created_dt
- , updated_dt
+ , created_date
+ , updated_date
, balance
, tags
, last_invoice_date
@@ -34,8 +34,8 @@ createAccount() ::= <<
, billing_address_country
) values (
:account_key
- , :created_dt
- , :updated_dt
+ , :created_date
+ , :updated_date
, :balance
, :tags
, :last_invoice_date
@@ -49,7 +49,7 @@ createAccount() ::= <<
saveAccount() ::= <<
update bac set
- updated_dt=:updated_dt
+ updated_date=:updated_date
, balance=:balance
, tags=:tags
, last_invoice_date=:last_invoice_date
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql b/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
index 6489b12..7240236 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
+++ b/analytics/src/main/resources/com/ning/billing/analytics/ddl.sql
@@ -40,8 +40,8 @@ create index bst_key_index on bst (event_key, requested_timestamp asc);
drop table if exists bac;
create table bac (
account_key varchar(50) not null
-, created_dt bigint not null
-, updated_dt bigint not null
+, created_date bigint not null
+, updated_date bigint not null
, balance numeric(10, 4) default 0
, tags varchar(500) default null
, last_invoice_date bigint default 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 1aafa7c..e1df28b 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
@@ -16,25 +16,25 @@
package com.ning.billing.analytics;
-import com.ning.billing.invoice.glue.InvoiceModule;
-import com.ning.billing.payment.setup.PaymentModule;
-import com.ning.billing.util.glue.CallContextModule;
-import com.ning.billing.util.glue.FieldStoreModule;
-import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
import org.skife.jdbi.v2.IDBI;
+
import com.ning.billing.account.glue.AccountModule;
import com.ning.billing.analytics.setup.AnalyticsModule;
-import com.ning.billing.catalog.glue.CatalogModule;
import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.entitlement.glue.EntitlementModule;
-
+import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
+import com.ning.billing.invoice.glue.DefaultInvoiceModule;
+import com.ning.billing.junction.glue.DefaultJunctionModule;
+import com.ning.billing.payment.setup.PaymentModule;
+import com.ning.billing.util.email.EmailModule;
+import com.ning.billing.util.email.templates.TemplateModule;
import com.ning.billing.util.glue.BusModule;
-
+import com.ning.billing.util.glue.CallContextModule;
import com.ning.billing.util.glue.ClockModule;
+import com.ning.billing.util.glue.FieldStoreModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
import com.ning.billing.util.glue.NotificationQueueModule;
import com.ning.billing.util.glue.TagStoreModule;
-
-import java.lang.reflect.Field;
+import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
public class AnalyticsTestModule extends AnalyticsModule
{
@@ -44,18 +44,21 @@ public class AnalyticsTestModule extends AnalyticsModule
super.configure();
// Need to configure a few more things for the EventBus
+ install(new EmailModule());
+ install(new GlobalLockerModule());
install(new ClockModule());
install(new CallContextModule());
install(new FieldStoreModule());
install(new TagStoreModule());
install(new AccountModule());
- install(new CatalogModule());
install(new BusModule());
- install(new EntitlementModule());
- install(new InvoiceModule());
+ install(new DefaultEntitlementModule());
+ install(new DefaultInvoiceModule());
+ install(new TemplateModule());
install(new PaymentModule());
install(new TagStoreModule());
install(new NotificationQueueModule());
+ install(new DefaultJunctionModule());
// 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 495e985..1477dfa 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
@@ -26,23 +26,19 @@ import java.util.Arrays;
import java.util.List;
import java.util.UUID;
-import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.payment.api.DefaultPaymentAttempt;
+import com.ning.billing.util.tag.TagDefinition;
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
import com.google.inject.Inject;
import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.AccountCreationNotification;
+import com.ning.billing.account.api.AccountCreationEvent;
import com.ning.billing.account.api.AccountUserApi;
import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
import com.ning.billing.analytics.AnalyticsTestModule;
@@ -56,45 +52,62 @@ import com.ning.billing.analytics.MockPlan;
import com.ning.billing.analytics.MockProduct;
import com.ning.billing.analytics.dao.BusinessAccountDao;
import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionDao;
+import com.ning.billing.catalog.MockCatalogModule;
+import com.ning.billing.catalog.MockPriceList;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogService;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PriceList;
import com.ning.billing.catalog.api.Product;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionEvent;
import com.ning.billing.entitlement.api.user.EntitlementUserApi;
import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
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.invoice.api.InvoiceCreationNotification;
-import com.ning.billing.invoice.api.user.DefaultInvoiceCreationNotification;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.invoice.api.user.DefaultInvoiceCreationEvent;
import com.ning.billing.invoice.dao.InvoiceDao;
import com.ning.billing.invoice.model.DefaultInvoice;
import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.payment.api.DefaultPaymentInfoEvent;
import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentInfoEvent;
import com.ning.billing.payment.dao.PaymentDao;
import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.DefaultClock;
import com.ning.billing.util.tag.DefaultTagDefinition;
-import com.ning.billing.util.tag.DescriptiveTag;
-import com.ning.billing.util.tag.Tag;
import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
-@Guice(modules = AnalyticsTestModule.class)
+@Guice(modules = {AnalyticsTestModule.class, MockCatalogModule.class})
public class TestAnalyticsService {
+
+ final Product product = new MockProduct("platinum", "subscription", ProductCategory.BASE);
+ final Plan plan = new MockPlan("platinum-monthly", product);
+ final PlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
+
+
private static final UUID ID = UUID.randomUUID();
private static final String KEY = "12345";
private static final String ACCOUNT_KEY = "pierre-12345";
private static final Currency ACCOUNT_CURRENCY = Currency.EUR;
- private static final DefaultTagDefinition TAG_ONE = new DefaultTagDefinition("batch20", "something");
- private static final DefaultTagDefinition TAG_TWO = new DefaultTagDefinition("awesome", "something");
+ private static final DefaultTagDefinition TAG_ONE = new DefaultTagDefinition("batch20", "something", false);
+ private static final DefaultTagDefinition TAG_TWO = new DefaultTagDefinition("awesome", "something", false);
private static final BigDecimal INVOICE_AMOUNT = BigDecimal.valueOf(1243.11);
private static final String PAYMENT_METHOD = "Paypal";
private static final String CARD_COUNTRY = "France";
@@ -132,36 +145,40 @@ public class TestAnalyticsService {
@Inject
private MysqlTestingHelper helper;
- private SubscriptionTransition transition;
+ private SubscriptionEvent transition;
private BusinessSubscriptionTransition expectedTransition;
- private AccountCreationNotification accountCreationNotification;
- private InvoiceCreationNotification invoiceCreationNotification;
- private PaymentInfo paymentInfoNotification;
-
- @BeforeMethod(groups = "slow")
- public void cleanup() throws Exception
- {
- helper.cleanupTable("bst");
- helper.cleanupTable("bac");
- }
-
+ private AccountCreationEvent accountCreationNotification;
+ private InvoiceCreationEvent invoiceCreationNotification;
+ private PaymentInfoEvent paymentInfoNotification;
+ @Inject
+ private CatalogService catalogService;
+
+ private Catalog catalog;
+
@BeforeClass(groups = "slow")
public void startMysql() throws IOException, ClassNotFoundException, SQLException, EntitlementUserApiException {
+
+ catalog = catalogService.getFullCatalog();
+ ((ZombieControl) catalog).addResult("findPlan", plan);
+ ((ZombieControl) catalog).addResult("findPhase", phase);
+
// Killbill generic setup
setupBusAndMySQL();
+ helper.cleanupAllTables();
+
tagDao.create(TAG_ONE, context);
tagDao.create(TAG_TWO, context);
final MockAccount account = new MockAccount(UUID.randomUUID(), ACCOUNT_KEY, ACCOUNT_CURRENCY);
try {
- final List<Tag> tags = new ArrayList<Tag>();
- tags.add(new DescriptiveTag(TAG_ONE));
- tags.add(new DescriptiveTag(TAG_TWO));
+ final List<TagDefinition> tagDefinitions = new ArrayList<TagDefinition>();
+ tagDefinitions.add(TAG_ONE);
+ tagDefinitions.add(TAG_TWO);
- final Account storedAccount = accountApi.createAccount(account, null, tags, context);
+ final Account storedAccount = accountApi.createAccount(account, null, tagDefinitions, context);
// Create events for the bus and expected results
createSubscriptionTransitionEvent(storedAccount);
@@ -173,7 +190,6 @@ public class TestAnalyticsService {
}
private void setupBusAndMySQL() throws IOException {
- bus.start();
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"));
@@ -181,6 +197,7 @@ public class TestAnalyticsService {
final String invoiceDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
final String paymentDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
final String utilDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+ final String junctionDdl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/junction/ddl.sql"));
helper.startMysql();
helper.initDb(analyticsDdl);
@@ -189,9 +206,11 @@ public class TestAnalyticsService {
helper.initDb(invoiceDdl);
helper.initDb(paymentDdl);
helper.initDb(utilDdl);
+ helper.initDb(junctionDdl);
- helper.cleanupTable("tag_definitions");
- helper.cleanupTable("accounts");
+ helper.cleanupAllTables();
+
+ bus.start();
}
private void createSubscriptionTransitionEvent(final Account account) throws EntitlementUserApiException {
@@ -202,15 +221,13 @@ public class TestAnalyticsService {
Assert.assertEquals(bundle.getKey(), KEY);
// Create a subscription transition event
- final Product product = new MockProduct("platinum", "subscription", ProductCategory.BASE);
- final Plan plan = new MockPlan("platinum-monthly", product);
- final PlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
final UUID subscriptionId = UUID.randomUUID();
final DateTime effectiveTransitionTime = clock.getUTCNow();
final DateTime requestedTransitionTime = clock.getUTCNow();
- final String priceList = "something";
+ final PriceList priceList = new MockPriceList().setName("something");
- transition = new SubscriptionTransitionData(
+
+ transition = new DefaultSubscriptionEvent(new SubscriptionTransitionData(
ID,
subscriptionId,
bundle.getId(),
@@ -227,28 +244,28 @@ public class TestAnalyticsService {
phase,
priceList,
1L,
- true
- );
+ null,
+ true), null);
expectedTransition = new BusinessSubscriptionTransition(
ID,
KEY,
ACCOUNT_KEY,
requestedTransitionTime,
- BusinessSubscriptionEvent.subscriptionCreated(plan),
+ BusinessSubscriptionEvent.subscriptionCreated(plan.getName(), catalog, new DateTime(), new DateTime()),
null,
- new BusinessSubscription(priceList, plan, phase, ACCOUNT_CURRENCY, effectiveTransitionTime, Subscription.SubscriptionState.ACTIVE, subscriptionId, bundle.getId())
+ new BusinessSubscription(priceList.getName(), plan.getName(), phase.getName(), ACCOUNT_CURRENCY, effectiveTransitionTime, Subscription.SubscriptionState.ACTIVE, subscriptionId, bundle.getId(), catalog)
);
}
private void createAccountCreationEvent(final Account account) {
- accountCreationNotification = new DefaultAccountCreationEvent(account);
+ accountCreationNotification = new DefaultAccountCreationEvent(account, null);
}
private void createInvoiceAndPaymentCreationEvents(final Account account) {
final DefaultInvoice invoice = new DefaultInvoice(account.getId(), clock.getUTCNow(), clock.getUTCNow(), ACCOUNT_CURRENCY);
final FixedPriceInvoiceItem invoiceItem = new FixedPriceInvoiceItem(
- UUID.randomUUID(), invoice.getId(), account.getId(), UUID.randomUUID(), "somePlan", "somePhase", clock.getUTCNow(), clock.getUTCNow().plusDays(1),
- INVOICE_AMOUNT, ACCOUNT_CURRENCY, context.getUserName(), clock.getUTCNow());
+ UUID.randomUUID(), invoice.getId(), account.getId(), UUID.randomUUID(), UUID.randomUUID(), "somePlan", "somePhase", clock.getUTCNow(), clock.getUTCNow().plusDays(1),
+ INVOICE_AMOUNT, ACCOUNT_CURRENCY);
invoice.addInvoiceItem(invoiceItem);
invoiceDao.create(invoice, context);
@@ -257,15 +274,15 @@ public class TestAnalyticsService {
Assert.assertEquals(invoices.get(0).getInvoiceItems().size(), 1);
// It doesn't really matter what the events contain - the listener will go back to the db
- invoiceCreationNotification = new DefaultInvoiceCreationNotification(invoice.getId(), account.getId(),
- INVOICE_AMOUNT, ACCOUNT_CURRENCY, clock.getUTCNow());
+ invoiceCreationNotification = new DefaultInvoiceCreationEvent(invoice.getId(), account.getId(),
+ INVOICE_AMOUNT, ACCOUNT_CURRENCY, clock.getUTCNow(), null);
- paymentInfoNotification = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString()).setPaymentMethod(PAYMENT_METHOD).setCardCountry(CARD_COUNTRY).build();
- final PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice.getId(), account.getId(), BigDecimal.TEN,
- ACCOUNT_CURRENCY, clock.getUTCNow(), clock.getUTCNow(), paymentInfoNotification.getPaymentId(), 1);
+ paymentInfoNotification = new DefaultPaymentInfoEvent.Builder().setId(UUID.randomUUID()).setPaymentMethod(PAYMENT_METHOD).setCardCountry(CARD_COUNTRY).build();
+ final PaymentAttempt paymentAttempt = new DefaultPaymentAttempt(UUID.randomUUID(), invoice.getId(), account.getId(), BigDecimal.TEN,
+ ACCOUNT_CURRENCY, clock.getUTCNow(), clock.getUTCNow(), paymentInfoNotification.getId(), 1, null, null);
paymentDao.createPaymentAttempt(paymentAttempt, context);
paymentDao.savePaymentInfo(paymentInfoNotification, context);
- Assert.assertEquals(paymentDao.getPaymentInfo(Arrays.asList(invoice.getId().toString())).size(), 1);
+ Assert.assertEquals(paymentDao.getPaymentInfoList(Arrays.asList(invoice.getId().toString())).size(), 1);
}
@AfterClass(groups = "slow")
@@ -273,7 +290,7 @@ public class TestAnalyticsService {
helper.stopMysql();
}
- @Test(groups = "slow")
+ @Test(groups = "slow", enabled=true)
public void testRegisterForNotifications() throws Exception {
// Make sure the service has been instantiated
Assert.assertEquals(service.getName(), "analytics-service");
@@ -290,7 +307,7 @@ public class TestAnalyticsService {
// Send events and wait for the async part...
bus.post(transition);
bus.post(accountCreationNotification);
- Thread.sleep(1000);
+ Thread.sleep(5000);
Assert.assertEquals(subscriptionDao.getTransitions(KEY).size(), 1);
Assert.assertEquals(subscriptionDao.getTransitions(KEY).get(0), expectedTransition);
@@ -305,12 +322,12 @@ public class TestAnalyticsService {
// Post the same invoice event again - the invoice balance shouldn't change
bus.post(invoiceCreationNotification);
- Thread.sleep(1000);
+ Thread.sleep(5000);
Assert.assertTrue(accountDao.getAccount(ACCOUNT_KEY).getTotalInvoiceBalance().compareTo(INVOICE_AMOUNT) == 0);
// Test payment integration - the fields have already been populated, just make sure the code is exercised
bus.post(paymentInfoNotification);
- Thread.sleep(1000);
+ Thread.sleep(5000);
Assert.assertEquals(accountDao.getAccount(ACCOUNT_KEY).getPaymentMethod(), PAYMENT_METHOD);
Assert.assertEquals(accountDao.getAccount(ACCOUNT_KEY).getBillingAddressCountry(), CARD_COUNTRY);
diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java b/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
index 1504b15..d8c856d 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
@@ -25,6 +25,8 @@ import com.ning.billing.analytics.MockPhase;
import com.ning.billing.analytics.MockPlan;
import com.ning.billing.analytics.MockProduct;
import com.ning.billing.analytics.utils.Rounder;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogService;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
@@ -33,6 +35,9 @@ import com.ning.billing.catalog.api.Product;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.dbi.MysqlTestingHelper;
import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
@@ -66,9 +71,17 @@ public class TestAnalyticsDao
private BusinessAccountDao businessAccountDao;
private BusinessAccount account;
+ private final CatalogService catalogService = BrainDeadProxyFactory.createBrainDeadProxyFor(CatalogService.class);
+ private final Catalog catalog = BrainDeadProxyFactory.createBrainDeadProxyFor(Catalog.class);
+
@BeforeClass(alwaysRun = true)
public void startMysql() throws IOException, ClassNotFoundException, SQLException
{
+
+ ((ZombieControl) catalog).addResult("findPlan", plan);
+ ((ZombieControl) catalog).addResult("findPhase", phase);
+ ((ZombieControl) catalogService).addResult("getFullCatalog", catalog);
+
final String ddl = IOUtils.toString(BusinessSubscriptionTransitionDao.class.getResourceAsStream("/com/ning/billing/analytics/ddl.sql"));
helper.startMysql();
@@ -80,10 +93,11 @@ public class TestAnalyticsDao
private void setupBusinessSubscriptionTransition()
{
- final BusinessSubscription prevSubscription = new BusinessSubscription(null, plan, phase, Currency.USD, new DateTime(DateTimeZone.UTC), Subscription.SubscriptionState.ACTIVE, UUID.randomUUID(), UUID.randomUUID());
- final BusinessSubscription nextSubscription = new BusinessSubscription(null, plan, phase, Currency.USD, new DateTime(DateTimeZone.UTC), Subscription.SubscriptionState.CANCELLED, UUID.randomUUID(), UUID.randomUUID());
- final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(plan);
final DateTime requestedTimestamp = new DateTime(DateTimeZone.UTC);
+ final BusinessSubscription prevSubscription = new BusinessSubscription(null, plan.getName(), phase.getName(), Currency.USD, new DateTime(DateTimeZone.UTC), Subscription.SubscriptionState.ACTIVE, UUID.randomUUID(), UUID.randomUUID(), catalog);
+ final BusinessSubscription nextSubscription = new BusinessSubscription(null, plan.getName(), phase.getName(), Currency.USD, new DateTime(DateTimeZone.UTC), Subscription.SubscriptionState.CANCELLED, UUID.randomUUID(), UUID.randomUUID(), catalog);
+ final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(plan.getName(), catalog, requestedTimestamp, requestedTimestamp);
+
transition = new BusinessSubscriptionTransition(EVENT_ID, EVENT_KEY, ACCOUNT_KEY, requestedTimestamp, event, prevSubscription, nextSubscription);
@@ -212,7 +226,7 @@ public class TestAnalyticsDao
@Test(groups = "slow")
public void testTransitionsWithNullFieldsInSubscription()
{
- final BusinessSubscription subscriptionWithNullFields = new BusinessSubscription(null, plan, phase, Currency.USD, null, null, null, null);
+ final BusinessSubscription subscriptionWithNullFields = new BusinessSubscription(null, plan.getName(), phase.getName(), Currency.USD, null, null, null, null, catalog);
final BusinessSubscriptionTransition transitionWithNullFields = new BusinessSubscriptionTransition(
transition.getId(),
transition.getKey(),
@@ -232,7 +246,7 @@ public class TestAnalyticsDao
@Test(groups = "slow")
public void testTransitionsWithNullPlanAndPhase() throws Exception
{
- final BusinessSubscription subscriptionWithNullPlanAndPhase = new BusinessSubscription(null, null, null, Currency.USD, null, null, null, null);
+ final BusinessSubscription subscriptionWithNullPlanAndPhase = new BusinessSubscription(null, null, null, Currency.USD, null, null, null, null, catalog);
final BusinessSubscriptionTransition transitionWithNullPlanAndPhase = new BusinessSubscriptionTransition(
transition.getId(),
transition.getKey(),
@@ -249,7 +263,6 @@ public class TestAnalyticsDao
Assert.assertEquals(transitions.get(0).getKey(), transition.getKey());
Assert.assertEquals(transitions.get(0).getRequestedTimestamp(), transition.getRequestedTimestamp());
Assert.assertEquals(transitions.get(0).getEvent(), transition.getEvent());
- // Null Plan and Phase doesn't make sense so we turn the subscription into a null
Assert.assertNull(transitions.get(0).getPreviousSubscription());
Assert.assertNull(transitions.get(0).getNextSubscription());
}
@@ -257,7 +270,7 @@ public class TestAnalyticsDao
@Test(groups = "slow")
public void testTransitionsWithNullPlan() throws Exception
{
- final BusinessSubscription subscriptionWithNullPlan = new BusinessSubscription(null, null, phase, Currency.USD, null, null, null, null);
+ final BusinessSubscription subscriptionWithNullPlan = new BusinessSubscription(null, null, phase.getName(), Currency.USD, null, null, null, null, catalog);
final BusinessSubscriptionTransition transitionWithNullPlan = new BusinessSubscriptionTransition(
transition.getId(),
transition.getKey(),
@@ -278,7 +291,7 @@ public class TestAnalyticsDao
@Test(groups = "slow")
public void testTransitionsWithNullPhase() throws Exception
{
- final BusinessSubscription subscriptionWithNullPhase = new BusinessSubscription(null, plan, null, Currency.USD, null, null, null, null);
+ final BusinessSubscription subscriptionWithNullPhase = new BusinessSubscription(null, plan.getName(), null, Currency.USD, null, null, null, null, catalog);
final BusinessSubscriptionTransition transitionWithNullPhase = new BusinessSubscriptionTransition(
transition.getId(),
transition.getKey(),
@@ -297,7 +310,7 @@ public class TestAnalyticsDao
Assert.assertEquals(transitions.get(0).getEvent(), transition.getEvent());
// Null Phase but Plan - we don't turn the subscription into a null, however price and mrr are both set to 0 (not null)
- final BusinessSubscription blankSubscription = new BusinessSubscription(null, plan, new MockPhase(null, null, null, 0.0), Currency.USD, null, null, null, null);
+ final BusinessSubscription blankSubscription = new BusinessSubscription(null, plan.getName(), new MockPhase(null, null, null, 0.0).getName(), Currency.USD, null, null, null, null, catalog);
Assert.assertEquals(transitions.get(0).getPreviousSubscription(), blankSubscription);
Assert.assertEquals(transitions.get(0).getNextSubscription(), blankSubscription);
}
@@ -342,7 +355,7 @@ public class TestAnalyticsDao
Assert.assertEquals("PayPal", account.getPaymentMethod());
Assert.assertTrue(account.getUpdatedDt().compareTo(previousUpdatedDt) > 0);
- // Account not found
+ // ACCOUNT not found
Assert.assertNull(businessAccountDao.getAccount("Doesn't exist"));
}
}
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 4942118..03a7767 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
@@ -19,13 +19,15 @@ package com.ning.billing.analytics;
import java.util.List;
import java.util.UUID;
-import com.ning.billing.util.callcontext.CallContext;
-import org.joda.time.DateTime;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.tag.ControlTagType;
import org.joda.time.DateTimeZone;
import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.MutableAccountData;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.tag.Tag;
import com.ning.billing.util.tag.TagDefinition;
@@ -61,6 +63,16 @@ public class MockAccount implements Account
}
@Override
+ public boolean isMigrated() {
+ return false;
+ }
+
+ @Override
+ public boolean isNotifiedForInvoices() {
+ return false;
+ }
+
+ @Override
public String getExternalKey()
{
return accountKey;
@@ -140,16 +152,6 @@ public class MockAccount implements Account
}
@Override
- public String getCreatedBy() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public DateTime getCreatedDate() {
- throw new UnsupportedOperationException();
- }
-
- @Override
public String getFieldValue(String fieldName) {
throw new UnsupportedOperationException();
}
@@ -190,7 +192,7 @@ public class MockAccount implements Account
}
@Override
- public String getObjectName() {
+ public ObjectType getObjectType() {
throw new UnsupportedOperationException();
}
@@ -200,7 +202,12 @@ public class MockAccount implements Account
}
@Override
- public boolean hasTag(String tagName) {
+ public boolean hasTag(TagDefinition tagDefinition) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean hasTag(ControlTagType controlTagType) {
throw new UnsupportedOperationException();
}
@@ -215,37 +222,37 @@ public class MockAccount implements Account
}
@Override
- public void clearTags() {
+ public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
throw new UnsupportedOperationException();
}
@Override
- public void removeTag(TagDefinition definition) {
+ public void clearTags() {
throw new UnsupportedOperationException();
}
@Override
- public boolean generateInvoice() {
+ public void removeTag(TagDefinition definition) {
throw new UnsupportedOperationException();
}
@Override
- public boolean processPayment() {
+ public boolean generateInvoice() {
throw new UnsupportedOperationException();
}
@Override
- public String getUpdatedBy() {
+ public boolean processPayment() {
throw new UnsupportedOperationException();
}
@Override
- public DateTime getUpdatedDate() {
+ public MutableAccountData toMutableAccountData() {
throw new UnsupportedOperationException();
}
@Override
- public MutableAccountData toMutableAccountData() {
+ public BlockingState getBlockingState() {
throw new UnsupportedOperationException();
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
index f10ff2f..e2fa1a2 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
@@ -20,6 +20,7 @@ import com.ning.billing.catalog.api.Duration;
import com.ning.billing.catalog.api.TimeUnit;
import org.apache.commons.lang.NotImplementedException;
import org.joda.time.DateTime;
+import org.joda.time.Period;
public class MockDuration
{
@@ -43,6 +44,10 @@ public class MockDuration
public DateTime addToDateTime(DateTime dateTime) {
throw new NotImplementedException();
}
+ @Override
+ public Period toJodaPeriod() {
+ throw new UnsupportedOperationException();
+ }
};
}
@@ -66,6 +71,10 @@ public class MockDuration
public DateTime addToDateTime(DateTime dateTime) {
throw new NotImplementedException();
}
+ @Override
+ public Period toJodaPeriod() {
+ throw new UnsupportedOperationException();
+ }
};
}
@@ -89,6 +98,10 @@ public class MockDuration
public DateTime addToDateTime(DateTime dateTime) {
throw new NotImplementedException();
}
+ @Override
+ public Period toJodaPeriod() {
+ throw new UnsupportedOperationException();
+ }
};
}
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockEntitlementUserApi.java b/analytics/src/test/java/com/ning/billing/analytics/MockEntitlementUserApi.java
index 06e3d79..725c62b 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockEntitlementUserApi.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockEntitlementUserApi.java
@@ -21,14 +21,17 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
-import com.ning.billing.util.callcontext.CallContext;
import org.joda.time.DateTime;
+
import com.ning.billing.catalog.api.PlanPhaseSpecifier;
import com.ning.billing.entitlement.api.user.EntitlementUserApi;
-
import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionStatusDryRun;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.overdue.OverdueState;
+import com.ning.billing.util.callcontext.CallContext;
public class MockEntitlementUserApi implements EntitlementUserApi
{
@@ -72,6 +75,16 @@ public class MockEntitlementUserApi implements EntitlementUserApi
{
return key;
}
+
+ @Override
+ public OverdueState<SubscriptionBundle> getOverdueState() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public BlockingState getBlockingState() {
+ throw new UnsupportedOperationException();
+ }
};
}
@@ -119,4 +132,16 @@ public class MockEntitlementUserApi implements EntitlementUserApi
public DateTime getNextBillingDate(UUID account) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public Subscription getBaseSubscription(UUID bundleId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<SubscriptionStatusDryRun> getDryRunChangePlanStatus(
+ UUID subscriptionId, String productName, DateTime requestedDate)
+ throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
}
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 99e435f..1a72707 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
@@ -16,24 +16,28 @@
package com.ning.billing.analytics;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.tag.ControlTagType;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceList;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.junction.api.BlockingState;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.customfield.CustomField;
-
import com.ning.billing.util.tag.Tag;
import com.ning.billing.util.tag.TagDefinition;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-
-import java.util.List;
-import java.util.UUID;
public class MockSubscription implements Subscription
{
@@ -53,13 +57,13 @@ public class MockSubscription implements Subscription
}
@Override
- public void cancel(DateTime requestedDate, boolean eot, CallContext context)
+ public boolean cancel(DateTime requestedDate, boolean eot, CallContext context)
{
throw new UnsupportedOperationException();
}
@Override
- public void changePlan(final String productName, final BillingPeriod term, final String planSet, DateTime requestedDate, CallContext context)
+ public boolean changePlan(final String productName, final BillingPeriod term, final String planSet, DateTime requestedDate, CallContext context)
{
throw new UnsupportedOperationException();
}
@@ -71,16 +75,6 @@ public class MockSubscription implements Subscription
}
@Override
- public String getCreatedBy() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public DateTime getCreatedDate() {
- throw new UnsupportedOperationException();
- }
-
- @Override
public UUID getBundleId()
{
return BUNDLE_ID;
@@ -112,13 +106,13 @@ public class MockSubscription implements Subscription
@Override
- public void uncancel(CallContext context) throws EntitlementUserApiException
+ public boolean uncancel(CallContext context) throws EntitlementUserApiException
{
throw new UnsupportedOperationException();
}
@Override
- public String getCurrentPriceList()
+ public PriceList getCurrentPriceList()
{
return null;
}
@@ -129,7 +123,7 @@ public class MockSubscription implements Subscription
}
@Override
- public SubscriptionTransition getPendingTransition() {
+ public SubscriptionEvent getPendingTransition() {
throw new UnsupportedOperationException();
}
@@ -144,7 +138,7 @@ public class MockSubscription implements Subscription
}
@Override
- public SubscriptionTransition getPreviousTransition() {
+ public SubscriptionEvent getPreviousTransition() {
return null;
}
@@ -154,7 +148,7 @@ public class MockSubscription implements Subscription
}
@Override
- public void recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
+ public boolean recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
throws EntitlementUserApiException {
throw new UnsupportedOperationException();
}
@@ -200,7 +194,7 @@ public class MockSubscription implements Subscription
}
@Override
- public String getObjectName() {
+ public ObjectType getObjectType() {
throw new UnsupportedOperationException();
}
@@ -210,7 +204,12 @@ public class MockSubscription implements Subscription
}
@Override
- public boolean hasTag(String tagName) {
+ public boolean hasTag(TagDefinition tagDefinition) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean hasTag(ControlTagType controlTagType) {
throw new UnsupportedOperationException();
}
@@ -225,6 +224,11 @@ public class MockSubscription implements Subscription
}
@Override
+ public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public void clearTags() {
throw new UnsupportedOperationException();
}
@@ -243,4 +247,15 @@ public class MockSubscription implements Subscription
public boolean processPayment() {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public List<SubscriptionEvent> getBillingTransitions() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public BlockingState getBlockingState() {
+ throw new UnsupportedOperationException();
+ }
+
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
index 7254a5d..72aa146 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
@@ -16,24 +16,33 @@
package com.ning.billing.analytics;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogService;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PriceList;
import com.ning.billing.catalog.api.Product;
import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionEvent;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
import com.ning.billing.entitlement.events.EntitlementEvent;
import com.ning.billing.entitlement.events.user.ApiEventType;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.testng.Assert;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import javax.annotation.Nullable;
-import java.util.UUID;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
public class TestAnalyticsListener
{
@@ -47,14 +56,25 @@ public class TestAnalyticsListener
private final Product product = new MockProduct("platinium", "subscription", ProductCategory.BASE);
private final Plan plan = new MockPlan("platinum-monthly", product);
private final PlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
- private final String priceList = null;
+ private final PriceList priceList = null;
+ private final CatalogService catalogService = BrainDeadProxyFactory.createBrainDeadProxyFor(CatalogService.class);
+ private final Catalog catalog = BrainDeadProxyFactory.createBrainDeadProxyFor(Catalog.class);
+
+
private AnalyticsListener listener;
+ @BeforeClass(alwaysRun = true)
+ public void setupCatalog() {
+ ((ZombieControl) catalog).addResult("findPlan", plan);
+ ((ZombieControl) catalog).addResult("findPhase", phase);
+ ((ZombieControl) catalogService).addResult("getFullCatalog", catalog);
+
+ }
@BeforeMethod(alwaysRun = true)
public void setUp() throws Exception
{
- final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(dao, new MockEntitlementUserApi(bundleUUID, KEY), new MockIAccountUserApi(ACCOUNT_KEY, CURRENCY));
+ final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(dao, catalogService, new MockEntitlementUserApi(bundleUUID, KEY), new MockAccountUserApi(ACCOUNT_KEY, CURRENCY));
listener = new AnalyticsListener(recorder, null);
}
@@ -63,28 +83,28 @@ public class TestAnalyticsListener
{
// Create a subscription
final DateTime effectiveTransitionTime = new DateTime(DateTimeZone.UTC);
- final DateTime requestedTransitionTime = new DateTime(DateTimeZone.UTC);
+ final DateTime requestedTransitionTime = effectiveTransitionTime;
final SubscriptionTransitionData firstTransition = createFirstSubscriptionTransition(requestedTransitionTime, effectiveTransitionTime);
final BusinessSubscriptionTransition firstBST = createExpectedFirstBST(firstTransition.getId(), requestedTransitionTime, effectiveTransitionTime);
- listener.handleSubscriptionTransitionChange(firstTransition);
+ listener.handleSubscriptionTransitionChange(new DefaultSubscriptionEvent(firstTransition, effectiveTransitionTime));
Assert.assertEquals(dao.getTransitions(KEY).size(), 1);
Assert.assertEquals(dao.getTransitions(KEY).get(0), firstBST);
// Cancel it
final DateTime effectiveCancelTransitionTime = new DateTime(DateTimeZone.UTC);
- final DateTime requestedCancelTransitionTime = new DateTime(DateTimeZone.UTC);
+ final DateTime requestedCancelTransitionTime = effectiveCancelTransitionTime;
final SubscriptionTransitionData cancelledSubscriptionTransition = createCancelSubscriptionTransition(requestedCancelTransitionTime, effectiveCancelTransitionTime, firstTransition.getNextState());
final BusinessSubscriptionTransition cancelledBST = createExpectedCancelledBST(cancelledSubscriptionTransition.getId(), requestedCancelTransitionTime, effectiveCancelTransitionTime, firstBST.getNextSubscription());
- listener.handleSubscriptionTransitionChange(cancelledSubscriptionTransition);
+ listener.handleSubscriptionTransitionChange(new DefaultSubscriptionEvent(cancelledSubscriptionTransition, effectiveTransitionTime));
Assert.assertEquals(dao.getTransitions(KEY).size(), 2);
Assert.assertEquals(dao.getTransitions(KEY).get(1), cancelledBST);
// Recreate it
final DateTime effectiveRecreatedTransitionTime = new DateTime(DateTimeZone.UTC);
- final DateTime requestedRecreatedTransitionTime = new DateTime(DateTimeZone.UTC);
+ final DateTime requestedRecreatedTransitionTime = effectiveRecreatedTransitionTime;
final SubscriptionTransitionData recreatedSubscriptionTransition = createRecreatedSubscriptionTransition(requestedRecreatedTransitionTime, effectiveRecreatedTransitionTime, cancelledSubscriptionTransition.getNextState());
final BusinessSubscriptionTransition recreatedBST = createExpectedRecreatedBST(recreatedSubscriptionTransition.getId(), requestedRecreatedTransitionTime, effectiveRecreatedTransitionTime, cancelledBST.getNextSubscription());
- listener.handleSubscriptionTransitionChange(recreatedSubscriptionTransition);
+ listener.handleSubscriptionTransitionChange(new DefaultSubscriptionEvent(recreatedSubscriptionTransition, effectiveTransitionTime));
Assert.assertEquals(dao.getTransitions(KEY).size(), 3);
Assert.assertEquals(dao.getTransitions(KEY).get(2), recreatedBST);
@@ -92,20 +112,21 @@ public class TestAnalyticsListener
private BusinessSubscriptionTransition createExpectedFirstBST(final UUID id, final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime)
{
- final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCreated(plan);
+ final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCreated(plan.getName(), catalog, effectiveTransitionTime, effectiveTransitionTime);
+
final Subscription.SubscriptionState subscriptionState = Subscription.SubscriptionState.ACTIVE;
return createExpectedBST(id, event, requestedTransitionTime, effectiveTransitionTime, null, subscriptionState);
}
private BusinessSubscriptionTransition createExpectedCancelledBST(final UUID id, final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final BusinessSubscription lastSubscription)
{
- final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(plan);
+ final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(plan.getName(), catalog, effectiveTransitionTime, effectiveTransitionTime);
return createExpectedBST(id, event, requestedTransitionTime, effectiveTransitionTime, lastSubscription, null);
}
private BusinessSubscriptionTransition createExpectedRecreatedBST(final UUID id, final DateTime requestedTransitionTime, final DateTime effectiveTransitionTime, final BusinessSubscription lastSubscription)
{
- final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionRecreated(plan);
+ final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionRecreated(plan.getName(), catalog, effectiveTransitionTime, effectiveTransitionTime);
final Subscription.SubscriptionState subscriptionState = Subscription.SubscriptionState.ACTIVE;
return createExpectedBST(id, event, requestedTransitionTime, effectiveTransitionTime, lastSubscription, subscriptionState);
}
@@ -129,13 +150,13 @@ public class TestAnalyticsListener
previousSubscription,
nextState == null ? null : new BusinessSubscription(
null,
- plan,
- phase,
+ plan.getName(),
+ phase.getName(),
CURRENCY,
effectiveTransitionTime,
nextState,
subscriptionId,
- bundleUUID
+ bundleUUID, catalog
)
);
}
@@ -161,6 +182,7 @@ public class TestAnalyticsListener
phase,
priceList,
1L,
+ null,
true
);
}
@@ -187,6 +209,7 @@ public class TestAnalyticsListener
null,
null,
1L,
+ null,
true
);
}
@@ -212,6 +235,7 @@ public class TestAnalyticsListener
phase,
priceList,
1L,
+ null,
true
);
}
@@ -242,6 +266,7 @@ public class TestAnalyticsListener
phase,
priceList,
1L,
+ null,
true
);
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
index 55c73ad..2df0aeb 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
@@ -16,6 +16,8 @@
package com.ning.billing.analytics;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogService;
import com.ning.billing.catalog.api.Duration;
import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
@@ -23,7 +25,11 @@ import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.Product;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+
import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@@ -35,7 +41,7 @@ public class TestBusinessSubscription
{
private final Duration MONTHLY = MockDuration.MONHTLY();
private final Duration YEARLY = MockDuration.YEARLY();
- final Object[][] catalog = {
+ final Object[][] catalogMapping = {
{MONTHLY, 229.0000, 229.0000},
{MONTHLY, 19.9500, 19.9500},
{MONTHLY, 14.9500, 14.9500},
@@ -53,21 +59,30 @@ public class TestBusinessSubscription
private Subscription isubscription;
private BusinessSubscription subscription;
+ private final CatalogService catalogService = BrainDeadProxyFactory.createBrainDeadProxyFor(CatalogService.class);
+ private final Catalog catalog = BrainDeadProxyFactory.createBrainDeadProxyFor(Catalog.class);
+
+
@BeforeMethod(alwaysRun = true)
public void setUp() throws Exception
{
product = new MockProduct("platinium", "subscription", ProductCategory.BASE);
plan = new MockPlan("platinum-monthly", product);
phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
+
+ ((ZombieControl) catalog).addResult("findPlan", plan);
+ ((ZombieControl) catalog).addResult("findPhase", phase);
+ ((ZombieControl) catalogService).addResult("getFullCatalog", catalog);
+
isubscription = new MockSubscription(Subscription.SubscriptionState.ACTIVE, plan, phase);
- subscription = new BusinessSubscription(isubscription, USD);
+ subscription = new BusinessSubscription(isubscription, USD, catalog);
}
@Test(groups = "fast")
public void testMrrComputation() throws Exception
{
int i = 0;
- for (final Object[] object : catalog) {
+ for (final Object[] object : catalogMapping) {
final Duration duration = (Duration) object[0];
final double price = (Double) object[1];
final double expectedMrr = (Double) object[2];
@@ -100,6 +115,6 @@ public class TestBusinessSubscription
Assert.assertTrue(subscription.equals(subscription));
final Subscription otherSubscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
- Assert.assertTrue(!subscription.equals(new BusinessSubscription(otherSubscription, USD)));
+ Assert.assertTrue(!subscription.equals(new BusinessSubscription(otherSubscription, USD, catalog)));
}
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
index 793feac..e42c72f 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
@@ -16,13 +16,20 @@
package com.ning.billing.analytics;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogService;
import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.Product;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+
+import org.joda.time.DateTime;
import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@@ -33,12 +40,20 @@ public class TestBusinessSubscriptionEvent
private PlanPhase phase;
private Subscription subscription;
+ private final CatalogService catalogService = BrainDeadProxyFactory.createBrainDeadProxyFor(CatalogService.class);
+ private final Catalog catalog = BrainDeadProxyFactory.createBrainDeadProxyFor(Catalog.class);
+
@BeforeMethod(alwaysRun = true)
public void setUp() throws Exception
{
product = new MockProduct("platinium", "subscription", ProductCategory.BASE);
plan = new MockPlan("platinum-monthly", product);
phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
+
+ ((ZombieControl) catalog).addResult("findPlan", plan);
+ ((ZombieControl) catalog).addResult("findPhase", phase);
+ ((ZombieControl) catalogService).addResult("getFullCatalog", catalog);
+
subscription = new MockSubscription(Subscription.SubscriptionState.ACTIVE, plan, phase);
}
@@ -65,29 +80,31 @@ public class TestBusinessSubscriptionEvent
{
BusinessSubscriptionEvent event;
- event = BusinessSubscriptionEvent.subscriptionCreated(subscription.getCurrentPlan());
+ DateTime now = new DateTime();
+
+ event = BusinessSubscriptionEvent.subscriptionCreated(subscription.getCurrentPlan().getName(), catalog, now, now);
Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.ADD);
Assert.assertEquals(event.getCategory(), product.getCategory());
Assert.assertEquals(event.toString(), "ADD_BASE");
- event = BusinessSubscriptionEvent.subscriptionCancelled(subscription.getCurrentPlan());
+ event = BusinessSubscriptionEvent.subscriptionCancelled(subscription.getCurrentPlan().getName(), catalog, now, now);
Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.CANCEL);
Assert.assertEquals(event.getCategory(), product.getCategory());
Assert.assertEquals(event.toString(), "CANCEL_BASE");
- event = BusinessSubscriptionEvent.subscriptionChanged(subscription.getCurrentPlan());
+ event = BusinessSubscriptionEvent.subscriptionChanged(subscription.getCurrentPlan().getName(), catalog, now, now);
Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.CHANGE);
Assert.assertEquals(event.getCategory(), product.getCategory());
Assert.assertEquals(event.toString(), "CHANGE_BASE");
- event = BusinessSubscriptionEvent.subscriptionPhaseChanged(subscription.getCurrentPlan(), subscription.getState());
+ event = BusinessSubscriptionEvent.subscriptionPhaseChanged(subscription.getCurrentPlan().getName(), subscription.getState(), catalog, now, now);
// The subscription is still active, it's a system change
Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.SYSTEM_CHANGE);
Assert.assertEquals(event.getCategory(), product.getCategory());
Assert.assertEquals(event.toString(), "SYSTEM_CHANGE_BASE");
subscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
- event = BusinessSubscriptionEvent.subscriptionPhaseChanged(subscription.getCurrentPlan(), subscription.getState());
+ event = BusinessSubscriptionEvent.subscriptionPhaseChanged(subscription.getCurrentPlan().getName(), subscription.getState(), catalog, now, now);
// The subscription is cancelled, it's a system cancellation
Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.SYSTEM_CANCEL);
Assert.assertEquals(event.getCategory(), product.getCategory());
@@ -97,7 +114,8 @@ public class TestBusinessSubscriptionEvent
@Test(groups = "fast")
public void testEquals() throws Exception
{
- final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionChanged(subscription.getCurrentPlan());
+ DateTime now = new DateTime();
+ final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionChanged(subscription.getCurrentPlan().getName(), catalog, now, now);
Assert.assertSame(event, event);
Assert.assertEquals(event, event);
Assert.assertTrue(event.equals(event));
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
index eaa4c96..be27a91 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
@@ -16,12 +16,17 @@
package com.ning.billing.analytics;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogService;
import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.Product;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.testng.Assert;
@@ -43,6 +48,9 @@ public class TestBusinessSubscriptionTransition
private String accountKey;
private BusinessSubscriptionTransition transition;
+ private final CatalogService catalogService = BrainDeadProxyFactory.createBrainDeadProxyFor(CatalogService.class);
+ private final Catalog catalog = BrainDeadProxyFactory.createBrainDeadProxyFor(Catalog.class);
+
@BeforeMethod(alwaysRun = true)
public void setUp() throws Exception
{
@@ -52,9 +60,15 @@ public class TestBusinessSubscriptionTransition
final Subscription prevISubscription = new MockSubscription(Subscription.SubscriptionState.ACTIVE, plan, phase);
final Subscription nextISubscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
- prevSubscription = new BusinessSubscription(prevISubscription, USD);
- nextSubscription = new BusinessSubscription(nextISubscription, USD);
- event = BusinessSubscriptionEvent.subscriptionCancelled(prevISubscription.getCurrentPlan());
+ ((ZombieControl) catalog).addResult("findPlan", plan);
+ ((ZombieControl) catalog).addResult("findPhase", phase);
+ ((ZombieControl) catalogService).addResult("getFullCatalog", catalog);
+
+ DateTime now = new DateTime();
+
+ prevSubscription = new BusinessSubscription(prevISubscription, USD, catalog);
+ nextSubscription = new BusinessSubscription(nextISubscription, USD, catalog);
+ event = BusinessSubscriptionEvent.subscriptionCancelled(prevISubscription.getCurrentPlan().getName(), catalog, now, now);
requestedTimestamp = new DateTime(DateTimeZone.UTC);
id = UUID.randomUUID();
key = "1234";
diff --git a/api/src/main/java/com/ning/billing/account/api/Account.java b/api/src/main/java/com/ning/billing/account/api/Account.java
index 3f1e383..1f7000d 100644
--- a/api/src/main/java/com/ning/billing/account/api/Account.java
+++ b/api/src/main/java/com/ning/billing/account/api/Account.java
@@ -16,13 +16,11 @@
package com.ning.billing.account.api;
-import com.ning.billing.util.entity.UpdatableEntity;
-
+import com.ning.billing.junction.api.Blockable;
import com.ning.billing.util.customfield.Customizable;
+import com.ning.billing.util.entity.UpdatableEntity;
import com.ning.billing.util.tag.Taggable;
-public interface Account extends AccountData, Customizable, UpdatableEntity, Taggable {
- public static String ObjectType = "account";
-
- public MutableAccountData toMutableAccountData();
+public interface Account extends AccountData, Customizable, UpdatableEntity, Taggable, Blockable {
+ public MutableAccountData toMutableAccountData();
}
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountData.java b/api/src/main/java/com/ning/billing/account/api/AccountData.java
index 654400b..3220971 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountData.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountData.java
@@ -55,4 +55,8 @@ public interface AccountData {
public String getCountry();
public String getPhone();
+
+ public boolean isMigrated();
+
+ public boolean isNotifiedForInvoices();
}
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountEmail.java b/api/src/main/java/com/ning/billing/account/api/AccountEmail.java
new file mode 100644
index 0000000..88ec469
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/account/api/AccountEmail.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.account.api;
+
+import com.ning.billing.util.entity.UpdatableEntity;
+
+import java.util.UUID;
+
+public interface AccountEmail extends UpdatableEntity {
+ UUID getAccountId();
+ String getEmail();
+}
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountService.java b/api/src/main/java/com/ning/billing/account/api/AccountService.java
index 43175da..febe8dd 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountService.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountService.java
@@ -20,5 +20,4 @@ import com.ning.billing.lifecycle.KillbillService;
public interface AccountService extends KillbillService {
- public AccountUserApi getAccountUserApi();
}
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 98d6272..89e5f70 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
@@ -21,22 +21,23 @@ import java.util.UUID;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
import javax.annotation.Nullable;
public interface AccountUserApi {
public Account createAccount(AccountData data, @Nullable List<CustomField> fields,
- @Nullable List<Tag> tags, CallContext context) throws AccountApiException;
+ @Nullable List<TagDefinition> tagDefinitions, CallContext context) throws AccountApiException;
public Account migrateAccount(MigrationAccountData data, @Nullable List<CustomField> fields,
- @Nullable List<Tag> tags, CallContext context) throws AccountApiException;
+ @Nullable List<TagDefinition> tagDefinitions, CallContext context) throws AccountApiException;
/***
*
* Note: does not update the external key
- * @param account
- * @throws AccountApiException
+ * @param account account to be updated
+ * @param context contains specific information about the call
+ * @throws AccountApiException if a failure occurs
*/
public void updateAccount(Account account, CallContext context) throws AccountApiException;
@@ -44,11 +45,15 @@ public interface AccountUserApi {
public void updateAccount(UUID accountId, AccountData accountData, CallContext context) throws AccountApiException;
- public Account getAccountByKey(String key);
+ public Account getAccountByKey(String key) throws AccountApiException;
- public Account getAccountById(UUID accountId);
+ public Account getAccountById(UUID accountId) throws AccountApiException;
public List<Account> getAccounts();
public UUID getIdFromKey(String externalKey) throws AccountApiException;
+
+ public List<AccountEmail> getEmails(UUID accountId);
+
+ public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context);
}
diff --git a/api/src/main/java/com/ning/billing/account/api/MutableAccountData.java b/api/src/main/java/com/ning/billing/account/api/MutableAccountData.java
index 2909b2d..6ea638d 100644
--- a/api/src/main/java/com/ning/billing/account/api/MutableAccountData.java
+++ b/api/src/main/java/com/ning/billing/account/api/MutableAccountData.java
@@ -16,182 +16,47 @@
package com.ning.billing.account.api;
-import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.util.tag.TagStore;
-
-public class MutableAccountData implements AccountData {
- private String externalKey;
- private String email;
- private String name;
- private int firstNameLength;
- private Currency currency;
- private int billCycleDay;
- private String paymentProviderName;
- private DateTimeZone timeZone;
- private String locale;
- private String address1;
- private String address2;
- private String companyName;
- private String city;
- private String stateOrProvince;
- private String country;
- private String postalCode;
- private String phone;
-
- public MutableAccountData(String externalKey, String email, String name,
- int firstNameLength, Currency currency, int billCycleDay,
- String paymentProviderName, TagStore tags, DateTimeZone timeZone,
- String locale, String address1, String address2,
- String companyName, String city, String stateOrProvince,
- String country, String postalCode, String phone,
- DateTime createdDate, DateTime updatedDate) {
- super();
- this.externalKey = externalKey;
- this.email = email;
- this.name = name;
- this.firstNameLength = firstNameLength;
- this.currency = currency;
- this.billCycleDay = billCycleDay;
- this.paymentProviderName = paymentProviderName;
- this.timeZone = timeZone;
- this.locale = locale;
- this.address1 = address1;
- this.address2 = address2;
- this.companyName = companyName;
- this.city = city;
- this.stateOrProvince = stateOrProvince;
- this.country = country;
- this.postalCode = postalCode;
- this.phone = phone;
- }
-
- public MutableAccountData(AccountData accountData) {
- super();
- this.externalKey = accountData.getExternalKey();
- this.email = accountData.getEmail();
- this.name = accountData.getName();
- this.firstNameLength = accountData.getFirstNameLength();
- this.currency = accountData.getCurrency();
- this.billCycleDay = accountData.getBillCycleDay();
- this.paymentProviderName = accountData.getPaymentProviderName();
- this.timeZone = accountData.getTimeZone();
- this.locale = accountData.getLocale();
- this.address1 = accountData.getAddress1();
- this.address2 = accountData.getAddress2();
- this.companyName = accountData.getCompanyName();
- this.city = accountData.getCity();
- this.stateOrProvince = accountData.getStateOrProvince();
- this.country = accountData.getCountry();
- this.postalCode = accountData.getPostalCode();
- this.phone = accountData.getPhone();
- }
-
- public String getExternalKey() {
- return externalKey;
- }
- public String getEmail() {
- return email;
- }
- public String getName() {
- return name;
- }
- public int getFirstNameLength() {
- return firstNameLength;
- }
- public Currency getCurrency() {
- return currency;
- }
- public int getBillCycleDay() {
- return billCycleDay;
- }
- public String getPaymentProviderName() {
- return paymentProviderName;
- }
- public DateTimeZone getTimeZone() {
- return timeZone;
- }
- public String getLocale() {
- return locale;
- }
- public String getAddress1() {
- return address1;
- }
- public String getAddress2() {
- return address2;
- }
- public String getCompanyName() {
- return companyName;
- }
- public String getCity() {
- return city;
- }
- public String getStateOrProvince() {
- return stateOrProvince;
- }
- public String getCountry() {
- return country;
- }
- public String getPostalCode() {
- return postalCode;
- }
- public String getPhone() {
- return phone;
- }
-
- public void setExternalKey(String externalKey) {
- this.externalKey = externalKey;
- }
- public void setEmail(String email) {
- this.email = email;
- }
- public void setName(String name) {
- this.name = name;
- }
- public void setFirstNameLength(int firstNameLength) {
- this.firstNameLength = firstNameLength;
- }
- public void setCurrency(Currency currency) {
- this.currency = currency;
- }
- public void setBillCycleDay(int billCycleDay) {
- this.billCycleDay = billCycleDay;
- }
- public void setPaymentProviderName(String paymentProviderName) {
- this.paymentProviderName = paymentProviderName;
- }
- public void setTimeZone(DateTimeZone timeZone) {
- this.timeZone = timeZone;
- }
- public void setLocale(String locale) {
- this.locale = locale;
- }
- public void setAddress1(String address1) {
- this.address1 = address1;
- }
- public void setAddress2(String address2) {
- this.address2 = address2;
- }
- public void setCompanyName(String companyName) {
- this.companyName = companyName;
- }
- public void setCity(String city) {
- this.city = city;
- }
- public void setStateOrProvince(String stateOrProvince) {
- this.stateOrProvince = stateOrProvince;
- }
- public void setCountry(String country) {
- this.country = country;
- }
- public void setPostalCode(String postalCode) {
- this.postalCode = postalCode;
- }
- public void setPhone(String phone) {
- this.phone = phone;
- }
-
-
-}
+
+public interface MutableAccountData extends AccountData {
+ public void setExternalKey(String externalKey);
+
+ public void setEmail(String email);
+
+ public void setName(String name);
+
+ public void setFirstNameLength(int firstNameLength);
+
+ public void setCurrency(Currency currency);
+
+ public void setBillCycleDay(int billCycleDay);
+
+ public void setPaymentProviderName(String paymentProviderName);
+
+ public void setTimeZone(DateTimeZone timeZone);
+
+ public void setLocale(String locale);
+
+ public void setAddress1(String address1);
+
+ public void setAddress2(String address2);
+
+ public void setCompanyName(String companyName);
+
+ public void setCity(String city);
+
+ public void setStateOrProvince(String stateOrProvince);
+
+ public void setCountry(String country);
+
+ public void setPostalCode(String postalCode);
+
+ public void setPhone(String phone);
+
+ public void setIsMigrated(boolean isMigrated);
+
+ public void setIsNotifiedForInvoices(boolean isNotifiedForInvoices);
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/BillingExceptionBase.java b/api/src/main/java/com/ning/billing/BillingExceptionBase.java
index 3a458fc..a4ff3cd 100644
--- a/api/src/main/java/com/ning/billing/BillingExceptionBase.java
+++ b/api/src/main/java/com/ning/billing/BillingExceptionBase.java
@@ -35,6 +35,13 @@ public class BillingExceptionBase extends Exception {
this.code = code;
this.cause = cause;
}
+
+ public BillingExceptionBase(BillingExceptionBase cause) {
+ this.formattedMsg = cause.getMessage();
+ this.code = cause.getCode();
+ this.cause = cause;
+ }
+
public BillingExceptionBase(Throwable cause, ErrorCode code, final Object... args) {
String tmp = null;
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 2b3609a..924a5e4 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
@@ -56,6 +56,11 @@ public interface Catalog {
public abstract PlanPhase findPhase(String name, DateTime requestedDate, DateTime subscriptionStartDate) throws CatalogApiException;
//
+ // Find a priceList
+ //
+ public abstract PriceList findPriceList(String name, DateTime requestedDate) throws CatalogApiException;
+
+ //
// Rules
//
public abstract ActionPolicy planChangePolicy(PlanPhaseSpecifier from,
@@ -74,7 +79,5 @@ public interface Catalog {
PlanSpecifier to, DateTime requestedDate) throws CatalogApiException;
public abstract boolean canCreatePlan(PlanSpecifier specifier, DateTime requestedDate) throws CatalogApiException;
-
-
-
+
}
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Duration.java b/api/src/main/java/com/ning/billing/catalog/api/Duration.java
index 9025367..90ea5c2 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Duration.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Duration.java
@@ -17,6 +17,7 @@
package com.ning.billing.catalog.api;
import org.joda.time.DateTime;
+import org.joda.time.Period;
public interface Duration {
@@ -25,4 +26,6 @@ public interface Duration {
public abstract int getNumber();
public DateTime addToDateTime(DateTime dateTime);
+
+ public Period toJodaPeriod();
}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/catalog/api/OverdueActions.java b/api/src/main/java/com/ning/billing/catalog/api/OverdueActions.java
new file mode 100644
index 0000000..8525d91
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/catalog/api/OverdueActions.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.catalog.api;
+
+public enum OverdueActions {
+ CANCEL,
+ PAYMENT_RETRY
+}
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
index c93fde8..17ea132 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java
@@ -48,10 +48,15 @@ public interface StaticCatalog {
//
// Find a phase
//
- public abstract PlanPhase findCurrentPhase(String name) throws CatalogApiException;
+ public abstract PlanPhase findCurrentPhase(String name) throws CatalogApiException;
//
- // Rules
+ // Find a pricelist
+ //
+ public abstract PriceList findCurrentPricelist(String name) throws CatalogApiException;
+
+ //
+ //
//
public abstract ActionPolicy planChangePolicy(PlanPhaseSpecifier from,
PlanSpecifier to) throws CatalogApiException;
@@ -71,6 +76,4 @@ public interface StaticCatalog {
public abstract boolean canCreatePlan(PlanSpecifier specifier) throws CatalogApiException;
-
-
}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/config/CatalogConfig.java b/api/src/main/java/com/ning/billing/config/CatalogConfig.java
index 21be358..73ffd9a 100644
--- a/api/src/main/java/com/ning/billing/config/CatalogConfig.java
+++ b/api/src/main/java/com/ning/billing/config/CatalogConfig.java
@@ -19,10 +19,10 @@ package com.ning.billing.config;
import org.skife.config.Config;
import org.skife.config.Default;
-public interface CatalogConfig {
+public interface CatalogConfig extends KillbillConfig {
@Config("killbill.catalog.uri")
- @Default("jar:///com/ning/billing/irs/catalog/NingCatalog.xml")
+ @Default("jar:///com/ning/billing/irs/catalog/Catalog.xml")
String getCatalogURI();
}
diff --git a/api/src/main/java/com/ning/billing/config/EntitlementConfig.java b/api/src/main/java/com/ning/billing/config/EntitlementConfig.java
index 1b6f3e2..12418c6 100644
--- a/api/src/main/java/com/ning/billing/config/EntitlementConfig.java
+++ b/api/src/main/java/com/ning/billing/config/EntitlementConfig.java
@@ -19,21 +19,17 @@ package com.ning.billing.config;
import org.skife.config.Config;
import org.skife.config.Default;
-public interface EntitlementConfig {
+import com.google.common.annotations.VisibleForTesting;
- @Config("killbill.entitlement.dao.claim.time")
- @Default("60000")
- public long getDaoClaimTimeMs();
-
- @Config("killbill.entitlement.dao.ready.max")
- @Default("10")
- public int getDaoMaxReadyEvents();
+public interface EntitlementConfig extends NotificationConfig, KillbillConfig {
+ @Override
@Config("killbill.entitlement.engine.notifications.sleep")
@Default("500")
- public long getNotificationSleepTimeMs();
+ public long getSleepTimeMs();
+ @Override
@Config("killbill.notifications.off")
@Default("false")
- public boolean isEventProcessingOff();
+ public boolean isNotificationProcessingOff();
}
diff --git a/api/src/main/java/com/ning/billing/config/InvoiceConfig.java b/api/src/main/java/com/ning/billing/config/InvoiceConfig.java
index f56d3c2..407f4d3 100644
--- a/api/src/main/java/com/ning/billing/config/InvoiceConfig.java
+++ b/api/src/main/java/com/ning/billing/config/InvoiceConfig.java
@@ -19,23 +19,17 @@ package com.ning.billing.config;
import org.skife.config.Config;
import org.skife.config.Default;
-public interface InvoiceConfig {
-
- @Config("killbill.invoice.dao.claim.time")
- @Default("60000")
- public long getDaoClaimTimeMs();
-
- @Config("killbill.invoice.dao.ready.max")
- @Default("10")
- public int getDaoMaxReadyEvents();
+public interface InvoiceConfig extends NotificationConfig, KillbillConfig {
+ @Override
@Config("killbill.invoice.engine.notifications.sleep")
@Default("500")
- public long getNotificationSleepTimeMs();
+ public long getSleepTimeMs();
+ @Override
@Config("killbill.notifications.off")
@Default("false")
- public boolean isEventProcessingOff();
+ public boolean isNotificationProcessingOff();
@Config("killbill.invoice.maxNumberOfMonthsInFuture")
@Default("36")
diff --git a/api/src/main/java/com/ning/billing/config/KillbillConfig.java b/api/src/main/java/com/ning/billing/config/KillbillConfig.java
new file mode 100644
index 0000000..336f806
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/config/KillbillConfig.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.config;
+
+/*
+ * Marker interface for killbill config files
+ */
+public interface KillbillConfig {
+
+}
diff --git a/api/src/main/java/com/ning/billing/config/PersistentQueueConfig.java b/api/src/main/java/com/ning/billing/config/PersistentQueueConfig.java
new file mode 100644
index 0000000..c7e1515
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/config/PersistentQueueConfig.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.config;
+
+public interface PersistentQueueConfig {
+ public long getSleepTimeMs();
+}
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 6340560..eb8410f 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,21 +16,26 @@
package com.ning.billing.entitlement.api.billing;
-import com.ning.billing.catalog.api.Currency;
+import java.math.BigDecimal;
+
import org.joda.time.DateTime;
+import com.ning.billing.account.api.Account;
import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.catalog.api.InternationalPrice;
+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.SubscriptionTransitionType;
import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
-
-import java.math.BigDecimal;
public interface BillingEvent extends Comparable<BillingEvent> {
/**
+ * @return the account that this billing event is associated with
+ */
+ public Account getAccount();
+
+ /**
*
* @return the billCycleDay as seen for that subscription at that time
*
@@ -108,5 +113,4 @@ public interface BillingEvent extends Comparable<BillingEvent> {
* @return a unique long indicating the ordering on which events got inserted on disk-- used for sorting only
*/
public Long getTotalOrdering();
-
-}
+ }
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/ChargeThruApi.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/ChargeThruApi.java
new file mode 100644
index 0000000..69eb454
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/ChargeThruApi.java
@@ -0,0 +1,42 @@
+/*
+ * 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.UUID;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+import com.ning.billing.util.callcontext.CallContext;
+
+public interface ChargeThruApi {
+
+ /**
+ * @param subscriptionId
+ * @return UUID of
+ */
+ public UUID getAccountIdFromSubscriptionId(UUID subscriptionId) throws EntitlementBillingApiException;
+
+ /**
+ * Sets the charged through date for the subscription with that Id.
+ *
+ * @param subscriptionId
+ * @param ctd
+ * @param context
+ */
+ public void setChargedThroughDate(UUID subscriptionId, DateTime ctd, CallContext context);
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java b/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
index 28084b2..50d45a1 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
@@ -16,19 +16,7 @@
package com.ning.billing.entitlement.api;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
-import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
-import com.ning.billing.entitlement.api.user.EntitlementUserApi;
import com.ning.billing.lifecycle.KillbillService;
public interface EntitlementService extends KillbillService {
-
- @Override
- public String getName();
-
- public EntitlementUserApi getUserApi();
-
- public EntitlementBillingApi getBillingApi();
-
- public EntitlementMigrationApi getMigrationApi();
}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/SubscriptionTransitionType.java b/api/src/main/java/com/ning/billing/entitlement/api/SubscriptionTransitionType.java
new file mode 100644
index 0000000..8e75675
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/SubscriptionTransitionType.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.entitlement.api;
+
+public enum SubscriptionTransitionType {
+ MIGRATE_ENTITLEMENT,
+ CREATE,
+ MIGRATE_BILLING,
+ CHANGE,
+ RE_CREATE,
+ CANCEL,
+ UNCANCEL,
+ PHASE
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/timeline/BundleTimeline.java b/api/src/main/java/com/ning/billing/entitlement/api/timeline/BundleTimeline.java
new file mode 100644
index 0000000..6b12f9d
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/timeline/BundleTimeline.java
@@ -0,0 +1,30 @@
+/*
+ * 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.timeline;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface BundleTimeline {
+
+ String getViewId();
+
+ UUID getBundleId();
+
+ String getExternalKey();
+
+ List<SubscriptionTimeline> getSubscriptions();
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementRepairException.java b/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementRepairException.java
new file mode 100644
index 0000000..56cdd99
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementRepairException.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.entitlement.api.timeline;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+
+public class EntitlementRepairException extends BillingExceptionBase {
+
+ private static final long serialVersionUID = 19067233L;
+
+ public EntitlementRepairException(EntitlementUserApiException e) {
+ super(e, e.getCode(), e.getMessage());
+ }
+
+ public EntitlementRepairException(CatalogApiException e) {
+ super(e, e.getCode(), e.getMessage());
+ }
+ public EntitlementRepairException(Throwable e, ErrorCode code, Object...args) {
+ super(e, code, args);
+ }
+
+ public EntitlementRepairException(ErrorCode code, Object...args) {
+ super(code, args);
+ }
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementTimelineApi.java b/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementTimelineApi.java
new file mode 100644
index 0000000..b76f25a
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/timeline/EntitlementTimelineApi.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.entitlement.api.timeline;
+
+import java.util.UUID;
+
+import com.ning.billing.util.callcontext.CallContext;
+
+public interface EntitlementTimelineApi {
+
+ public BundleTimeline getBundleRepair(final UUID bundleId) throws EntitlementRepairException;
+
+ public BundleTimeline repairBundle(final BundleTimeline input, final boolean dryRun, final CallContext context) throws EntitlementRepairException;
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementEvent.java b/api/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementEvent.java
new file mode 100644
index 0000000..0ab6720
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementEvent.java
@@ -0,0 +1,31 @@
+/*
+ * 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.timeline;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public interface RepairEntitlementEvent extends BusEvent {
+
+ public UUID getAccountId();
+
+ public UUID getBundleId();
+
+ public DateTime getEffectiveDate();
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionTimeline.java b/api/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionTimeline.java
new file mode 100644
index 0000000..434cb39
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionTimeline.java
@@ -0,0 +1,50 @@
+/*
+ * 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.timeline;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+
+public interface SubscriptionTimeline {
+
+ public UUID getId();
+
+ public List<DeletedEvent> getDeletedEvents();
+
+ public List<NewEvent> getNewEvents();
+
+ public List<ExistingEvent> getExistingEvents();
+
+ public interface DeletedEvent {
+ public UUID getEventId();
+ }
+
+ public interface NewEvent {
+ public PlanPhaseSpecifier getPlanPhaseSpecifier();
+ public DateTime getRequestedDate();
+ public SubscriptionTransitionType getSubscriptionTransitionType();
+
+ }
+
+ public interface ExistingEvent extends DeletedEvent, NewEvent {
+ public DateTime getEffectiveDate();
+ }
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java b/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java
index 205f256..1e8d7fd 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java
@@ -21,22 +21,26 @@ import java.util.UUID;
import com.ning.billing.util.callcontext.CallContext;
import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.PlanPhaseSpecifier;
public interface EntitlementUserApi {
- public SubscriptionBundle getBundleFromId(UUID id);
+ public SubscriptionBundle getBundleFromId(UUID id) throws EntitlementUserApiException;
- public Subscription getSubscriptionFromId(UUID id);
+ public Subscription getSubscriptionFromId(UUID id) throws EntitlementUserApiException;
- public SubscriptionBundle getBundleForKey(String bundleKey);
+ public SubscriptionBundle getBundleForKey(String bundleKey) throws EntitlementUserApiException;
public List<SubscriptionBundle> getBundlesForAccount(UUID accountId);
public List<Subscription> getSubscriptionsForBundle(UUID bundleId);
public List<Subscription> getSubscriptionsForKey(String bundleKey);
+
+ public Subscription getBaseSubscription(UUID bundleId) throws EntitlementUserApiException;
public SubscriptionBundle createBundleForAccount(UUID accountId, String bundleKey, CallContext context)
throws EntitlementUserApiException;
@@ -44,5 +48,8 @@ public interface EntitlementUserApi {
public Subscription createSubscription(UUID bundleId, PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
throws EntitlementUserApiException;
+ public List<SubscriptionStatusDryRun> getDryRunChangePlanStatus(UUID subscriptionId, String productName, DateTime requestedDate)
+ throws EntitlementUserApiException;
+
public DateTime getNextBillingDate(UUID account);
}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApiException.java b/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApiException.java
index 693938b..11cf455 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApiException.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApiException.java
@@ -31,8 +31,11 @@ public class EntitlementUserApiException extends BillingExceptionBase {
super(e, code, args);
}
+ public EntitlementUserApiException(Throwable e, int code, String message) {
+ super(e, code, message);
+ }
+
public EntitlementUserApiException(ErrorCode code, Object...args) {
super(code, args);
}
-
}
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 0854e2f..af8e237 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
@@ -16,31 +16,34 @@
package com.ning.billing.entitlement.api.user;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceList;
import com.ning.billing.catalog.api.ProductCategory;
-
+import com.ning.billing.junction.api.Blockable;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.entity.ExtendedEntity;
-import org.joda.time.DateTime;
-
-import java.util.UUID;
-public interface Subscription extends ExtendedEntity {
+public interface Subscription extends ExtendedEntity, Blockable {
- public void cancel(DateTime requestedDate, boolean eot, CallContext context)
+ public boolean cancel(DateTime requestedDate, boolean eot, CallContext context)
throws EntitlementUserApiException;
- public void uncancel(CallContext context)
+ public boolean uncancel(CallContext context)
throws EntitlementUserApiException;
- public void changePlan(String productName, BillingPeriod term, String planSet, DateTime requestedDate, CallContext context)
- throws EntitlementUserApiException;
+ public boolean changePlan(String productName, BillingPeriod term, String planSet, DateTime requestedDate, CallContext context)
+ throws EntitlementUserApiException;
- public void recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
+ public boolean recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
throws EntitlementUserApiException;
public enum SubscriptionState {
@@ -58,7 +61,7 @@ public interface Subscription extends ExtendedEntity {
public Plan getCurrentPlan();
- public String getCurrentPriceList();
+ public PriceList getCurrentPriceList();
public PlanPhase getCurrentPhase();
@@ -68,7 +71,9 @@ public interface Subscription extends ExtendedEntity {
public ProductCategory getCategory();
- public SubscriptionTransition getPendingTransition();
+ public SubscriptionEvent getPendingTransition();
- public SubscriptionTransition getPreviousTransition();
+ public SubscriptionEvent getPreviousTransition();
+
+ public List<SubscriptionEvent> getBillingTransitions();
}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundle.java b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundle.java
index f7c2e84..fddb5e9 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundle.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundle.java
@@ -16,17 +16,23 @@
package com.ning.billing.entitlement.api.user;
+import java.util.UUID;
+
+import com.ning.billing.util.entity.Entity;
import org.joda.time.DateTime;
-import java.util.UUID;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.overdue.OverdueState;
-public interface SubscriptionBundle {
+public interface SubscriptionBundle extends Blockable, Entity {
public UUID getAccountId();
- public UUID getId();
-
public DateTime getStartDate();
public String getKey();
+
+ public OverdueState<SubscriptionBundle> getOverdueState();
+
}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionStatusDryRun.java b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionStatusDryRun.java
new file mode 100644
index 0000000..36048fa
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionStatusDryRun.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.entitlement.api.user;
+
+import java.util.UUID;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+
+public interface SubscriptionStatusDryRun {
+
+ public UUID getId();
+
+ public String getProductName();
+
+ public BillingPeriod getBillingPeriod();
+
+ public String getPriceList();
+
+ public PhaseType getPhaseType();
+
+ public DryRunChangeReason getReason();
+
+ public enum DryRunChangeReason {
+ AO_INCLUDED_IN_NEW_PLAN,
+ AO_NOT_AVAILABLE_IN_NEW_PLAN,
+ AO_AVAILABLE_IN_NEW_PLAN
+ }
+}
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index 57ea8d9..860ae6c 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -18,6 +18,8 @@ package com.ning.billing;
public enum ErrorCode {
+
+
/*
* Range 0 : COMMON EXCEPTIONS
*/
@@ -44,6 +46,8 @@ public enum ErrorCode {
/* Change plan */
ENT_CHANGE_NON_ACTIVE(1021, "Subscription %s is in state %s: Failed to change plan"),
ENT_CHANGE_FUTURE_CANCELLED(1022, "Subscription %s is future cancelled: Failed to change plan"),
+ ENT_CHANGE_DRY_RUN_NOT_BP(1022, "Change DryRun API is only available for BP"),
+
/* Cancellation */
ENT_CANCEL_BAD_STATE(1031, "Subscription %s is in state %s: Failed to cancel"),
/* Recreation */
@@ -56,6 +60,31 @@ public enum ErrorCode {
ENT_GET_NO_BUNDLE_FOR_SUBSCRIPTION(1080, "Could not find a bundle for subscription %s"),
ENT_GET_INVALID_BUNDLE_ID(1081, "Could not find a bundle matching id %s"),
ENT_INVALID_SUBSCRIPTION_ID(1082, "Unknown subscription %s"),
+ ENT_GET_INVALID_BUNDLE_KEY(1083, "Could not find a bundle matching key %s"),
+ ENT_GET_NO_SUCH_BASE_SUBSCRIPTION(1084, "Could not base subscription for bundle %s"),
+
+ /* Repair */
+ ENT_REPAIR_INVALID_DELETE_SET(1091, "Event %s is not deleted for subscription %s but prior events were"),
+ ENT_REPAIR_NON_EXISTENT_DELETE_EVENT(1092, "Event %s does not exist for subscription %s"),
+ ENT_REPAIR_MISSING_AO_DELETE_EVENT(1093, "Event %s should be in deleted set for subscription %s because BP events got deleted earlier"),
+ ENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING(1094, "New event %s for subscription %s is before last remaining event for BP"),
+ ENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING(1095, "New event %s for subscription %s is before last remaining event"),
+ ENT_REPAIR_UNKNOWN_TYPE(1096, "Unknown new event type %s for subscription %s"),
+ ENT_REPAIR_UNKNOWN_BUNDLE(1097, "Unknown bundle %s"),
+ ENT_REPAIR_UNKNOWN_SUBSCRIPTION(1098, "Unknown subscription %s"),
+ ENT_REPAIR_NO_ACTIVE_SUBSCRIPTIONS(1099, "No active subscriptions on bundle %s"),
+ ENT_REPAIR_VIEW_CHANGED(1100, "View for bundle %s has changed from %s to %s"),
+ ENT_REPAIR_SUB_RECREATE_NOT_EMPTY(1101, "Subscription %s with recreation for bundle %s should specify all existing events to be deleted"),
+ ENT_REPAIR_SUB_EMPTY(1102, "Subscription %s with recreation for bundle %s should specify all existing events to be deleted"),
+ ENT_REPAIR_BP_RECREATE_MISSING_AO(1103, "BP recreation for bundle %s implies repair all subscriptions"),
+ ENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE(1104, "BP recreation for bundle %s implies that all AO should be start also with a CREATE"),
+ ENT_REPAIR_AO_CREATE_BEFORE_BP_START(1105, "Can't recreate AO %s for bundle %s before BP starts"),
+
+
+ ENT_BUNDLE_IS_OVERDUE_BLOCKED(1090, "Changes to this bundle are blocked by overdue enforcement (%s : %s)"),
+ ENT_ACCOUNT_IS_OVERDUE_BLOCKED(1091, "Changes to this account are blocked by overdue enforcement (%s)"),
+
+
/*
*
* Range 2000 : CATALOG
@@ -107,7 +136,12 @@ public enum ErrorCode {
* Billing Alignment
*/
CAT_INVALID_BILLING_ALIGNMENT(2060, "Invalid billing alignment '%s'"),
-
+ /*
+ * Overdue
+ */
+ CAT_NO_SUCH_OVEDUE_STATE(2070, "No such overdue state '%s'"),
+ CAT_MISSING_CLEAR_STATE(2071, "Missing a clear state"),
+ CAT_NO_OVERDUEABLE_TYPE(2072, "No such overdueable type: "),
/*
*
* Range 3000 : ACCOUNT
@@ -150,9 +184,53 @@ public enum ErrorCode {
INVOICE_INVALID_TRANSITION(4002, "Transition did not contain a subscription id."),
INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID(4003, "No account id was retrieved for subscription id %s"),
INVOICE_INVALID_DATE_SEQUENCE(4004, "Date sequence was invalid. Start Date: %s; End Date: %s; Target Date: %s"),
- INVOICE_TARGET_DATE_TOO_FAR_IN_THE_FUTURE(4005, "The target date was too far in the future. Target Date: %s")
- ;
+ INVOICE_TARGET_DATE_TOO_FAR_IN_THE_FUTURE(4005, "The target date was too far in the future. Target Date: %s"),
+
+ /*
+ *
+ * Range 5000: Overdue system
+ *
+ */
+ OVERDUE_CAT_ERROR_ENCOUNTERED(5001,"Catalog error encountered on Overdueable: id='%s', type='%s'"),
+ OVERDUE_TYPE_NOT_SUPPORTED(5002,"Overdue of this type is not supported: id='%s', type='%s'"),
+ OVERDUE_NO_REEVALUATION_INTERVAL(5003,"No valid reevaluation interval for state (name: %s)"),
+ /*
+ *
+ * Range 6000: Blocking system
+ *
+ */
+ BLOCK_BLOCKED_ACTION(6000, "The action %s is block on this %s with id=%s"),
+ BLOCK_TYPE_NOT_SUPPORTED(6001, "The Blockable type '%s' is not supported"),
+
+
+ /*
+ * Range 7000 : Payment
+ */
+ PAYMENT_NO_SUCH_PAYMENT_METHOD(7001, "Payment method for account %s, and paymentId %s does not exist"),
+ PAYMENT_NO_PAYMENT_METHODS(7002, "Payment methods for account %s don't exist"),
+ PAYMENT_UPD_GATEWAY_FAILED(7003, "Failed to update payment gateway for account %s : %s"),
+ PAYMENT_GET_PAYMENT_PROVIDER(7004, "Failed to retrieve payment provider for account %s : %s"),
+ PAYMENT_ADD_PAYMENT_METHOD(7005, "Failed to add payment method for account %s : %s"),
+ PAYMENT_DEL_PAYMENT_METHOD(7006, "Failed to delete payment method for account %s : %s"),
+ PAYMENT_UPD_PAYMENT_METHOD(7007, "Failed to update payment method for account %s : %s"),
+ PAYMENT_CREATE_PAYMENT(7008, "Failed to create payment for account %s : %s"),
+ PAYMENT_CREATE_PAYMENT_FOR_ATTEMPT(7009, "Failed to create payment for account %s and attempt %s : %s"),
+ PAYMENT_CREATE_PAYMENT_FOR_ATTEMPT_WITH_NON_POSITIVE_INV(70010, "Got payment attempt with negative or null invoice for account %s"),
+ PAYMENT_CREATE_PAYMENT_FOR_ATTEMPT_BAD(7011, "Failed to create payment for attempts %s "),
+ PAYMENT_CREATE_PAYMENT_PROVIDER_ACCOUNT(7012, "Failed to create payment provider account for account %s : %s"),
+ PAYMENT_UPD_PAYMENT_PROVIDER_ACCOUNT(7013, "Failed to update payment provider account for account %s : %s"),
+ PAYMENT_CREATE_REFUND(7014, "Failed to create refund for account %s : %s"),
+ /*
+ *
+ * Range 9000: Miscellaneous
+ *
+ */
+ EMAIL_SENDING_FAILED(9000, "Sending email failed"),
+ EMAIL_PROPERTIES_FILE_MISSING(9001, "The properties file for email configuration could not be found."),
+ MISSING_TRANSLATION_RESOURCE(9010, "The resources for %s translation could not be found."),
+ MISSING_DEFAULT_TRANSLATION_RESOURCE(9011, "The default resource for %s translation could not be found.");
+
private int code;
private String format;
diff --git a/api/src/main/java/com/ning/billing/glue/EntitlementModule.java b/api/src/main/java/com/ning/billing/glue/EntitlementModule.java
new file mode 100644
index 0000000..421e68d
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/glue/EntitlementModule.java
@@ -0,0 +1,31 @@
+/*
+ * 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.glue;
+
+public interface EntitlementModule {
+
+ public abstract void installEntitlementService();
+
+ public abstract void installEntitlementUserApi();
+
+ public abstract void installEntitlementMigrationApi();
+
+ public abstract void installChargeThruApi();
+
+ public abstract void installEntitlementTimelineApi();
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/glue/InvoiceModule.java b/api/src/main/java/com/ning/billing/glue/InvoiceModule.java
new file mode 100644
index 0000000..7e55895
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/glue/InvoiceModule.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.glue;
+
+public interface InvoiceModule {
+
+ public abstract void installInvoiceUserApi();
+
+ public abstract void installInvoicePaymentApi();
+
+ public abstract void installInvoiceMigrationApi();
+
+ public abstract void installInvoiceTestApi();
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/glue/JunctionModule.java b/api/src/main/java/com/ning/billing/glue/JunctionModule.java
new file mode 100644
index 0000000..413e99c
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/glue/JunctionModule.java
@@ -0,0 +1,30 @@
+/*
+ * 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.glue;
+
+
+public interface JunctionModule {
+
+ public void installBillingApi();
+
+ public void installAccountUserApi() ;
+
+ public void installBlockingApi() ;
+
+ public void installEntitlementUserApi();
+
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/EmptyInvoiceEvent.java b/api/src/main/java/com/ning/billing/invoice/api/EmptyInvoiceEvent.java
new file mode 100644
index 0000000..a0012a8
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/EmptyInvoiceEvent.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.invoice.api;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public interface EmptyInvoiceEvent extends BusEvent {
+
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatterFactory.java b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatterFactory.java
new file mode 100644
index 0000000..af4af92
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatterFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.api.formatters;
+
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+import java.util.Locale;
+
+public interface InvoiceFormatterFactory {
+ public InvoiceFormatter createInvoiceFormatter(TranslatorConfig config, Invoice invoice, Locale locale);
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java
new file mode 100644
index 0000000..c14e621
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.api.formatters;
+
+import com.ning.billing.invoice.api.InvoiceItem;
+
+public interface InvoiceItemFormatter extends InvoiceItem {
+ public String getFormattedStartDate();
+ public String getFormattedEndDate();
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/Invoice.java b/api/src/main/java/com/ning/billing/invoice/api/Invoice.java
index 0cf30ab..cfa172a 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/Invoice.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/Invoice.java
@@ -25,8 +25,6 @@ import java.util.List;
import java.util.UUID;
public interface Invoice extends ExtendedEntity {
- public static String ObjectType = "invoice";
-
boolean addInvoiceItem(InvoiceItem item);
boolean addInvoiceItems(List<InvoiceItem> items);
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
index abbc3f1..cd7decd 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
@@ -28,6 +28,8 @@ public interface InvoiceItem extends Entity, Comparable<InvoiceItem> {
UUID getAccountId();
+ UUID getBundleId();
+
UUID getSubscriptionId();
String getPlanName();
@@ -44,5 +46,5 @@ public interface InvoiceItem extends Entity, Comparable<InvoiceItem> {
Currency getCurrency();
- InvoiceItem asCredit();
+ InvoiceItem asReversingItem();
}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceNotifier.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceNotifier.java
new file mode 100644
index 0000000..cd0b7b6
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceNotifier.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.api;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.account.api.Account;
+
+import java.io.IOException;
+
+public interface InvoiceNotifier {
+ public void notify(Account account, Invoice invoice) throws InvoiceApiException;
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
index 9fb0bb3..0a34604 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
@@ -42,7 +42,7 @@ public interface InvoicePaymentApi {
public void notifyOfPaymentAttempt(InvoicePayment invoicePayment, CallContext context);
public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate, CallContext context);
-
+
public void notifyOfPaymentAttempt(UUID invoiceId, UUID paymentAttemptId, DateTime paymentAttemptDate, CallContext context);
}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceService.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceService.java
index 7109223..c9f9f0e 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceService.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceService.java
@@ -19,6 +19,5 @@ package com.ning.billing.invoice.api;
import com.ning.billing.lifecycle.KillbillService;
public interface InvoiceService extends KillbillService {
- public InvoiceUserApi getUserApi();
- public InvoicePaymentApi getPaymentApi();
+
}
diff --git a/api/src/main/java/com/ning/billing/junction/api/Blockable.java b/api/src/main/java/com/ning/billing/junction/api/Blockable.java
new file mode 100644
index 0000000..39900cc
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/junction/api/Blockable.java
@@ -0,0 +1,60 @@
+/*
+ * 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.junction.api;
+
+import java.util.UUID;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+
+public interface Blockable {
+
+ public enum Type {
+ ACCOUNT,
+ SUBSCRIPTION_BUNDLE,
+ SUBSCRIPTION;
+
+ public static Type get(Blockable o) throws BlockingApiException{
+ if (o instanceof Account){
+ return ACCOUNT;
+ } else if (o instanceof SubscriptionBundle){
+ return SUBSCRIPTION_BUNDLE;
+ } else if (o instanceof Subscription){
+ return SUBSCRIPTION;
+ }
+ throw new BlockingApiException(ErrorCode.BLOCK_TYPE_NOT_SUPPORTED , o.getClass().getName());
+ }
+
+ public static Type get(String type) throws BlockingApiException {
+ if (type.equalsIgnoreCase(ACCOUNT.name())) {
+ return ACCOUNT;
+ } else if (type.equalsIgnoreCase(SUBSCRIPTION_BUNDLE.name())) {
+ return SUBSCRIPTION_BUNDLE;
+ } else if (type.equalsIgnoreCase(SUBSCRIPTION.name())) {
+ return SUBSCRIPTION;
+ }
+ throw new BlockingApiException(ErrorCode.BLOCK_TYPE_NOT_SUPPORTED , type);
+ }
+
+ }
+
+ public UUID getId();
+
+ public BlockingState getBlockingState();
+}
diff --git a/api/src/main/java/com/ning/billing/junction/api/BlockingApi.java b/api/src/main/java/com/ning/billing/junction/api/BlockingApi.java
new file mode 100644
index 0000000..d7fc678
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/junction/api/BlockingApi.java
@@ -0,0 +1,36 @@
+/*
+ * 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.junction.api;
+
+import java.util.SortedSet;
+import java.util.UUID;
+
+
+public interface BlockingApi {
+ public static final String CLEAR_STATE_NAME = "__KILLBILL__CLEAR__OVERDUE_STATE__";
+
+ public BlockingState getBlockingStateFor(Blockable overdueable);
+
+ public BlockingState getBlockingStateFor(UUID overdueableId);
+
+ public SortedSet<BlockingState> getBlockingHistory(Blockable overdueable);
+
+ public SortedSet<BlockingState> getBlockingHistory(UUID overdueableId);
+
+ public <T extends Blockable> void setBlockingState(BlockingState state);
+
+}
diff --git a/api/src/main/java/com/ning/billing/junction/api/BlockingApiException.java b/api/src/main/java/com/ning/billing/junction/api/BlockingApiException.java
new file mode 100644
index 0000000..3a7a6c5
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/junction/api/BlockingApiException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.junction.api;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+
+public class BlockingApiException extends BillingExceptionBase {
+ private static final long serialVersionUID = 1L;
+
+ public BlockingApiException(Throwable cause, ErrorCode code, Object... args) {
+ super(cause, code, args);
+ }
+
+ public BlockingApiException(ErrorCode code, Object... args) {
+ super(code, args);
+ }
+
+}
diff --git a/api/src/main/java/com/ning/billing/junction/api/BlockingState.java b/api/src/main/java/com/ning/billing/junction/api/BlockingState.java
new file mode 100644
index 0000000..08202a2
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/junction/api/BlockingState.java
@@ -0,0 +1,42 @@
+/*
+ * 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.junction.api;
+
+import org.joda.time.DateTime;
+
+public interface BlockingState extends Comparable<BlockingState> {
+
+ public abstract String getStateName();
+
+ public abstract Blockable.Type getType();
+
+ public abstract DateTime getTimestamp();
+
+ public abstract boolean isBlockChange();
+
+ public abstract boolean isBlockEntitlement();
+
+ public abstract boolean isBlockBilling();
+
+ public abstract int compareTo(BlockingState arg0);
+
+ public abstract int hashCode();
+
+ public abstract String getDescription();
+
+ public abstract String toString();
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/junction/api/DefaultBlockingState.java b/api/src/main/java/com/ning/billing/junction/api/DefaultBlockingState.java
new file mode 100644
index 0000000..332cb85
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/junction/api/DefaultBlockingState.java
@@ -0,0 +1,228 @@
+/*
+ * 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.junction.api;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+
+public class DefaultBlockingState implements BlockingState{
+
+ private static BlockingState clearState= null;
+
+ private final UUID blockingId;
+ private final Blockable.Type type;
+ private final String stateName;
+ private final String service;
+ private final boolean blockChange;
+ private final boolean blockEntitlement;
+ private final boolean blockBilling;
+ private final DateTime timestamp;
+
+ public static BlockingState getClearState() {
+ if(clearState == null) {
+ clearState = new DefaultBlockingState(null, BlockingApi.CLEAR_STATE_NAME, null, null, false, false, false);
+ }
+ return clearState;
+ }
+
+ public DefaultBlockingState(UUID blockingId,
+ String stateName,
+ Blockable.Type type,
+ String service,
+ boolean blockChange,
+ boolean blockEntitlement,
+ boolean blockBilling
+ ) {
+ this( blockingId,
+ stateName,
+ type,
+ service,
+ blockChange,
+ blockEntitlement,
+ blockBilling,
+ null);
+ }
+
+ public DefaultBlockingState(UUID blockingId,
+ String stateName,
+ Blockable.Type type,
+ String service,
+ boolean blockChange,
+ boolean blockEntitlement,
+ boolean blockBilling,
+ DateTime timestamp
+ ) {
+ super();
+ this.blockingId = blockingId;
+ this.stateName = stateName;
+ this.service = service;
+ this.blockChange = blockChange;
+ this.blockEntitlement = blockEntitlement;
+ this.blockBilling = blockBilling;
+ this.type = type;
+ this.timestamp = timestamp;
+ }
+
+ public UUID getBlockedId() {
+ return blockingId;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.junction.api.blocking.BlockingState#getStateName()
+ */
+ @Override
+ public String getStateName() {
+ return stateName;
+ }
+
+ @Override
+ public Blockable.Type getType() {
+ return type;
+ }
+ /* (non-Javadoc)
+ * @see com.ning.billing.junction.api.blocking.BlockingState#getTimestamp()
+ */
+ @Override
+ public DateTime getTimestamp() {
+ return timestamp;
+ }
+
+ public String getService() {
+ return service;
+ }
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.junction.api.blocking.BlockingState#isBlockChange()
+ */
+ @Override
+ public boolean isBlockChange() {
+ return blockChange;
+ }
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.junction.api.blocking.BlockingState#isBlockEntitlement()
+ */
+ @Override
+ public boolean isBlockEntitlement() {
+ return blockEntitlement;
+ }
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.junction.api.blocking.BlockingState#isBlockBilling()
+ */
+ @Override
+ public boolean isBlockBilling() {
+ return blockBilling;
+ }
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.junction.api.blocking.BlockingState#compareTo(com.ning.billing.junction.api.blocking.DefaultBlockingState)
+ */
+ public int compareTo(BlockingState arg0) {
+ if (timestamp.compareTo(arg0.getTimestamp()) != 0) {
+ return timestamp.compareTo(arg0.getTimestamp());
+ } else {
+ return hashCode() - arg0.hashCode();
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (blockBilling ? 1231 : 1237);
+ result = prime * result + (blockChange ? 1231 : 1237);
+ result = prime * result + (blockEntitlement ? 1231 : 1237);
+ result = prime * result + ((blockingId == null) ? 0 : blockingId.hashCode());
+ result = prime * result + ((service == null) ? 0 : service.hashCode());
+ result = prime * result + ((stateName == null) ? 0 : stateName.hashCode());
+ result = prime * result + ((timestamp == null) ? 0 : timestamp.hashCode());
+ result = prime * result + ((type == null) ? 0 : type.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DefaultBlockingState other = (DefaultBlockingState) obj;
+ if (blockBilling != other.blockBilling)
+ return false;
+ if (blockChange != other.blockChange)
+ return false;
+ if (blockEntitlement != other.blockEntitlement)
+ return false;
+ if (blockingId == null) {
+ if (other.blockingId != null)
+ return false;
+ } else if (!blockingId.equals(other.blockingId))
+ return false;
+ if (service == null) {
+ if (other.service != null)
+ return false;
+ } else if (!service.equals(other.service))
+ return false;
+ if (stateName == null) {
+ if (other.stateName != null)
+ return false;
+ } else if (!stateName.equals(other.stateName))
+ return false;
+ if (timestamp == null) {
+ if (other.timestamp != null)
+ return false;
+ } else if (!timestamp.equals(other.timestamp))
+ return false;
+ if (type != other.type)
+ return false;
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.junction.api.blocking.BlockingState#getDescription()
+ */
+ @Override
+ public String getDescription() {
+ String entitlement = onOff(isBlockEntitlement());
+ String billing = onOff(isBlockBilling());
+ String change = onOff(isBlockChange());
+
+ return String.format("(Change: %s, Entitlement: %s, Billing: %s)", change, entitlement, billing);
+ }
+
+ private String onOff(boolean val) {
+ if(val) {
+ return "Off";
+ } else {
+ return "On";
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "BlockingState [blockingId=" + blockingId + ", type=" + type + ", stateName=" + stateName + ", service="
+ + service + ", blockChange=" + blockChange + ", blockEntitlement=" + blockEntitlement
+ + ", blockBilling=" + blockBilling + ", timestamp=" + timestamp + "]";
+ }
+
+
+
+}
diff --git a/api/src/main/java/com/ning/billing/overdue/config/api/BillingState.java b/api/src/main/java/com/ning/billing/overdue/config/api/BillingState.java
new file mode 100644
index 0000000..965e402
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/config/api/BillingState.java
@@ -0,0 +1,72 @@
+/*
+ * 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.overdue.config.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.util.tag.Tag;
+
+public class BillingState<T extends Blockable> {
+ private final UUID objectId;
+ private final int numberOfUnpaidInvoices;
+ private final BigDecimal balanceOfUnpaidInvoices;
+ private final DateTime dateOfEarliestUnpaidInvoice;
+ private final PaymentResponse responseForLastFailedPayment;
+ private final Tag[] tags;
+
+ public BillingState(UUID id, int numberOfUnpaidInvoices, BigDecimal balanceOfUnpaidInvoices,
+ DateTime dateOfEarliestUnpaidInvoice,
+ PaymentResponse responseForLastFailedPayment,
+ Tag[] tags) {
+ super();
+ this.objectId = id;
+ this.numberOfUnpaidInvoices = numberOfUnpaidInvoices;
+ this.balanceOfUnpaidInvoices = balanceOfUnpaidInvoices;
+ this.dateOfEarliestUnpaidInvoice = dateOfEarliestUnpaidInvoice;
+ this.responseForLastFailedPayment = responseForLastFailedPayment;
+ this.tags = tags;
+ }
+
+ public UUID getObjectId() {
+ return objectId;
+ }
+
+ public int getNumberOfUnpaidInvoices() {
+ return numberOfUnpaidInvoices;
+ }
+
+ public BigDecimal getBalanceOfUnpaidInvoices() {
+ return balanceOfUnpaidInvoices;
+ }
+
+ public DateTime getDateOfEarliestUnpaidInvoice() {
+ return dateOfEarliestUnpaidInvoice;
+ }
+
+ public PaymentResponse getResponseForLastFailedPayment() {
+ return responseForLastFailedPayment;
+ }
+
+ public Tag[] getTags() {
+ return tags;
+ }
+
+}
diff --git a/api/src/main/java/com/ning/billing/overdue/config/api/BillingStateBundle.java b/api/src/main/java/com/ning/billing/overdue/config/api/BillingStateBundle.java
new file mode 100644
index 0000000..15eebc8
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/config/api/BillingStateBundle.java
@@ -0,0 +1,68 @@
+/*
+ * 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.overdue.config.api;
+
+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.PhaseType;
+import com.ning.billing.catalog.api.PriceList;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.util.tag.Tag;
+
+public class BillingStateBundle extends BillingState<SubscriptionBundle> {
+ private final Product basePlanProduct;
+ private final BillingPeriod basePlanBillingPeriod;
+ private final PriceList basePlanPriceList;
+ private final PhaseType basePlanPhaseType;
+
+ public BillingStateBundle(UUID id, int numberOfUnpaidInvoices, BigDecimal unpaidInvoiceBalance,
+ DateTime dateOfEarliestUnpaidInvoice,
+ PaymentResponse responseForLastFailedPayment,
+ Tag[] tags,
+ Product basePlanProduct,
+ BillingPeriod basePlanBillingPeriod,
+ PriceList basePlanPriceList, PhaseType basePlanPhaseType) {
+ super(id, numberOfUnpaidInvoices, unpaidInvoiceBalance,
+ dateOfEarliestUnpaidInvoice, responseForLastFailedPayment, tags);
+
+ this.basePlanProduct = basePlanProduct;
+ this.basePlanBillingPeriod = basePlanBillingPeriod;
+ this.basePlanPriceList = basePlanPriceList;
+ this.basePlanPhaseType = basePlanPhaseType;
+ }
+
+ public Product getBasePlanProduct() {
+ return basePlanProduct;
+ }
+
+ public BillingPeriod getBasePlanBillingPeriod() {
+ return basePlanBillingPeriod;
+ }
+
+ public PriceList getBasePlanPriceList() {
+ return basePlanPriceList;
+ }
+
+ public PhaseType getBasePlanPhaseType() {
+ return basePlanPhaseType;
+ }
+}
diff --git a/api/src/main/java/com/ning/billing/overdue/config/api/OverdueError.java b/api/src/main/java/com/ning/billing/overdue/config/api/OverdueError.java
new file mode 100644
index 0000000..78f62c6
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/config/api/OverdueError.java
@@ -0,0 +1,42 @@
+/*
+ * 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.overdue.config.api;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+
+public class OverdueError extends BillingExceptionBase {
+
+ public OverdueError(BillingExceptionBase cause) {
+ super(cause);
+ }
+
+ public OverdueError(Throwable cause, int code, String msg) {
+ super(cause, code, msg);
+ }
+
+ private static final long serialVersionUID = 1L;
+
+ public OverdueError(Throwable cause, ErrorCode code, Object... args) {
+ super(cause, code, args);
+ }
+
+ public OverdueError(ErrorCode code, Object... args) {
+ super(code, args);
+ }
+
+}
diff --git a/api/src/main/java/com/ning/billing/overdue/config/api/OverdueStateSet.java b/api/src/main/java/com/ning/billing/overdue/config/api/OverdueStateSet.java
new file mode 100644
index 0000000..693fa8f
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/config/api/OverdueStateSet.java
@@ -0,0 +1,32 @@
+/*
+ * 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.overdue.config.api;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.OverdueState;
+
+public interface OverdueStateSet<T extends Blockable> {
+
+ public abstract OverdueState<T> findClearState() throws OverdueApiException;
+
+ public abstract OverdueState<T> findState(String stateName) throws OverdueApiException;
+
+ public abstract OverdueState<T> calculateOverdueState(BillingState<T> billingState, DateTime now) throws OverdueApiException;
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/overdue/config/api/PaymentResponse.java b/api/src/main/java/com/ning/billing/overdue/config/api/PaymentResponse.java
new file mode 100644
index 0000000..fb82d7c
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/config/api/PaymentResponse.java
@@ -0,0 +1,92 @@
+/*
+ * 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.overdue.config.api;
+
+public enum PaymentResponse {
+ // Card issues
+ INVALID_CARD("The card number, expiry date or cvc is invalid or incorrect"),
+ EXPIRED_CARD("The card has expired"),
+ LOST_OR_STOLEN_CARD("The card has been lost or stolen"),
+
+ // Account issues
+ DO_NOT_HONOR("Do not honor the card - usually a problem with account"),
+ INSUFFICIENT_FUNDS("The account had insufficient funds to fulfil the payment"),
+ DECLINE("Generic payment decline"),
+
+ //Transaction
+ PROCESSING_ERROR("Error processing card"),
+ INVALID_AMOUNT("An invalid amount was entered"),
+ DUPLICATE_TRANSACTION("A transaction with identical amount and credit card information was submitted very recently."),
+
+ //Other
+ OTHER("Some other error");
+
+ private String description;
+
+ private PaymentResponse(String description) {
+ this.description = description;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+// 690118 | Approved
+// 136956 | Do Not Honor
+// 119640 | Insufficient Funds
+// 68514 | Invalid Account Number
+// 66824 | Declined: 10417-The transaction cannot complete successfully. Instruct the customer to use an alternative payment
+// 55473 | Declined: 10201-Agreement was canceled
+// 30930 | Pick Up Card
+// 29857 | Lost/Stolen Card
+// 28197 | Declined
+// 24830 | Declined: 10207-Transaction failed but user has alternate funding source
+// 18445 | Generic Decline
+// 18254 | Expired Card
+// 16521 | Cardholder transaction not permitted
+// 11576 | Restricted Card
+// 7410 | Account Number Does Not Match Payment Type
+// 7312 | Invalid merchant information: 10507-Payer's account is denied
+// 6425 | Invalid Transaction
+// 2825 | Declined: 10204-User's account is closed or restricted
+// 2730 | Invalid account number
+// 1331 |
+// 1240 | Field format error: 10561-There's an error with this transaction. Please enter a complete billing address.
+// 1125 | Cardholder requested that recurring or installment payment be stopped
+// 1060 | No such issuer
+// 1047 | Issuer Unavailable
+// 816 | Not signed up for this tender type
+// 749 | Transaction not allowed at terminal
+// 663 | Invalid expiration date: 0910
+// 548 | Invalid expiration date: 1010
+// 542 | Invalid expiration date:
+// 500 | Invalid expiration date: 0810
+// 492 | Invalid expiration date: 1110
+// 410 | Invalid expiration date: 0710
+// 388 | Exceeds Approval Amount Limit
+// 362 | Generic processor error: 10001-Internal Error
+// 313 | Exceeds per transaction limit: 10553-This transaction cannot be processed.
+// 310 | Decline CVV2/CID Fail
+// 309 | Generic processor error: 10201-Agreement was canceled
+// 278 | Generic processor error: 10417-The transaction cannot complete successfully. Instruct the customer to use an alte
+// 246 | Call Issuer
+// 237 | Generic processor error: 11091-The transaction was blocked as it would exceed the sending limit for this buyer.
+// 202 | Failed to connect to host Input Server Uri = https://payflowpro.paypal.com:443
+// 166 | Exceeds number of PIN entries
+// 150 | Invalid Amount
+
+}
diff --git a/api/src/main/java/com/ning/billing/overdue/OverdueApiException.java b/api/src/main/java/com/ning/billing/overdue/OverdueApiException.java
new file mode 100644
index 0000000..7629abf
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/OverdueApiException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.overdue;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+
+public class OverdueApiException extends BillingExceptionBase {
+ private static final long serialVersionUID = 1L;
+
+ public OverdueApiException(Throwable cause, ErrorCode code, Object... args) {
+ super(cause, code, args);
+ }
+
+ public OverdueApiException(ErrorCode code, Object... args) {
+ super(code, args);
+ }
+
+}
diff --git a/api/src/main/java/com/ning/billing/overdue/OverdueService.java b/api/src/main/java/com/ning/billing/overdue/OverdueService.java
new file mode 100644
index 0000000..06c1f15
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/OverdueService.java
@@ -0,0 +1,28 @@
+/*
+ * 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.overdue;
+
+import com.ning.billing.lifecycle.KillbillService;
+
+public interface OverdueService extends KillbillService {
+ String OVERDUE_SERVICE_NAME = "overdue-service";
+
+ public String getName();
+
+ public OverdueUserApi getUserApi();
+
+}
diff --git a/api/src/main/java/com/ning/billing/overdue/OverdueState.java b/api/src/main/java/com/ning/billing/overdue/OverdueState.java
new file mode 100644
index 0000000..3de2c94
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/OverdueState.java
@@ -0,0 +1,40 @@
+/*
+ * 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.overdue;
+
+import org.joda.time.Period;
+
+import com.ning.billing.junction.api.Blockable;
+
+
+
+public interface OverdueState<T extends Blockable> {
+
+ public String getName();
+
+ public String getExternalMessage();
+
+ public int getDaysBetweenPaymentRetries();
+
+ public boolean disableEntitlementAndChangesBlocked();
+
+ public boolean blockChanges();
+
+ public boolean isClearState();
+
+ public Period getReevaluationInterval() throws OverdueApiException;
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/overdue/OverdueUserApi.java b/api/src/main/java/com/ning/billing/overdue/OverdueUserApi.java
new file mode 100644
index 0000000..3101c10
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/overdue/OverdueUserApi.java
@@ -0,0 +1,30 @@
+/*
+ * 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.overdue;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.overdue.config.api.OverdueError;
+
+public interface OverdueUserApi {
+
+ public <T extends Blockable> OverdueState<T> refreshOverdueStateFor(T overdueable) throws OverdueError, OverdueApiException;
+
+ public <T extends Blockable> void setOverrideBillingStateForAccount(T overdueable, BillingState<T> state) throws OverdueError;
+
+ public <T extends Blockable> OverdueState<T> getOverdueStateFor(T overdueable) throws OverdueError;
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/Either.java b/api/src/main/java/com/ning/billing/payment/api/Either.java
index 25ce8f8..c71c5b1 100644
--- a/api/src/main/java/com/ning/billing/payment/api/Either.java
+++ b/api/src/main/java/com/ning/billing/payment/api/Either.java
@@ -16,6 +16,7 @@
package com.ning.billing.payment.api;
+import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonValue;
public abstract class Either<T, V> {
@@ -29,9 +30,12 @@ public abstract class Either<T, V> {
private Either() {
}
+ @JsonIgnore
public boolean isLeft() {
return false;
}
+
+ @JsonIgnore
public boolean isRight() {
return false;
}
@@ -49,6 +53,7 @@ public abstract class Either<T, V> {
this.value = value;
}
@Override
+ @JsonIgnore
public boolean isLeft() {
return true;
}
@@ -66,6 +71,7 @@ public abstract class Either<T, V> {
this.value = value;
}
@Override
+ @JsonIgnore
public boolean isRight() {
return true;
}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
index 9076256..b1bef23 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
@@ -26,36 +26,57 @@ import com.ning.billing.util.callcontext.CallContext;
public interface PaymentApi {
- Either<PaymentError, Void> updatePaymentGateway(String accountKey, CallContext context);
+ public void updatePaymentGateway(final String accountKey, final CallContext context)
+ throws PaymentApiException;
- Either<PaymentError, PaymentMethodInfo> getPaymentMethod(@Nullable String accountKey, String paymentMethodId);
+ public PaymentMethodInfo getPaymentMethod(final String accountKey, final String paymentMethodId)
+ throws PaymentApiException;
- Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountKey);
+ public List<PaymentMethodInfo> getPaymentMethods(final String accountKey)
+ throws PaymentApiException;
- Either<PaymentError, String> addPaymentMethod(@Nullable String accountKey, PaymentMethodInfo paymentMethod, CallContext context);
+ public String addPaymentMethod(final String accountKey, final PaymentMethodInfo paymentMethod, final CallContext context)
+ throws PaymentApiException;
- Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo, CallContext context);
+ public PaymentMethodInfo updatePaymentMethod(final String accountKey, final PaymentMethodInfo paymentMethodInfo, final CallContext context)
+ throws PaymentApiException;
- Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId, CallContext context);
+ public void deletePaymentMethod(final String accountKey, final String paymentMethodId, final CallContext context)
+ throws PaymentApiException;
- List<Either<PaymentError, PaymentInfo>> createPayment(String accountKey, List<String> invoiceIds, CallContext context);
- List<Either<PaymentError, PaymentInfo>> createPayment(Account account, List<String> invoiceIds, CallContext context);
- Either<PaymentError, PaymentInfo> createPaymentForPaymentAttempt(UUID paymentAttemptId, CallContext context);
+ public List<PaymentInfoEvent> createPayment(final String accountKey, final List<String> invoiceIds, final CallContext context)
+ throws PaymentApiException;
+
+ public List<PaymentInfoEvent> createPayment(final Account account, final List<String> invoiceIds, final CallContext context)
+ throws PaymentApiException;
+
+ public PaymentInfoEvent createPaymentForPaymentAttempt(final UUID paymentAttemptId, final CallContext context)
+ throws PaymentApiException;
- List<Either<PaymentError, PaymentInfo>> createRefund(Account account, List<String> invoiceIds, CallContext context); //TODO
+ public List<PaymentInfoEvent> createRefund(final Account account, final List<String> invoiceIds, final CallContext context)
+ throws PaymentApiException;
- Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey);
+ public PaymentProviderAccount getPaymentProviderAccount(final String accountKey)
+ throws PaymentApiException;
- Either<PaymentError, String> createPaymentProviderAccount(Account account, CallContext context);
+ public String createPaymentProviderAccount(final Account account, final CallContext context)
+ throws PaymentApiException;
- Either<PaymentError, Void> updatePaymentProviderAccountContact(String accountKey, CallContext context);
+ public void updatePaymentProviderAccountContact(String accountKey, CallContext context)
+ throws PaymentApiException;
- PaymentAttempt getPaymentAttemptForPaymentId(String id);
+ public PaymentAttempt getPaymentAttemptForPaymentId(final UUID id)
+ throws PaymentApiException;
- List<PaymentInfo> getPaymentInfo(List<String> invoiceIds);
+ public List<PaymentInfoEvent> getPaymentInfoList(final List<String> invoiceIds)
+ throws PaymentApiException;
- List<PaymentAttempt> getPaymentAttemptsForInvoiceId(String invoiceId);
+ public PaymentInfoEvent getLastPaymentInfo(final List<String> invoiceIds)
+ throws PaymentApiException;
- PaymentInfo getPaymentInfoForPaymentAttemptId(String paymentAttemptId);
+ public List<PaymentAttempt> getPaymentAttemptsForInvoiceId(final String invoiceId)
+ throws PaymentApiException;
+ public PaymentInfoEvent getPaymentInfoForPaymentAttemptId(final String paymentAttemptId)
+ throws PaymentApiException;
}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentApiException.java b/api/src/main/java/com/ning/billing/payment/api/PaymentApiException.java
new file mode 100644
index 0000000..ff81b16
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApiException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.payment.api;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.catalog.api.CatalogApiException;
+
+public class PaymentApiException extends BillingExceptionBase {
+
+ private static final long serialVersionUID = 39445033L;
+
+
+ public PaymentApiException(AccountApiException e) {
+ super(e, e.getCode(), e.getMessage());
+ }
+
+ /*
+ public PaymentApiException(CatalogApiException e) {
+ super(e, e.getCode(), e.getMessage());
+ }
+ */
+
+ public PaymentApiException(Throwable e, ErrorCode code, Object...args) {
+ super(e, code, args);
+ }
+
+ public PaymentApiException(Throwable e, int code, String message) {
+ super(e, code, message);
+ }
+
+ public PaymentApiException(ErrorCode code, Object...args) {
+ super(code, args);
+ }
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java b/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
index 15ac4ee..a424fc6 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
@@ -16,281 +16,31 @@
package com.ning.billing.payment.api;
-import java.math.BigDecimal;
-import java.util.UUID;
-
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-
-import com.google.common.base.Objects;
import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.Invoice;
-
-public class PaymentAttempt {
- private final UUID paymentAttemptId;
- private final UUID invoiceId;
- private final UUID accountId;
- private final BigDecimal amount;
- private final Currency currency;
- private final String paymentId;
- private final DateTime invoiceDate;
- private final DateTime paymentAttemptDate;
- private final Integer retryCount;
- private final DateTime createdDate;
- private final DateTime updatedDate;
-
- public PaymentAttempt(UUID paymentAttemptId,
- UUID invoiceId,
- UUID accountId,
- BigDecimal amount,
- Currency currency,
- DateTime invoiceDate,
- DateTime paymentAttemptDate,
- String paymentId,
- Integer retryCount,
- DateTime createdDate,
- DateTime updatedDate) {
- this.paymentAttemptId = paymentAttemptId;
- this.invoiceId = invoiceId;
- this.accountId = accountId;
- this.amount = amount;
- this.currency = currency;
- this.invoiceDate = invoiceDate;
- this.paymentAttemptDate = paymentAttemptDate == null ? new DateTime(DateTimeZone.UTC) : paymentAttemptDate;
- this.paymentId = paymentId;
- this.retryCount = retryCount == null ? 0 : retryCount;
- this.createdDate = createdDate;
- this.updatedDate = updatedDate;
- }
-
- public PaymentAttempt(UUID paymentAttemptId,
- UUID invoiceId,
- UUID accountId,
- BigDecimal amount,
- Currency currency,
- DateTime invoiceDate,
- DateTime paymentAttemptDate,
- String paymentId,
- Integer retryCount) {
- this(paymentAttemptId,
- invoiceId,
- accountId,
- amount,
- currency,
- invoiceDate,
- paymentAttemptDate,
- paymentId,
- retryCount,
- null,
- null);
- }
-
- public PaymentAttempt(UUID paymentAttemptId, UUID invoiceId, UUID accountId, BigDecimal amount, Currency currency, DateTime invoiceDate, DateTime paymentAttemptDate) {
- this(paymentAttemptId, invoiceId, accountId, amount, currency, invoiceDate, paymentAttemptDate, null, null);
- }
-
- public PaymentAttempt(UUID paymentAttemptId, UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime paymentAttemptDate) {
- this(paymentAttemptId, invoiceId, accountId, null, null, invoiceDate, paymentAttemptDate, null, null);
- }
-
- public PaymentAttempt(UUID paymentAttemptId, Invoice invoice) {
- this(paymentAttemptId, invoice.getId(), invoice.getAccountId(), invoice.getBalance(), invoice.getCurrency(), invoice.getInvoiceDate(), null, null, null);
- }
-
- public DateTime getInvoiceDate() {
- return invoiceDate;
- }
-
- public UUID getPaymentAttemptId() {
- return paymentAttemptId;
- }
-
- public String getPaymentId() {
- return paymentId;
- }
-
- public DateTime getPaymentAttemptDate() {
- return paymentAttemptDate;
- }
-
- public UUID getInvoiceId() {
- return invoiceId;
- }
-
- public UUID getAccountId() {
- return accountId;
- }
-
- public DateTime getCreatedDate() {
- return createdDate;
- }
-
- public DateTime getUpdatedDate() {
- return updatedDate;
- }
-
- public BigDecimal getAmount() {
- return amount;
- }
-
- public Currency getCurrency() {
- return currency;
- }
-
- public Integer getRetryCount() {
- return retryCount;
- }
-
- @Override
- public String toString() {
- return "PaymentAttempt [paymentAttemptId=" + paymentAttemptId + ", invoiceId=" + invoiceId + ", accountId=" + accountId + ", amount=" + amount + ", currency=" + currency + ", paymentId=" + paymentId + ", invoiceDate=" + invoiceDate + ", paymentAttemptDate=" + paymentAttemptDate + ", retryCount=" + retryCount + ", createdDate=" + createdDate + ", updatedDate=" + updatedDate + "]";
- }
-
- public Builder cloner() {
- return new Builder(this);
- }
-
- public static class Builder {
- private UUID paymentAttemptId;
- private UUID invoiceId;
- private UUID accountId;
- private BigDecimal amount;
- private Currency currency;
- private DateTime invoiceDate;
- private DateTime paymentAttemptDate;
- private String paymentId;
- private Integer retryCount;
- private DateTime createdDate;
- private DateTime updatedDate;
-
- public Builder() {
- }
-
- public Builder(PaymentAttempt src) {
- this.paymentAttemptId = src.paymentAttemptId;
- this.invoiceId = src.invoiceId;
- this.accountId = src.accountId;
- this.amount = src.amount;
- this.currency = src.currency;
- this.invoiceDate = src.invoiceDate;
- this.paymentAttemptDate = src.paymentAttemptDate;
- this.paymentId = src.paymentId;
- this.retryCount = src.retryCount;
- this.createdDate = src.createdDate;
- this.updatedDate = src.updatedDate;
- }
-
- public Builder setPaymentAttemptId(UUID paymentAttemptId) {
- this.paymentAttemptId = paymentAttemptId;
- return this;
- }
-
- public Builder setInvoiceId(UUID invoiceId) {
- this.invoiceId = invoiceId;
- return this;
- }
-
- public Builder setAccountId(UUID accountId) {
- this.accountId = accountId;
- return this;
- }
-
- public Builder setAmount(BigDecimal amount) {
- this.amount = amount;
- return this;
- }
-
- public Builder setCurrency(Currency currency) {
- this.currency = currency;
- return this;
- }
-
- public Builder setCreatedDate(DateTime createdDate) {
- this.createdDate = createdDate;
- return this;
- }
-
- public Builder setUpdatedDate(DateTime updatedDate) {
- this.updatedDate = updatedDate;
- return this;
- }
-
- public Builder setInvoiceDate(DateTime invoiceDate) {
- this.invoiceDate = invoiceDate;
- return this;
- }
+import com.ning.billing.util.entity.Entity;
+import org.joda.time.DateTime;
- public Builder setPaymentAttemptDate(DateTime paymentAttemptDate) {
- this.paymentAttemptDate = paymentAttemptDate;
- return this;
- }
+import java.math.BigDecimal;
+import java.util.UUID;
- public Builder setPaymentId(String paymentId) {
- this.paymentId = paymentId;
- return this;
- }
+public interface PaymentAttempt extends Entity {
+ DateTime getInvoiceDate();
- public Builder setRetryCount(Integer retryCount) {
- this.retryCount = retryCount;
- return this;
- }
+ UUID getPaymentId();
- public PaymentAttempt build() {
- return new PaymentAttempt(paymentAttemptId,
- invoiceId,
- accountId,
- amount,
- currency,
- invoiceDate,
- paymentAttemptDate,
- paymentId,
- retryCount,
- createdDate,
- updatedDate);
- }
+ DateTime getPaymentAttemptDate();
- }
+ UUID getInvoiceId();
- @Override
- public int hashCode() {
- return Objects.hashCode(paymentAttemptId,
- invoiceId,
- accountId,
- amount,
- currency,
- invoiceDate,
- paymentAttemptDate,
- paymentId,
- retryCount,
- createdDate,
- updatedDate);
- }
+ UUID getAccountId();
- @Override
- public boolean equals(final Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ BigDecimal getAmount();
- final PaymentAttempt that = (PaymentAttempt) o;
+ Currency getCurrency();
- if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) return false;
- if (amount != null ? !(amount.compareTo(that.amount) == 0) : that.amount != null) return false;
- if (createdDate != null ? !(getUnixTimestamp(createdDate) == getUnixTimestamp(that.createdDate)) : that.createdDate != null) return false;
- if (currency != that.currency) return false;
- if (invoiceDate != null ? !(getUnixTimestamp(invoiceDate) == getUnixTimestamp(that.invoiceDate)) : that.invoiceDate != null) return false;
- if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) return false;
- if (paymentAttemptDate != null ? !(getUnixTimestamp(paymentAttemptDate) == getUnixTimestamp(that.paymentAttemptDate)) : that.paymentAttemptDate != null)
- return false;
- if (paymentAttemptId != null ? !paymentAttemptId.equals(that.paymentAttemptId) : that.paymentAttemptId != null)
- return false;
- if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) return false;
- if (retryCount != null ? !retryCount.equals(that.retryCount) : that.retryCount != null) return false;
- if (updatedDate != null ? !(getUnixTimestamp(updatedDate) == getUnixTimestamp(that.updatedDate)) : that.updatedDate != null) return false;
+ Integer getRetryCount();
- return true;
- }
+ DateTime getCreatedDate();
- private static long getUnixTimestamp(final DateTime dateTime) {
- return dateTime.getMillis() / 1000;
- }
+ DateTime getUpdatedDate();
}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentErrorEvent.java b/api/src/main/java/com/ning/billing/payment/api/PaymentErrorEvent.java
new file mode 100644
index 0000000..56cc879
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentErrorEvent.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.payment.api;
+
+import java.util.UUID;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public interface PaymentErrorEvent extends BusEvent {
+
+ public String getType();
+
+ public String getMessage();
+
+ public UUID getInvoiceId();
+
+ public UUID getAccountId();
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentInfoEvent.java b/api/src/main/java/com/ning/billing/payment/api/PaymentInfoEvent.java
new file mode 100644
index 0000000..c4cabb9
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentInfoEvent.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.payment.api;
+
+import java.math.BigDecimal;
+
+import com.ning.billing.util.entity.Entity;
+import org.joda.time.DateTime;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public interface PaymentInfoEvent extends Entity, BusEvent {
+ public BigDecimal getAmount();
+
+ public String getBankIdentificationNumber();
+
+ public DateTime getEffectiveDate();
+
+ public String getPaymentNumber();
+
+ public String getPaymentMethod();
+
+ public String getCardType();
+
+ public String getCardCountry();
+
+ public String getReferenceId();
+
+ public String getPaymentMethodId();
+
+ public BigDecimal getRefundAmount();
+
+ public String getStatus();
+
+ public String getType();
+}
diff --git a/api/src/main/java/com/ning/billing/util/api/TagUserApi.java b/api/src/main/java/com/ning/billing/util/api/TagUserApi.java
index d5cb8f2..2ecd93d 100644
--- a/api/src/main/java/com/ning/billing/util/api/TagUserApi.java
+++ b/api/src/main/java/com/ning/billing/util/api/TagUserApi.java
@@ -17,13 +17,15 @@
package com.ning.billing.util.api;
import java.util.List;
+import java.util.UUID;
import com.ning.billing.util.callcontext.CallContext;
-import org.joda.time.DateTime;
+import com.ning.billing.util.dao.ObjectType;
import com.ning.billing.util.tag.Tag;
import com.ning.billing.util.tag.TagDefinition;
+// TODO: add ability to create, update and remove tags
public interface TagUserApi {
/***
*
@@ -33,13 +35,13 @@ public interface TagUserApi {
/***
*
- * @param name Identifies the definition.
+ * @param definitionName Identifies the definition.
* @param description Describes the use of the definition.
* @param context The call context, for auditing purposes
* @return the newly created tag definition
* @throws TagDefinitionApiException
*/
- public TagDefinition create(String name, String description, CallContext context) throws TagDefinitionApiException;
+ public TagDefinition create(String definitionName, String description, CallContext context) throws TagDefinitionApiException;
/***
*
@@ -57,7 +59,6 @@ public interface TagUserApi {
*/
public void deleteTagDefinition(String definitionName, CallContext context) throws TagDefinitionApiException;
-
/**
*
* @param name
@@ -65,18 +66,12 @@ public interface TagUserApi {
* @throws TagDefinitionApiException
*/
public TagDefinition getTagDefinition(String name) throws TagDefinitionApiException;
-
- /**
- * @param controlTagName
- * @throws TagDefinitionApiException
- */
- public Tag createControlTag(String controlTagName) throws TagDefinitionApiException;
-
-
- /**
- * @param tagDefinitionName
- * @return
- */
- public Tag createDescriptiveTag(String tagDefinitionName) throws TagDefinitionApiException;
-
+
+ public List<Tag> createControlTags(UUID objectId, ObjectType objectType, List<TagDefinition> tagDefinitions) throws TagDefinitionApiException;
+
+ public Tag createControlTag(UUID objectId, ObjectType objectType, TagDefinition tagDefinition) throws TagDefinitionApiException;
+
+ public List<Tag> createDescriptiveTags(UUID objectId, ObjectType objectType, List<TagDefinition> tagDefinitions) throws TagDefinitionApiException;
+
+ public Tag createDescriptiveTag(UUID objectId, ObjectType objectType, TagDefinition tagDefinition) throws TagDefinitionApiException;
}
diff --git a/api/src/main/java/com/ning/billing/util/bus/BusEvent.java b/api/src/main/java/com/ning/billing/util/bus/BusEvent.java
index 481cb74..d969440 100644
--- a/api/src/main/java/com/ning/billing/util/bus/BusEvent.java
+++ b/api/src/main/java/com/ning/billing/util/bus/BusEvent.java
@@ -16,6 +16,22 @@
package com.ning.billing.util.bus;
+import java.util.UUID;
+
public interface BusEvent {
+
+ public enum BusEventType {
+ ACCOUNT_CREATE,
+ ACCOUNT_CHANGE,
+ SUBSCRIPTION_TRANSITION,
+ BUNDLE_REPAIR,
+ INVOICE_EMPTY,
+ INVOICE_CREATION,
+ PAYMENT_INFO,
+ PAYMENT_ERROR
+ }
+ public BusEventType getBusEventType();
+
+ public UUID getUserToken();
}
diff --git a/api/src/main/java/com/ning/billing/util/callcontext/CallContext.java b/api/src/main/java/com/ning/billing/util/callcontext/CallContext.java
index da36f10..8be4760 100644
--- a/api/src/main/java/com/ning/billing/util/callcontext/CallContext.java
+++ b/api/src/main/java/com/ning/billing/util/callcontext/CallContext.java
@@ -16,9 +16,12 @@
package com.ning.billing.util.callcontext;
+import java.util.UUID;
+
import org.joda.time.DateTime;
public interface CallContext {
+ public UUID getUserToken();
public String getUserName();
public CallOrigin getCallOrigin();
public UserType getUserType();
diff --git a/api/src/main/java/com/ning/billing/util/customfield/Customizable.java b/api/src/main/java/com/ning/billing/util/customfield/Customizable.java
index daf549c..e43ed82 100644
--- a/api/src/main/java/com/ning/billing/util/customfield/Customizable.java
+++ b/api/src/main/java/com/ning/billing/util/customfield/Customizable.java
@@ -19,6 +19,7 @@ package com.ning.billing.util.customfield;
import java.util.List;
import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.dao.ObjectType;
import com.ning.billing.util.entity.Entity;
public interface Customizable {
@@ -38,5 +39,5 @@ public interface Customizable {
public void clearPersistedFields(CallContext context);
- public String getObjectName();
+ public ObjectType getObjectType();
}
diff --git a/api/src/main/java/com/ning/billing/util/dao/ObjectType.java b/api/src/main/java/com/ning/billing/util/dao/ObjectType.java
new file mode 100644
index 0000000..18d987c
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/dao/ObjectType.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.dao;
+
+public enum ObjectType {
+ ACCOUNT("account"),
+ ACCOUNT_EMAIL("account email"),
+ BUNDLE("subscription bundle"),
+ INVOICE("invoice"),
+ RECURRING_INVOICE_ITEM("recurring_invoice_item"),
+ SUBSCRIPTION("subscription");
+
+ private final String objectName;
+ ObjectType(String objectName) {
+ this.objectName = objectName;
+ }
+
+ public String getObjectName() {
+ return objectName;
+ }
+}
diff --git a/api/src/main/java/com/ning/billing/util/email/EmailApiException.java b/api/src/main/java/com/ning/billing/util/email/EmailApiException.java
new file mode 100644
index 0000000..9b5d6ba
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/email/EmailApiException.java
@@ -0,0 +1,36 @@
+/*
+ * 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.email;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+
+public class EmailApiException extends BillingExceptionBase {
+ private static final long serialVersionUID = 1L;
+
+ public EmailApiException(Throwable cause, int code, final String msg) {
+ super(cause, code, msg);
+ }
+
+ public EmailApiException(Throwable cause, ErrorCode code, final Object... args) {
+ super(cause, code, args);
+ }
+
+ public EmailApiException(ErrorCode code, final Object... args) {
+ super(code, args);
+ }
+}
diff --git a/api/src/main/java/com/ning/billing/util/entity/Entity.java b/api/src/main/java/com/ning/billing/util/entity/Entity.java
index 3369538..f363534 100644
--- a/api/src/main/java/com/ning/billing/util/entity/Entity.java
+++ b/api/src/main/java/com/ning/billing/util/entity/Entity.java
@@ -16,12 +16,8 @@
package com.ning.billing.util.entity;
-import org.joda.time.DateTime;
-
import java.util.UUID;
-public interface Entity<T> {
+public interface Entity {
public UUID getId();
- public String getCreatedBy();
- public DateTime getCreatedDate();
}
diff --git a/api/src/main/java/com/ning/billing/util/entity/UpdatableEntity.java b/api/src/main/java/com/ning/billing/util/entity/UpdatableEntity.java
index c860ddb..758d7dc 100644
--- a/api/src/main/java/com/ning/billing/util/entity/UpdatableEntity.java
+++ b/api/src/main/java/com/ning/billing/util/entity/UpdatableEntity.java
@@ -16,9 +16,5 @@
package com.ning.billing.util.entity;
-import org.joda.time.DateTime;
-
public interface UpdatableEntity extends Entity {
- public String getUpdatedBy();
- public DateTime getUpdatedDate();
}
diff --git a/api/src/main/java/com/ning/billing/util/queue/QueueLifecycle.java b/api/src/main/java/com/ning/billing/util/queue/QueueLifecycle.java
new file mode 100644
index 0000000..8db4ec7
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/queue/QueueLifecycle.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.util.queue;
+
+public interface QueueLifecycle {
+ /**
+ * Starts the queue
+ */
+ public void startQueue();
+
+ /**
+ * Stop the queue
+ *
+ */
+ public void stopQueue();
+
+ /**
+ * Processes event from queue
+ */
+ public int doProcessEvents();
+}
diff --git a/api/src/main/java/com/ning/billing/util/tag/ControlTagType.java b/api/src/main/java/com/ning/billing/util/tag/ControlTagType.java
index 406ec54..9f4bb38 100644
--- a/api/src/main/java/com/ning/billing/util/tag/ControlTagType.java
+++ b/api/src/main/java/com/ning/billing/util/tag/ControlTagType.java
@@ -18,7 +18,8 @@ package com.ning.billing.util.tag;
public enum ControlTagType {
AUTO_PAY_OFF("Suspends payments until removed.", true, false),
- AUTO_INVOICING_OFF("Suspends invoicing until removed.", false, true),
+ AUTO_INVOICING_OFF("Suspends invoicing until removed.", false, true),
+ OVERDUE_ENFORCEMENT_OFF("Suspends overdue enforcement behaviour until removed.", false, false),
WRITTEN_OFF("Indicated that an invoice is written off. No billing or payment effect.", false, false);
private final String description;
diff --git a/api/src/main/java/com/ning/billing/util/tag/TagDefinition.java b/api/src/main/java/com/ning/billing/util/tag/TagDefinition.java
index 408e09d..d35e011 100644
--- a/api/src/main/java/com/ning/billing/util/tag/TagDefinition.java
+++ b/api/src/main/java/com/ning/billing/util/tag/TagDefinition.java
@@ -18,8 +18,11 @@ package com.ning.billing.util.tag;
import com.ning.billing.util.entity.Entity;
+// TODO: needs to surface created date, created by, isControlTag
public interface TagDefinition extends Entity {
String getName();
String getDescription();
+
+ Boolean isControlTag();
}
diff --git a/api/src/main/java/com/ning/billing/util/tag/Taggable.java b/api/src/main/java/com/ning/billing/util/tag/Taggable.java
index 3b2e8b0..37d15bf 100644
--- a/api/src/main/java/com/ning/billing/util/tag/Taggable.java
+++ b/api/src/main/java/com/ning/billing/util/tag/Taggable.java
@@ -21,11 +21,16 @@ import org.joda.time.DateTime;
public interface Taggable {
public List<Tag> getTagList();
- public boolean hasTag(String tagName);
+
+ public boolean hasTag(TagDefinition tagDefinition);
+ public boolean hasTag(ControlTagType controlTagType);
+
public void addTag(TagDefinition definition);
public void addTags(List<Tag> tags);
+ public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions);
public void clearTags();
public void removeTag(TagDefinition definition);
+
public boolean generateInvoice();
public boolean processPayment();
}
diff --git a/api/src/main/java/com/ning/billing/util/tag/TagStore.java b/api/src/main/java/com/ning/billing/util/tag/TagStore.java
index 705e203..22749b9 100644
--- a/api/src/main/java/com/ning/billing/util/tag/TagStore.java
+++ b/api/src/main/java/com/ning/billing/util/tag/TagStore.java
@@ -23,7 +23,9 @@ public interface TagStore extends EntityCollection<Tag> {
public boolean generateInvoice();
- public void remove(String tagName);
+ public boolean containsTagForDefinition(TagDefinition definition);
- public boolean containsTag(String tagName);
+ public boolean containsTagForControlTagType(ControlTagType controlTagType);
+
+ public Tag remove(TagDefinition tagDefinition);
}
diff --git a/api/src/main/java/com/ning/billing/util/template/translation/TranslatorConfig.java b/api/src/main/java/com/ning/billing/util/template/translation/TranslatorConfig.java
new file mode 100644
index 0000000..506c8bc
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/template/translation/TranslatorConfig.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.template.translation;
+
+import org.skife.config.Config;
+import org.skife.config.Default;
+
+public interface TranslatorConfig {
+ @Config("email.default.locale")
+ @Default("en_US")
+ public String getDefaultLocale();
+}
diff --git a/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequest.java b/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequest.java
new file mode 100644
index 0000000..bf67736
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequest.java
@@ -0,0 +1,19 @@
+/*
+ * 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.userrequest;
+
+public interface CompletionUserRequest extends CompletionUserRequestNotifier, CompletionUserRequestWaiter{
+}
diff --git a/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestNotifier.java b/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestNotifier.java
new file mode 100644
index 0000000..62b9256
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestNotifier.java
@@ -0,0 +1,25 @@
+/*
+ * 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.userrequest;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public interface CompletionUserRequestNotifier {
+
+ public void notifyForCompletion();
+
+ public void onBusEvent(BusEvent curEvent);
+}
diff --git a/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestWaiter.java b/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestWaiter.java
new file mode 100644
index 0000000..d1258c8
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestWaiter.java
@@ -0,0 +1,48 @@
+/*
+ * 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.userrequest;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeoutException;
+
+import com.ning.billing.account.api.AccountChangeEvent;
+import com.ning.billing.account.api.AccountCreationEvent;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.invoice.api.EmptyInvoiceEvent;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
+import com.ning.billing.util.bus.BusEvent;
+
+public interface CompletionUserRequestWaiter {
+
+ public List<BusEvent> waitForCompletion(final long timeoutMilliSec) throws InterruptedException, TimeoutException;
+
+ public void onAccountCreation(final AccountCreationEvent curEvent);
+
+ public void onAccountChange(final AccountChangeEvent curEvent);
+
+ public void onSubscriptionTransition(final SubscriptionEvent curEvent);
+
+ public void onInvoiceCreation(final InvoiceCreationEvent curEvent);
+
+ public void onEmptyInvoice(final EmptyInvoiceEvent curEvent);
+
+ public void onPaymentInfo(final PaymentInfoEvent curEvent);
+
+ public void onPaymentError(final PaymentErrorEvent curEvent);
+}
beatrix/pom.xml 39(+39 -0)
diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 1ac3279..a742878 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -41,6 +41,10 @@
<artifactId>killbill-catalog</artifactId>
</dependency>
<dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-junction</artifactId>
+ </dependency>
+ <dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
@@ -61,6 +65,19 @@
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
+
+ <!-- TEST SCOPE -->
+ <dependency>
+ <groupId>com.jayway.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-junction</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>com.ning.billing</groupId>
<artifactId>killbill-payment</artifactId>
@@ -68,6 +85,28 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-invoice</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-account</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-account</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-overdue</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi</artifactId>
<scope>test</scope>
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
index 83115de..e27dff9 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
@@ -60,7 +60,7 @@ public class ServiceFinder {
final Set<String> packageFilter = new HashSet<String>();
packageFilter.add("com.ning.billing");
final String jarFilter = "killbill";
- return findClasses(loader, KillbillService.class.getName().toString(), jarFilter, packageFilter);
+ return findClasses(loader, KillbillService.class.getName(), jarFilter, packageFilter);
} catch (ClassNotFoundException nfe) {
throw new RuntimeException("Failed to initialize ClassFinder", nfe);
}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
new file mode 100644
index 0000000..e44ddc8
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.beatrix.integration.overdue;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.beatrix.integration.BeatrixModule;
+import com.ning.billing.beatrix.integration.TestIntegrationBase;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.overdue.config.OverdueConfig;
+import com.ning.billing.payment.provider.MockPaymentProviderPlugin;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.config.XMLLoader;
+
+@Test(groups = "slow")
+@Guice(modules = {BeatrixModule.class})
+public class TestOverdueIntegration extends TestIntegrationBase {
+ private final String configXml =
+ "<overdueConfig>" +
+ " <bundleOverdueStates>" +
+ " <state name=\"OD1\">" +
+ " <condition>" +
+ " <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+ " <unit>MONTHS</unit><number>1</number>" +
+ " </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+ " </condition>" +
+ " <externalMessage>Reached OD1</externalMessage>" +
+ " <blockChanges>true</blockChanges>" +
+ " <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" +
+ " </state>" +
+ " <state name=\"OD2\">" +
+ " <condition>" +
+ " <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+ " <unit>MONTHS</unit><number>2</number>" +
+ " </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+ " </condition>" +
+ " <externalMessage>Reached OD1</externalMessage>" +
+ " <blockChanges>true</blockChanges>" +
+ " <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>" +
+ " </state>" +
+ " </bundleOverdueStates>" +
+ "</overdueConfig>";
+ private OverdueConfig config;
+
+ @Inject
+ private ClockMock clock;
+
+ @Inject
+ private MockPaymentProviderPlugin paymentPlugin;
+
+ @Inject
+ private BlockingApi blockingApi;
+
+ private Account account;
+ private SubscriptionBundle bundle;
+ private String productName;
+ private BillingPeriod term;
+ private String planSetName;
+
+ @BeforeMethod(groups = {"slow"})
+ public void setupOverdue() throws Exception {
+ InputStream is = new ByteArrayInputStream(configXml.getBytes());
+ config = XMLLoader.getObjectFromStreamNoValidation(is, OverdueConfig.class);
+ Account account = accountUserApi.createAccount(getAccountData(25), null, null, context);
+ assertNotNull(account);
+
+ bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever", context);
+
+ productName = "Shotgun";
+ term = BillingPeriod.MONTHLY;
+ planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ // create account
+ // set mock payments to fail
+ // reset clock
+ // configure basic OD state rules for 2 states OD1 1-2month, OD2 2-3 month
+ }
+
+ @AfterMethod
+ public void cleanup(){
+ // Clear databases
+ }
+
+ @Test(groups={"slow"}, enabled = true)
+ public void testBasicOverdueState() throws Exception {
+ clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
+
+ // set next invoice to fail and create network
+ paymentPlugin.makeNextInvoiceFail();
+ SubscriptionData baseSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+ new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context));
+ assertNotNull(baseSubscription);
+
+ // advance time 2weeks
+ clock.addWeeks(2);
+
+ // should still be in clear state
+ BlockingState state = blockingApi.getBlockingStateFor(bundle);
+ Assert.assertEquals(state.getStateName(), BlockingApi.CLEAR_STATE_NAME);
+ // set next invoice to fail and advance time 1 month
+ clock.addWeeks(4);
+
+ // should now be in OD1 state
+ // set next invoice to fail and advance time 1 month
+ // should now be in OD2 state
+ clock.addWeeks(4);
+
+ }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/PaymentTestModule.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/PaymentTestModule.java
new file mode 100644
index 0000000..d444776
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/PaymentTestModule.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.beatrix.integration.payment;
+
+import com.ning.billing.account.dao.MockAccountDao;
+import com.ning.billing.invoice.dao.MockInvoiceDao;
+import org.apache.commons.collections.MapUtils;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Provider;
+import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.config.PaymentConfig;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.junction.api.BillingApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.payment.dao.MockPaymentDao;
+import com.ning.billing.payment.dao.PaymentDao;
+import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
+import com.ning.billing.payment.setup.PaymentModule;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.InMemoryBus;
+import com.ning.billing.util.notificationq.MockNotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+
+public class PaymentTestModule extends PaymentModule {
+ public static class MockProvider implements Provider<BillingApi> {
+ @Override
+ public BillingApi get() {
+ return BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
+ }
+
+ }
+
+ public PaymentTestModule() {
+ super(MapUtils.toProperties(ImmutableMap.of("killbill.payment.provider.default", "my-mock",
+ "killbill.payment.engine.events.off", "false")));
+ }
+
+ @Override
+ protected void installPaymentDao() {
+ bind(PaymentDao.class).to(MockPaymentDao.class).asEagerSingleton();
+ }
+
+ @Override
+ protected void installPaymentProviderPlugins(PaymentConfig config) {
+ install(new MockPaymentProviderPluginModule("my-mock"));
+ }
+
+ @Override
+ protected void configure() {
+ super.configure();
+ bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+ bind(MockAccountDao.class).asEagerSingleton();
+ bind(AccountDao.class).to(MockAccountDao.class);
+ bind(MockInvoiceDao.class).asEagerSingleton();
+ bind(InvoiceDao.class).to(MockInvoiceDao.class);
+ bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
+
+ }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestHelper.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestHelper.java
new file mode 100644
index 0000000..753b966
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestHelper.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.beatrix.integration.payment;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import org.apache.commons.lang.RandomStringUtils;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.test.InvoiceTestApi;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.entity.EntityPersistenceException;
+
+public class TestHelper {
+ protected final AccountUserApi accountUserApi;
+ protected final InvoiceTestApi invoiceTestApi;
+ private final CallContext context;
+
+ @Inject
+ public TestHelper(CallContextFactory factory, AccountUserApi accountUserApi, InvoiceTestApi invoiceTestApi) {
+ this.accountUserApi = accountUserApi;
+ this.invoiceTestApi = invoiceTestApi;
+ context = factory.createCallContext("Payment Test", CallOrigin.TEST, UserType.TEST);
+ }
+
+ // These helper methods can be overridden in a plugin implementation
+ public Account createTestCreditCardAccount() throws EntityPersistenceException {
+ String email = "ccuser" + RandomStringUtils.randomAlphanumeric(8) + "@example.com";
+ final Account account = createTestAccount(email);
+
+ ((ZombieControl)accountUserApi).addResult("getAccountById", account);
+ ((ZombieControl)accountUserApi).addResult("getAccountByKey", account);
+ return account;
+ }
+
+ public Account createTestPayPalAccount() throws EntityPersistenceException {
+ final Account account = createTestAccount("ppuser@example.com");
+ ((ZombieControl)accountUserApi).addResult("getAccountById", account);
+ ((ZombieControl)accountUserApi).addResult("getAccountByKey", account);
+ return account;
+ }
+
+ private Account createTestAccount(String email) {
+ Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+ ZombieControl zombie = (ZombieControl) account;
+ zombie.addResult("getId", UUID.randomUUID());
+ String name = "First" + RandomStringUtils.randomAlphanumeric(5) + " " + "Last" + RandomStringUtils.randomAlphanumeric(5);
+ zombie.addResult("getName", name);
+ zombie.addResult("getFirstNameLength", 10);
+ String externalKey = RandomStringUtils.randomAlphanumeric(10);
+ zombie.addResult("getExternalKey", externalKey);
+ zombie.addResult("getPhone", "123-456-7890");
+ zombie.addResult("getEmail", email);
+ zombie.addResult("getCurrency", Currency.USD);
+ zombie.addResult("getBillCycleDay", 1);
+ zombie.addResult("getPaymentProviderName", "");
+
+ return account;
+ }
+
+ public Invoice createTestInvoice(Account account,
+ DateTime targetDate,
+ Currency currency,
+ InvoiceItem... items) {
+ Invoice invoice = new DefaultInvoice(account.getId(), new DateTime(), targetDate, currency);
+
+ for (InvoiceItem item : items) {
+ if (item instanceof RecurringInvoiceItem) {
+ RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
+ invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
+ account.getId(),
+ recurringInvoiceItem.getBundleId(),
+ recurringInvoiceItem.getSubscriptionId(),
+ recurringInvoiceItem.getPlanName(),
+ recurringInvoiceItem.getPhaseName(),
+ recurringInvoiceItem.getStartDate(),
+ recurringInvoiceItem.getEndDate(),
+ recurringInvoiceItem.getAmount(),
+ recurringInvoiceItem.getRate(),
+ recurringInvoiceItem.getCurrency()));
+ }
+ }
+
+ invoiceTestApi.create(invoice, context);
+ return invoice;
+ }
+
+ public Invoice createTestInvoice(Account account) {
+ final DateTime now = new DateTime(DateTimeZone.UTC);
+ final UUID subscriptionId = UUID.randomUUID();
+ final UUID bundleId = UUID.randomUUID();
+ final BigDecimal amount = new BigDecimal("10.00");
+
+ final InvoiceItem item = new RecurringInvoiceItem(null, account.getId(), bundleId, subscriptionId, "test plan", "test phase", now, now.plusMonths(1),
+ amount, new BigDecimal("1.0"), Currency.USD);
+
+
+ return createTestInvoice(account, now, Currency.USD, item);
+ }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
index e334966..dae1175 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
@@ -19,272 +19,62 @@ package com.ning.billing.beatrix.integration;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
-import java.io.IOException;
import java.math.BigDecimal;
-import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
-import com.ning.billing.account.api.AccountApiException;
-import com.ning.billing.catalog.api.PhaseType;
-import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
-
-import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.model.InvoicingConfiguration;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang.RandomStringUtils;
import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
import org.joda.time.Interval;
-import org.skife.jdbi.v2.Handle;
-import org.skife.jdbi.v2.IDBI;
-import org.skife.jdbi.v2.TransactionCallback;
-import org.skife.jdbi.v2.TransactionStatus;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.AfterSuite;
-
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.BeforeSuite;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
-import com.google.inject.Inject;
import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.AccountData;
-import com.ning.billing.account.api.AccountService;
-import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.beatrix.integration.TestBusHandler.NextEvent;
-import com.ning.billing.beatrix.lifecycle.Lifecycle;
+import com.ning.billing.api.TestApiListener.NextEvent;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.PlanPhaseSpecifier;
import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.entitlement.api.EntitlementService;
-import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.invoice.api.InvoiceService;
-import com.ning.billing.invoice.api.InvoiceUserApi;
-
-import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.bus.BusService;
+import com.ning.billing.invoice.api.Invoice;
@Test(groups = "slow")
-@Guice(modules = {MockModule.class})
-public class TestIntegration {
- private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
- private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
-
- private static final BigDecimal ONE = new BigDecimal("1.0000").setScale(NUMBER_OF_DECIMALS);
- private static final BigDecimal TWENTY_NINE = new BigDecimal("29.0000").setScale(NUMBER_OF_DECIMALS);
- private static final BigDecimal THIRTY = new BigDecimal("30.0000").setScale(NUMBER_OF_DECIMALS);
- private static final BigDecimal THIRTY_ONE = new BigDecimal("31.0000").setScale(NUMBER_OF_DECIMALS);
-
- private static final Logger log = LoggerFactory.getLogger(TestIntegration.class);
- private static long AT_LEAST_ONE_MONTH_MS = 31L * 24L * 3600L * 1000L;
-
- private static final long DELAY = 5000;
-
- @Inject IDBI dbi;
-
- @Inject
- private ClockMock clock;
- private CallContext context;
-
- @Inject
- private Lifecycle lifecycle;
-
- @Inject
- private BusService busService;
-
- @Inject
- private EntitlementService entitlementService;
-
- @Inject
- private InvoiceService invoiceService;
-
- @Inject
- private AccountService accountService;
-
- @Inject
- private MysqlTestingHelper helper;
-
- private EntitlementUserApi entitlementUserApi;
-
- private InvoiceUserApi invoiceUserApi;
-
- private AccountUserApi accountUserApi;
-
- private TestBusHandler busHandler;
-
- private void setupMySQL() throws IOException
- {
- final String accountDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
- final String entitlementDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
- final String invoiceDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
- final String paymentDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
- final String utilDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
-
- helper.startMysql();
-
- helper.initDb(accountDdl);
- helper.initDb(entitlementDdl);
- helper.initDb(invoiceDdl);
- helper.initDb(paymentDdl);
- helper.initDb(utilDdl);
- }
-
- @BeforeSuite(groups = "slow")
- public void setup() throws Exception{
-
- setupMySQL();
-
- context = new DefaultCallContextFactory(clock).createCallContext("Integration Test", CallOrigin.TEST, UserType.TEST);
-
- /**
- * Initialize lifecyle for subset of services
- */
- busHandler = new TestBusHandler();
- lifecycle.fireStartupSequencePriorEventRegistration();
- busService.getBus().register(busHandler);
- lifecycle.fireStartupSequencePostEventRegistration();
-
-
-
- /**
- * Retrieve APIs
- */
- entitlementUserApi = entitlementService.getUserApi();
- invoiceUserApi = invoiceService.getUserApi();
- accountUserApi = accountService.getAccountUserApi();
- }
-
- @AfterSuite(groups = "slow")
- public void tearDown() throws Exception {
- lifecycle.fireShutdownSequencePriorEventUnRegistration();
- busService.getBus().unregister(busHandler);
- lifecycle.fireShutdownSequencePostEventUnRegistration();
- helper.stopMysql();
- }
-
-
- @BeforeMethod(groups = "slow")
- public void setupTest() {
-
- log.warn("\n");
- log.warn("RESET TEST FRAMEWORK\n\n");
- busHandler.reset();
- clock.resetDeltaFromReality();
- cleanupData();
- }
-
- @AfterMethod(groups = "slow")
- public void cleanupTest() {
- log.warn("DONE WITH TEST\n");
- }
-
- private void cleanupData() {
- dbi.inTransaction(new TransactionCallback<Void>() {
- @Override
- public Void inTransaction(Handle h, TransactionStatus status)
- throws Exception {
- h.execute("truncate table accounts");
- h.execute("truncate table entitlement_events");
- h.execute("truncate table subscriptions");
- h.execute("truncate table bundles");
- h.execute("truncate table notifications");
- h.execute("truncate table claimed_notifications");
- h.execute("truncate table invoices");
- h.execute("truncate table fixed_invoice_items");
- h.execute("truncate table recurring_invoice_items");
- h.execute("truncate table tag_definitions");
- h.execute("truncate table tags");
- h.execute("truncate table custom_fields");
- h.execute("truncate table invoice_payments");
- h.execute("truncate table payment_attempts");
- h.execute("truncate table payments");
- return null;
- }
- });
- }
-
- private void verifyTestResult(UUID accountId, UUID subscriptionId,
- DateTime startDate, DateTime endDate,
- BigDecimal amount, DateTime chargeThroughDate,
- int totalInvoiceItemCount) {
- SubscriptionData subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscriptionId);
-
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId);
- List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
- for (Invoice invoice : invoices) {
- invoiceItems.addAll(invoice.getInvoiceItems());
- }
- assertEquals(invoiceItems.size(), totalInvoiceItemCount);
-
- boolean wasFound = false;
-
- for (InvoiceItem item : invoiceItems) {
- if (item.getStartDate().compareTo(startDate) == 0) {
- if (item.getEndDate().compareTo(endDate) == 0) {
- if (item.getAmount().compareTo(amount) == 0) {
- wasFound = true;
- break;
- }
- }
- }
- }
-
- if (!wasFound) {
- fail();
- }
-
- DateTime ctd = subscription.getChargedThroughDate();
- assertNotNull(ctd);
- log.info("Checking CTD: " + ctd.toString() + "; clock is " + clock.getUTCNow().toString());
- assertTrue(clock.getUTCNow().isBefore(ctd));
- assertTrue(ctd.compareTo(chargeThroughDate) == 0);
- }
-
+@Guice(modules = {BeatrixModule.class})
+public class TestIntegration extends TestIntegrationBase {
@Test(groups = "slow", enabled = true)
public void testBasePlanCompleteWithBillingDayInPast() throws Exception {
+ log.info("Starting testBasePlanCompleteWithBillingDayInPast");
DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
testBasePlanComplete(startDate, 31, false);
}
@Test(groups = "slow", enabled = true)
public void testBasePlanCompleteWithBillingDayPresent() throws Exception {
+ log.info("Starting testBasePlanCompleteWithBillingDayPresent");
DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
testBasePlanComplete(startDate, 1, false);
}
@Test(groups = "slow", enabled = true)
public void testBasePlanCompleteWithBillingDayAlignedWithTrial() throws Exception {
+ log.info("Starting testBasePlanCompleteWithBillingDayAlignedWithTrial");
DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
testBasePlanComplete(startDate, 2, false);
}
@Test(groups = "slow", enabled = true)
public void testBasePlanCompleteWithBillingDayInFuture() throws Exception {
+ log.info("Starting testBasePlanCompleteWithBillingDayInFuture");
DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
testBasePlanComplete(startDate, 3, true);
}
- private void waitForDebug() throws Exception {
- Thread.sleep(600000);
- }
-
@Test(groups = {"slow", "stress"}, enabled = false)
public void stressTest() throws Exception {
final int maxIterations = 7;
@@ -305,9 +95,101 @@ public class TestIntegration {
}
}
+ // STEPH set to disabled until test written properly and fixed
+ @Test(groups = "slow", enabled = false)
+ public void testRepairChangeBPWithAddonIncluded() throws Exception {
+
+ log.info("Starting testRepairChangeBPWithAddonIncluded");
+
+ DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ Account account = accountUserApi.createAccount(getAccountData(25), null, null, context);
+ assertNotNull(account);
+
+ SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever", context);
+
+ String productName = "Shotgun";
+ BillingPeriod term = BillingPeriod.MONTHLY;
+ String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ busHandler.pushExpectedEvent(NextEvent.CREATE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ SubscriptionData baseSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+ new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context));
+ assertNotNull(baseSubscription);
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ busHandler.pushExpectedEvent(NextEvent.CREATE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ SubscriptionData aoSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+ new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null, context));
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ busHandler.pushExpectedEvent(NextEvent.CREATE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ SubscriptionData aoSubscription2 = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+ new PlanPhaseSpecifier("Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null, context));
+ assertTrue(busHandler.isCompleted(DELAY));
+
+
+ // MOVE CLOCK A LITTLE BIT MORE -- EITHER STAY IN TRIAL OR GET OUT
+ int duration = 35;
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(duration));
+ busHandler.pushExpectedEvent(NextEvent.PHASE);
+ busHandler.pushExpectedEvent(NextEvent.PHASE);
+ busHandler.pushExpectedEvent(NextEvent.PHASE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ assertListenerStatus();
+ }
+
+ @Test(groups = {"slow"})
+ public void testRepairForInvoicing() throws AccountApiException, EntitlementUserApiException {
+
+ log.info("Starting testRepairForInvoicing");
+
+ Account account = accountUserApi.createAccount(getAccountData(1), null, null, context);
+ UUID accountId = account.getId();
+ assertNotNull(account);
+
+ DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "someBundle", context);
+ assertNotNull(bundle);
+
+ String productName = "Shotgun";
+ BillingPeriod term = BillingPeriod.MONTHLY;
+ String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+ entitlementUserApi.createSubscription(bundle.getId(),
+ new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context);
+
+ busHandler.reset();
+ busHandler.pushExpectedEvent(NextEvent.CREATE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId);
+ assertEquals(invoices.size(), 1);
+
+ // TODO: Jeff implement repair
+ }
+
@Test(groups = "slow", enabled = true)
public void testWithRecreatePlan() throws Exception {
+ log.info("Starting testWithRecreatePlan");
+
DateTime initialDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
int billingDay = 2;
@@ -329,8 +211,10 @@ public class TestIntegration {
//
busHandler.pushExpectedEvent(NextEvent.CREATE);
busHandler.pushExpectedEvent(NextEvent.INVOICE);
- SubscriptionData subscription = (SubscriptionData) entitlementUserApi.createSubscription(bundle.getId(),
- new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context);
+
+ SubscriptionData subscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+ new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context));
+
assertNotNull(subscription);
assertTrue(busHandler.isCompleted(DELAY));
@@ -352,7 +236,7 @@ public class TestIntegration {
clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
assertTrue(busHandler.isCompleted(DELAY));
- subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+ subscription = subscriptionDataFromSubscription(entitlementUserApi.getSubscriptionFromId(subscription.getId()));
subscription.cancel(clock.getUTCNow(), false, context);
// MOVE AFTER CANCEL DATE AND EXPECT EVENT : NextEvent.CANCEL
@@ -366,19 +250,21 @@ public class TestIntegration {
term = BillingPeriod.MONTHLY;
planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
- busHandler.pushExpectedEvent(NextEvent.CREATE);
+ busHandler.pushExpectedEvent(NextEvent.RE_CREATE);
busHandler.pushExpectedEvent(NextEvent.INVOICE);
busHandler.pushExpectedEvent(NextEvent.PAYMENT);
subscription.recreate(new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), endDate, context);
assertTrue(busHandler.isCompleted(DELAY));
-
+ assertListenerStatus();
}
+
private void testBasePlanComplete(DateTime initialCreationDate, int billingDay,
boolean proRationExpected) throws Exception {
log.info("Beginning test with BCD of " + billingDay);
- Account account = accountUserApi.createAccount(getAccountData(billingDay), null, null, context);
+ AccountData accountData = getAccountData(billingDay);
+ Account account = accountUserApi.createAccount(accountData, null, null, context);
UUID accountId = account.getId();
assertNotNull(account);
@@ -395,8 +281,9 @@ public class TestIntegration {
//
busHandler.pushExpectedEvent(NextEvent.CREATE);
busHandler.pushExpectedEvent(NextEvent.INVOICE);
- SubscriptionData subscription = (SubscriptionData) entitlementUserApi.createSubscription(bundle.getId(),
- new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context);
+ SubscriptionData subscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+ new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context));
+
assertNotNull(subscription);
assertTrue(busHandler.isCompleted(DELAY));
@@ -493,7 +380,7 @@ public class TestIntegration {
newTerm = BillingPeriod.MONTHLY;
newPlanSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
newProductName = "Pistol";
- subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+ subscription = subscriptionDataFromSubscription(entitlementUserApi.getSubscriptionFromId(subscription.getId()));
subscription.changePlan(newProductName, newTerm, newPlanSetName, clock.getUTCNow(), context);
//
@@ -541,7 +428,7 @@ public class TestIntegration {
//
// FINALLY CANCEL SUBSCRIPTION EOT
//
- subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+ subscription = subscriptionDataFromSubscription(entitlementUserApi.getSubscriptionFromId(subscription.getId()));
subscription.cancel(clock.getUTCNow(), false, context);
// MOVE AFTER CANCEL DATE AND EXPECT EVENT : NextEvent.CANCEL
@@ -557,7 +444,7 @@ public class TestIntegration {
clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
assertTrue(busHandler.isCompleted(DELAY));
- subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+ subscription = subscriptionDataFromSubscription(entitlementUserApi.getSubscriptionFromId(subscription.getId()));
DateTime lastCtd = subscription.getChargedThroughDate();
assertNotNull(lastCtd);
log.info("Checking CTD: " + lastCtd.toString() + "; clock is " + clock.getUTCNow().toString());
@@ -565,49 +452,22 @@ public class TestIntegration {
// The invoice system is still working to verify there is nothing to do
Thread.sleep(DELAY);
+
+ assertListenerStatus();
+
log.info("TEST PASSED !");
}
- @Test(groups = "slow")
- public void testHappyPath() throws AccountApiException, EntitlementUserApiException {
- Account account = accountUserApi.createAccount(getAccountData(3), null, null, context);
- assertNotNull(account);
-
- SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever", context);
-
- String productName = "Shotgun";
- BillingPeriod term = BillingPeriod.MONTHLY;
- String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
-
- busHandler.pushExpectedEvent(NextEvent.CREATE);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- SubscriptionData subscription = (SubscriptionData) entitlementUserApi.createSubscription(bundle.getId(),
- new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context);
- assertNotNull(subscription);
-
- assertTrue(busHandler.isCompleted(DELAY));
-
- busHandler.pushExpectedEvent(NextEvent.CHANGE);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- BillingPeriod newTerm = BillingPeriod.MONTHLY;
- String newPlanSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
- String newProductName = "Assault-Rifle";
- subscription.changePlan(newProductName, newTerm, newPlanSetName, clock.getUTCNow(), context);
-
- assertTrue(busHandler.isCompleted(DELAY));
-
- busHandler.pushExpectedEvent(NextEvent.PHASE);
- busHandler.pushExpectedEvent(NextEvent.INVOICE);
- clock.setDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
- assertTrue(busHandler.isCompleted(DELAY));
-
- }
@Test(groups = "slow")
public void testForMultipleRecurringPhases() throws AccountApiException, EntitlementUserApiException, InterruptedException {
- clock.setDeltaFromReality(new DateTime().getMillis() - clock.getUTCNow().getMillis());
- Account account = accountUserApi.createAccount(getAccountData(15), null, null, context);
+ log.info("Starting testForMultipleRecurringPhases");
+
+ DateTime initialCreationDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
+ clock.setDeltaFromReality(initialCreationDate.getMillis() - clock.getUTCNow().getMillis());
+
+ Account account = accountUserApi.createAccount(getAccountData(2), null, null, context);
UUID accountId = account.getId();
String productName = "Blowdart";
@@ -616,114 +476,54 @@ public class TestIntegration {
busHandler.pushExpectedEvent(NextEvent.CREATE);
busHandler.pushExpectedEvent(NextEvent.INVOICE);
SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(accountId, "testKey", context);
- SubscriptionData subscription = (SubscriptionData) entitlementUserApi.createSubscription(bundle.getId(),
+ subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
new PlanPhaseSpecifier(productName, ProductCategory.BASE,
- BillingPeriod.MONTHLY, planSetName, PhaseType.TRIAL), null, context);
+ BillingPeriod.MONTHLY, planSetName, PhaseType.TRIAL), null, context));
+
assertTrue(busHandler.isCompleted(DELAY));
List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId);
assertNotNull(invoices);
assertTrue(invoices.size() == 1);
-
+
busHandler.pushExpectedEvent(NextEvent.PHASE);
busHandler.pushExpectedEvent(NextEvent.INVOICE);
busHandler.pushExpectedEvent(NextEvent.PAYMENT);
- clock.addDeltaFromReality(6 * AT_LEAST_ONE_MONTH_MS);
- assertTrue(busHandler.isCompleted(DELAY));
+ clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+ assertTrue(busHandler.isCompleted(DELAY));
invoices = invoiceUserApi.getInvoicesByAccount(accountId);
assertNotNull(invoices);
- assertTrue(invoices.size() == 2);
-
- busHandler.pushExpectedEvent(NextEvent.PHASE);
+ assertEquals(invoices.size(),2);
+
+ for (int i = 0; i < 5; i++) {
+ log.info("============== loop number " + i +"=======================");
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+ assertTrue(busHandler.isCompleted(DELAY));
+ }
+
busHandler.pushExpectedEvent(NextEvent.INVOICE);
busHandler.pushExpectedEvent(NextEvent.PAYMENT);
- clock.addDeltaFromReality(6 * AT_LEAST_ONE_MONTH_MS);
- assertTrue(busHandler.isCompleted(DELAY));
+ busHandler.pushExpectedEvent(NextEvent.PHASE);
+ clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+ assertTrue(busHandler.isCompleted(DELAY));
+
invoices = invoiceUserApi.getInvoicesByAccount(accountId);
assertNotNull(invoices);
- assertTrue(invoices.size() == 3);
- }
-
- protected AccountData getAccountData(final int billingDay) {
+ assertEquals(invoices.size(),8);
- final String someRandomKey = RandomStringUtils.randomAlphanumeric(10);
- return new AccountData() {
- @Override
- public String getName() {
- return "firstName lastName";
- }
- @Override
- public int getFirstNameLength() {
- return "firstName".length();
- }
- @Override
- public String getEmail() {
- return someRandomKey + "@laposte.fr";
- }
- @Override
- public String getPhone() {
- return "4152876341";
- }
- @Override
- public String getExternalKey() {
- return someRandomKey;
- }
- @Override
- public int getBillCycleDay() {
- return billingDay;
- }
- @Override
- public Currency getCurrency() {
- return Currency.USD;
- }
- @Override
- public String getPaymentProviderName() {
- return MockModule.PLUGIN_NAME;
- }
-
- @Override
- public DateTimeZone getTimeZone() {
- return null;
- }
-
- @Override
- public String getLocale() {
- return null;
- }
-
- @Override
- public String getAddress1() {
- return null;
- }
-
- @Override
- public String getAddress2() {
- return null;
- }
-
- @Override
- public String getCompanyName() {
- return null;
- }
-
- @Override
- public String getCity() {
- return null;
- }
-
- @Override
- public String getStateOrProvince() {
- return null;
- }
-
- @Override
- public String getPostalCode() {
- return null;
- }
+ for (int i = 0; i <= 5; i++) {
+ log.info("============== second loop number " + i +"=======================");
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+ assertTrue(busHandler.isCompleted(DELAY));
+ }
+
+ invoices = invoiceUserApi.getInvoicesByAccount(accountId);
+ assertNotNull(invoices);
+ assertEquals(invoices.size(),14);
- @Override
- public String getCountry() {
- return null;
- }
- };
+ assertListenerStatus();
}
}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
new file mode 100644
index 0000000..78e37d0
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.beatrix.integration;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.RandomStringUtils;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.IDBI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.AccountService;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.api.TestApiListener;
+import com.ning.billing.api.TestListenerStatus;
+import com.ning.billing.beatrix.lifecycle.Lifecycle;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.EntitlementService;
+import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceService;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.invoice.model.InvoicingConfiguration;
+import com.ning.billing.junction.plumbing.api.BlockingSubscription;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.ClockMock;
+
+public class TestIntegrationBase implements TestListenerStatus {
+
+ protected static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
+ protected static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
+
+ protected static final BigDecimal ONE = new BigDecimal("1.0000").setScale(NUMBER_OF_DECIMALS);
+ protected static final BigDecimal TWENTY_NINE = new BigDecimal("29.0000").setScale(NUMBER_OF_DECIMALS);
+ protected static final BigDecimal THIRTY = new BigDecimal("30.0000").setScale(NUMBER_OF_DECIMALS);
+ protected static final BigDecimal THIRTY_ONE = new BigDecimal("31.0000").setScale(NUMBER_OF_DECIMALS);
+
+ protected static final Logger log = LoggerFactory.getLogger(TestIntegration.class);
+ protected static long AT_LEAST_ONE_MONTH_MS = 31L * 24L * 3600L * 1000L;
+
+
+ protected static final long DELAY = 5000;
+
+ @Inject
+ protected IDBI dbi;
+
+ @Inject
+ protected ClockMock clock;
+
+ protected CallContext context;
+
+ @Inject
+ protected Lifecycle lifecycle;
+
+ @Inject
+ protected BusService busService;
+
+ @Inject
+ protected EntitlementService entitlementService;
+
+ @Inject
+ protected InvoiceService invoiceService;
+
+ @Inject
+ protected AccountService accountService;
+
+ @Inject
+ protected MysqlTestingHelper helper;
+ @Inject
+ protected EntitlementUserApi entitlementUserApi;
+
+ @Inject
+ protected EntitlementTimelineApi repairApi;
+
+ @Inject
+ protected InvoiceUserApi invoiceUserApi;
+
+ @Inject
+ protected AccountUserApi accountUserApi;
+
+ protected TestApiListener busHandler;
+
+
+ private boolean isListenerFailed;
+ private String listenerFailedMsg;
+
+ @Override
+ public void failed(String msg) {
+ isListenerFailed = true;
+ listenerFailedMsg = msg;
+ }
+
+ @Override
+ public void resetTestListenerStatus() {
+ isListenerFailed = false;
+ listenerFailedMsg = null;
+ }
+
+
+ protected void assertListenerStatus() {
+ if (isListenerFailed) {
+ log.error(listenerFailedMsg);
+ Assert.fail(listenerFailedMsg);
+ }
+ }
+
+ protected void setupMySQL() throws IOException
+ {
+ final String accountDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
+ final String entitlementDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+ final String invoiceDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+ final String paymentDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+ final String utilDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+ final String junctionDb = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/junction/ddl.sql"));
+
+ helper.startMysql();
+
+ helper.initDb(accountDdl);
+ helper.initDb(entitlementDdl);
+ helper.initDb(invoiceDdl);
+ helper.initDb(paymentDdl);
+ helper.initDb(utilDdl);
+ helper.initDb(junctionDb);
+ }
+
+
+ @BeforeClass(groups = "slow")
+ public void setup() throws Exception{
+
+ setupMySQL();
+
+ context = new DefaultCallContextFactory(clock).createCallContext("Integration Test", CallOrigin.TEST, UserType.TEST);
+ busHandler = new TestApiListener(this);
+
+ }
+
+ @AfterClass(groups = "slow")
+ public void tearDown() throws Exception {
+ helper.stopMysql();
+ }
+
+
+ @BeforeMethod(groups = "slow")
+ public void setupTest() throws Exception {
+
+ log.warn("\n");
+ log.warn("RESET TEST FRAMEWORK\n\n");
+
+ // Pre test cleanup
+ helper.cleanupAllTables();
+
+ clock.resetDeltaFromReality();
+ resetTestListenerStatus();
+
+ // Start services
+ lifecycle.fireStartupSequencePriorEventRegistration();
+ busService.getBus().register(busHandler);
+ lifecycle.fireStartupSequencePostEventRegistration();
+ }
+
+ @AfterMethod(groups = "slow")
+ public void cleanupTest() throws Exception {
+ lifecycle.fireShutdownSequencePriorEventUnRegistration();
+ busService.getBus().unregister(busHandler);
+ lifecycle.fireShutdownSequencePostEventUnRegistration();
+
+ log.warn("DONE WITH TEST\n");
+ }
+
+
+ protected void verifyTestResult(UUID accountId, UUID subscriptionId,
+ DateTime startDate, DateTime endDate,
+ BigDecimal amount, DateTime chargeThroughDate,
+ int totalInvoiceItemCount) throws EntitlementUserApiException {
+ SubscriptionData subscription = subscriptionDataFromSubscription(entitlementUserApi.getSubscriptionFromId(subscriptionId));
+
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId);
+ List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
+ for (Invoice invoice : invoices) {
+ invoiceItems.addAll(invoice.getInvoiceItems());
+ }
+ assertEquals(invoiceItems.size(), totalInvoiceItemCount);
+
+ boolean wasFound = false;
+
+ for (InvoiceItem item : invoiceItems) {
+ if (item.getStartDate().compareTo(startDate) == 0) {
+ if (item.getEndDate().compareTo(endDate) == 0) {
+ if (item.getAmount().compareTo(amount) == 0) {
+ wasFound = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!wasFound) {
+ fail();
+ }
+
+ DateTime ctd = subscription.getChargedThroughDate();
+ assertNotNull(ctd);
+ log.info("Checking CTD: " + ctd.toString() + "; clock is " + clock.getUTCNow().toString());
+ assertTrue(clock.getUTCNow().isBefore(ctd));
+ assertTrue(ctd.compareTo(chargeThroughDate) == 0);
+ }
+
+ protected SubscriptionData subscriptionDataFromSubscription(Subscription sub) {
+ return (SubscriptionData)((BlockingSubscription)sub).getDelegateSubscription();
+ }
+
+ protected AccountData getAccountData(final int billingDay) {
+
+ final String someRandomKey = RandomStringUtils.randomAlphanumeric(10);
+ return new AccountData() {
+ @Override
+ public String getName() {
+ return "firstName lastName";
+ }
+ @Override
+ public int getFirstNameLength() {
+ return "firstName".length();
+ }
+ @Override
+ public String getEmail() {
+ return someRandomKey + "@laposte.fr";
+ }
+ @Override
+ public String getPhone() {
+ return "4152876341";
+ }
+
+ @Override
+ public boolean isMigrated() {
+ return false;
+ }
+
+ @Override
+ public boolean isNotifiedForInvoices() {
+ return false;
+ }
+
+ @Override
+ public String getExternalKey() {
+ return someRandomKey;
+ }
+ @Override
+ public int getBillCycleDay() {
+ return billingDay;
+ }
+ @Override
+ public Currency getCurrency() {
+ return Currency.USD;
+ }
+ @Override
+ public String getPaymentProviderName() {
+ return BeatrixModule.PLUGIN_NAME;
+ }
+
+ @Override
+ public DateTimeZone getTimeZone() {
+ return null;
+ }
+
+ @Override
+ public String getLocale() {
+ return null;
+ }
+
+ @Override
+ public String getAddress1() {
+ return null;
+ }
+
+ @Override
+ public String getAddress2() {
+ return null;
+ }
+
+ @Override
+ public String getCompanyName() {
+ return null;
+ }
+
+ @Override
+ public String getCity() {
+ return null;
+ }
+
+ @Override
+ public String getStateOrProvince() {
+ return null;
+ }
+
+ @Override
+ public String getPostalCode() {
+ return null;
+ }
+
+ @Override
+ public String getCountry() {
+ return null;
+ }
+ };
+ }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestRepairIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestRepairIntegration.java
new file mode 100644
index 0000000..fe6f8a8
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestRepairIntegration.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.beatrix.integration;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.testng.Assert;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionEvents;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+
+@Test(groups = "slow")
+@Guice(modules = {BeatrixModule.class})
+public class TestRepairIntegration extends TestIntegrationBase {
+
+
+ @Test(groups={"slow"}, enabled=false)
+ public void testRepairChangeBPWithAddonIncludedIntrial() throws Exception {
+ log.info("Starting testRepairChangeBPWithAddonIncludedIntrial");
+ testRepairChangeBPWithAddonIncluded(true);
+ }
+
+ @Test(groups={"slow"}, enabled=false)
+ public void testRepairChangeBPWithAddonIncludedOutOfTrial() throws Exception {
+ log.info("Starting testRepairChangeBPWithAddonIncludedOutOfTrial");
+ testRepairChangeBPWithAddonIncluded(false);
+ }
+
+ private void testRepairChangeBPWithAddonIncluded(boolean inTrial) throws Exception {
+
+ DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ Account account = accountUserApi.createAccount(getAccountData(25), null, null, context);
+ assertNotNull(account);
+
+ SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever", context);
+
+ String productName = "Shotgun";
+ BillingPeriod term = BillingPeriod.MONTHLY;
+ String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ busHandler.pushExpectedEvent(NextEvent.CREATE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ SubscriptionData baseSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+ new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context));
+ assertNotNull(baseSubscription);
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ busHandler.pushExpectedEvent(NextEvent.CREATE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ SubscriptionData aoSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+ new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null, context));
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ busHandler.pushExpectedEvent(NextEvent.CREATE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ SubscriptionData aoSubscription2 = subscriptionDataFromSubscription( entitlementUserApi.createSubscription(bundle.getId(),
+ new PlanPhaseSpecifier("Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null), null, context));
+ assertTrue(busHandler.isCompleted(DELAY));
+
+
+ // MOVE CLOCK A LITTLE BIT MORE -- EITHER STAY IN TRIAL OR GET OUT
+ int duration = inTrial ? 3 : 35;
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(duration));
+ if (!inTrial) {
+ busHandler.pushExpectedEvent(NextEvent.PHASE);
+ busHandler.pushExpectedEvent(NextEvent.PHASE);
+ busHandler.pushExpectedEvent(NextEvent.PHASE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ }
+ clock.addDeltaFromReality(it.toDurationMillis());
+ if (!inTrial) {
+ assertTrue(busHandler.isCompleted(DELAY));
+ }
+ boolean ifRepair = false;
+ if (ifRepair) {
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+
+ // Quick check
+ SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+ assertEquals(bpRepair.getExistingEvents().size(), 2);
+
+ SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+ SubscriptionTimeline aoRepair2 = getSubscriptionRepair(aoSubscription2.getId(), bundleRepair);
+ assertEquals(aoRepair2.getExistingEvents().size(), 2);
+
+ DateTime bpChangeDate = clock.getUTCNow().minusDays(1);
+
+ List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+ des.add(createDeletedEvent(bpRepair.getExistingEvents().get(1).getEventId()));
+
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, bpChangeDate, spec);
+
+ bpRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+ bundleRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(bpRepair));
+
+ // TIME TO REPAIR
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ busHandler.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+ repairApi.repairBundle(bundleRepair, false, context);
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ SubscriptionData newAoSubscription = subscriptionDataFromSubscription( entitlementUserApi.getSubscriptionFromId(aoSubscription.getId()));
+ assertEquals(newAoSubscription.getState(), SubscriptionState.CANCELLED);
+ assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+ assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+ SubscriptionData newAoSubscription2 = subscriptionDataFromSubscription( entitlementUserApi.getSubscriptionFromId(aoSubscription2.getId()));
+ assertEquals(newAoSubscription2.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newAoSubscription2.getAllTransitions().size(), 2);
+ assertEquals(newAoSubscription2.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+
+ SubscriptionData newBaseSubscription = subscriptionDataFromSubscription( entitlementUserApi.getSubscriptionFromId(baseSubscription.getId()));
+ assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newBaseSubscription.getAllTransitions().size(), 3);
+ assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+ assertListenerStatus();
+ }
+ }
+
+ protected SubscriptionTimeline createSubscriptionReapir(final UUID id, final List<DeletedEvent> deletedEvents, final List<NewEvent> newEvents) {
+ return new SubscriptionTimeline() {
+ @Override
+ public UUID getId() {
+ return id;
+ }
+ @Override
+ public List<NewEvent> getNewEvents() {
+ return newEvents;
+ }
+ @Override
+ public List<ExistingEvent> getExistingEvents() {
+ return null;
+ }
+ @Override
+ public List<DeletedEvent> getDeletedEvents() {
+ return deletedEvents;
+ }
+ };
+ }
+
+
+ protected BundleTimeline createBundleRepair(final UUID bundleId, final String viewId, final List<SubscriptionTimeline> subscriptionRepair) {
+ return new BundleTimeline() {
+ @Override
+ public String getViewId() {
+ return viewId;
+ }
+ @Override
+ public List<SubscriptionTimeline> getSubscriptions() {
+ return subscriptionRepair;
+ }
+ @Override
+ public UUID getBundleId() {
+ return bundleId;
+ }
+ @Override
+ public String getExternalKey() {
+ return null;
+ }
+ };
+ }
+
+ protected ExistingEvent createExistingEventForAssertion(final SubscriptionTransitionType type,
+ final String productName, final PhaseType phaseType, final ProductCategory category, final String priceListName, final BillingPeriod billingPeriod,
+ final DateTime effectiveDateTime) {
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
+ ExistingEvent ev = new ExistingEvent() {
+ @Override
+ public SubscriptionTransitionType getSubscriptionTransitionType() {
+ return type;
+ }
+ @Override
+ public DateTime getRequestedDate() {
+ return null;
+ }
+ @Override
+ public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+ return spec;
+ }
+ @Override
+ public UUID getEventId() {
+ return null;
+ }
+ @Override
+ public DateTime getEffectiveDate() {
+ return effectiveDateTime;
+ }
+ };
+ return ev;
+ }
+
+ protected SubscriptionTimeline getSubscriptionRepair(final UUID id, final BundleTimeline bundleRepair) {
+ for (SubscriptionTimeline cur : bundleRepair.getSubscriptions()) {
+ if (cur.getId().equals(id)) {
+ return cur;
+ }
+ }
+ Assert.fail("Failed to find SubscriptionReapir " + id);
+ return null;
+ }
+ protected void validateExistingEventForAssertion(final ExistingEvent expected, final ExistingEvent input) {
+
+ log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName()));
+ assertEquals(input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName());
+ log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType()));
+ assertEquals(input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType());
+ log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory()));
+ assertEquals(input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory());
+ log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName()));
+ assertEquals(input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName());
+ log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod()));
+ assertEquals(input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod());
+ log.info(String.format("Got %s -> Expected %s", input.getEffectiveDate(), expected.getEffectiveDate()));
+ assertEquals(input.getEffectiveDate(), expected.getEffectiveDate());
+ }
+
+ protected DeletedEvent createDeletedEvent(final UUID eventId) {
+ return new DeletedEvent() {
+ @Override
+ public UUID getEventId() {
+ return eventId;
+ }
+ };
+ }
+
+ protected NewEvent createNewEvent(final SubscriptionTransitionType type, final DateTime requestedDate, final PlanPhaseSpecifier spec) {
+
+ return new NewEvent() {
+ @Override
+ public SubscriptionTransitionType getSubscriptionTransitionType() {
+ return type;
+ }
+ @Override
+ public DateTime getRequestedDate() {
+ return requestedDate;
+ }
+ @Override
+ public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+ return spec;
+ }
+ };
+ }
+
+ protected void sortEventsOnBundle(final BundleTimeline bundle) {
+ if (bundle.getSubscriptions() == null) {
+ return;
+ }
+ for (SubscriptionTimeline cur : bundle.getSubscriptions()) {
+ if (cur.getExistingEvents() != null) {
+ sortExistingEvent(cur.getExistingEvents());
+ }
+ if (cur.getNewEvents() != null) {
+ sortNewEvent(cur.getNewEvents());
+ }
+ }
+ }
+
+ protected void sortExistingEvent(final List<ExistingEvent> events) {
+ Collections.sort(events, new Comparator<ExistingEvent>() {
+ @Override
+ public int compare(ExistingEvent arg0, ExistingEvent arg1) {
+ return arg0.getEffectiveDate().compareTo(arg1.getEffectiveDate());
+ }
+ });
+ }
+ protected void sortNewEvent(final List<NewEvent> events) {
+ Collections.sort(events, new Comparator<NewEvent>() {
+ @Override
+ public int compare(NewEvent arg0, NewEvent arg1) {
+ return arg0.getRequestedDate().compareTo(arg1.getRequestedDate());
+ }
+ });
+ }
+}
beatrix/src/test/resources/catalogSample.xml 585(+395 -190)
diff --git a/beatrix/src/test/resources/catalogSample.xml b/beatrix/src/test/resources/catalogSample.xml
index 5b7aeaf..8c13707 100644
--- a/beatrix/src/test/resources/catalogSample.xml
+++ b/beatrix/src/test/resources/catalogSample.xml
@@ -1,43 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!--
- ~ Copyright 2010-2011 Ning, Inc.
- ~
- ~ Ning licenses this file to you under the Apache License, version 2.0
- ~ (the "License"); you may not use this file except in compliance with the
- ~ License. You may obtain a copy of the License at:
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- ~ License for the specific language governing permissions and limitations
- ~ under the License.
- -->
+<!-- ~ Copyright 2010-2011 Ning, Inc. ~ ~ Ning licenses this file to you
+ under the Apache License, version 2.0 ~ (the "License"); you may not use
+ this file except in compliance with the ~ License. You may obtain a copy
+ of the License at: ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless
+ required by applicable law or agreed to in writing, software ~ distributed
+ under the License is distributed on an "AS IS" BASIS, WITHOUT ~ WARRANTIES
+ OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for
+ the specific language governing permissions and limitations ~ under the License. -->
-<!--
-Use cases covered so far:
- Tiered Product (Pistol/Shotgun/Assault-Rifle)
- Multiple changeEvent plan policies
- Multiple PlanAlignment (see below, trial add-on alignments and rescue discount package)
- Product transition rules
- Add on (Scopes, Holster)
- Multi-pack addon (Extra-Ammo)
- Addon Trial aligned to base plan (holster-monthly-regular)
- Addon Trial aligned to creation (holster-monthly-special)
- Rescue discount package (assault-rifle-annual-rescue)
- Plan phase with a recurring and a one off (refurbish-maintenance)
- Plan with more than 2 phase (gunclub discount plans)
-
-Use Cases to do:
- Tiered Add On
- Riskfree period
-
-
-
- -->
+<!-- Use cases covered so far: Tiered Product (Pistol/Shotgun/Assault-Rifle)
+ Multiple changeEvent plan policies Multiple PlanAlignment (see below, trial
+ add-on alignments and rescue discount package) Product transition rules Add
+ on (Scopes, Hoster) Multi-pack addon (Extra-Ammo) Addon Trial aligned to
+ base plan (holster-monthly-regular) Addon Trial aligned to creation (holster-monthly-special)
+ Rescue discount package (assault-rifle-annual-rescue) Plan phase with a reccurring
+ and a one off (refurbish-maintenance) Phan with more than 2 phase (gunclub
+ discount plans) Use Cases to do: Tiered Add On Riskfree period -->
<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:noNamespaceSchemaLocation="CatalogSchema.xsd">
+ xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
<effectiveDate>2011-01-01T00:00:00+00:00</effectiveDate>
<catalogName>Firearms</catalogName>
@@ -47,24 +27,24 @@ Use Cases to do:
<currency>EUR</currency>
<currency>GBP</currency>
</currencies>
-
+
<products>
+ <product name="Blowdart">
+ <category>BASE</category>
+ </product>
<product name="Pistol">
<category>BASE</category>
+ </product>
+ <product name="Shotgun">
+ <category>BASE</category>
<available>
<addonProduct>Telescopic-Scope</addonProduct>
<addonProduct>Laser-Scope</addonProduct>
</available>
</product>
- <product name="Blowdart">
- <category>BASE</category>
- </product>
- <product name="Shotgun">
- <category>BASE</category>
- </product>
<product name="Assault-Rifle">
<category>BASE</category>
- <included>
+ <included>
<addonProduct>Telescopic-Scope</addonProduct>
</included>
<available>
@@ -87,60 +67,42 @@ Use Cases to do:
<category>ADD_ON</category>
</product>
</products>
-
+
<rules>
<changePolicy>
- <changePolicyCase>
+ <changePolicyCase>
<phaseType>TRIAL</phaseType>
<policy>IMMEDIATE</policy>
</changePolicyCase>
- <changePolicyCase>
- <toProduct>Pistol</toProduct>
- <policy>END_OF_TERM</policy>
+ <changePolicyCase>
+ <toProduct>Assault-Rifle</toProduct>
+ <policy>IMMEDIATE</policy>
</changePolicyCase>
- <changePolicyCase>
- <toPriceList>rescue</toPriceList>
- <policy>END_OF_TERM</policy>
- </changePolicyCase>
- <changePolicyCase>
+ <changePolicyCase>
<fromProduct>Pistol</fromProduct>
<toProduct>Shotgun</toProduct>
<policy>IMMEDIATE</policy>
</changePolicyCase>
- <changePolicyCase>
- <fromProduct>Assault-Rifle</fromProduct>
- <toProduct>Shotgun</toProduct>
- <policy>END_OF_TERM</policy>
- </changePolicyCase>
- <changePolicyCase>
- <fromBillingPeriod>MONTHLY</fromBillingPeriod>
- <toProduct>Assault-Rifle</toProduct>
- <toBillingPeriod>MONTHLY</toBillingPeriod>
+ <changePolicyCase>
+ <toPriceList>rescue</toPriceList>
<policy>END_OF_TERM</policy>
</changePolicyCase>
- <changePolicyCase>
- <toProduct>Assault-Rifle</toProduct>
- <policy>IMMEDIATE</policy>
- </changePolicyCase>
- <changePolicyCase>
+ <changePolicyCase>
<fromBillingPeriod>MONTHLY</fromBillingPeriod>
<toBillingPeriod>ANNUAL</toBillingPeriod>
<policy>IMMEDIATE</policy>
</changePolicyCase>
- <changePolicyCase>
+ <changePolicyCase>
<fromBillingPeriod>ANNUAL</fromBillingPeriod>
<toBillingPeriod>MONTHLY</toBillingPeriod>
<policy>END_OF_TERM</policy>
</changePolicyCase>
- <changePolicyCase>
+ <changePolicyCase>
<policy>END_OF_TERM</policy>
</changePolicyCase>
</changePolicy>
<changeAlignment>
<changeAlignmentCase>
- <alignment>START_OF_SUBSCRIPTION</alignment>
- </changeAlignmentCase>
- <changeAlignmentCase>
<toPriceList>rescue</toPriceList>
<alignment>CHANGE_OF_PLAN</alignment>
</changeAlignmentCase>
@@ -149,18 +111,25 @@ Use Cases to do:
<toPriceList>rescue</toPriceList>
<alignment>CHANGE_OF_PRICELIST</alignment>
</changeAlignmentCase>
+ <changeAlignmentCase>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </changeAlignmentCase>
</changeAlignment>
<cancelPolicy>
<cancelPolicyCase>
- <policy>END_OF_TERM</policy>
- </cancelPolicyCase>
- <cancelPolicyCase>
<phaseType>TRIAL</phaseType>
<policy>IMMEDIATE</policy>
</cancelPolicyCase>
+ <cancelPolicyCase>
+ <policy>END_OF_TERM</policy>
+ </cancelPolicyCase>
</cancelPolicy>
<createAlignment>
<createAlignmentCase>
+ <product>Laser-Scope</product>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </createAlignmentCase>
+ <createAlignmentCase>
<alignment>START_OF_BUNDLE</alignment>
</createAlignmentCase>
</createAlignment>
@@ -186,21 +155,7 @@ Use Cases to do:
</rules>
<plans>
- <plan name="pistol-monthly-no-trial">
- <product>Pistol</product>
- <finalPhase type="EVERGREEN">
- <duration>
- <unit>UNLIMITED</unit>
- </duration>
- <billingPeriod>MONTHLY</billingPeriod>
- <recurringPrice>
- <price><currency>GBP</currency><value>29.95</value></price>
- <price><currency>EUR</currency><value>29.95</value></price>
- <price><currency>USD</currency><value>29.95</value></price>
- </recurringPrice>
- </finalPhase>
- </plan>
- <plan name="blowdart-monthly">
+ <plan name="blowdart-monthly">
<product>Blowdart</product>
<initialPhases>
<phase type="TRIAL">
@@ -219,9 +174,18 @@ Use Cases to do:
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>9.95</value></price>
- <price><currency>EUR</currency><value>9.95</value></price>
- <price><currency>GBP</currency><value>9.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>9.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>9.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>9.95</value>
+ </price>
</recurringPrice>
</phase>
</initialPhases>
@@ -231,25 +195,34 @@ Use Cases to do:
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>29.95</value></price>
- <price><currency>EUR</currency><value>29.95</value></price>
- <price><currency>GBP</currency><value>29.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>29.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
+
<plan name="pistol-monthly">
<product>Pistol</product>
<initialPhases>
- <phase type="TRIAL">
- <duration>
- <unit>DAYS</unit>
- <number>30</number>
- </duration>
- <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
- <fixedPrice>
- </fixedPrice>
- <!-- no price implies $0 -->
- </phase>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice> <!-- empty price implies $0 -->
+ </fixedPrice>
+ </phase>
</initialPhases>
<finalPhase type="EVERGREEN">
<duration>
@@ -257,9 +230,18 @@ Use Cases to do:
</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>29.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>USD</currency>
+ <value>29.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -271,10 +253,9 @@ Use Cases to do:
<unit>DAYS</unit>
<number>30</number>
</duration>
- <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
- <fixedPrice>
- </fixedPrice>
- <!-- no price implies $0 -->
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice></fixedPrice>
+ <!-- no price implies $0 -->
</phase>
</initialPhases>
<finalPhase type="EVERGREEN">
@@ -284,25 +265,33 @@ Use Cases to do:
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>249.95</value></price>
- <price><currency>EUR</currency><value>149.95</value></price>
- <price><currency>GBP</currency><value>169.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>249.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>149.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>169.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
<plan name="assault-rifle-monthly">
<product>Assault-Rifle</product>
<initialPhases>
- <phase type="TRIAL">
- <duration>
- <unit>DAYS</unit>
- <number>30</number>
- </duration>
- <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
- <fixedPrice>
- </fixedPrice>
- <!-- no price implies $0 -->
- </phase>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
</initialPhases>
<finalPhase type="EVERGREEN">
<duration>
@@ -310,9 +299,18 @@ Use Cases to do:
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>599.95</value></price>
- <price><currency>EUR</currency><value>349.95</value></price>
- <price><currency>GBP</currency><value>399.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>599.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>349.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>399.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -335,9 +333,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>199.95</value></price>
- <price><currency>EUR</currency><value>199.95</value></price>
- <price><currency>GBP</currency><value>199.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>199.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -360,9 +367,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>2399.95</value></price>
- <price><currency>EUR</currency><value>1499.95</value></price>
- <price><currency>GBP</currency><value>1699.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>2399.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>1499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>1699.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -385,9 +401,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>5999.95</value></price>
- <price><currency>EUR</currency><value>3499.95</value></price>
- <price><currency>GBP</currency><value>3999.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>5999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>3499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>3999.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -410,9 +435,18 @@ Use Cases to do:
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>9.95</value></price>
- <price><currency>EUR</currency><value>9.95</value></price>
- <price><currency>GBP</currency><value>9.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>9.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>9.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>9.95</value>
+ </price>
</recurringPrice>
</phase>
</initialPhases>
@@ -422,9 +456,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>199.95</value></price>
- <price><currency>EUR</currency><value>199.95</value></price>
- <price><currency>GBP</currency><value>199.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>199.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -447,9 +490,18 @@ Use Cases to do:
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>19.95</value></price>
- <price><currency>EUR</currency><value>49.95</value></price>
- <price><currency>GBP</currency><value>69.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>19.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>49.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>69.95</value>
+ </price>
</recurringPrice>
</phase>
</initialPhases>
@@ -459,9 +511,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>2399.95</value></price>
- <price><currency>EUR</currency><value>1499.95</value></price>
- <price><currency>GBP</currency><value>1699.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>2399.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>1499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>1699.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -484,10 +545,19 @@ Use Cases to do:
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>99.95</value></price>
- <price><currency>EUR</currency><value>99.95</value></price>
- <price><currency>GBP</currency><value>99.95</value></price>
- </recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>99.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>99.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>99.95</value>
+ </price>
+ </recurringPrice>
</phase>
</initialPhases>
<finalPhase type="EVERGREEN">
@@ -496,37 +566,110 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>5999.95</value></price>
- <price><currency>EUR</currency><value>3499.95</value></price>
- <price><currency>GBP</currency><value>3999.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>5999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>3499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>3999.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
<plan name="laser-scope-monthly">
- <product>Laser-Scope</product>
+ <product>Laser-Scope</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>1</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>999.95</value>
+ </price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
<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>
+ <price>
+ <currency>USD</currency>
+ <value>1999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>1499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>1999.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
<plan name="telescopic-scope-monthly">
<product>Telescopic-Scope</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>1</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>399.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>299.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>399.95</value>
+ </price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
<finalPhase type="EVERGREEN">
<duration>
<unit>UNLIMITED</unit>
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>999.95</value></price>
- <price><currency>EUR</currency><value>499.95</value></price>
- <price><currency>GBP</currency><value>999.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>999.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -538,9 +681,18 @@ Use Cases to do:
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>999.95</value></price>
- <price><currency>EUR</currency><value>499.95</value></price>
- <price><currency>GBP</currency><value>999.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>999.95</value>
+ </price>
</recurringPrice>
</finalPhase>
<plansAllowedInBundle>-1</plansAllowedInBundle> <!-- arbitrary number of these (multipack) -->
@@ -564,9 +716,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>199.95</value></price>
- <price><currency>EUR</currency><value>199.95</value></price>
- <price><currency>GBP</currency><value>199.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>199.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -589,9 +750,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>199.95</value></price>
- <price><currency>EUR</currency><value>199.95</value></price>
- <price><currency>GBP</currency><value>199.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>199.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -605,9 +775,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>5999.95</value></price>
- <price><currency>EUR</currency><value>3499.95</value></price>
- <price><currency>GBP</currency><value>3999.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>5999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>3499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>3999.95</value>
+ </price>
</recurringPrice>
</phase>
</initialPhases>
@@ -617,9 +796,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>5999.95</value></price>
- <price><currency>EUR</currency><value>3499.95</value></price>
- <price><currency>GBP</currency><value>3999.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>5999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>3499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>3999.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -632,23 +820,40 @@ Use Cases to do:
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>199.95</value></price>
- <price><currency>EUR</currency><value>199.95</value></price>
- <price><currency>GBP</currency><value>199.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>199.95</value>
+ </price>
</recurringPrice>
<fixedPrice>
- <price><currency>USD</currency><value>599.95</value></price>
- <price><currency>EUR</currency><value>599.95</value></price>
- <price><currency>GBP</currency><value>599.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>599.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>599.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>599.95</value>
+ </price>
</fixedPrice>
</finalPhase>
</plan>
</plans>
-
<priceLists>
- <defaultPriceList name="DEFAULT">
+ <defaultPriceList name="DEFAULT">
<plans>
- <plan>blowdart-monthly</plan>
+ <plan>blowdart-monthly</plan>
<plan>pistol-monthly</plan>
<plan>shotgun-monthly</plan>
<plan>assault-rifle-monthly</plan>
diff --git a/beatrix/src/test/resources/resource.properties b/beatrix/src/test/resources/resource.properties
index d63334b..a2c4ec1 100644
--- a/beatrix/src/test/resources/resource.properties
+++ b/beatrix/src/test/resources/resource.properties
@@ -1,7 +1,11 @@
killbill.catalog.uri=file:src/test/resources/catalogSample.xml
killbill.entitlement.dao.claim.time=60000
killbill.entitlement.dao.ready.max=1
-killbill.entitlement.engine.notifications.sleep=500
+killbill.payment.engine.notifications.sleep=100
+killbill.invoice.engine.notifications.sleep=100
+killbill.entitlement.engine.notifications.sleep=100
+killbill.billing.util.persistent.bus.sleep=100
+killbill.billing.util.persistent.bus.nbThreads=1
user.timezone=UTC
bin/clean-and-install 22(+22 -0)
diff --git a/bin/clean-and-install b/bin/clean-and-install
new file mode 100755
index 0000000..c6c3ac4
--- /dev/null
+++ b/bin/clean-and-install
@@ -0,0 +1,22 @@
+#! /usr/bin/env bash
+
+###################################################################################
+# #
+# 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. #
+# #
+###################################################################################
+
+bin/db-helper -a clean -d killbill;
+mvn -Dcom.ning.billing.dbi.test.useLocalDb=true clean install
bin/cleanAndInstall 23(+23 -0)
diff --git a/bin/cleanAndInstall b/bin/cleanAndInstall
new file mode 100755
index 0000000..fca07ad
--- /dev/null
+++ b/bin/cleanAndInstall
@@ -0,0 +1,23 @@
+#! /usr/bin/env bash
+
+###################################################################################
+# #
+# 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. #
+# #
+###################################################################################
+
+bin/db-helper -a clean -d killbill;
+bin/db-helper -a clean -d test_killbill;
+mvn -Dcom.ning.billing.dbi.test.useLocalDb=true clean install
bin/db-helper 156(+156 -0)
diff --git a/bin/db-helper b/bin/db-helper
new file mode 100755
index 0000000..cc8b746
--- /dev/null
+++ b/bin/db-helper
@@ -0,0 +1,156 @@
+#! /usr/bin/env bash
+
+
+###################################################################################
+# #
+# 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. #
+# #
+###################################################################################
+
+#set -x
+
+HERE=`cd \`dirname $0\`; pwd`
+TOP=$HERE/..
+
+POM="$TOP/pom.xml"
+
+ACTION=
+DATABASE="killbill"
+USER="root"
+PWD="root"
+TEST_ALSO=
+
+DDL_FILE=
+CLEAN_FILE=
+
+function usage() {
+ echo -n "./db_helper "
+ echo -n " -a <create|clean|dump>"
+ echo -n " -d database_name (default = killbill)"
+ echo -n " -u user_name (default = root)"
+ echo -n " -p password (default = root)"
+ echo -n " -t (also include test ddl)"
+ echo -n "-h this message"
+ echo
+ exit 1
+}
+
+function get_modules() {
+ local modules=`grep module $POM | grep -v modules | cut -d '>' -f 2 | cut -d '<' -f 1`
+ echo $modules
+}
+
+function find_test_ddl() {
+ local modules=`get_modules`
+ local ddl_test=
+
+ local cur_ddl=
+ for m in $modules; do
+ cur_ddl=`find $m/src/test/resources/ -name ddl_test.sql 2>/dev/null`
+ ddl_test="$ddl_test $cur_ddl"
+ done
+ echo "$ddl_test"
+
+}
+function find_src_ddl() {
+
+ local modules=`get_modules`
+ local ddl_src=
+
+ local cur_ddl=
+ for m in $modules; do
+ cur_ddl=`find $m/src/main/resources/ -name ddl.sql 2>/dev/null`
+ ddl_src="$ddl_src $cur_ddl"
+ done
+ echo "$ddl_src"
+}
+
+
+function create_clean_file() {
+ local ddl_file=$1
+ local tables=`cat $ddl_file | grep -i "create table" | awk ' { print $3 } '`
+
+ local tmp="/tmp/clean-$DATABASE.$$"
+ echo "use $DATABASE;" >> $tmp
+ echo "" >> $tmp
+ for t in $tables; do
+ echo "truncate $t;" >> $tmp
+ done
+ echo $tmp
+}
+
+function create_ddl_file() {
+ local ddls=`find_src_ddl`
+ local test_ddls=
+ if [ ! -z $TEST_ALSO ]; then
+ test_ddls=`find_test_ddl`
+ ddls="$ddls $test_ddls"
+ fi
+
+ local tmp="/tmp/ddl-$DATABASE.$$"
+ touch $tmp
+ echo "use $DATABASE;" >> $tmp
+ echo "" >> $tmp
+ for d in $ddls; do
+ cat $d >> $tmp
+ echo "" >> $tmp
+ done
+ echo $tmp
+}
+
+function cleanup() {
+ rm -f "/tmp/*.$$"
+}
+
+
+while getopts ":a:d:u:pt" options; do
+ case $options in
+ a ) ACTION=$OPTARG;;
+ d ) DATABASE=$OPTARG;;
+ u ) USER=$OPTARG;;
+ p ) PWD=$OPTARG;;
+ t ) TEST_ALSO=1;;
+ h ) usage;;
+ * ) usage;;
+ esac
+done
+
+
+
+if [ -z $ACTION ]; then
+ echo "Need to specify an action <CREATE|CLEAN>"
+ usage
+fi
+
+
+if [ $ACTION == "dump" ]; then
+ DDL_FILE=`create_ddl_file`
+ cat $DDL_FILE
+fi
+
+if [ $ACTION == "create" ]; then
+ DDL_FILE=`create_ddl_file`
+ echo "Applying new schema $tmp to database $DATABASE"
+ mysql -u $USER --password=$PWD < $DDL_FILE
+fi
+
+if [ $ACTION == "clean" ]; then
+ DDL_FILE=`create_ddl_file`
+ CLEAN_FILE=`create_clean_file $DDL_FILE`
+ echo "Cleaning db tables on database $DATABASE"
+ mysql -u $USER --password=$PWD < $DDL_FILE
+fi
+
+cleanup
bin/start-server 99(+99 -0)
diff --git a/bin/start-server b/bin/start-server
new file mode 100755
index 0000000..ce254da
--- /dev/null
+++ b/bin/start-server
@@ -0,0 +1,99 @@
+#! /usr/bin/env bash
+
+###################################################################################
+# #
+# 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. #
+# #
+###################################################################################
+
+
+HERE=`cd \`dirname $0\`; pwd`
+TOP=$HERE/..
+SERVER=$TOP/server
+
+PROPERTIES="$SERVER/src/main/resources/killbill-server.properties"
+
+DEBUG_OPTS_ECLIPSE=" -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=12345 "
+DEBUG_OPTS_ECLIPSE_WAIT=" -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=12345 "
+
+OPTS_ECLIPSE=" -Xmx2048m -XX:+UseConcMarkSweepGC -XX:MaxPermSize=128m "
+
+LOG="$SERVER/src/main/resources/log4j.xml"
+
+# From Argument Options
+PORT=8080
+START=
+DEBUG=
+WAIT_DEBUGGER=
+
+
+function usage() {
+ echo -n "./start-server "
+ echo -n " -s (start server)"
+ echo -n " -d (debugger turned on)"
+ echo -n " -w (along with -d, wait for debugger before starting)"
+ echo -n " -p <port_number> default 8080"
+ echo -n "-h this message"
+ exit 1
+}
+
+function build_properties() {
+ local opts=
+ local prop=
+ for prop in `cat $PROPERTIES | grep =`; do
+ local k=`echo $prop | awk ' BEGIN {FS="="} { print $1 }'`
+ local v=`echo $prop | awk 'BEGIN {FS="="} { print $2 }'`
+ opts="$opts -D$k=$v"
+ done
+ echo $opts
+}
+
+function start() {
+ local opts=`build_properties`
+ local start_cmd="mvn $opts -Dlog4j.configuration=file://$LOG -Dning.jmx.http.port=$PORT -Dxn.host.external.port=$PORT -DjettyPort=$PORT -Dxn.server.port=$PORT jetty:run"
+
+ local debug_opts_eclipse=
+ if [ ! -z $DEBUG ]; then
+ if [ ! -z $WAIT_DEBUGGER ]; then
+ debug_opts_eclipse=$DEBUG_OPTS_ECLIPSE_WAIT
+ else
+ debug_opts_eclipse=$DEBUG_OPTS_ECLIPSE
+ fi
+ fi
+ export MAVEN_OPTS=" -Duser.timezone=UTC $OPTS_ECLIPSE $debug_opts_eclipse"
+
+ echo "Starting IRS MAVEN_OPTS = $MAVEN_OPTS"
+ echo "$start_cmd"
+ cd $SERVER
+ $start_cmd
+}
+
+
+while getopts ":pswdh" options; do
+ case $options in
+ s ) START=1;;
+ d ) DEBUG=1;;
+ w ) WAIT_DEBUGGER=1;;
+ p ) PORT=$OPTARG;;
+ h ) usage;;
+ * ) usage;;
+ esac
+done
+
+if [ ! -z $START ]; then
+ start
+else
+ usage
+fi
catalog/pom.xml 6(+6 -0)
diff --git a/catalog/pom.xml b/catalog/pom.xml
index ecabef8..0e181ae 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -33,6 +33,12 @@
<artifactId>killbill-util</artifactId>
</dependency>
<dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-util</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
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 b824617..2ed1cfa 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
@@ -43,7 +43,6 @@ public class DefaultCatalogService implements KillbillService, Provider<Catalog>
@Inject
public DefaultCatalogService(CatalogConfig config, VersionedCatalogLoader loader) {
this.config = config;
- System.out.println(config.getCatalogURI());
this.isInitialized = false;
this.loader = loader;
}
@@ -52,7 +51,6 @@ public class DefaultCatalogService implements KillbillService, Provider<Catalog>
public synchronized void loadCatalog() throws ServiceException {
if (!isInitialized) {
try {
- System.out.println("Really really::" + config.getCatalogURI());
String url = config.getCatalogURI();
catalog = loader.load(url);
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
index 0e821ef..71bfe50 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
@@ -21,6 +21,7 @@ import com.ning.billing.catalog.api.TimeUnit;
import com.ning.billing.util.config.ValidatingConfig;
import com.ning.billing.util.config.ValidationErrors;
import org.joda.time.DateTime;
+import org.joda.time.Period;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@@ -52,7 +53,7 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
@Override
public DateTime addToDateTime(DateTime dateTime) {
- if (number == null) {return dateTime;}
+ if ((number == null) && (unit != TimeUnit.UNLIMITED)) {return dateTime;}
switch (unit) {
case DAYS:
@@ -84,5 +85,21 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
return this;
}
-
+ @Override
+ public Period toJodaPeriod() {
+ if ((number == null) && (unit != TimeUnit.UNLIMITED)) {return new Period();}
+
+ switch (unit) {
+ case DAYS:
+ return new Period().withDays(number);
+ case MONTHS:
+ return new Period().withMonths(number);
+ case YEARS:
+ return new Period().withYears(number);
+ case UNLIMITED:
+ return new Period().withYears(100);
+ default:
+ return new Period();
+ }
+ }
}
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 aba447d..c597dc0 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
@@ -107,10 +107,20 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
return count;
}
- public DefaultPriceList setRetired(boolean retired) {
+ protected DefaultPriceList setRetired(boolean retired) {
this.retired = retired;
return this;
}
+ public DefaultPriceList setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public DefaultPriceList setPlans(DefaultPlan[] plans) {
+ this.plans = plans;
+ 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 f0636dd..83d810b 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceListSet.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceListSet.java
@@ -48,7 +48,7 @@ public class DefaultPriceListSet extends ValidatingConfig<StandaloneCatalog> {
this.childPriceLists = childPriceLists;
}
- public DefaultPlan getPlanListFrom(String priceListName, Product product,
+ public DefaultPlan getPlanFrom(String priceListName, Product product,
BillingPeriod period) throws CatalogApiException {
DefaultPlan result = null;
DefaultPriceList pl = findPriceListFrom(priceListName);
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 ff3868c..6687c75 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
@@ -55,7 +55,6 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
//Not included in XML
private String catalogName;
-
@Override
public String getCatalogName() {
return catalogName;
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 029cdcd..a63645c 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/StandaloneCatalog.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/StandaloneCatalog.java
@@ -17,7 +17,6 @@
package com.ning.billing.catalog;
import java.net.URI;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
@@ -40,6 +39,7 @@ 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.PriceList;
import com.ning.billing.catalog.api.Product;
import com.ning.billing.catalog.api.StaticCatalog;
import com.ning.billing.catalog.rules.PlanRules;
@@ -70,11 +70,11 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
private PlanRules planRules;
@XmlElementWrapper(name="plans", required=true)
- @XmlElement(name="plan", required=true)
+ @XmlElement(name="plan", required=true)
private DefaultPlan[] plans;
- @XmlElement(name="priceLists", required=true)
- private DefaultPriceListSet priceLists;
+ @XmlElement(name="priceLists", required=true)
+ private DefaultPriceListSet priceLists;
public StandaloneCatalog() {}
@@ -142,7 +142,7 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
throw new CatalogApiException(ErrorCode.CAT_PRICE_LIST_NOT_FOUND,priceListName);
}
Product product = findCurrentProduct(productName);
- DefaultPlan result = priceLists.getPlanListFrom(priceListName, product, period);
+ DefaultPlan result = priceLists.getPlanFrom(priceListName, product, period);
if ( result == null) {
String periodString = (period == null) ? "NULL" : period.toString();
throw new CatalogApiException(ErrorCode.CAT_PLAN_NOT_FOUND, productName, periodString, priceListName);
@@ -187,6 +187,16 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
return plan.findPhase(name);
}
+ @Override
+ public PriceList findCurrentPricelist(String name)
+ throws CatalogApiException {
+ if (name == null || priceLists == null) {
+ throw new CatalogApiException(ErrorCode.CAT_PRICE_LIST_NOT_FOUND, name);
+ }
+
+ return priceLists.findPriceListFrom(name);
+ }
+
//////////////////////////////////////////////////////////////////////////////
@@ -288,10 +298,10 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
return this;
}
- protected StandaloneCatalog setPriceLists(DefaultPriceListSet priceLists) {
- this.priceLists = priceLists;
- return this;
- }
+ protected StandaloneCatalog setPriceLists(DefaultPriceListSet priceLists) {
+ this.priceLists = priceLists;
+ return this;
+ }
@Override
public boolean canCreatePlan(PlanSpecifier specifier) throws CatalogApiException {
@@ -303,4 +313,5 @@ public class StandaloneCatalog extends ValidatingConfig<StandaloneCatalog> imple
(!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 ef9b125..215b142 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java
@@ -46,6 +46,7 @@ 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.PriceList;
import com.ning.billing.catalog.api.Product;
import com.ning.billing.catalog.api.StaticCatalog;
import com.ning.billing.util.clock.Clock;
@@ -160,6 +161,8 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
throw new CatalogApiException(ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE, requestedDate.toDate().toString());
}
+
+
//
// Public methods not exposed in interface
//
@@ -270,6 +273,17 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
return plan.findPhase(phaseName);
}
+
+ //
+ // Find a price list
+ //
+ @Override
+ public PriceList findPriceList(String name, DateTime requestedDate)
+ throws CatalogApiException {
+ return versionForDate(requestedDate).findCurrentPriceList(name);
+ }
+
+
//
// Rules
//
@@ -381,6 +395,13 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
public PlanPhase findCurrentPhase(String name) throws CatalogApiException {
return versionForDate(clock.getUTCNow()).findCurrentPhase(name);
}
+
+
+ @Override
+ public PriceList findCurrentPricelist(String name)
+ throws CatalogApiException {
+ return versionForDate(clock.getUTCNow()).findCurrentPriceList(name);
+ }
@Override
public ActionPolicy planChangePolicy(PlanPhaseSpecifier from,
@@ -424,8 +445,4 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
return versionForDate(clock.getUTCNow()).canCreatePlan(specifier);
}
-
-
-
-
}
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 244ca49..40dc146 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
@@ -18,14 +18,35 @@ package com.ning.billing.catalog;
import java.util.Date;
+import org.joda.time.DateTime;
+
+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.Catalog;
+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.PriceList;
+import com.ning.billing.catalog.api.Product;
import com.ning.billing.catalog.rules.CaseCancelPolicy;
import com.ning.billing.catalog.rules.CaseChangePlanAlignment;
import com.ning.billing.catalog.rules.CaseChangePlanPolicy;
import com.ning.billing.catalog.rules.CaseCreateAlignment;
import com.ning.billing.catalog.rules.PlanRules;
-public class MockCatalog extends StandaloneCatalog {
+public class MockCatalog extends StandaloneCatalog implements Catalog {
private static final String[] PRODUCT_NAMES = new String[]{ "TestProduct1", "TestProduct2", "TestProduct3"};
+ private boolean canCreatePlan;
+ private PlanChangeResult planChange;
+ private BillingAlignment billingAlignment;
+ private PlanAlignmentCreate planCreateAlignment;
public MockCatalog() {
setEffectiveDate(new Date());
@@ -34,8 +55,8 @@ public class MockCatalog extends StandaloneCatalog {
populateRules();
populatePriceLists();
}
-
- public void populateRules(){
+
+ public void populateRules(){
setPlanRules(new PlanRules());
}
@@ -47,6 +68,8 @@ public class MockCatalog extends StandaloneCatalog {
){
}
+
+
public void populatePriceLists() {
DefaultPlan[] plans = getCurrentPlans();
@@ -64,6 +87,156 @@ public class MockCatalog extends StandaloneCatalog {
return PRODUCT_NAMES;
}
+ @Override
+ public Currency[] getSupportedCurrencies(DateTime requestedDate) throws CatalogApiException {
+ return getCurrentSupportedCurrencies();
+ }
+
+ @Override
+ public Product[] getProducts(DateTime requestedDate) throws CatalogApiException {
+ return getCurrentProducts();
+ }
+
+ @Override
+ public Plan[] getPlans(DateTime requestedDate) throws CatalogApiException {
+ return getCurrentPlans();
+ }
+
+ @Override
+ public Plan findPlan(String name, DateTime requestedDate) throws CatalogApiException {
+ return findCurrentPlan(name);
+ }
+
+ @Override
+ public Plan findPlan(String productName, BillingPeriod term, String priceListName, DateTime requestedDate)
+ throws CatalogApiException {
+ return findCurrentPlan(productName, term, priceListName);
+ }
+
+ @Override
+ public Plan findPlan(String name, DateTime effectiveDate, DateTime subscriptionStartDate)
+ throws CatalogApiException {
+ return findCurrentPlan(name);
+ }
+
+ @Override
+ public Plan findPlan(String productName, BillingPeriod term, String priceListName, DateTime requestedDate,
+ DateTime subscriptionStartDate) throws CatalogApiException {
+ return findCurrentPlan(productName, term, priceListName);
+ }
+
+ @Override
+ public Product findProduct(String name, DateTime requestedDate) throws CatalogApiException {
+ return findCurrentProduct(name);
+ }
+
+ @Override
+ public PlanPhase findPhase(String name, DateTime requestedDate, DateTime subscriptionStartDate)
+ throws CatalogApiException {
+ return findCurrentPhase(name);
+ }
+
+ @Override
+ public PriceList findPriceList(String name, DateTime requestedDate) throws CatalogApiException {
+ return findCurrentPricelist(name);
+ }
+
+ @Override
+ public ActionPolicy planChangePolicy(PlanPhaseSpecifier from, PlanSpecifier to, DateTime requestedDate)
+ throws CatalogApiException {
+ return planChangePolicy(from, to);
+ }
+
+ @Override
+ public PlanChangeResult planChange(PlanPhaseSpecifier from, PlanSpecifier to, DateTime requestedDate)
+ throws CatalogApiException {
+ return planChange(from, to);
+ }
+
+ @Override
+ public ActionPolicy planCancelPolicy(PlanPhaseSpecifier planPhase, DateTime requestedDate)
+ throws CatalogApiException {
+ return planCancelPolicy(planPhase);
+ }
+
+ @Override
+ public PlanAlignmentCreate planCreateAlignment(PlanSpecifier specifier, DateTime requestedDate)
+ throws CatalogApiException {
+ return planCreateAlignment(specifier);
+ }
+
+ @Override
+ public BillingAlignment billingAlignment(PlanPhaseSpecifier planPhase, DateTime requestedDate)
+ throws CatalogApiException {
+ return billingAlignment(planPhase);
+ }
+
+ @Override
+ public PlanAlignmentChange planChangeAlignment(PlanPhaseSpecifier from, PlanSpecifier to, DateTime requestedDate)
+ throws CatalogApiException {
+ return planChangeAlignment(from, to);
+ }
+
+ @Override
+ public boolean canCreatePlan(PlanSpecifier specifier, DateTime requestedDate) throws CatalogApiException {
+ return canCreatePlan(specifier);
+ }
+
+ @Override
+ public ActionPolicy planChangePolicy(PlanPhaseSpecifier from, PlanSpecifier to) throws CatalogApiException {
+ // TODO Auto-generated method stub
+ return super.planChangePolicy(from, to);
+ }
+
+ @Override
+ public PlanAlignmentChange planChangeAlignment(PlanPhaseSpecifier from, PlanSpecifier to)
+ throws CatalogApiException {
+ // TODO Auto-generated method stub
+ return super.planChangeAlignment(from, to);
+ }
+
+ @Override
+ public ActionPolicy planCancelPolicy(PlanPhaseSpecifier planPhase) throws CatalogApiException {
+ // TODO Auto-generated method stub
+ return super.planCancelPolicy(planPhase);
+ }
+
+ @Override
+ public PlanAlignmentCreate planCreateAlignment(PlanSpecifier specifier) throws CatalogApiException {
+ return planCreateAlignment;
+ }
+
+ @Override
+ public BillingAlignment billingAlignment(PlanPhaseSpecifier planPhase) throws CatalogApiException {
+ // TODO Auto-generated method stub
+ return billingAlignment;
+ }
+
+ @Override
+ public PlanChangeResult planChange(PlanPhaseSpecifier from, PlanSpecifier to) throws CatalogApiException {
+ // TODO Auto-generated method stub
+ return planChange;
+ }
+
+ @Override
+ public boolean canCreatePlan(PlanSpecifier specifier) throws CatalogApiException {
+ return canCreatePlan;
+ }
+
+ public void setCanCreatePlan(boolean canCreatePlan) {
+ this.canCreatePlan = canCreatePlan;
+ }
+
+ public void setPlanChange(PlanChangeResult planChange) {
+ this.planChange = planChange;
+ }
+
+ public void setBillingAlignment(BillingAlignment billingAlignment) {
+ this.billingAlignment = billingAlignment;
+ }
+
+ public void setPlanCreateAlignment(PlanAlignmentCreate planCreateAlignment) {
+ this.planCreateAlignment = planCreateAlignment;
+ }
-
}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockCatalogModule.java b/catalog/src/test/java/com/ning/billing/catalog/MockCatalogModule.java
new file mode 100644
index 0000000..d61d6ae
--- /dev/null
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockCatalogModule.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.catalog;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+
+public class MockCatalogModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ CatalogService catalogService = BrainDeadProxyFactory.createBrainDeadProxyFor(CatalogService.class);
+ ((ZombieControl) catalogService).addResult("getCurrentCatalog", new MockCatalog());
+
+ catalogService = BrainDeadProxyFactory.createBrainDeadProxyFor(CatalogService.class);
+ Catalog catalog = BrainDeadProxyFactory.createBrainDeadProxyFor(Catalog.class);
+
+ ((ZombieControl) catalogService).addResult("getFullCatalog", catalog);
+
+ bind(CatalogService.class).toInstance(catalogService);
+ }
+}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java b/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
index deececb..f4dfab1 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
@@ -60,13 +60,21 @@ public class MockPlan extends DefaultPlan {
-1);
}
- public static MockPlan createJetTrialFixedTermEvergreen1000USD() {
- return new MockPlan("JetTrialEvergreen1000USD",
- MockProduct.createJet(),
- new DefaultPlanPhase[]{ MockPlanPhase.create30DayTrial(), MockPlanPhase.createUSDMonthlyFixedTerm("500.00", null, 6) },
- MockPlanPhase.create1USDMonthlyEvergreen(),
- -1);
- }
+ public static MockPlan createJetTrialFixedTermEvergreen1000USD() {
+ return new MockPlan("JetTrialEvergreen1000USD",
+ MockProduct.createJet(),
+ new DefaultPlanPhase[]{ MockPlanPhase.create30DayTrial(), MockPlanPhase.createUSDMonthlyFixedTerm("500.00", null, 6) },
+ MockPlanPhase.create1USDMonthlyEvergreen(),
+ -1);
+ }
+
+ public static MockPlan createHornMonthlyNoTrial1USD() {
+ return new MockPlan("Horn1USD",
+ MockProduct.createHorn(),
+ new DefaultPlanPhase[]{ },
+ MockPlanPhase.create1USDMonthlyEvergreen(),
+ -1);
+ }
public MockPlan() {
this("BicycleTrialEvergreen1USD",
@@ -123,7 +131,8 @@ public class MockPlan extends DefaultPlan {
createPickupTrialEvergreen10USD(),
createSportsCarTrialEvergreen100USD(),
createJetTrialEvergreen1000USD(),
- createJetTrialFixedTermEvergreen1000USD()
+ createJetTrialFixedTermEvergreen1000USD(),
+ createHornMonthlyNoTrial1USD()
};
}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockPriceList.java b/catalog/src/test/java/com/ning/billing/catalog/MockPriceList.java
new file mode 100644
index 0000000..9b672ba
--- /dev/null
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockPriceList.java
@@ -0,0 +1,28 @@
+/*
+ * 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 com.ning.billing.catalog.api.PriceListSet;
+
+public class MockPriceList extends DefaultPriceList {
+
+ public MockPriceList() {
+ setName(PriceListSet.DEFAULT_PRICELIST_NAME);
+ setRetired(false);
+ setPlans(MockPlan.createAll());
+ }
+}
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 e25ac82..bee4e3d 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
@@ -58,23 +58,23 @@ public class TestPlanPhase {
DefaultPlanPhase ppDiscount = MockPlanPhase.create1USDMonthlyEvergreen().setPhaseType(PhaseType.DISCOUNT).setPlan(p);
DefaultPlanPhase ppTrial = MockPlanPhase.create30DayTrial().setPhaseType(PhaseType.TRIAL).setPlan(p);
DefaultPlanPhase ppEvergreen = MockPlanPhase.create1USDMonthlyEvergreen().setPhaseType(PhaseType.EVERGREEN).setPlan(p);
- DefaultPlanPhase ppFixedterm = MockPlanPhase.create1USDMonthlyEvergreen().setPhaseType(PhaseType.FIXEDTERM).setPlan(p);
+ DefaultPlanPhase ppFixedTerm = MockPlanPhase.create1USDMonthlyEvergreen().setPhaseType(PhaseType.FIXEDTERM).setPlan(p);
String ppnDiscount = DefaultPlanPhase.phaseName(p.getName(), ppDiscount.getPhaseType());
String ppnTrial = DefaultPlanPhase.phaseName(p.getName(), ppTrial.getPhaseType());
String ppnEvergreen = DefaultPlanPhase.phaseName(p.getName(), ppEvergreen.getPhaseType());
- String ppnFixedterm = DefaultPlanPhase.phaseName(p.getName(), ppFixedterm.getPhaseType());
+ String ppnFixedTerm = DefaultPlanPhase.phaseName(p.getName(), ppFixedTerm.getPhaseType());
Assert.assertEquals(ppnTrial, planNameExt + "trial");
Assert.assertEquals(ppnEvergreen, planNameExt + "evergreen");
- Assert.assertEquals(ppnFixedterm, planNameExt + "fixedterm");
+ 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);
+ 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 3cb8d2f..a26ce10 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestPriceListSet.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestPriceListSet.java
@@ -50,10 +50,10 @@ public class TestPriceListSet {
};
DefaultPriceListSet set = new DefaultPriceListSet(defaultPriceList, childPriceLists);
- Assert.assertEquals(set.getPlanListFrom(PriceListSet.DEFAULT_PRICELIST_NAME, foo, BillingPeriod.ANNUAL).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
- Assert.assertEquals(set.getPlanListFrom(PriceListSet.DEFAULT_PRICELIST_NAME, foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
- Assert.assertEquals(set.getPlanListFrom("child", foo, BillingPeriod.ANNUAL).getFinalPhase().getPhaseType(), PhaseType.DISCOUNT);
- Assert.assertEquals(set.getPlanListFrom("child", foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
+ Assert.assertEquals(set.getPlanFrom(PriceListSet.DEFAULT_PRICELIST_NAME, foo, BillingPeriod.ANNUAL).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
+ Assert.assertEquals(set.getPlanFrom(PriceListSet.DEFAULT_PRICELIST_NAME, foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
+ Assert.assertEquals(set.getPlanFrom("child", foo, BillingPeriod.ANNUAL).getFinalPhase().getPhaseType(), PhaseType.DISCOUNT);
+ Assert.assertEquals(set.getPlanFrom("child", foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
}
public void testForNullBillingPeriod() throws CatalogApiException {
@@ -76,10 +76,10 @@ public class TestPriceListSet {
};
DefaultPriceListSet set = new DefaultPriceListSet(defaultPriceList, childPriceLists);
- Assert.assertEquals(set.getPlanListFrom("child", foo, BillingPeriod.ANNUAL).getFinalPhase().getPhaseType(), PhaseType.DISCOUNT);
- Assert.assertEquals(set.getPlanListFrom("child", foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
- Assert.assertEquals(set.getPlanListFrom(PriceListSet.DEFAULT_PRICELIST_NAME, foo, BillingPeriod.ANNUAL).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
- Assert.assertEquals(set.getPlanListFrom(PriceListSet.DEFAULT_PRICELIST_NAME, foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
+ Assert.assertEquals(set.getPlanFrom("child", foo, BillingPeriod.ANNUAL).getFinalPhase().getPhaseType(), PhaseType.DISCOUNT);
+ Assert.assertEquals(set.getPlanFrom("child", foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
+ Assert.assertEquals(set.getPlanFrom(PriceListSet.DEFAULT_PRICELIST_NAME, foo, BillingPeriod.ANNUAL).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
+ Assert.assertEquals(set.getPlanFrom(PriceListSet.DEFAULT_PRICELIST_NAME, foo, BillingPeriod.MONTHLY).getFinalPhase().getPhaseType(), PhaseType.EVERGREEN);
}
}
diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml
index c21aac1..ca17050 100644
--- a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml
+++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-1.xml
@@ -67,8 +67,7 @@
</createAlignmentCase>
</createAlignment>
</rules>
-
-
+
<plans>
<plan name="pistol-monthly">
<product>Pistol</product>
diff --git a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml
index f7ae066..ccbd964 100644
--- a/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml
+++ b/catalog/src/test/resources/versionedCatalog/WeaponsHireSmall-3.xml
@@ -68,7 +68,6 @@
</createAlignment>
</rules>
-
<plans>
<plan name="pistol-monthly">
<product>Pistol</product>
diff --git a/catalog/src/test/resources/WeaponsHire.xml b/catalog/src/test/resources/WeaponsHire.xml
index 3d36b9d..4a895b0 100644
--- a/catalog/src/test/resources/WeaponsHire.xml
+++ b/catalog/src/test/resources/WeaponsHire.xml
@@ -169,8 +169,8 @@ Use Cases to do:
<toPriceList>DEFAULT</toPriceList>
</priceListCase>
</priceList>
- </rules>
-
+ </rules>
+
<plans>
<plan name="pistol-monthly">
<product>Pistol</product>
doc/api.html 91(+91 -0)
diff --git a/doc/api.html b/doc/api.html
new file mode 100644
index 0000000..f44a165
--- /dev/null
+++ b/doc/api.html
@@ -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. -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>Collector core | Dwarf</title>
+ <meta name="user guide" content="">
+ <meta name="author" content="Stephane Brossier">
+
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
+ <![endif]-->
+
+ <link rel=stylesheet type="text/css" href="css/bootstrap.min.css">
+ <link rel=stylesheet type="text/css" href="css/prettify.css">
+ <link rel=stylesheet type="text/css" href="css/killbill.css">
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+ <script src="js/prettify.js"></script>
+ <script src="js/bootstrap-dropdown.js"></script>
+
+ <script type="text/javascript">
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-19297396-1']);
+ _gaq.push(['_trackPageview']);
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+ </script>
+</head>
+
+<body onload="prettyPrint();">
+
+ <div class="topbar" data-dropdown="dropdown">
+ <div class="topbar-inner">
+ <div class="container-fluid">
+ <a class="brand" href="../index.html">Killbill</a>
+ <ul class="nav">
+ <li><a href="user.html">User Guide</a></li>
+ <li><a href="setup.html">Setup/Installation</a></li>
+ <li class="active"><a href="user.html">APIs</a></li>
+ <li><a href="design.html">Design</a></li>
+ <li><a href="http://groups.google.com/group/killbill-users">Mailing List</a></li>
+ </ul>
+ <ul class="nav secondary-nav">
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle">Maven sites</a>
+ <ul class="dropdown-menu">
+ <li><a href="http://sbrossie.github.com/killbill" target="_blank">Killbill</a></li>
+ </ul>
+ </li>
+ </ul>
+ <ul class="nav secondary-nav">
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle">Github projects</a>
+ <ul class="dropdown-menu">
+ <li><a href="http://github.com/ning/killbill" target="_blank">Killbill</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ <div class="container-fluid">
+ <div class="sidebar">
+ <div class="well">
+ <h5>Setup</h5>
+ <ul>
+ <li><a href="#overview">Prerequisite</a></li>
+ <li><a href="#Details">Installation</a></li>
+ </ul>
+ </div>
+ </div>
+ <div class="content">
+ <h2 class="overview">Overview</h2>
+ Bla bla get me prerequisite
+ <h2 class="details">Details</h2>
+ details
+ </div>
+ </div>
+</body>
+
doc/css/bootstrap.min.css 356(+356 -0)
diff --git a/doc/css/bootstrap.min.css b/doc/css/bootstrap.min.css
new file mode 100644
index 0000000..75e85d3
--- /dev/null
+++ b/doc/css/bootstrap.min.css
@@ -0,0 +1,356 @@
+html,body{margin:0;padding:0;}
+h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,cite,code,del,dfn,em,img,q,s,samp,small,strike,strong,sub,sup,tt,var,dd,dl,dt,li,ol,ul,fieldset,form,label,legend,button,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;font-weight:normal;font-style:normal;font-size:100%;line-height:1;font-family:inherit;}
+table{border-collapse:collapse;border-spacing:0;}
+ol,ul{list-style:none;}
+q:before,q:after,blockquote:before,blockquote:after{content:"";}
+html{overflow-y:scroll;font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
+a:focus{outline:thin dotted;}
+a:hover,a:active{outline:0;}
+article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}
+audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}
+audio:not([controls]){display:none;}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}
+sup{top:-0.5em;}
+sub{bottom:-0.25em;}
+img{border:0;-ms-interpolation-mode:bicubic;}
+button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;}
+button,input{line-height:normal;*overflow:visible;}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}
+button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;}
+input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;}
+input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}
+textarea{overflow:auto;vertical-align:top;}
+body{background-color:#ffffff;margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:18px;color:#404040;}
+.container{width:940px;margin-left:auto;margin-right:auto;zoom:1;}.container:before,.container:after{display:table;content:"";zoom:1;}
+.container:after{clear:both;}
+.container-fluid{position:relative;min-width:940px;padding-left:20px;padding-right:20px;zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";zoom:1;}
+.container-fluid:after{clear:both;}
+.container-fluid>.sidebar{position:absolute;top:0;left:20px;width:220px;}
+.container-fluid>.content{margin-left:240px;}
+a{color:#0069d6;text-decoration:none;line-height:inherit;font-weight:inherit;}a:hover{color:#00438a;text-decoration:underline;}
+.pull-right{float:right;}
+.pull-left{float:left;}
+.hide{display:none;}
+.show{display:block;}
+.row{zoom:1;margin-left:-20px;}.row:before,.row:after{display:table;content:"";zoom:1;}
+.row:after{clear:both;}
+.row>[class*="span"]{display:inline;float:left;margin-left:20px;}
+.span1{width:40px;}
+.span2{width:100px;}
+.span3{width:160px;}
+.span4{width:220px;}
+.span5{width:280px;}
+.span6{width:340px;}
+.span7{width:400px;}
+.span8{width:460px;}
+.span9{width:520px;}
+.span10{width:580px;}
+.span11{width:640px;}
+.span12{width:700px;}
+.span13{width:760px;}
+.span14{width:820px;}
+.span15{width:880px;}
+.span16{width:940px;}
+.span17{width:1000px;}
+.span18{width:1060px;}
+.span19{width:1120px;}
+.span20{width:1180px;}
+.span21{width:1240px;}
+.span22{width:1300px;}
+.span23{width:1360px;}
+.span24{width:1420px;}
+.row >.offset1{margin-left:80px;}
+.row >.offset2{margin-left:140px;}
+.row >.offset3{margin-left:200px;}
+.row >.offset4{margin-left:260px;}
+.row >.offset5{margin-left:320px;}
+.row >.offset6{margin-left:380px;}
+.row >.offset7{margin-left:440px;}
+.row >.offset8{margin-left:500px;}
+.row >.offset9{margin-left:560px;}
+.row >.offset10{margin-left:620px;}
+.row >.offset11{margin-left:680px;}
+.row >.offset12{margin-left:740px;}
+.span-one-third{width:300px;}
+.span-two-thirds{width:620px;}
+.offset-one-third{margin-left:340px;}
+.offset-two-thirds{margin-left:660px;}
+p{font-size:13px;font-weight:normal;line-height:18px;margin-bottom:9px;}p small{font-size:11px;color:#bfbfbf;}
+h1,h2,h3,h4,h5,h6{font-weight:bold;color:#404040;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{color:#bfbfbf;}
+h1{margin-bottom:18px;font-size:30px;line-height:36px;}h1 small{font-size:18px;}
+h2{font-size:24px;line-height:36px;}h2 small{font-size:14px;}
+h3,h4,h5,h6{line-height:36px;}
+h3{font-size:18px;}h3 small{font-size:14px;}
+h4{font-size:16px;}h4 small{font-size:12px;}
+h5{font-size:14px;}
+h6{font-size:13px;color:#bfbfbf;text-transform:uppercase;}
+ul,ol{margin:0 0 18px 25px;}
+ul ul,ul ol,ol ol,ol ul{margin-bottom:0;}
+ul{list-style:disc;}
+ol{list-style:decimal;}
+li{line-height:18px;color:#808080;}
+ul.unstyled{list-style:none;margin-left:0;}
+dl{margin-bottom:18px;}dl dt,dl dd{line-height:18px;}
+dl dt{font-weight:bold;}
+dl dd{margin-left:9px;}
+hr{margin:20px 0 19px;border:0;border-bottom:1px solid #eee;}
+strong{font-style:inherit;font-weight:bold;}
+em{font-style:italic;font-weight:inherit;line-height:inherit;}
+.muted{color:#bfbfbf;}
+blockquote{margin-bottom:18px;border-left:5px solid #eee;padding-left:15px;}blockquote p{font-size:14px;font-weight:300;line-height:18px;margin-bottom:0;}
+blockquote small{display:block;font-size:12px;font-weight:300;line-height:18px;color:#bfbfbf;}blockquote small:before{content:'\2014 \00A0';}
+address{display:block;line-height:18px;margin-bottom:18px;}
+code,pre{padding:0 3px 2px;font-family:Monaco, Andale Mono, Courier New, monospace;font-size:12px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+code{background-color:#fee9cc;color:rgba(0, 0, 0, 0.75);padding:1px 3px;}
+pre{background-color:#f5f5f5;display:block;padding:8.5px;margin:0 0 18px;line-height:18px;font-size:12px;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;white-space:pre;white-space:pre-wrap;word-wrap:break-word;}
+form{margin-bottom:18px;}
+fieldset{margin-bottom:18px;padding-top:18px;}fieldset legend{display:block;padding-left:150px;font-size:19.5px;line-height:1;color:#404040;*padding:0 0 5px 145px;*line-height:1.5;}
+form .clearfix{margin-bottom:18px;zoom:1;}form .clearfix:before,form .clearfix:after{display:table;content:"";zoom:1;}
+form .clearfix:after{clear:both;}
+label,input,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:normal;}
+label{padding-top:6px;font-size:13px;line-height:18px;float:left;width:130px;text-align:right;color:#404040;}
+form .input{margin-left:150px;}
+input[type=checkbox],input[type=radio]{cursor:pointer;}
+input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;font-size:13px;line-height:18px;color:#808080;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+select{padding:initial;}
+input[type=checkbox],input[type=radio]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;border:none;}
+input[type=file]{background-color:#ffffff;padding:initial;border:initial;line-height:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+input[type=button],input[type=reset],input[type=submit]{width:auto;height:auto;}
+select,input[type=file]{height:27px;*height:auto;line-height:27px;*margin-top:4px;}
+select[multiple]{height:inherit;background-color:#ffffff;}
+textarea{height:auto;}
+.uneditable-input{background-color:#ffffff;display:block;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;}
+:-moz-placeholder{color:#bfbfbf;}
+::-webkit-input-placeholder{color:#bfbfbf;}
+input,textarea{-webkit-transform-style:preserve-3d;-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);}
+input:focus,textarea:focus{outline:0;border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);}
+input[type=file]:focus,input[type=checkbox]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:1px dotted #666;}
+form .clearfix.error>label,form .clearfix.error .help-block,form .clearfix.error .help-inline{color:#b94a48;}
+form .clearfix.error input,form .clearfix.error textarea{color:#b94a48;border-color:#ee5f5b;}form .clearfix.error input:focus,form .clearfix.error textarea:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;}
+form .clearfix.error .input-prepend .add-on,form .clearfix.error .input-append .add-on{color:#b94a48;background-color:#fce6e6;border-color:#b94a48;}
+form .clearfix.warning>label,form .clearfix.warning .help-block,form .clearfix.warning .help-inline{color:#c09853;}
+form .clearfix.warning input,form .clearfix.warning textarea{color:#c09853;border-color:#ccae64;}form .clearfix.warning input:focus,form .clearfix.warning textarea:focus{border-color:#be9a3f;-webkit-box-shadow:0 0 6px #e5d6b1;-moz-box-shadow:0 0 6px #e5d6b1;box-shadow:0 0 6px #e5d6b1;}
+form .clearfix.warning .input-prepend .add-on,form .clearfix.warning .input-append .add-on{color:#c09853;background-color:#d2b877;border-color:#c09853;}
+form .clearfix.success>label,form .clearfix.success .help-block,form .clearfix.success .help-inline{color:#468847;}
+form .clearfix.success input,form .clearfix.success textarea{color:#468847;border-color:#57a957;}form .clearfix.success input:focus,form .clearfix.success textarea:focus{border-color:#458845;-webkit-box-shadow:0 0 6px #9acc9a;-moz-box-shadow:0 0 6px #9acc9a;box-shadow:0 0 6px #9acc9a;}
+form .clearfix.success .input-prepend .add-on,form .clearfix.success .input-append .add-on{color:#468847;background-color:#bcddbc;border-color:#468847;}
+.input-mini,input.mini,textarea.mini,select.mini{width:60px;}
+.input-small,input.small,textarea.small,select.small{width:90px;}
+.input-medium,input.medium,textarea.medium,select.medium{width:150px;}
+.input-large,input.large,textarea.large,select.large{width:210px;}
+.input-xlarge,input.xlarge,textarea.xlarge,select.xlarge{width:270px;}
+.input-xxlarge,input.xxlarge,textarea.xxlarge,select.xxlarge{width:530px;}
+textarea.xxlarge{overflow-y:auto;}
+input.span1,textarea.span1{display:inline-block;float:none;width:30px;margin-left:0;}
+input.span2,textarea.span2{display:inline-block;float:none;width:90px;margin-left:0;}
+input.span3,textarea.span3{display:inline-block;float:none;width:150px;margin-left:0;}
+input.span4,textarea.span4{display:inline-block;float:none;width:210px;margin-left:0;}
+input.span5,textarea.span5{display:inline-block;float:none;width:270px;margin-left:0;}
+input.span6,textarea.span6{display:inline-block;float:none;width:330px;margin-left:0;}
+input.span7,textarea.span7{display:inline-block;float:none;width:390px;margin-left:0;}
+input.span8,textarea.span8{display:inline-block;float:none;width:450px;margin-left:0;}
+input.span9,textarea.span9{display:inline-block;float:none;width:510px;margin-left:0;}
+input.span10,textarea.span10{display:inline-block;float:none;width:570px;margin-left:0;}
+input.span11,textarea.span11{display:inline-block;float:none;width:630px;margin-left:0;}
+input.span12,textarea.span12{display:inline-block;float:none;width:690px;margin-left:0;}
+input.span13,textarea.span13{display:inline-block;float:none;width:750px;margin-left:0;}
+input.span14,textarea.span14{display:inline-block;float:none;width:810px;margin-left:0;}
+input.span15,textarea.span15{display:inline-block;float:none;width:870px;margin-left:0;}
+input.span16,textarea.span16{display:inline-block;float:none;width:930px;margin-left:0;}
+input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#f5f5f5;border-color:#ddd;cursor:not-allowed;}
+.actions{background:#f5f5f5;margin-top:18px;margin-bottom:18px;padding:17px 20px 18px 150px;border-top:1px solid #ddd;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;}.actions .secondary-action{float:right;}.actions .secondary-action a{line-height:30px;}.actions .secondary-action a:hover{text-decoration:underline;}
+.help-inline,.help-block{font-size:13px;line-height:18px;color:#bfbfbf;}
+.help-inline{padding-left:5px;*position:relative;*top:-5px;}
+.help-block{display:block;max-width:600px;}
+.inline-inputs{color:#808080;}.inline-inputs span{padding:0 2px 0 1px;}
+.input-prepend input,.input-append input{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
+.input-prepend .add-on,.input-append .add-on{position:relative;background:#f5f5f5;border:1px solid #ccc;z-index:2;float:left;display:block;width:auto;min-width:16px;height:18px;padding:4px 4px 4px 5px;margin-right:-1px;font-weight:normal;line-height:18px;color:#bfbfbf;text-align:center;text-shadow:0 1px 0 #ffffff;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
+.input-prepend .active,.input-append .active{background:#a9dba9;border-color:#46a546;}
+.input-prepend .add-on{*margin-top:1px;}
+.input-append input{float:left;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
+.input-append .add-on{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;margin-right:0;margin-left:-1px;}
+.inputs-list{margin:0 0 5px;width:100%;}.inputs-list li{display:block;padding:0;width:100%;}
+.inputs-list label{display:block;float:none;width:auto;padding:0;margin-left:20px;line-height:18px;text-align:left;white-space:normal;}.inputs-list label strong{color:#808080;}
+.inputs-list label small{font-size:11px;font-weight:normal;}
+.inputs-list .inputs-list{margin-left:25px;margin-bottom:10px;padding-top:0;}
+.inputs-list:first-child{padding-top:6px;}
+.inputs-list li+li{padding-top:2px;}
+.inputs-list input[type=radio],.inputs-list input[type=checkbox]{margin-bottom:0;margin-left:-20px;float:left;}
+.form-stacked{padding-left:20px;}.form-stacked fieldset{padding-top:9px;}
+.form-stacked legend{padding-left:0;}
+.form-stacked label{display:block;float:none;width:auto;font-weight:bold;text-align:left;line-height:20px;padding-top:0;}
+.form-stacked .clearfix{margin-bottom:9px;}.form-stacked .clearfix div.input{margin-left:0;}
+.form-stacked .inputs-list{margin-bottom:0;}.form-stacked .inputs-list li{padding-top:0;}.form-stacked .inputs-list li label{font-weight:normal;padding-top:0;}
+.form-stacked div.clearfix.error{padding-top:10px;padding-bottom:10px;padding-left:10px;margin-top:0;margin-left:-10px;}
+.form-stacked .actions{margin-left:-20px;padding-left:20px;}
+table{width:100%;margin-bottom:18px;padding:0;font-size:13px;border-collapse:collapse;}table th,table td{padding:10px 10px 9px;line-height:18px;text-align:left;}
+table th{padding-top:9px;font-weight:bold;vertical-align:middle;}
+table td{vertical-align:top;border-top:1px solid #ddd;}
+table tbody th{border-top:1px solid #ddd;vertical-align:top;}
+.condensed-table th,.condensed-table td{padding:5px 5px 4px;}
+.bordered-table{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.bordered-table th+th,.bordered-table td+td,.bordered-table th+td{border-left:1px solid #ddd;}
+.bordered-table thead tr:first-child th:first-child,.bordered-table tbody tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;}
+.bordered-table thead tr:first-child th:last-child,.bordered-table tbody tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;}
+.bordered-table tbody tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;}
+.bordered-table tbody tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;}
+table .span1{width:20px;}
+table .span2{width:60px;}
+table .span3{width:100px;}
+table .span4{width:140px;}
+table .span5{width:180px;}
+table .span6{width:220px;}
+table .span7{width:260px;}
+table .span8{width:300px;}
+table .span9{width:340px;}
+table .span10{width:380px;}
+table .span11{width:420px;}
+table .span12{width:460px;}
+table .span13{width:500px;}
+table .span14{width:540px;}
+table .span15{width:580px;}
+table .span16{width:620px;}
+.zebra-striped tbody tr:nth-child(odd) td,.zebra-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;}
+.zebra-striped tbody tr:hover td,.zebra-striped tbody tr:hover th{background-color:#f5f5f5;}
+table .header{cursor:pointer;}table .header:after{content:"";float:right;margin-top:7px;border-width:0 4px 4px;border-style:solid;border-color:#000 transparent;visibility:hidden;}
+table .headerSortUp,table .headerSortDown{background-color:rgba(141, 192, 219, 0.25);text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);}
+table .header:hover:after{visibility:visible;}
+table .headerSortDown:after,table .headerSortDown:hover:after{visibility:visible;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;}
+table .headerSortUp:after{border-bottom:none;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000;visibility:visible;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;}
+table .blue{color:#049cdb;border-bottom-color:#049cdb;}
+table .headerSortUp.blue,table .headerSortDown.blue{background-color:#ade6fe;}
+table .green{color:#46a546;border-bottom-color:#46a546;}
+table .headerSortUp.green,table .headerSortDown.green{background-color:#cdeacd;}
+table .red{color:#9d261d;border-bottom-color:#9d261d;}
+table .headerSortUp.red,table .headerSortDown.red{background-color:#f4c8c5;}
+table .yellow{color:#ffc40d;border-bottom-color:#ffc40d;}
+table .headerSortUp.yellow,table .headerSortDown.yellow{background-color:#fff6d9;}
+table .orange{color:#f89406;border-bottom-color:#f89406;}
+table .headerSortUp.orange,table .headerSortDown.orange{background-color:#fee9cc;}
+table .purple{color:#7a43b6;border-bottom-color:#7a43b6;}
+table .headerSortUp.purple,table .headerSortDown.purple{background-color:#e2d5f0;}
+.topbar{height:40px;position:fixed;top:0;left:0;right:0;z-index:10000;overflow:visible;}.topbar a{color:#bfbfbf;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}
+.topbar h3 a:hover,.topbar .brand:hover,.topbar ul .active>a{background-color:#333;background-color:rgba(255, 255, 255, 0.05);color:#ffffff;text-decoration:none;}
+.topbar h3{position:relative;}
+.topbar h3 a,.topbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;color:#ffffff;font-size:20px;font-weight:200;line-height:1;}
+.topbar p{margin:0;line-height:40px;}.topbar p a:hover{background-color:transparent;color:#ffffff;}
+.topbar form{float:left;margin:5px 0 0 0;position:relative;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;}
+.topbar form.pull-right{float:right;}
+.topbar input{background-color:#444;background-color:rgba(255, 255, 255, 0.3);font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:normal;font-weight:13px;line-height:1;padding:4px 9px;color:#ffffff;color:rgba(255, 255, 255, 0.75);border:1px solid #111;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-webkit-transform-style:preserve-3d;-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.topbar input:-moz-placeholder{color:#e6e6e6;}
+.topbar input::-webkit-input-placeholder{color:#e6e6e6;}
+.topbar input:hover{background-color:#bfbfbf;background-color:rgba(255, 255, 255, 0.5);color:#ffffff;}
+.topbar input:focus,.topbar input.focused{outline:0;background-color:#ffffff;color:#404040;text-shadow:0 1px 0 #ffffff;border:0;padding:5px 10px;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);}
+.topbar-inner,.topbar .fill{background-color:#222;background-color:#222222;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#333333), to(#222222));background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #333333), color-stop(100%, #222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);}
+.topbar div>ul,.nav{display:block;float:left;margin:0 10px 0 0;position:relative;left:0;}.topbar div>ul>li,.nav>li{display:block;float:left;}
+.topbar div>ul a,.nav a{display:block;float:none;padding:10px 10px 11px;line-height:19px;text-decoration:none;}.topbar div>ul a:hover,.nav a:hover{color:#ffffff;text-decoration:none;}
+.topbar div>ul .active>a,.nav .active>a{background-color:#222;background-color:rgba(0, 0, 0, 0.5);}
+.topbar div>ul.secondary-nav,.nav.secondary-nav{float:right;margin-left:10px;margin-right:0;}.topbar div>ul.secondary-nav .menu-dropdown,.nav.secondary-nav .menu-dropdown,.topbar div>ul.secondary-nav .dropdown-menu,.nav.secondary-nav .dropdown-menu{right:0;border:0;}
+.topbar div>ul a.menu:hover,.nav a.menu:hover,.topbar div>ul li.open .menu,.nav li.open .menu,.topbar div>ul .dropdown-toggle:hover,.nav .dropdown-toggle:hover,.topbar div>ul .dropdown.open .dropdown-toggle,.nav .dropdown.open .dropdown-toggle{background:#444;background:rgba(255, 255, 255, 0.05);}
+.topbar div>ul .menu-dropdown,.nav .menu-dropdown,.topbar div>ul .dropdown-menu,.nav .dropdown-menu{background-color:#333;}.topbar div>ul .menu-dropdown a.menu,.nav .menu-dropdown a.menu,.topbar div>ul .dropdown-menu a.menu,.nav .dropdown-menu a.menu,.topbar div>ul .menu-dropdown .dropdown-toggle,.nav .menu-dropdown .dropdown-toggle,.topbar div>ul .dropdown-menu .dropdown-toggle,.nav .dropdown-menu .dropdown-toggle{color:#ffffff;}.topbar div>ul .menu-dropdown a.menu.open,.nav .menu-dropdown a.menu.open,.topbar div>ul .dropdown-menu a.menu.open,.nav .dropdown-menu a.menu.open,.topbar div>ul .menu-dropdown .dropdown-toggle.open,.nav .menu-dropdown .dropdown-toggle.open,.topbar div>ul .dropdown-menu .dropdown-toggle.open,.nav .dropdown-menu .dropdown-toggle.open{background:#444;background:rgba(255, 255, 255, 0.05);}
+.topbar div>ul .menu-dropdown li a,.nav .menu-dropdown li a,.topbar div>ul .dropdown-menu li a,.nav .dropdown-menu li a{color:#999;text-shadow:0 1px 0 rgba(0, 0, 0, 0.5);}.topbar div>ul .menu-dropdown li a:hover,.nav .menu-dropdown li a:hover,.topbar div>ul .dropdown-menu li a:hover,.nav .dropdown-menu li a:hover{background-color:#191919;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#292929), to(#191919));background-image:-moz-linear-gradient(top, #292929, #191919);background-image:-ms-linear-gradient(top, #292929, #191919);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #292929), color-stop(100%, #191919));background-image:-webkit-linear-gradient(top, #292929, #191919);background-image:-o-linear-gradient(top, #292929, #191919);background-image:linear-gradient(top, #292929, #191919);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#292929', endColorstr='#191919', GradientType=0);color:#ffffff;}
+.topbar div>ul .menu-dropdown .active a,.nav .menu-dropdown .active a,.topbar div>ul .dropdown-menu .active a,.nav .dropdown-menu .active a{color:#ffffff;}
+.topbar div>ul .menu-dropdown .divider,.nav .menu-dropdown .divider,.topbar div>ul .dropdown-menu .divider,.nav .dropdown-menu .divider{background-color:#222;border-color:#444;}
+.topbar ul .menu-dropdown li a,.topbar ul .dropdown-menu li a{padding:4px 15px;}
+li.menu,.dropdown{position:relative;}
+a.menu:after,.dropdown-toggle:after{width:0;height:0;display:inline-block;content:"↓";text-indent:-99999px;vertical-align:top;margin-top:8px;margin-left:4px;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #ffffff;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;}
+.menu-dropdown,.dropdown-menu{background-color:#ffffff;float:left;display:none;position:absolute;top:40px;z-index:900;min-width:160px;max-width:220px;_width:160px;margin-left:0;margin-right:0;padding:6px 0;zoom:1;border-color:#999;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:0 1px 1px;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.menu-dropdown li,.dropdown-menu li{float:none;display:block;background-color:none;}
+.menu-dropdown .divider,.dropdown-menu .divider{height:1px;margin:5px 0;overflow:hidden;background-color:#eee;border-bottom:1px solid #ffffff;}
+.topbar .dropdown-menu a,.dropdown-menu a{display:block;padding:4px 15px;clear:both;font-weight:normal;line-height:18px;color:#808080;text-shadow:0 1px 0 #ffffff;}.topbar .dropdown-menu a:hover,.dropdown-menu a:hover,.topbar .dropdown-menu a.hover,.dropdown-menu a.hover{background-color:#dddddd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#eeeeee), to(#dddddd));background-image:-moz-linear-gradient(top, #eeeeee, #dddddd);background-image:-ms-linear-gradient(top, #eeeeee, #dddddd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #dddddd));background-image:-webkit-linear-gradient(top, #eeeeee, #dddddd);background-image:-o-linear-gradient(top, #eeeeee, #dddddd);background-image:linear-gradient(top, #eeeeee, #dddddd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dddddd', GradientType=0);color:#404040;text-decoration:none;-webkit-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);}
+.open .menu,.dropdown.open .menu,.open .dropdown-toggle,.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);}
+.open .menu-dropdown,.dropdown.open .menu-dropdown,.open .dropdown-menu,.dropdown.open .dropdown-menu{display:block;}
+.tabs,.pills{margin:0 0 18px;padding:0;list-style:none;zoom:1;}.tabs:before,.pills:before,.tabs:after,.pills:after{display:table;content:"";zoom:1;}
+.tabs:after,.pills:after{clear:both;}
+.tabs>li,.pills>li{float:left;}.tabs>li>a,.pills>li>a{display:block;}
+.tabs{border-color:#ddd;border-style:solid;border-width:0 0 1px;}.tabs>li{position:relative;margin-bottom:-1px;}.tabs>li>a{padding:0 15px;margin-right:2px;line-height:34px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.tabs>li>a:hover{text-decoration:none;background-color:#eee;border-color:#eee #eee #ddd;}
+.tabs .active>a,.tabs .active>a:hover{color:#808080;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;}
+.tabs .menu-dropdown,.tabs .dropdown-menu{top:35px;border-width:1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;}
+.tabs a.menu:after,.tabs .dropdown-toggle:after{border-top-color:#999;margin-top:15px;margin-left:5px;}
+.tabs li.open.menu .menu,.tabs .open.dropdown .dropdown-toggle{border-color:#999;}
+.tabs li.open a.menu:after,.tabs .dropdown.open .dropdown-toggle:after{border-top-color:#555;}
+.pills a{margin:5px 3px 5px 0;padding:0 15px;line-height:30px;text-shadow:0 1px 1px #ffffff;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}.pills a:hover{color:#ffffff;text-decoration:none;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);background-color:#00438a;}
+.pills .active a{color:#ffffff;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);background-color:#0069d6;}
+.pills-vertical>li{float:none;}
+.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;}
+.tab-content>.active,.pill-content>.active{display:block;}
+.breadcrumb{padding:7px 14px;margin:0 0 18px;background-color:#f5f5f5;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ffffff), to(#f5f5f5));background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline;text-shadow:0 1px 0 #ffffff;}
+.breadcrumb .divider{padding:0 5px;color:#bfbfbf;}
+.breadcrumb .active a{color:#404040;}
+.hero-unit{background-color:#f5f5f5;margin-bottom:30px;padding:60px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;}
+.hero-unit p{font-size:18px;font-weight:200;line-height:27px;}
+footer{margin-top:17px;padding-top:17px;border-top:1px solid #eee;}
+.page-header{margin-bottom:17px;border-bottom:1px solid #ddd;-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}.page-header h1{margin-bottom:8px;}
+.btn.danger,.alert-message.danger,.btn.danger:hover,.alert-message.danger:hover,.btn.error,.alert-message.error,.btn.error:hover,.alert-message.error:hover,.btn.success,.alert-message.success,.btn.success:hover,.alert-message.success:hover,.btn.info,.alert-message.info,.btn.info:hover,.alert-message.info:hover{color:#ffffff;}
+.btn .close,.alert-message .close{font-family:Arial,sans-serif;line-height:18px;}
+.btn.danger,.alert-message.danger,.btn.error,.alert-message.error{background-color:#c43c35;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#c43c35 #c43c35 #882a25;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
+.btn.success,.alert-message.success{background-color:#57a957;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#57a957 #57a957 #3d773d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
+.btn.info,.alert-message.info{background-color:#339bb9;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#339bb9 #339bb9 #22697d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
+.btn{cursor:pointer;display:inline-block;background-color:#e6e6e6;background-repeat:no-repeat;background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6);background-image:-ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);padding:5px 14px 6px;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);color:#333;font-size:13px;line-height:normal;border:1px solid #ccc;border-bottom-color:#bbb;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-webkit-transform-style:preserve-3d;-webkit-transition:0.1s linear all;-moz-transition:0.1s linear all;-ms-transition:0.1s linear all;-o-transition:0.1s linear all;transition:0.1s linear all;}.btn:hover{background-position:0 -15px;color:#333;text-decoration:none;}
+.btn:focus{outline:1px dotted #666;}
+.btn.primary{color:#ffffff;background-color:#0064cd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));background-image:-moz-linear-gradient(top, #049cdb, #0064cd);background-image:-ms-linear-gradient(top, #049cdb, #0064cd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));background-image:-webkit-linear-gradient(top, #049cdb, #0064cd);background-image:-o-linear-gradient(top, #049cdb, #0064cd);background-image:linear-gradient(top, #049cdb, #0064cd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#049cdb', endColorstr='#0064cd', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#0064cd #0064cd #003f81;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
+.btn.active,.btn :active{-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);}
+.btn.disabled{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+.btn[disabled]{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+.btn.large{font-size:15px;line-height:normal;padding:9px 14px 9px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
+.btn.small{padding:7px 9px 7px;font-size:11px;}
+:root .alert-message,:root .btn{border-radius:0 \0;}
+button.btn::-moz-focus-inner,input[type=submit].btn::-moz-focus-inner{padding:0;border:0;}
+.close{float:right;color:#000000;font-size:20px;font-weight:bold;line-height:13.5px;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=25);-khtml-opacity:0.25;-moz-opacity:0.25;opacity:0.25;}.close:hover{color:#000000;text-decoration:none;filter:alpha(opacity=40);-khtml-opacity:0.4;-moz-opacity:0.4;opacity:0.4;}
+.alert-message{position:relative;padding:7px 15px;margin-bottom:18px;color:#404040;background-color:#eedc94;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94));background-image:-moz-linear-gradient(top, #fceec1, #eedc94);background-image:-ms-linear-gradient(top, #fceec1, #eedc94);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94));background-image:-webkit-linear-gradient(top, #fceec1, #eedc94);background-image:-o-linear-gradient(top, #fceec1, #eedc94);background-image:linear-gradient(top, #fceec1, #eedc94);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#eedc94 #eedc94 #e4c652;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);border-width:1px;border-style:solid;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);}.alert-message .close{margin-top:1px;*margin-top:0;}
+.alert-message a{font-weight:bold;color:#404040;}
+.alert-message.danger p a,.alert-message.error p a,.alert-message.success p a,.alert-message.info p a{color:#ffffff;}
+.alert-message h5{line-height:18px;}
+.alert-message p{margin-bottom:0;}
+.alert-message div{margin-top:5px;margin-bottom:2px;line-height:28px;}
+.alert-message .btn{-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);}
+.alert-message.block-message{background-image:none;background-color:#fdf5d9;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);padding:14px;border-color:#fceec1;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}.alert-message.block-message ul,.alert-message.block-message p{margin-right:30px;}
+.alert-message.block-message ul{margin-bottom:0;}
+.alert-message.block-message li{color:#404040;}
+.alert-message.block-message .alert-actions{margin-top:5px;}
+.alert-message.block-message.error,.alert-message.block-message.success,.alert-message.block-message.info{color:#404040;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}
+.alert-message.block-message.error{background-color:#fddfde;border-color:#fbc7c6;}
+.alert-message.block-message.success{background-color:#d1eed1;border-color:#bfe7bf;}
+.alert-message.block-message.info{background-color:#ddf4fb;border-color:#c6edf9;}
+.alert-message.block-message.danger p a,.alert-message.block-message.error p a,.alert-message.block-message.success p a,.alert-message.block-message.info p a{color:#404040;}
+.pagination{height:36px;margin:18px 0;}.pagination ul{float:left;margin:0;border:1px solid #ddd;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);}
+.pagination li{display:inline;}
+.pagination a{float:left;padding:0 14px;line-height:34px;border-right:1px solid;border-right-color:#ddd;border-right-color:rgba(0, 0, 0, 0.15);*border-right-color:#ddd;text-decoration:none;}
+.pagination a:hover,.pagination .active a{background-color:#c7eefe;}
+.pagination .disabled a,.pagination .disabled a:hover{background-color:transparent;color:#bfbfbf;}
+.pagination .next a{border:0;}
+.well{background-color:#f5f5f5;margin-bottom:20px;padding:19px;min-height:20px;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);}
+.modal-backdrop{background-color:#000000;position:fixed;top:0;left:0;right:0;bottom:0;z-index:10000;}.modal-backdrop.fade{opacity:0;}
+.modal-backdrop,.modal-backdrop.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}
+.modal{position:fixed;top:50%;left:50%;z-index:11000;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal .close{margin-top:7px;}
+.modal.fade{-webkit-transform-style:preserve-3d;-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;}
+.modal.fade.in{top:50%;}
+.modal-header{border-bottom:1px solid #eee;padding:5px 15px;}
+.modal-body{padding:15px;}
+.modal-body form{margin-bottom:0;}
+.modal-footer{background-color:#f5f5f5;padding:14px 15px 15px;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;zoom:1;margin-bottom:0;}.modal-footer:before,.modal-footer:after{display:table;content:"";zoom:1;}
+.modal-footer:after{clear:both;}
+.modal-footer .btn{float:right;margin-left:5px;}
+.modal .popover,.modal .twipsy{z-index:12000;}
+.twipsy{display:block;position:absolute;visibility:visible;padding:5px;font-size:11px;z-index:1000;filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}.twipsy.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}
+.twipsy.above .twipsy-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
+.twipsy.left .twipsy-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
+.twipsy.below .twipsy-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
+.twipsy.right .twipsy-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
+.twipsy-inner{padding:3px 8px;background-color:#000000;color:white;text-align:center;max-width:200px;text-decoration:none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.twipsy-arrow{position:absolute;width:0;height:0;}
+.popover{position:absolute;top:0;left:0;z-index:1000;padding:5px;display:none;}.popover.above .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
+.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
+.popover.below .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
+.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
+.popover .arrow{position:absolute;width:0;height:0;}
+.popover .inner{background:#000000;background:rgba(0, 0, 0, 0.8);padding:3px;overflow:hidden;width:280px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);}
+.popover .title{background-color:#f5f5f5;padding:9px 15px;line-height:1;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;border-bottom:1px solid #eee;}
+.popover .content{background-color:#ffffff;padding:14px;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover .content p,.popover .content ul,.popover .content ol{margin-bottom:0;}
+.fade{-webkit-transform-style:preserve-3d;-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;}
+.label{padding:1px 3px 2px;font-size:9.75px;font-weight:bold;color:#ffffff;text-transform:uppercase;white-space:nowrap;background-color:#bfbfbf;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}.label.important{background-color:#c43c35;}
+.label.warning{background-color:#f89406;}
+.label.success{background-color:#46a546;}
+.label.notice{background-color:#62cffc;}
+.media-grid{margin-left:-20px;margin-bottom:0;zoom:1;}.media-grid:before,.media-grid:after{display:table;content:"";zoom:1;}
+.media-grid:after{clear:both;}
+.media-grid li{display:inline;}
+.media-grid a{float:left;padding:4px;margin:0 0 18px 20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);}.media-grid a img{display:block;}
+.media-grid a:hover{border-color:#0069d6;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);}
doc/css/killbill.css 3(+3 -0)
diff --git a/doc/css/killbill.css b/doc/css/killbill.css
new file mode 100644
index 0000000..cf8ccee
--- /dev/null
+++ b/doc/css/killbill.css
@@ -0,0 +1,3 @@
+body {
+ padding-top: 60px;
+}
\ No newline at end of file
doc/css/prettify.css 118(+118 -0)
diff --git a/doc/css/prettify.css b/doc/css/prettify.css
new file mode 100644
index 0000000..3b38616
--- /dev/null
+++ b/doc/css/prettify.css
@@ -0,0 +1,118 @@
+
+/*
+ * Derived from einaros's Sons of Obsidian theme at
+ * http://studiostyl.es/schemes/son-of-obsidian by
+ * Alex Ford of CodeTunnel:
+ * http://CodeTunnel.com/blog/post/71/google-code-prettify-obsidian-theme
+ */
+
+.str
+{
+ color: #EC7600;
+}
+.kwd
+{
+ color: #93C763;
+}
+.com
+{
+ color: #66747B;
+}
+.typ
+{
+ color: #678CB1;
+}
+.lit
+{
+ color: #FACD22;
+}
+.pun
+{
+ color: #F1F2F3;
+}
+.pln
+{
+ color: #F1F2F3;
+}
+.tag
+{
+ color: #8AC763;
+}
+.atn
+{
+ color: #E0E2E4;
+}
+.atv
+{
+ color: #EC7600;
+}
+.dec
+{
+ color: purple;
+}
+pre.prettyprint
+{
+ border: 0px solid #888;
+}
+ol.linenums
+{
+ margin-top: 0;
+ margin-bottom: 0;
+}
+.prettyprint {
+ background: #000;
+}
+li.L0, li.L1, li.L2, li.L3, li.L4, li.L5, li.L6, li.L7, li.L8, li.L9
+{
+ color: #555;
+}
+li.L1, li.L3, li.L5, li.L7, li.L9 {
+ background: #111;
+}
+@media print
+{
+ .str
+ {
+ color: #060;
+ }
+ .kwd
+ {
+ color: #006;
+ font-weight: bold;
+ }
+ .com
+ {
+ color: #600;
+ font-style: italic;
+ }
+ .typ
+ {
+ color: #404;
+ font-weight: bold;
+ }
+ .lit
+ {
+ color: #044;
+ }
+ .pun
+ {
+ color: #440;
+ }
+ .pln
+ {
+ color: #000;
+ }
+ .tag
+ {
+ color: #006;
+ font-weight: bold;
+ }
+ .atn
+ {
+ color: #404;
+ }
+ .atv
+ {
+ color: #060;
+ }
+}
\ No newline at end of file
doc/design.html 100(+100 -0)
diff --git a/doc/design.html b/doc/design.html
new file mode 100644
index 0000000..0ff23be
--- /dev/null
+++ b/doc/design.html
@@ -0,0 +1,100 @@
+<!-- ~ Copyright 2010-2011 Ning, Inc. ~ ~ Ning licenses this file to you
+ under the Apache License, version 2.0 ~ (the "License"); you may not use
+ this file except in compliance with the ~ License. You may obtain a copy
+ of the License at: ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless
+ required by applicable law or agreed to in writing, software ~ distributed
+ under the License is distributed on an "AS IS" BASIS, WITHOUT ~ WARRANTIES
+ OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for
+ the specific language governing permissions and limitations ~ under the License. -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>Collector core | Dwarf</title>
+ <meta name="user guide" content="">
+ <meta name="author" content="Stephane Brossier">
+
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
+ <![endif]-->
+
+ <link rel=stylesheet type="text/css" href="css/bootstrap.min.css">
+ <link rel=stylesheet type="text/css" href="css/prettify.css">
+ <link rel=stylesheet type="text/css" href="css/killbill.css">
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+ <script src="js/prettify.js"></script>
+ <script src="js/bootstrap-dropdown.js"></script>
+
+ <script type="text/javascript">
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-19297396-1']);
+ _gaq.push(['_trackPageview']);
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+ </script>
+</head>
+
+<body onload="prettyPrint();">
+
+ <div class="topbar" data-dropdown="dropdown">
+ <div class="topbar-inner">
+ <div class="container-fluid">
+ <a class="brand" href="../index.html">Killbill</a>
+ <ul class="nav">
+ <li><a href="user.html">User Guide</a></li>
+ <li><a href="setup.html">Setup/Installation</a></li>
+ <li><a href="api.html">APIs</a></li>
+ <li class="active"><a href="user.html">Setup/Installation</a></li>
+ <li><a href="http://groups.google.com/group/killbill-users">Mailing List</a></li>
+ </ul>
+ <ul class="nav secondary-nav">
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle">Maven sites</a>
+ <ul class="dropdown-menu">
+ <li><a href="http://sbrossie.github.com/killbill" target="_blank">Killbill</a></li>
+ </ul>
+ </li>
+ </ul>
+ <ul class="nav secondary-nav">
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle">Github projects</a>
+ <ul class="dropdown-menu">
+ <li><a href="http://github.com/ning/killbill" target="_blank">Killbill</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ <div class="container-fluid">
+ <div class="sidebar">
+ <div class="well">
+ <h5>Setup</h5>
+ <ul>
+ <li><a href="#modularity">Modularity</a></li>
+ <li><a href="#big-picture">Big Picture</a></li>
+ <li><a href="#lifecycle">Lifecycle</a></li>
+ <li><a href="#event-bus">Event Bus</a></li>
+ <li><a href="#entitlement">Entitlement</a></li>
+ </ul>
+ </div>
+ </div>
+ <div class="content">
+ <h2 class="modularity">Modularity</h2>
+ Bla bla
+ <h2 class="big-picture">Big Picture</h2>
+ Bla bla
+ <h2 class="lifecycle">Lifecycle</h2>
+ Bla bla
+ <h2 class="event-bus">Event Bus</h2>
+ Bla bla
+ <h2 class="entitlement">Entitlement</h2>
+ Bla bla
+ </div>
+ </div>
+</body>
+
doc/js/bootstrap-dropdown.js 55(+55 -0)
diff --git a/doc/js/bootstrap-dropdown.js b/doc/js/bootstrap-dropdown.js
new file mode 100644
index 0000000..fda6da5
--- /dev/null
+++ b/doc/js/bootstrap-dropdown.js
@@ -0,0 +1,55 @@
+/* ============================================================
+ * bootstrap-dropdown.js v1.4.0
+ * http://twitter.github.com/bootstrap/javascript.html#dropdown
+ * ============================================================
+ * Copyright 2011 Twitter, Inc.
+ *
+ * Licensed 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.
+ * ============================================================ */
+
+
+!function( $ ){
+
+ "use strict"
+
+ /* DROPDOWN PLUGIN DEFINITION
+ * ========================== */
+
+ $.fn.dropdown = function ( selector ) {
+ return this.each(function () {
+ $(this).delegate(selector || d, 'click', function (e) {
+ var li = $(this).parent('li')
+ , isActive = li.hasClass('open')
+
+ clearMenus()
+ !isActive && li.toggleClass('open')
+ return false
+ })
+ })
+ }
+
+ /* APPLY TO STANDARD DROPDOWN ELEMENTS
+ * =================================== */
+
+ var d = 'a.menu, .dropdown-toggle'
+
+ function clearMenus() {
+ $(d).parent('li').removeClass('open')
+ }
+
+ $(function () {
+ $('html').bind("click", clearMenus)
+ $('body').dropdown( '[data-dropdown] a.menu, [data-dropdown] .dropdown-toggle' )
+ })
+
+}( window.jQuery || window.ender );
doc/js/prettify.js 28(+28 -0)
diff --git a/doc/js/prettify.js b/doc/js/prettify.js
new file mode 100644
index 0000000..eef5ad7
--- /dev/null
+++ b/doc/js/prettify.js
@@ -0,0 +1,28 @@
+var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
+(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
+[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
+f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
+(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
+{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
+t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
+"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
+l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
+q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
+q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
+"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
+a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
+for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
+m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
+a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
+j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
+"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
+H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
+J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
+I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
+["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
+/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
+["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
+hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
+!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
+250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
+PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();
doc/setup.html 97(+97 -0)
diff --git a/doc/setup.html b/doc/setup.html
new file mode 100644
index 0000000..02190dd
--- /dev/null
+++ b/doc/setup.html
@@ -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. -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>Collector core | Dwarf</title>
+ <meta name="user guide" content="">
+ <meta name="author" content="Stephane Brossier">
+
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
+ <![endif]-->
+
+ <link rel=stylesheet type="text/css" href="css/bootstrap.min.css">
+ <link rel=stylesheet type="text/css" href="css/prettify.css">
+ <link rel=stylesheet type="text/css" href="css/killbill.css">
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+ <script src="js/prettify.js"></script>
+ <script src="js/bootstrap-dropdown.js"></script>
+
+ <script type="text/javascript">
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-19297396-1']);
+ _gaq.push(['_trackPageview']);
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+ </script>
+</head>
+
+<body onload="prettyPrint();">
+
+ <div class="topbar" data-dropdown="dropdown">
+ <div class="topbar-inner">
+ <div class="container-fluid">
+ <a class="brand" href="../index.html">Killbill</a>
+ <ul class="nav">
+ <li><a href="user.html">User Guide</a></li>
+ <li class="active"><a href="user.html">Setup/Installation</a></li>
+ <li><a href="api.html">APIs</a></li>
+ <li><a href="design.html">Design</a></li>
+ <li><a href="http://groups.google.com/group/killbill-users">Mailing List</a></li>
+ </ul>
+ <ul class="nav secondary-nav">
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle">Maven sites</a>
+ <ul class="dropdown-menu">
+ <li><a href="http://sbrossie.github.com/killbill" target="_blank">Killbill</a></li>
+ </ul>
+ </li>
+ </ul>
+ <ul class="nav secondary-nav">
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle">Github projects</a>
+ <ul class="dropdown-menu">
+ <li><a href="http://github.com/ning/killbill" target="_blank">Killbill</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ <div class="container-fluid">
+ <div class="sidebar">
+ <div class="well">
+ <h5>Setup</h5>
+ <ul>
+ <li><a href="#prerequisite">Prerequisite</a></li>
+ <li><a href="#install">Installation</a></li>
+ <li><a href="#configuration">Configuration</a></li>
+ <li><a href="#demo">Demo</a></li>
+ </ul>
+ </div>
+ </div>
+ <div class="content">
+ <h2 class="prerequisite">Prerequisite</h2>
+ Bla bla get me prerequisite
+ <h2 class="install">Installation</h2>
+ install
+ <h2 class="configuration">Configuration</h2>
+ Configuration
+ <h2 class="demo">Demo</h2>
+ Demo
+ </div>
+ </div>
+</body>
+
doc/user.html 102(+102 -0)
diff --git a/doc/user.html b/doc/user.html
new file mode 100644
index 0000000..774fa84
--- /dev/null
+++ b/doc/user.html
@@ -0,0 +1,102 @@
+<!-- ~ Copyright 2010-2011 Ning, Inc. ~ ~ Ning licenses this file to you
+ under the Apache License, version 2.0 ~ (the "License"); you may not use
+ this file except in compliance with the ~ License. You may obtain a copy
+ of the License at: ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless
+ required by applicable law or agreed to in writing, software ~ distributed
+ under the License is distributed on an "AS IS" BASIS, WITHOUT ~ WARRANTIES
+ OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for
+ the specific language governing permissions and limitations ~ under the License. -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>Collector core | Dwarf</title>
+ <meta name="user guide" content="">
+ <meta name="author" content="Stephane Brossier">
+
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
+ <![endif]-->
+
+ <link rel=stylesheet type="text/css" href="css/bootstrap.min.css">
+ <link rel=stylesheet type="text/css" href="css/prettify.css">
+ <link rel=stylesheet type="text/css" href="css/killbill.css">
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+ <script src="js/prettify.js"></script>
+ <script src="js/bootstrap-dropdown.js"></script>
+
+ <script type="text/javascript">
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-19297396-1']);
+ _gaq.push(['_trackPageview']);
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+ </script>
+</head>
+
+<body onload="prettyPrint();">
+
+ <div class="topbar" data-dropdown="dropdown">
+ <div class="topbar-inner">
+ <div class="container-fluid">
+ <a class="brand" href="../index.html">Killbill</a>
+ <ul class="nav">
+ <li class="active"><a href="user.html">User Guide</a></li>
+ <li><a href="setup.html">Setup/Installation</a></li>
+ <li><a href="api.html">APIs</a></li>
+ <li><a href="design.html">Design</a></li>
+ <li><a href="http://groups.google.com/group/killbill-users">Mailing List</a></li>
+ </ul>
+ <ul class="nav secondary-nav">
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle">Maven sites</a>
+ <ul class="dropdown-menu">
+ <li><a href="http://sbrossie.github.com/killbill" target="_blank">Killbill</a></li>
+ </ul>
+ </li>
+ </ul>
+ <ul class="nav secondary-nav">
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle">Github projects</a>
+ <ul class="dropdown-menu">
+ <li><a href="http://github.com/ning/killbill" target="_blank">Killbill</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+
+
+ <div class="container-fluid">
+ <div class="sidebar">
+ <div class="well">
+ <h5>User Guide</h5>
+ <ul>
+ <li><a href="#getting-started">Getting Started</a></li>
+ <li><a href="#catalog-configuration">Catalog Configuration</a></li>
+ <li><a href="#automatic-billing">Automatic Billing</a></li>
+ <li><a href="#proration">Proration</a></li>
+ <li><a href="#payment-pluggin">Admin UI</a></li>
+ </ul>
+ </div>
+ </div>
+ <div class="content">
+ <h2 class="getting-started">Getting Started</h2>
+ Bla bla get me started
+ <h2 class="catalog-configuration">Catalog Configuration</h2>
+ Bla bla catalog config
+ <h2 class="automatic-billing">Automatic Billing</h2>
+ Automatic billing
+ <h2 class="proration">Proration Logic</h2>
+ Bla bla proration
+ <h2 class="payment-pluggin">Payment Pluggin</h2>
+ Bla bla payment plugin
+ </div>
+ </div>
+</body>
+
entitlement/pom.xml 54(+24 -30)
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 5f8b125..245e200 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -39,23 +39,6 @@
</dependency>
<dependency>
<groupId>com.ning.billing</groupId>
- <artifactId>killbill-catalog</artifactId>
- <type>test-jar</type>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>com.ning.billing</groupId>
- <artifactId>killbill-catalog</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>com.ning.billing</groupId>
- <artifactId>killbill-util</artifactId>
- <type>test-jar</type>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>com.ning.billing</groupId>
<artifactId>killbill-util</artifactId>
</dependency>
<dependency>
@@ -63,14 +46,7 @@
<artifactId>commons-io</artifactId>
<scope>test</scope>
</dependency>
- <!-- Same here, this is really debatable whether or not we should keep that here -->
- <dependency>
- <groupId>com.ning.billing</groupId>
- <artifactId>killbill-account</artifactId>
- <type>test-jar</type>
- <scope>test</scope>
- </dependency>
- <dependency>
+ <dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
@@ -87,11 +63,6 @@
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
- <groupId>org.testng</groupId>
- <artifactId>testng</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
</dependency>
@@ -108,11 +79,34 @@
<artifactId>stringtemplate</artifactId>
<scope>runtime</scope>
</dependency>
+ <!-- TEST SCOPE -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-mxj</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-catalog</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-catalog</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-util</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-mxj-db-files</artifactId>
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 82a83bf..0e44528 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
@@ -21,7 +21,7 @@ import com.ning.billing.ErrorCode;
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.SubscriptionTransitionData;
import com.ning.billing.entitlement.exceptions.EntitlementError;
import com.ning.billing.util.clock.DefaultClock;
import org.joda.time.DateTime;
@@ -119,7 +119,7 @@ public class PlanAligner {
public TimedPhase getNextTimedPhase(final SubscriptionData subscription, final DateTime requestedDate, final DateTime effectiveDate) {
try {
- SubscriptionTransition lastPlanTransition = subscription.getInitialTransitionForCurrentPlan();
+ SubscriptionTransitionData lastPlanTransition = subscription.getInitialTransitionForCurrentPlan();
if (effectiveDate.isBefore(lastPlanTransition.getEffectiveTransitionTime())) {
throw new EntitlementError(String.format("Cannot specify an effectiveDate prior to last Plan Change, subscription = %s, effectiveDate = %s",
subscription.getId(), effectiveDate));
@@ -129,11 +129,12 @@ public class PlanAligner {
// If we never had any Plan change, borrow the logics for createPlan alignment
case MIGRATE_ENTITLEMENT:
case CREATE:
+ case RE_CREATE:
List<TimedPhase> timedPhases = getTimedPhaseOnCreate(subscription.getStartDate(),
subscription.getBundleStartDate(),
lastPlanTransition.getNextPlan(),
lastPlanTransition.getNextPhase().getPhaseType(),
- lastPlanTransition.getNextPriceList(),
+ lastPlanTransition.getNextPriceList().getName(),
requestedDate);
return getTimedPhase(timedPhases, effectiveDate, WhichPhase.NEXT);
// If we went through Plan changes, borrow the logics for changePlan alignement
@@ -142,9 +143,9 @@ public class PlanAligner {
subscription.getBundleStartDate(),
lastPlanTransition.getPreviousPhase(),
lastPlanTransition.getPreviousPlan(),
- lastPlanTransition.getPreviousPriceList(),
+ lastPlanTransition.getPreviousPriceList().getName(),
lastPlanTransition.getNextPlan(),
- lastPlanTransition.getNextPriceList(),
+ lastPlanTransition.getNextPriceList().getName(),
requestedDate,
effectiveDate,
WhichPhase.NEXT);
@@ -192,7 +193,7 @@ public class PlanAligner {
subscription.getBundleStartDate(),
subscription.getCurrentPhase(),
subscription.getCurrentPlan(),
- subscription.getCurrentPriceList(),
+ subscription.getCurrentPriceList().getName(),
nextPlan,
nextPriceList,
requestedDate,
@@ -211,11 +212,6 @@ public class PlanAligner {
Catalog catalog = catalogService.getFullCatalog();
ProductCategory currentCategory = currentPlan.getProduct().getCategory();
- // STEPH tiered ADDON not implemented yet
- if (currentCategory != ProductCategory.BASE) {
- throw new EntitlementError(String.format("Only implemented changePlan for BasePlan"));
- }
-
PlanPhaseSpecifier fromPlanPhaseSpecifier = new PlanPhaseSpecifier(currentPlan.getProduct().getName(),
currentCategory,
currentPlan.getBillingPeriod(),
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultChargeThruApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultChargeThruApi.java
new file mode 100644
index 0000000..21b97f8
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultChargeThruApi.java
@@ -0,0 +1,55 @@
+/*
+w * 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.UUID;
+
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.util.callcontext.CallContext;
+
+public class DefaultChargeThruApi implements ChargeThruApi {
+ private final EntitlementDao entitlementDao;
+ private final SubscriptionFactory subscriptionFactory;
+
+ @Inject
+ public DefaultChargeThruApi(final SubscriptionFactory subscriptionFactory, final EntitlementDao dao) {
+ super();
+ this.subscriptionFactory = subscriptionFactory;
+ this.entitlementDao = dao;
+ }
+
+ @Override
+ public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+ return entitlementDao.getAccountIdFromSubscriptionId(subscriptionId);
+ }
+
+ @Override
+ public void setChargedThroughDate(final UUID subscriptionId, final DateTime ctd, CallContext context) {
+ SubscriptionData subscription = (SubscriptionData) entitlementDao.getSubscriptionFromId(subscriptionFactory, subscriptionId);
+
+ SubscriptionBuilder builder = new SubscriptionBuilder(subscription)
+ .setChargedThroughDate(ctd)
+ .setPaidThroughDate(subscription.getPaidThroughDate());
+ entitlementDao.updateChargedThroughDate(new SubscriptionData(builder), context);
+ }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/AccountMigrationData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/AccountMigrationData.java
index 24c4e07..07a0034 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/AccountMigrationData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/migration/AccountMigrationData.java
@@ -20,7 +20,7 @@ import java.util.List;
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.DefaultSubscriptionFactory.SubscriptionBuilder;
import com.ning.billing.entitlement.events.EntitlementEvent;
import com.ning.billing.entitlement.events.user.ApiEventMigrateBilling;
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 d0fb35b..b09fc17 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
@@ -31,12 +31,12 @@ import com.google.inject.Inject;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.alignment.MigrationPlanAligner;
import com.ning.billing.entitlement.alignment.TimedMigration;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
import com.ning.billing.entitlement.api.migration.AccountMigrationData.SubscriptionMigrationData;
import com.ning.billing.entitlement.api.user.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.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
import com.ning.billing.entitlement.engine.dao.EntitlementDao;
import com.ning.billing.entitlement.events.EntitlementEvent;
import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
@@ -72,11 +72,11 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
@Override
public void migrate(EntitlementAccountMigration toBeMigrated, CallContext context)
throws EntitlementMigrationApiException {
- AccountMigrationData accountMigrationData = createAccountMigrationData(toBeMigrated);
+ AccountMigrationData accountMigrationData = createAccountMigrationData(toBeMigrated, context);
dao.migrate(toBeMigrated.getAccountKey(), accountMigrationData, context);
}
- private AccountMigrationData createAccountMigrationData(EntitlementAccountMigration toBeMigrated)
+ private AccountMigrationData createAccountMigrationData(EntitlementAccountMigration toBeMigrated, CallContext context)
throws EntitlementMigrationApiException {
final UUID accountId = toBeMigrated.getAccountKey();
@@ -86,7 +86,7 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
for (final EntitlementBundleMigration curBundle : toBeMigrated.getBundles()) {
- SubscriptionBundleData bundleData = new SubscriptionBundleData(curBundle.getBundleKey(), accountId);
+ SubscriptionBundleData bundleData = new SubscriptionBundleData(curBundle.getBundleKey(), accountId, clock.getUTCNow());
List<SubscriptionMigrationData> bundleSubscriptionData = new LinkedList<AccountMigrationData.SubscriptionMigrationData>();
@@ -118,10 +118,11 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
for (EntitlementSubscriptionMigration curSub : sortedSubscriptions) {
SubscriptionMigrationData data = null;
if (bundleStartDate == null) {
- data = createInitialSubscription(bundleData.getId(), curSub.getCategory(), curSub.getSubscriptionCases(), now, curSub.getChargedThroughDate());
+ data = createInitialSubscription(bundleData.getId(), curSub.getCategory(), curSub.getSubscriptionCases(), now, curSub.getChargedThroughDate(), context);
bundleStartDate = data.getInitialEvents().get(0).getEffectiveDate();
} else {
- data = createSubscriptionMigrationDataWithBundleDate(bundleData.getId(), curSub.getCategory(), curSub.getSubscriptionCases(), now, bundleStartDate, curSub.getChargedThroughDate());
+ data = createSubscriptionMigrationDataWithBundleDate(bundleData.getId(), curSub.getCategory(), curSub.getSubscriptionCases(), now,
+ bundleStartDate, curSub.getChargedThroughDate(), context);
}
if (data != null) {
bundleSubscriptionData.add(data);
@@ -135,7 +136,7 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
}
private SubscriptionMigrationData createInitialSubscription(UUID bundleId, ProductCategory productCategory,
- EntitlementSubscriptionMigrationCase [] input, DateTime now, DateTime ctd)
+ EntitlementSubscriptionMigrationCase [] input, DateTime now, DateTime ctd, CallContext context)
throws EntitlementMigrationApiException {
TimedMigration [] events = migrationAligner.getEventsMigration(input, now);
@@ -148,11 +149,11 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
.setBundleStartDate(migrationStartDate)
.setStartDate(migrationStartDate),
emptyEvents);
- return new SubscriptionMigrationData(subscriptionData, toEvents(subscriptionData, now, ctd, events));
+ return new SubscriptionMigrationData(subscriptionData, toEvents(subscriptionData, now, ctd, events, context));
}
private SubscriptionMigrationData createSubscriptionMigrationDataWithBundleDate(UUID bundleId, ProductCategory productCategory,
- EntitlementSubscriptionMigrationCase [] input, DateTime now, DateTime bundleStartDate, DateTime ctd)
+ EntitlementSubscriptionMigrationCase [] input, DateTime now, DateTime bundleStartDate, DateTime ctd, CallContext context)
throws EntitlementMigrationApiException {
TimedMigration [] events = migrationAligner.getEventsMigration(input, now);
DateTime migrationStartDate= events[0].getEventTime();
@@ -164,10 +165,10 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
.setBundleStartDate(bundleStartDate)
.setStartDate(migrationStartDate),
emptyEvents);
- return new SubscriptionMigrationData(subscriptionData, toEvents(subscriptionData, now, ctd, events));
+ return new SubscriptionMigrationData(subscriptionData, toEvents(subscriptionData, now, ctd, events, context));
}
- private List<EntitlementEvent> toEvents(SubscriptionData subscriptionData, DateTime now, DateTime ctd, TimedMigration [] migrationEvents) {
+ private List<EntitlementEvent> toEvents(SubscriptionData subscriptionData, DateTime now, DateTime ctd, TimedMigration [] migrationEvents, CallContext context) {
ApiEventMigrateEntitlement creationEvent = null;
@@ -189,6 +190,7 @@ public class DefaultEntitlementMigrationApi implements EntitlementMigrationApi {
.setEffectiveDate(cur.getEventTime())
.setProcessedDate(now)
.setRequestedDate(now)
+ .setUserToken(context.getUserToken())
.setFromDisk(true);
switch(cur.getApiEventType()) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionApiService.java
new file mode 100644
index 0000000..5b0f192
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionApiService.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+import java.util.List;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionStatusDryRun;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.util.callcontext.CallContext;
+
+public interface SubscriptionApiService {
+
+ public SubscriptionData createPlan(SubscriptionBuilder builder, Plan plan, PhaseType initialPhase,
+ String realPriceList, DateTime requestedDate, DateTime effectiveDate, DateTime processedDate,
+ CallContext context)
+ throws EntitlementUserApiException;
+
+ public boolean recreatePlan(SubscriptionData subscription, PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
+ throws EntitlementUserApiException;
+
+
+ public boolean cancel(SubscriptionData subscription, DateTime requestedDate, boolean eot, CallContext context)
+ throws EntitlementUserApiException;
+
+ public boolean uncancel(SubscriptionData subscription, CallContext context)
+ throws EntitlementUserApiException;
+
+ public boolean changePlan(SubscriptionData subscription, String productName, BillingPeriod term,
+ String priceList, DateTime requestedDate, CallContext context)
+ throws EntitlementUserApiException;
+
+ public void commitCustomFields(SubscriptionData subscription, CallContext context);
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionFactory.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionFactory.java
new file mode 100644
index 0000000..2b12487
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/SubscriptionFactory.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.entitlement.api;
+
+import java.util.List;
+
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+
+public interface SubscriptionFactory {
+
+ public SubscriptionData createSubscription(SubscriptionBuilder builder, List<EntitlementEvent> events);
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultDeletedEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultDeletedEvent.java
new file mode 100644
index 0000000..14b1e9e
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultDeletedEvent.java
@@ -0,0 +1,42 @@
+/*
+ * 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.timeline;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+
+public class DefaultDeletedEvent implements DeletedEvent {
+
+ private final UUID id;
+ private final DateTime effectiveDate;
+
+ public DefaultDeletedEvent(UUID id, DateTime effectiveDate) {
+ this.id = id;
+ this.effectiveDate = effectiveDate;
+ }
+
+ @Override
+ public UUID getEventId() {
+ return id;
+ }
+
+ public DateTime getEffectiveDate() {
+ return effectiveDate;
+ }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultEntitlementTimelineApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultEntitlementTimelineApi.java
new file mode 100644
index 0000000..9f73725
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultEntitlementTimelineApi.java
@@ -0,0 +1,465 @@
+/*
+ * 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.timeline;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+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.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
+import com.ning.billing.util.callcontext.CallContext;
+
+public class DefaultEntitlementTimelineApi implements EntitlementTimelineApi {
+
+ private final EntitlementDao dao;
+ private final SubscriptionFactory factory;
+ private final RepairEntitlementLifecycleDao repairDao;
+ private final CatalogService catalogService;
+
+
+ private enum RepairType {
+ BASE_REPAIR,
+ ADD_ON_REPAIR,
+ STANDALONE_REPAIR
+ }
+
+
+ @Inject
+ public DefaultEntitlementTimelineApi(@Named(DefaultEntitlementModule.REPAIR_NAMED) final SubscriptionFactory factory, final CatalogService catalogService,
+ @Named(DefaultEntitlementModule.REPAIR_NAMED) final RepairEntitlementLifecycleDao repairDao, final EntitlementDao dao) {
+ this.catalogService = catalogService;
+ this.dao = dao;
+ this.repairDao = repairDao;
+ this.factory = factory;
+ }
+
+
+ @Override
+ public BundleTimeline getBundleRepair(final UUID bundleId)
+ throws EntitlementRepairException {
+
+ try {
+ SubscriptionBundle bundle = dao.getSubscriptionBundleFromId(bundleId);
+ if (bundle == null) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_BUNDLE, bundleId);
+ }
+ final List<Subscription> subscriptions = dao.getSubscriptions(factory, bundleId);
+ if (subscriptions.size() == 0) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_NO_ACTIVE_SUBSCRIPTIONS, bundleId);
+ }
+ final String viewId = getViewId(((SubscriptionBundleData) bundle).getLastSysUpdateTime(), subscriptions);
+ final List<SubscriptionTimeline> repairs = createGetSubscriptionRepairList(subscriptions, Collections.<SubscriptionTimeline>emptyList());
+ return createGetBundleRepair(bundleId, bundle.getKey(), viewId, repairs);
+ } catch (CatalogApiException e) {
+ throw new EntitlementRepairException(e);
+ }
+ }
+
+
+
+ @Override
+ public BundleTimeline repairBundle(final BundleTimeline input, final boolean dryRun, final CallContext context)
+ throws EntitlementRepairException {
+
+ try {
+
+ SubscriptionBundle bundle = dao.getSubscriptionBundleFromId(input.getBundleId());
+ if (bundle == null) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_BUNDLE, input.getBundleId());
+ }
+
+
+ // Subscriptions are ordered with BASE subscription first-- if exists
+ final List<Subscription> subscriptions = dao.getSubscriptions(factory, input.getBundleId());
+ if (subscriptions.size() == 0) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_NO_ACTIVE_SUBSCRIPTIONS, input.getBundleId());
+ }
+
+ final String viewId = getViewId(((SubscriptionBundleData) bundle).getLastSysUpdateTime(), subscriptions);
+ if (!viewId.equals(input.getViewId())) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_VIEW_CHANGED,input.getBundleId(), input.getViewId(), viewId);
+ }
+
+ DateTime firstDeletedBPEventTime = null;
+ DateTime lastRemainingBPEventTime = null;
+
+ boolean isBasePlanRecreate = false;
+ DateTime newBundleStartDate = null;
+
+ SubscriptionDataRepair baseSubscriptionRepair = null;
+ List<SubscriptionDataRepair> addOnSubscriptionInRepair = new LinkedList<SubscriptionDataRepair>();
+ List<SubscriptionDataRepair> inRepair = new LinkedList<SubscriptionDataRepair>();
+ for (Subscription cur : subscriptions) {
+
+ //
+ SubscriptionTimeline curRepair = findAndCreateSubscriptionRepair(cur.getId(), input.getSubscriptions());
+ if (curRepair != null) {
+ SubscriptionDataRepair curInputRepair = ((SubscriptionDataRepair) cur);
+ final List<EntitlementEvent> remaining = getRemainingEventsAndValidateDeletedEvents(curInputRepair, firstDeletedBPEventTime, curRepair.getDeletedEvents());
+
+ final boolean isPlanRecreate = (curRepair.getNewEvents().size() > 0
+ && (curRepair.getNewEvents().get(0).getSubscriptionTransitionType() == SubscriptionTransitionType.CREATE
+ || curRepair.getNewEvents().get(0).getSubscriptionTransitionType() == SubscriptionTransitionType.RE_CREATE));
+
+ final DateTime newSubscriptionStartDate = isPlanRecreate ? curRepair.getNewEvents().get(0).getRequestedDate() : null;
+
+ if (isPlanRecreate && remaining.size() != 0) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_SUB_RECREATE_NOT_EMPTY, cur.getId(), cur.getBundleId());
+ }
+
+ if (!isPlanRecreate && remaining.size() == 0) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_SUB_EMPTY, cur.getId(), cur.getBundleId());
+ }
+
+ if (cur.getCategory() == ProductCategory.BASE) {
+
+ int bpTransitionSize =((SubscriptionData) cur).getAllTransitions().size();
+ lastRemainingBPEventTime = (remaining.size() > 0) ? curInputRepair.getAllTransitions().get(remaining.size() - 1).getEffectiveTransitionTime() : null;
+ firstDeletedBPEventTime = (remaining.size() < bpTransitionSize) ? curInputRepair.getAllTransitions().get(remaining.size()).getEffectiveTransitionTime() : null;
+
+ isBasePlanRecreate = isPlanRecreate;
+ newBundleStartDate = newSubscriptionStartDate;
+ }
+
+ if (curRepair.getNewEvents().size() > 0) {
+ DateTime lastRemainingEventTime = (remaining.size() == 0) ? null : curInputRepair.getAllTransitions().get(remaining.size() - 1).getEffectiveTransitionTime();
+ validateFirstNewEvent(curInputRepair, curRepair.getNewEvents().get(0), lastRemainingBPEventTime, lastRemainingEventTime);
+ }
+
+
+ SubscriptionDataRepair curOutputRepair = createSubscriptionDataRepair(curInputRepair, newBundleStartDate, newSubscriptionStartDate, remaining);
+ repairDao.initializeRepair(curInputRepair.getId(), remaining);
+ inRepair.add(curOutputRepair);
+ if (curOutputRepair.getCategory() == ProductCategory.ADD_ON) {
+ // Check if ADD_ON RE_CREATE is before BP start
+ if (isPlanRecreate && subscriptions.get(0).getStartDate().isAfter(curRepair.getNewEvents().get(0).getRequestedDate())) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_AO_CREATE_BEFORE_BP_START, cur.getId(), cur.getBundleId());
+ }
+ addOnSubscriptionInRepair.add(curOutputRepair);
+ } else if (curOutputRepair.getCategory() == ProductCategory.BASE) {
+ baseSubscriptionRepair = curOutputRepair;
+ }
+ }
+ }
+
+ final RepairType repairType = getRepairType(subscriptions.get(0), (baseSubscriptionRepair != null));
+ switch(repairType) {
+ case BASE_REPAIR:
+ // We need to add any existing addon that are not in the input repair list
+ for (Subscription cur : subscriptions) {
+ if (cur.getCategory() == ProductCategory.ADD_ON && !inRepair.contains(cur)) {
+ SubscriptionDataRepair curOutputRepair = createSubscriptionDataRepair((SubscriptionDataRepair) cur, newBundleStartDate, null, ((SubscriptionDataRepair) cur).getEvents());
+ repairDao.initializeRepair(curOutputRepair.getId(), ((SubscriptionDataRepair) cur).getEvents());
+ inRepair.add(curOutputRepair);
+ addOnSubscriptionInRepair.add(curOutputRepair);
+ }
+ }
+
+ break;
+ case ADD_ON_REPAIR:
+ // We need to set the baseSubscription as it is useful to calculate addon validity
+ SubscriptionDataRepair baseSubscription = (SubscriptionDataRepair) subscriptions.get(0);
+ baseSubscriptionRepair = createSubscriptionDataRepair(baseSubscription, baseSubscription.getBundleStartDate(), baseSubscription.getStartDate(), baseSubscription.getEvents());
+ break;
+ case STANDALONE_REPAIR:
+ default:
+ break;
+
+ }
+
+ validateBasePlanRecreate(isBasePlanRecreate, subscriptions, input.getSubscriptions());
+ validateInputSubscriptionsKnown(subscriptions, input.getSubscriptions());
+
+
+ Collection<NewEvent> newEvents = createOrderedNewEventInput(input.getSubscriptions());
+ Iterator<NewEvent> it = newEvents.iterator();
+ while (it.hasNext()) {
+ DefaultNewEvent cur = (DefaultNewEvent) it.next();
+ SubscriptionDataRepair curDataRepair = findSubscriptionDataRepair(cur.getSubscriptionId(), inRepair);
+ if (curDataRepair == null) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_SUBSCRIPTION, cur.getSubscriptionId());
+ }
+ curDataRepair.addNewRepairEvent(cur, baseSubscriptionRepair, addOnSubscriptionInRepair, context);
+ }
+
+ if (dryRun) {
+
+ baseSubscriptionRepair.addFutureAddonCancellation(addOnSubscriptionInRepair, context);
+
+ final List<SubscriptionTimeline> repairs = createGetSubscriptionRepairList(subscriptions, convertDataRepair(inRepair));
+ return createGetBundleRepair(input.getBundleId(), bundle.getKey(), input.getViewId(), repairs);
+ } else {
+ dao.repair(bundle.getAccountId(), input.getBundleId(), inRepair, context);
+ return getBundleRepair(input.getBundleId());
+ }
+ } catch (CatalogApiException e) {
+ throw new EntitlementRepairException(e);
+ } finally {
+ repairDao.cleanup();
+ }
+ }
+
+
+
+
+ private RepairType getRepairType(final Subscription firstSubscription, final boolean gotBaseSubscription) {
+ if (firstSubscription.getCategory() == ProductCategory.BASE) {
+ return gotBaseSubscription ? RepairType.BASE_REPAIR : RepairType.ADD_ON_REPAIR;
+ } else {
+ return RepairType.STANDALONE_REPAIR;
+ }
+ }
+
+ private void validateBasePlanRecreate(boolean isBasePlanRecreate, List<Subscription> subscriptions, List<SubscriptionTimeline> input)
+ throws EntitlementRepairException {
+
+ if (!isBasePlanRecreate) {
+ return;
+ }
+ if (subscriptions.size() != input.size()) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_BP_RECREATE_MISSING_AO, subscriptions.get(0).getBundleId());
+ }
+ for (SubscriptionTimeline cur : input) {
+ if (cur.getNewEvents().size() != 0
+ && (cur.getNewEvents().get(0).getSubscriptionTransitionType() != SubscriptionTransitionType.CREATE
+ && cur.getNewEvents().get(0).getSubscriptionTransitionType() != SubscriptionTransitionType.RE_CREATE)) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE, subscriptions.get(0).getBundleId());
+ }
+ }
+ }
+
+
+ private void validateInputSubscriptionsKnown(List<Subscription> subscriptions, List<SubscriptionTimeline> input)
+ throws EntitlementRepairException {
+
+ for (SubscriptionTimeline cur : input) {
+ boolean found = false;
+ for (Subscription s : subscriptions) {
+ if (s.getId().equals(cur.getId())) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_SUBSCRIPTION, cur.getId());
+ }
+ }
+ }
+
+ private void validateFirstNewEvent(final SubscriptionData data, final NewEvent firstNewEvent, final DateTime lastBPRemainingTime, final DateTime lastRemainingTime)
+ throws EntitlementRepairException {
+ if (lastBPRemainingTime != null &&
+ firstNewEvent.getRequestedDate().isBefore(lastBPRemainingTime)) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING, firstNewEvent.getSubscriptionTransitionType(), data.getId());
+ }
+ if (lastRemainingTime != null &&
+ firstNewEvent.getRequestedDate().isBefore(lastRemainingTime)) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING, firstNewEvent.getSubscriptionTransitionType(), data.getId());
+ }
+
+ }
+
+ private Collection<NewEvent> createOrderedNewEventInput(List<SubscriptionTimeline> subscriptionsReapir) {
+ TreeSet<NewEvent> newEventSet = new TreeSet<SubscriptionTimeline.NewEvent>(new Comparator<NewEvent>() {
+ @Override
+ public int compare(NewEvent o1, NewEvent o2) {
+ return o1.getRequestedDate().compareTo(o2.getRequestedDate());
+ }
+ });
+ for (SubscriptionTimeline cur : subscriptionsReapir) {
+ for (NewEvent e : cur.getNewEvents()) {
+ newEventSet.add(new DefaultNewEvent(cur.getId(), e.getPlanPhaseSpecifier(), e.getRequestedDate(), e.getSubscriptionTransitionType()));
+ }
+ }
+ return newEventSet;
+ }
+
+
+ private List<EntitlementEvent> getRemainingEventsAndValidateDeletedEvents(final SubscriptionDataRepair data, final DateTime firstBPDeletedTime,
+ final List<SubscriptionTimeline.DeletedEvent> deletedEvents)
+ throws EntitlementRepairException {
+
+ if (deletedEvents == null || deletedEvents.size() == 0) {
+ return data.getEvents();
+ }
+
+ int nbDeleted = 0;
+ LinkedList<EntitlementEvent> result = new LinkedList<EntitlementEvent>();
+ for (EntitlementEvent cur : data.getEvents()) {
+
+ boolean foundDeletedEvent = false;
+ for (SubscriptionTimeline.DeletedEvent d : deletedEvents) {
+ if (cur.getId().equals(d.getEventId())) {
+ foundDeletedEvent = true;
+ nbDeleted++;
+ break;
+ }
+ }
+ if (!foundDeletedEvent && nbDeleted > 0) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_INVALID_DELETE_SET, cur.getId(), data.getId());
+ }
+ if (firstBPDeletedTime != null &&
+ ! cur.getEffectiveDate().isBefore(firstBPDeletedTime) &&
+ ! foundDeletedEvent) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_MISSING_AO_DELETE_EVENT, cur.getId(), data.getId());
+ }
+
+ if (nbDeleted == 0) {
+ result.add(cur);
+ }
+ }
+ if (nbDeleted != deletedEvents.size()) {
+ for (SubscriptionTimeline.DeletedEvent d : deletedEvents) {
+ boolean found = false;
+ for (SubscriptionTransitionData cur : data.getAllTransitions()) {
+ if (cur.getId().equals(d.getEventId())) {
+ found = true;
+ continue;
+ }
+ }
+ if (!found) {
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_NON_EXISTENT_DELETE_EVENT, d.getEventId(), data.getId());
+ }
+ }
+
+ }
+ return result;
+ }
+
+
+ private String getViewId(DateTime lastUpdateBundleDate, List<Subscription> subscriptions) {
+ StringBuilder tmp = new StringBuilder();
+ long lastOrderedId = -1;
+ for (Subscription cur : subscriptions) {
+ lastOrderedId = lastOrderedId < ((SubscriptionData) cur).getLastEventOrderedId() ? ((SubscriptionData) cur).getLastEventOrderedId() : lastOrderedId;
+ }
+ tmp.append(lastOrderedId);
+ tmp.append("-");
+ tmp.append(lastUpdateBundleDate.toDate().getTime());
+ return tmp.toString();
+ }
+
+ private BundleTimeline createGetBundleRepair(final UUID bundleId, final String externalKey, final String viewId, final List<SubscriptionTimeline> repairList) {
+ return new BundleTimeline() {
+ @Override
+ public String getViewId() {
+ return viewId;
+ }
+ @Override
+ public List<SubscriptionTimeline> getSubscriptions() {
+ return repairList;
+ }
+ @Override
+ public UUID getBundleId() {
+ return bundleId;
+ }
+ @Override
+ public String getExternalKey() {
+ return externalKey;
+ }
+ };
+
+ }
+
+ private List<SubscriptionTimeline> createGetSubscriptionRepairList(final List<Subscription> subscriptions, final List<SubscriptionTimeline> inRepair) throws CatalogApiException {
+
+ final List<SubscriptionTimeline> result = new LinkedList<SubscriptionTimeline>();
+ Set<UUID> repairIds = new TreeSet<UUID>();
+ for (final SubscriptionTimeline cur : inRepair) {
+ repairIds.add(cur.getId());
+ result.add(cur);
+ }
+ for (final Subscription cur : subscriptions) {
+ if ( !repairIds.contains(cur.getId())) {
+ result.add(new DefaultSubscriptionTimeline((SubscriptionDataRepair) cur, catalogService.getFullCatalog()));
+ }
+ }
+ return result;
+ }
+
+
+ private List<SubscriptionTimeline> convertDataRepair(List<SubscriptionDataRepair> input) throws CatalogApiException {
+ List<SubscriptionTimeline> result = new LinkedList<SubscriptionTimeline>();
+ for (SubscriptionDataRepair cur : input) {
+ result.add(new DefaultSubscriptionTimeline(cur, catalogService.getFullCatalog()));
+ }
+ return result;
+ }
+
+ private SubscriptionDataRepair findSubscriptionDataRepair(final UUID targetId, final List<SubscriptionDataRepair> input) {
+ for (SubscriptionDataRepair cur : input) {
+ if (cur.getId().equals(targetId)) {
+ return cur;
+ }
+ }
+ return null;
+ }
+
+
+ private SubscriptionDataRepair createSubscriptionDataRepair(final SubscriptionData curData, final DateTime newBundleStartDate, final DateTime newSubscriptionStartDate, final List<EntitlementEvent> initialEvents) {
+ SubscriptionBuilder builder = new SubscriptionBuilder(curData);
+ builder.setActiveVersion(curData.getActiveVersion() + 1);
+ if (newBundleStartDate != null) {
+ builder.setBundleStartDate(newBundleStartDate);
+ }
+ if (newSubscriptionStartDate != null) {
+ builder.setStartDate(newSubscriptionStartDate);
+ }
+ if (initialEvents.size() > 0) {
+ for (EntitlementEvent cur : initialEvents) {
+ cur.setActiveVersion(builder.getActiveVersion());
+ }
+ }
+ SubscriptionDataRepair result = (SubscriptionDataRepair) factory.createSubscription(builder, initialEvents);
+ return result;
+ }
+
+
+ private SubscriptionTimeline findAndCreateSubscriptionRepair(final UUID target, final List<SubscriptionTimeline> input) {
+ for (SubscriptionTimeline cur : input) {
+ if (target.equals(cur.getId())) {
+ return new DefaultSubscriptionTimeline(cur);
+ }
+ }
+ return null;
+ }
+}
+
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultNewEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultNewEvent.java
new file mode 100644
index 0000000..87ece9f
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultNewEvent.java
@@ -0,0 +1,58 @@
+/*
+ * 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.timeline;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+
+public class DefaultNewEvent implements NewEvent {
+
+ private final UUID subscriptionId;
+ private final PlanPhaseSpecifier spec;
+ private final DateTime requestedDate;
+ private final SubscriptionTransitionType transitionType;
+
+ public DefaultNewEvent(final UUID subscriptionId, final PlanPhaseSpecifier spec, final DateTime requestedDate, final SubscriptionTransitionType transitionType) {
+ this.subscriptionId = subscriptionId;
+ this.spec = spec;
+ this.requestedDate = requestedDate;
+ this.transitionType = transitionType;
+ }
+
+ @Override
+ public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+ return spec;
+ }
+
+ @Override
+ public DateTime getRequestedDate() {
+ return requestedDate;
+ }
+
+ @Override
+ public SubscriptionTransitionType getSubscriptionTransitionType() {
+ return transitionType;
+ }
+
+ public UUID getSubscriptionId() {
+ return subscriptionId;
+ }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultRepairEntitlementEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultRepairEntitlementEvent.java
new file mode 100644
index 0000000..938f99f
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultRepairEntitlementEvent.java
@@ -0,0 +1,118 @@
+/*
+ * 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.timeline;
+
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementEvent;
+
+public class DefaultRepairEntitlementEvent implements RepairEntitlementEvent {
+
+ private final UUID userToken;
+ private final UUID bundleId;
+ private final UUID accountId;
+ private final DateTime effectiveDate;
+
+
+ @JsonCreator
+ public DefaultRepairEntitlementEvent(@JsonProperty("userToken") final UUID userToken,
+ @JsonProperty("accountId") final UUID accountId,
+ @JsonProperty("bundleId") final UUID bundleId,
+ @JsonProperty("effectiveDate") final DateTime effectiveDate) {
+ this.userToken = userToken;
+ this.bundleId = bundleId;
+ this.accountId = accountId;
+ this.effectiveDate = effectiveDate;
+ }
+
+ @JsonIgnore
+ @Override
+ public BusEventType getBusEventType() {
+ return BusEventType.BUNDLE_REPAIR;
+ }
+
+ @Override
+ public UUID getUserToken() {
+ return userToken;
+ }
+
+ @Override
+ public UUID getBundleId() {
+ return bundleId;
+ }
+
+ @Override
+ public UUID getAccountId() {
+ return accountId;
+ }
+
+ @Override
+ public DateTime getEffectiveDate() {
+ return effectiveDate;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((accountId == null) ? 0 : accountId.hashCode());
+ result = prime * result
+ + ((bundleId == null) ? 0 : bundleId.hashCode());
+ result = prime * result
+ + ((effectiveDate == null) ? 0 : effectiveDate.hashCode());
+ result = prime * result
+ + ((userToken == null) ? 0 : userToken.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DefaultRepairEntitlementEvent other = (DefaultRepairEntitlementEvent) obj;
+ if (accountId == null) {
+ if (other.accountId != null)
+ return false;
+ } else if (!accountId.equals(other.accountId))
+ return false;
+ if (bundleId == null) {
+ if (other.bundleId != null)
+ return false;
+ } else if (!bundleId.equals(other.bundleId))
+ return false;
+ if (effectiveDate == null) {
+ if (other.effectiveDate != null)
+ return false;
+ } else if (effectiveDate.compareTo(other.effectiveDate) != 0)
+ return false;
+ if (userToken == null) {
+ if (other.userToken != null)
+ return false;
+ } else if (!userToken.equals(other.userToken))
+ return false;
+ return true;
+ }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultSubscriptionTimeline.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultSubscriptionTimeline.java
new file mode 100644
index 0000000..4080326
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/DefaultSubscriptionTimeline.java
@@ -0,0 +1,288 @@
+/*
+ * 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.timeline;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.events.phase.PhaseEvent;
+import com.ning.billing.entitlement.events.user.ApiEvent;
+import com.ning.billing.entitlement.events.user.ApiEventType;
+
+public class DefaultSubscriptionTimeline implements SubscriptionTimeline {
+
+ private final UUID id;
+ private final List<ExistingEvent> existingEvents;
+ private final List<NewEvent> newEvents;
+ private final List<DeletedEvent> deletedEvents;
+
+ public DefaultSubscriptionTimeline(final UUID id) {
+ this.id = id;
+ this.existingEvents = Collections.<SubscriptionTimeline.ExistingEvent>emptyList();
+ this.deletedEvents = Collections.<SubscriptionTimeline.DeletedEvent>emptyList();
+ this.newEvents = Collections.<SubscriptionTimeline.NewEvent>emptyList();
+ }
+
+ public DefaultSubscriptionTimeline(SubscriptionTimeline input) {
+ this.id = input.getId();
+ this.existingEvents = (input.getExistingEvents() != null) ? new ArrayList<SubscriptionTimeline.ExistingEvent>(input.getExistingEvents()) :
+ Collections.<SubscriptionTimeline.ExistingEvent>emptyList();
+ sortExistingEvent(this.existingEvents);
+ this.deletedEvents = (input.getDeletedEvents() != null) ? new ArrayList<SubscriptionTimeline.DeletedEvent>(input.getDeletedEvents()) :
+ Collections.<SubscriptionTimeline.DeletedEvent>emptyList();
+ this.newEvents = (input.getNewEvents() != null) ? new ArrayList<SubscriptionTimeline.NewEvent>(input.getNewEvents()) :
+ Collections.<SubscriptionTimeline.NewEvent>emptyList();
+ sortNewEvent(this.newEvents);
+ }
+
+ // CTOR for returning events only
+ public DefaultSubscriptionTimeline(SubscriptionDataRepair input, Catalog catalog) throws CatalogApiException {
+ this.id = input.getId();
+ this.existingEvents = toExistingEvents(catalog, input.getActiveVersion(), input.getCategory(), input.getEvents());
+ this.deletedEvents = null;
+ this.newEvents = null;
+ }
+
+ private List<ExistingEvent> toExistingEvents(final Catalog catalog, final long activeVersion, final ProductCategory category, final List<EntitlementEvent> events)
+ throws CatalogApiException {
+
+ List<ExistingEvent> result = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+
+ String prevProductName = null;
+ BillingPeriod prevBillingPeriod = null;
+ String prevPriceListName = null;
+ PhaseType prevPhaseType = null;
+
+ DateTime startDate = null;
+
+ for (final EntitlementEvent cur : events) {
+
+ // First active event is used to figure out which catalog version to use.
+ //startDate = (startDate == null && cur.getActiveVersion() == activeVersion) ? cur.getEffectiveDate() : startDate;
+
+ // STEPH that needs tp be reviewed if we support mutli version events
+ if (cur.getActiveVersion() != activeVersion) {
+ continue;
+ }
+ startDate = (startDate == null) ? cur.getEffectiveDate() : startDate;
+
+
+ String productName = null;
+ BillingPeriod billingPeriod = null;
+ String priceListName = null;
+ PhaseType phaseType = null;
+
+ ApiEventType apiType = null;
+ switch (cur.getType()) {
+ case PHASE:
+ PhaseEvent phaseEV = (PhaseEvent) cur;
+ phaseType = catalog.findPhase(phaseEV.getPhase(), cur.getEffectiveDate(), startDate).getPhaseType();
+ productName = prevProductName;
+ billingPeriod = catalog.findPhase(phaseEV.getPhase(), cur.getEffectiveDate(), startDate).getBillingPeriod();
+ priceListName = prevPriceListName;
+ break;
+
+ case API_USER:
+ ApiEvent userEV = (ApiEvent) cur;
+ apiType = userEV.getEventType();
+ Plan plan = (userEV.getEventPlan() != null) ? catalog.findPlan(userEV.getEventPlan(), cur.getRequestedDate(), startDate) : null;
+ phaseType = (userEV.getEventPlanPhase() != null) ? catalog.findPhase(userEV.getEventPlanPhase(), cur.getEffectiveDate(), startDate).getPhaseType() : prevPhaseType;
+ productName = (plan != null) ? plan.getProduct().getName() : prevProductName;
+ billingPeriod = (userEV.getEventPlanPhase() != null) ? catalog.findPhase(userEV.getEventPlanPhase(), cur.getEffectiveDate(), startDate).getBillingPeriod() : prevBillingPeriod;
+ priceListName = (userEV.getPriceList() != null) ? userEV.getPriceList() : prevPriceListName;
+ break;
+ }
+
+ final SubscriptionTransitionType transitionType = SubscriptionTransitionData.toSubscriptionTransitionType(cur.getType(), apiType);
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
+ result.add(new ExistingEvent() {
+ @Override
+ public SubscriptionTransitionType getSubscriptionTransitionType() {
+ return transitionType;
+ }
+ @Override
+ public DateTime getRequestedDate() {
+ return cur.getRequestedDate();
+ }
+ @Override
+ public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+ return spec;
+ }
+ @Override
+ public UUID getEventId() {
+ return cur.getId();
+ }
+ @Override
+ public DateTime getEffectiveDate() {
+ return cur.getEffectiveDate();
+ }
+ });
+
+ prevProductName = productName;
+ prevBillingPeriod = billingPeriod;
+ prevPriceListName = priceListName;
+ prevPhaseType = phaseType;
+
+ }
+ sortExistingEvent(result);
+ return result;
+ }
+
+
+ /*
+
+ private List<ExistingEvent> toExistingEvents(final Catalog catalog, final long processingVersion, final ProductCategory category, final List<EntitlementEvent> events, List<ExistingEvent> result)
+ throws CatalogApiException {
+
+
+ String prevProductName = null;
+ BillingPeriod prevBillingPeriod = null;
+ String prevPriceListName = null;
+ PhaseType prevPhaseType = null;
+
+ DateTime startDate = null;
+
+ for (final EntitlementEvent cur : events) {
+
+ if (processingVersion != cur.getActiveVersion()) {
+ continue;
+ }
+
+ // First active event is used to figure out which catalog version to use.
+ startDate = (startDate == null && cur.getActiveVersion() == processingVersion) ? cur.getEffectiveDate() : startDate;
+
+ String productName = null;
+ BillingPeriod billingPeriod = null;
+ String priceListName = null;
+ PhaseType phaseType = null;
+
+ ApiEventType apiType = null;
+ switch (cur.getType()) {
+ case PHASE:
+ PhaseEvent phaseEV = (PhaseEvent) cur;
+ phaseType = catalog.findPhase(phaseEV.getPhase(), cur.getEffectiveDate(), startDate).getPhaseType();
+ productName = prevProductName;
+ billingPeriod = prevBillingPeriod;
+ priceListName = prevPriceListName;
+ break;
+
+ case API_USER:
+ ApiEvent userEV = (ApiEvent) cur;
+ apiType = userEV.getEventType();
+ Plan plan = (userEV.getEventPlan() != null) ? catalog.findPlan(userEV.getEventPlan(), cur.getRequestedDate(), startDate) : null;
+ phaseType = (userEV.getEventPlanPhase() != null) ? catalog.findPhase(userEV.getEventPlanPhase(), cur.getEffectiveDate(), startDate).getPhaseType() : prevPhaseType;
+ productName = (plan != null) ? plan.getProduct().getName() : prevProductName;
+ billingPeriod = (plan != null) ? plan.getBillingPeriod() : prevBillingPeriod;
+ priceListName = (userEV.getPriceList() != null) ? userEV.getPriceList() : prevPriceListName;
+ break;
+ }
+
+ final SubscriptionTransitionType transitionType = SubscriptionTransitionData.toSubscriptionTransitionType(cur.getType(), apiType);
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
+ result.add(new ExistingEvent() {
+ @Override
+ public SubscriptionTransitionType getSubscriptionTransitionType() {
+ return transitionType;
+ }
+ @Override
+ public DateTime getRequestedDate() {
+ return cur.getRequestedDate();
+ }
+ @Override
+ public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+ return spec;
+ }
+ @Override
+ public UUID getEventId() {
+ return cur.getId();
+ }
+ @Override
+ public DateTime getEffectiveDate() {
+ return cur.getEffectiveDate();
+ }
+ });
+ prevProductName = productName;
+ prevBillingPeriod = billingPeriod;
+ prevPriceListName = priceListName;
+ prevPhaseType = phaseType;
+ }
+ }
+ */
+
+
+
+
+
+
+ @Override
+ public UUID getId() {
+ return id;
+ }
+
+ @Override
+ public List<DeletedEvent> getDeletedEvents() {
+ return deletedEvents;
+ }
+
+ @Override
+ public List<NewEvent> getNewEvents() {
+ return newEvents;
+ }
+
+ @Override
+ public List<ExistingEvent> getExistingEvents() {
+ return existingEvents;
+ }
+
+ private void sortExistingEvent(final List<ExistingEvent> events) {
+ if (events != null) {
+ Collections.sort(events, new Comparator<ExistingEvent>() {
+ @Override
+ public int compare(ExistingEvent arg0, ExistingEvent arg1) {
+ return arg0.getEffectiveDate().compareTo(arg1.getEffectiveDate());
+ }
+ });
+ }
+ }
+ private void sortNewEvent(final List<NewEvent> events) {
+ if (events != null) {
+ Collections.sort(events, new Comparator<NewEvent>() {
+ @Override
+ public int compare(NewEvent arg0, NewEvent arg1) {
+ return arg0.getRequestedDate().compareTo(arg1.getRequestedDate());
+ }
+ });
+ }
+ }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementLifecycleDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementLifecycleDao.java
new file mode 100644
index 0000000..dff77b3
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairEntitlementLifecycleDao.java
@@ -0,0 +1,28 @@
+/*
+ * 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.timeline;
+
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.entitlement.events.EntitlementEvent;
+
+public interface RepairEntitlementLifecycleDao {
+
+ public void initializeRepair(final UUID subscriptionId, final List<EntitlementEvent> initialEvents);
+
+ public void cleanup();
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionApiService.java
new file mode 100644
index 0000000..dcc0c95
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionApiService.java
@@ -0,0 +1,36 @@
+/*
+ * 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.timeline;
+
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.entitlement.alignment.PlanAligner;
+import com.ning.billing.entitlement.api.SubscriptionApiService;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionApiService;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
+import com.ning.billing.util.clock.Clock;
+
+public class RepairSubscriptionApiService extends DefaultSubscriptionApiService implements SubscriptionApiService {
+
+ @Inject
+ public RepairSubscriptionApiService(Clock clock, @Named(DefaultEntitlementModule.REPAIR_NAMED) EntitlementDao dao,
+ CatalogService catalogService, PlanAligner planAligner) {
+ super(clock, dao, catalogService, planAligner);
+ }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionFactory.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionFactory.java
new file mode 100644
index 0000000..61933c3
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/RepairSubscriptionFactory.java
@@ -0,0 +1,56 @@
+/*
+ * 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.timeline;
+
+import java.util.List;
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.entitlement.api.SubscriptionApiService;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionApiService;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.engine.addon.AddonUtils;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
+import com.ning.billing.util.clock.Clock;
+
+public class RepairSubscriptionFactory extends DefaultSubscriptionFactory implements SubscriptionFactory {
+
+ private final AddonUtils addonUtils;
+ private final EntitlementDao repairDao;
+
+ @Inject
+ public RepairSubscriptionFactory(@Named(DefaultEntitlementModule.REPAIR_NAMED) SubscriptionApiService apiService,
+ @Named(DefaultEntitlementModule.REPAIR_NAMED) EntitlementDao dao,
+ Clock clock, CatalogService catalogService, AddonUtils addonUtils) {
+ super(apiService, clock, catalogService);
+ this.addonUtils = addonUtils;
+ this.repairDao = dao;
+ }
+
+ @Override
+ public SubscriptionData createSubscription(SubscriptionBuilder builder,
+ List<EntitlementEvent> events) {
+ SubscriptionData subscription = new SubscriptionDataRepair(builder, events, apiService, repairDao, clock, addonUtils, catalogService);
+ subscription.rebuildTransitions(events, catalogService.getFullCatalog());
+ return subscription;
+ }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionDataRepair.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionDataRepair.java
new file mode 100644
index 0000000..7f526d4
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/timeline/SubscriptionDataRepair.java
@@ -0,0 +1,210 @@
+/*
+ * 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.timeline;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.joda.time.DateTime;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.ning.billing.ErrorCode;
+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.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionApiService;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
+import com.ning.billing.entitlement.engine.addon.AddonUtils;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
+import com.ning.billing.entitlement.events.user.ApiEventBuilder;
+import com.ning.billing.entitlement.events.user.ApiEventCancel;
+
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.clock.Clock;
+
+public class SubscriptionDataRepair extends SubscriptionData {
+
+ private final AddonUtils addonUtils;
+ private final Clock clock;
+ private final EntitlementDao repairDao;
+ private final CatalogService catalogService;
+
+ private final List<EntitlementEvent> initialEvents;
+
+ // Low level events are ONLY used for Repair APIs
+ protected List<EntitlementEvent> events;
+
+
+ public SubscriptionDataRepair(SubscriptionBuilder builder, List<EntitlementEvent> initialEvents, SubscriptionApiService apiService,
+ EntitlementDao dao, Clock clock, AddonUtils addonUtils, CatalogService catalogService) {
+ super(builder, apiService, clock);
+ this.repairDao = dao;
+ this.addonUtils = addonUtils;
+ this.clock = clock;
+ this.catalogService = catalogService;
+ this.initialEvents = initialEvents;
+ }
+
+
+ DateTime getLastUserEventEffectiveDate() {
+ DateTime res = null;
+ for (EntitlementEvent cur : events) {
+ if (cur.getActiveVersion() != getActiveVersion()) {
+ break;
+ }
+ if (cur.getType() == EventType.PHASE) {
+ continue;
+ }
+ res = cur.getEffectiveDate();
+ }
+ return res;
+ }
+
+ public void addNewRepairEvent(final DefaultNewEvent input, final SubscriptionDataRepair baseSubscription, final List<SubscriptionDataRepair> addonSubscriptions, final CallContext context)
+ throws EntitlementRepairException {
+
+ try {
+ final PlanPhaseSpecifier spec = input.getPlanPhaseSpecifier();
+ switch(input.getSubscriptionTransitionType()) {
+ case CREATE:
+ case RE_CREATE:
+ recreate(spec, input.getRequestedDate(), context);
+ checkAddonRights(baseSubscription);
+ break;
+ case CHANGE:
+ changePlan(spec.getProductName(), spec.getBillingPeriod(), spec.getPriceListName(), input.getRequestedDate(), context);
+ checkAddonRights(baseSubscription);
+ trickleDownBPEffectForAddon(addonSubscriptions, getLastUserEventEffectiveDate(), context);
+ break;
+ case CANCEL:
+ cancel(input.getRequestedDate(), false, context);
+ trickleDownBPEffectForAddon(addonSubscriptions, getLastUserEventEffectiveDate(), context);
+ break;
+ case PHASE:
+ break;
+ default:
+ throw new EntitlementRepairException(ErrorCode.ENT_REPAIR_UNKNOWN_TYPE, input.getSubscriptionTransitionType(), id);
+ }
+ } catch (EntitlementUserApiException e) {
+ throw new EntitlementRepairException(e);
+ } catch (CatalogApiException e) {
+ throw new EntitlementRepairException(e);
+ }
+ }
+
+
+ public void addFutureAddonCancellation(List<SubscriptionDataRepair> addOnSubscriptionInRepair, final CallContext context) {
+
+ if (category != ProductCategory.BASE) {
+ return;
+ }
+
+ SubscriptionTransitionData pendingTransition = getPendingTransitionData();
+ if (pendingTransition == null) {
+ return;
+ }
+ Product baseProduct = (pendingTransition.getTransitionType() == SubscriptionTransitionType.CANCEL) ? null :
+ pendingTransition.getNextPlan().getProduct();
+
+ addAddonCancellationIfRequired(addOnSubscriptionInRepair, baseProduct, pendingTransition.getEffectiveTransitionTime(), context);
+ }
+
+ private void trickleDownBPEffectForAddon(final List<SubscriptionDataRepair> addOnSubscriptionInRepair, final DateTime effectiveDate, final CallContext context)
+ throws EntitlementUserApiException {
+
+ if (category != ProductCategory.BASE) {
+ return;
+ }
+
+ Product baseProduct = (getState() == SubscriptionState.CANCELLED ) ?
+ null : getCurrentPlan().getProduct();
+ addAddonCancellationIfRequired(addOnSubscriptionInRepair, baseProduct, effectiveDate, context);
+ }
+
+
+
+ private void addAddonCancellationIfRequired(final List<SubscriptionDataRepair> addOnSubscriptionInRepair, Product baseProduct, final DateTime effectiveDate, final CallContext context) {
+
+ DateTime now = clock.getUTCNow();
+ Iterator<SubscriptionDataRepair> it = addOnSubscriptionInRepair.iterator();
+ while (it.hasNext()) {
+ SubscriptionDataRepair cur = it.next();
+ if (cur.getState() == SubscriptionState.CANCELLED ||
+ cur.getCategory() != ProductCategory.ADD_ON) {
+ continue;
+ }
+ Plan addonCurrentPlan = cur.getCurrentPlan();
+ if (baseProduct == null ||
+ addonUtils.isAddonIncluded(baseProduct, addonCurrentPlan) ||
+ ! addonUtils.isAddonAvailable(baseProduct, addonCurrentPlan)) {
+
+ EntitlementEvent cancelEvent = new ApiEventCancel(new ApiEventBuilder()
+ .setSubscriptionId(cur.getId())
+ .setActiveVersion(cur.getActiveVersion())
+ .setProcessedDate(now)
+ .setEffectiveDate(effectiveDate)
+ .setRequestedDate(now)
+ .setUserToken(context.getUserToken())
+ .setFromDisk(true));
+ repairDao.cancelSubscription(cur.getId(), cancelEvent, context, 0);
+ cur.rebuildTransitions(repairDao.getEventsForSubscription(cur.getId()), catalogService.getFullCatalog());
+ }
+ }
+ }
+
+ private void checkAddonRights(final SubscriptionDataRepair baseSubscription)
+ throws EntitlementUserApiException, CatalogApiException {
+ if (category == ProductCategory.ADD_ON) {
+ addonUtils.checkAddonCreationRights(baseSubscription, getCurrentPlan());
+ }
+ }
+
+ public void rebuildTransitions(final List<EntitlementEvent> inputEvents, final Catalog catalog) {
+ this.events = inputEvents;
+ super.rebuildTransitions(inputEvents, catalog);
+ }
+
+ public List<EntitlementEvent> getEvents() {
+ return events;
+ }
+
+ public List<EntitlementEvent> getInitialEvents() {
+ return initialEvents;
+ }
+
+
+ public Collection<EntitlementEvent> getNewEvents() {
+ Collection<EntitlementEvent> newEvents = Collections2.filter(events, new Predicate<EntitlementEvent>() {
+ @Override
+ public boolean apply(EntitlementEvent input) {
+ return ! initialEvents.contains(input);
+ }
+ });
+ return newEvents;
+ }
+}
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 5554c9c..7d92852 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
@@ -16,26 +16,30 @@
package com.ning.billing.entitlement.api.user;
+import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
-import com.ning.billing.catalog.api.Catalog;
-import com.ning.billing.util.callcontext.CallContext;
import org.joda.time.DateTime;
+
import com.google.inject.Inject;
import com.ning.billing.ErrorCode;
+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.PriceListSet;
-import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.SubscriptionStatusDryRun.DryRunChangeReason;
import com.ning.billing.entitlement.engine.addon.AddonUtils;
import com.ning.billing.entitlement.engine.dao.EntitlementDao;
import com.ning.billing.entitlement.exceptions.EntitlementError;
+import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.DefaultClock;
@@ -43,13 +47,13 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
private final Clock clock;
private final EntitlementDao dao;
private final CatalogService catalogService;
- private final SubscriptionApiService apiService;
+ private final DefaultSubscriptionApiService apiService;
private final AddonUtils addonUtils;
private final SubscriptionFactory subscriptionFactory;
@Inject
public DefaultEntitlementUserApi(Clock clock, EntitlementDao dao, CatalogService catalogService,
- SubscriptionApiService apiService, final SubscriptionFactory subscriptionFactory, AddonUtils addonUtils) {
+ DefaultSubscriptionApiService apiService, final SubscriptionFactory subscriptionFactory, AddonUtils addonUtils) {
super();
this.clock = clock;
this.apiService = apiService;
@@ -59,19 +63,32 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
this.subscriptionFactory = subscriptionFactory;
}
+
@Override
- public SubscriptionBundle getBundleFromId(UUID id) {
- return dao.getSubscriptionBundleFromId(id);
+ public SubscriptionBundle getBundleFromId(UUID id) throws EntitlementUserApiException {
+ SubscriptionBundle result = dao.getSubscriptionBundleFromId(id);
+ if (result == null) {
+ throw new EntitlementUserApiException(ErrorCode.ENT_GET_INVALID_BUNDLE_ID, id.toString());
+ }
+ return result;
}
@Override
- public Subscription getSubscriptionFromId(UUID id) {
- return dao.getSubscriptionFromId(subscriptionFactory, id);
+ public Subscription getSubscriptionFromId(UUID id) throws EntitlementUserApiException {
+ Subscription result = dao.getSubscriptionFromId(subscriptionFactory, id);
+ if (result == null) {
+ throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_SUBSCRIPTION_ID, id);
+ }
+ return result;
}
@Override
- public SubscriptionBundle getBundleForKey(String bundleKey) {
- return dao.getSubscriptionBundleFromKey(bundleKey);
+ public SubscriptionBundle getBundleForKey(String bundleKey) throws EntitlementUserApiException {
+ SubscriptionBundle result = dao.getSubscriptionBundleFromKey(bundleKey);
+ if (result == null) {
+ throw new EntitlementUserApiException(ErrorCode.ENT_GET_INVALID_BUNDLE_KEY, bundleKey);
+ }
+ return result;
}
@Override
@@ -90,9 +107,18 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
}
@Override
+ public Subscription getBaseSubscription(UUID bundleId) throws EntitlementUserApiException {
+ Subscription result = dao.getBaseSubscription(subscriptionFactory, bundleId);
+ if (result == null) {
+ throw new EntitlementUserApiException(ErrorCode.ENT_GET_NO_SUCH_BASE_SUBSCRIPTION, bundleId);
+ }
+ return result;
+ }
+
+
public SubscriptionBundle createBundleForAccount(UUID accountId, String bundleName, CallContext context)
throws EntitlementUserApiException {
- SubscriptionBundleData bundle = new SubscriptionBundleData(bundleName, accountId);
+ SubscriptionBundleData bundle = new SubscriptionBundleData(bundleName, accountId, clock.getUTCNow());
return dao.createSubscriptionBundle(bundle, context);
}
@@ -141,7 +167,10 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
if (baseSubscription == null) {
throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_NO_BP, bundleId);
}
- checkAddonCreationRights(baseSubscription, plan);
+ if (effectiveDate.isBefore(baseSubscription.getStartDate())) {
+ throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_DATE, requestedDate.toString());
+ }
+ addonUtils.checkAddonCreationRights(baseSubscription, plan);
bundleStartDate = baseSubscription.getStartDate();
break;
case STANDALONE:
@@ -157,7 +186,7 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
}
SubscriptionData subscription = apiService.createPlan(new SubscriptionBuilder()
- .setId(UUID.randomUUID())
+ .setId(UUID.randomUUID())
.setBundleId(bundleId)
.setCategory(plan.getProduct().getCategory())
.setBundleStartDate(bundleStartDate)
@@ -171,39 +200,58 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
}
- private void checkAddonCreationRights(SubscriptionData baseSubscription, Plan targetAddOnPlan)
- throws EntitlementUserApiException, CatalogApiException {
-
- if (baseSubscription.getState() != SubscriptionState.ACTIVE) {
- throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_BP_NON_ACTIVE, targetAddOnPlan.getName());
+ @Override
+ public DateTime getNextBillingDate(UUID accountId) {
+ List<SubscriptionBundle> bundles = getBundlesForAccount(accountId);
+ DateTime result = null;
+ for(SubscriptionBundle bundle : bundles) {
+ List<Subscription> subscriptions = getSubscriptionsForBundle(bundle.getId());
+ for(Subscription subscription : subscriptions) {
+ DateTime chargedThruDate = subscription.getChargedThroughDate();
+ if(result == null ||
+ (chargedThruDate != null && chargedThruDate.isBefore(result))) {
+ result = subscription.getChargedThroughDate();
+ }
+ }
}
+ return result;
+ }
- Product baseProduct = baseSubscription.getCurrentPlan().getProduct();
- if (addonUtils.isAddonIncluded(baseProduct, targetAddOnPlan)) {
- throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_ALREADY_INCLUDED,
- targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
- }
- if (!addonUtils.isAddonAvailable(baseProduct, targetAddOnPlan)) {
- throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_NOT_AVAILABLE,
- targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
+ @Override
+ public List<SubscriptionStatusDryRun> getDryRunChangePlanStatus(UUID subscriptionId, String baseProductName, DateTime requestedDate)
+ throws EntitlementUserApiException {
+
+ Subscription subscription = dao.getSubscriptionFromId(subscriptionFactory, subscriptionId);
+ if (subscription == null) {
+ throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_SUBSCRIPTION_ID, subscriptionId);
+ }
+ if (subscription.getCategory() != ProductCategory.BASE) {
+ throw new EntitlementUserApiException(ErrorCode.ENT_CHANGE_DRY_RUN_NOT_BP);
+ }
+
+ List<SubscriptionStatusDryRun> result = new LinkedList<SubscriptionStatusDryRun>();
+
+ List<Subscription> bundleSubscriptions = dao.getSubscriptions(subscriptionFactory, subscription.getBundleId());
+ for (Subscription cur : bundleSubscriptions) {
+ if (cur.getId().equals(subscriptionId)) {
+ continue;
+ }
+
+ DryRunChangeReason reason = null;
+ if (addonUtils.isAddonIncludedFromProdName(baseProductName, requestedDate, cur.getCurrentPlan())) {
+ reason = DryRunChangeReason.AO_INCLUDED_IN_NEW_PLAN;
+ } else if (addonUtils.isAddonAvailableFromProdName(baseProductName, requestedDate, cur.getCurrentPlan())) {
+ reason = DryRunChangeReason.AO_AVAILABLE_IN_NEW_PLAN;
+ } else {
+ reason = DryRunChangeReason.AO_NOT_AVAILABLE_IN_NEW_PLAN;
+ }
+ SubscriptionStatusDryRun status = new DefaultSubscriptionStatusDryRun(cur.getId(),
+ cur.getCurrentPlan().getProduct().getName(), cur.getCurrentPhase().getPhaseType(),
+ cur.getCurrentPlan().getBillingPeriod(),
+ cur.getCurrentPriceList().getName(), reason);
+ result.add(status);
}
+ return result;
}
-
- @Override
- public DateTime getNextBillingDate(UUID accountId) {
- List<SubscriptionBundle> bundles = getBundlesForAccount(accountId);
- DateTime result = null;
- for(SubscriptionBundle bundle : bundles) {
- List<Subscription> subscriptions = getSubscriptionsForBundle(bundle.getId());
- for(Subscription subscription : subscriptions) {
- DateTime chargedThruDate = subscription.getChargedThroughDate();
- if(result == null ||
- (chargedThruDate != null && chargedThruDate.isBefore(result))) {
- result = subscription.getChargedThroughDate();
- }
- }
- }
- return result;
- }
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionEvent.java
new file mode 100644
index 0000000..3cf74ec
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionEvent.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.entitlement.api.user;
+
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+
+public class DefaultSubscriptionEvent implements SubscriptionEvent {
+
+ private final Long totalOrdering;
+ private final UUID subscriptionId;
+ private final UUID bundleId;
+ private final UUID eventId;
+ private final DateTime requestedTransitionTime;
+ private final DateTime effectiveTransitionTime;
+ private final SubscriptionState previousState;
+ private final String previousPriceList;
+ private final String previousPlan;
+ private final String previousPhase;
+ private final SubscriptionState nextState;
+ private final String nextPriceList;
+ private final String nextPlan;
+ private final String nextPhase;
+ private final Integer remainingEventsForUserOperation;
+ private final UUID userToken;
+ private final SubscriptionTransitionType transitionType;
+
+ private final DateTime startDate;
+
+ public DefaultSubscriptionEvent(final SubscriptionTransitionData in, final DateTime startDate) {
+ this(in.getId(),
+ in.getSubscriptionId(),
+ in.getBundleId(),
+ in.getRequestedTransitionTime(),
+ in.getEffectiveTransitionTime(),
+ in.getPreviousState(),
+ (in.getPreviousPlan() != null ) ? in.getPreviousPlan().getName() : null,
+ (in.getPreviousPhase() != null) ? in.getPreviousPhase().getName() : null,
+ (in.getPreviousPriceList() != null) ? in.getPreviousPriceList().getName() : null,
+ in.getNextState(),
+ (in.getNextPlan() != null) ? in.getNextPlan().getName() : null,
+ (in.getNextPhase() != null) ? in.getNextPhase().getName() : null,
+ (in.getNextPriceList() != null) ? in.getNextPriceList().getName() : null,
+ in.getTotalOrdering(),
+ in.getUserToken(),
+ in.getTransitionType(),
+ in.getRemainingEventsForUserOperation(),
+ startDate);
+ }
+
+ @JsonCreator
+ public DefaultSubscriptionEvent(@JsonProperty("eventId") UUID eventId,
+ @JsonProperty("subscriptionId") UUID subscriptionId,
+ @JsonProperty("bundleId") UUID bundleId,
+ @JsonProperty("requestedTransitionTime") DateTime requestedTransitionTime,
+ @JsonProperty("effectiveTransitionTime") DateTime effectiveTransitionTime,
+ @JsonProperty("previousState") SubscriptionState previousState,
+ @JsonProperty("previousPlan") String previousPlan,
+ @JsonProperty("previousPhase") String previousPhase,
+ @JsonProperty("previousPriceList") String previousPriceList,
+ @JsonProperty("nextState") SubscriptionState nextState,
+ @JsonProperty("nextPlan") String nextPlan,
+ @JsonProperty("nextPhase") String nextPhase,
+ @JsonProperty("nextPriceList") String nextPriceList,
+ @JsonProperty("totalOrdering") Long totalOrdering,
+ @JsonProperty("userToken") UUID userToken,
+ @JsonProperty("transitionType") SubscriptionTransitionType transitionType,
+ @JsonProperty("remainingEventsForUserOperation") Integer remainingEventsForUserOperation,
+ @JsonProperty("startDate") DateTime startDate) {
+ super();
+ this.eventId = eventId;
+ this.subscriptionId = subscriptionId;
+ this.bundleId = bundleId;
+ this.requestedTransitionTime = requestedTransitionTime;
+ this.effectiveTransitionTime = effectiveTransitionTime;
+ this.previousState = previousState;
+ this.previousPriceList = previousPriceList;
+ this.previousPlan = previousPlan;
+ this.previousPhase = previousPhase;
+ this.nextState = nextState;
+ this.nextPlan = nextPlan;
+ this.nextPriceList = nextPriceList;
+ this.nextPhase = nextPhase;
+ this.totalOrdering = totalOrdering;
+ this.userToken = userToken;
+ this.transitionType = transitionType;
+ this.remainingEventsForUserOperation = remainingEventsForUserOperation;
+ this.startDate = startDate;
+ }
+
+ @JsonIgnore
+ @Override
+ public BusEventType getBusEventType() {
+ return BusEventType.SUBSCRIPTION_TRANSITION;
+ }
+
+ @JsonProperty("eventId")
+ @Override
+ public UUID getId() {
+ return eventId;
+ }
+
+ @Override
+ public UUID getSubscriptionId() {
+ return subscriptionId;
+ }
+
+ @Override
+ public UUID getBundleId() {
+ return bundleId;
+ }
+
+
+ @Override
+ public SubscriptionState getPreviousState() {
+ return previousState;
+ }
+
+ @Override
+ public String getPreviousPlan() {
+ return previousPlan;
+ }
+
+ @Override
+ public String getPreviousPhase() {
+ return previousPhase;
+ }
+
+ @Override
+ public String getNextPlan() {
+ return nextPlan;
+ }
+
+ @Override
+ public String getNextPhase() {
+ return nextPhase;
+ }
+
+ @Override
+ public SubscriptionState getNextState() {
+ return nextState;
+ }
+
+
+ @Override
+ public String getPreviousPriceList() {
+ return previousPriceList;
+ }
+
+ @Override
+ public String getNextPriceList() {
+ return nextPriceList;
+ }
+
+ @Override
+ public UUID getUserToken() {
+ return userToken;
+ }
+
+ @Override
+ public Integer getRemainingEventsForUserOperation() {
+ return remainingEventsForUserOperation;
+ }
+
+
+ @Override
+ public DateTime getRequestedTransitionTime() {
+ return requestedTransitionTime;
+ }
+
+ @Override
+ public DateTime getEffectiveTransitionTime() {
+ return effectiveTransitionTime;
+ }
+
+ @Override
+ public Long getTotalOrdering() {
+ return totalOrdering;
+ }
+
+ @Override
+ public SubscriptionTransitionType getTransitionType() {
+ return transitionType;
+ }
+
+ @JsonProperty("startDate")
+ @Override
+ public DateTime getSubscriptionStartDate() {
+ return startDate;
+ }
+
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((bundleId == null) ? 0 : bundleId.hashCode());
+ result = prime
+ * result
+ + ((effectiveTransitionTime == null) ? 0
+ : effectiveTransitionTime.hashCode());
+ result = prime * result + ((eventId == null) ? 0 : eventId.hashCode());
+ result = prime * result
+ + ((nextPhase == null) ? 0 : nextPhase.hashCode());
+ result = prime * result
+ + ((nextPlan == null) ? 0 : nextPlan.hashCode());
+ result = prime * result
+ + ((nextPriceList == null) ? 0 : nextPriceList.hashCode());
+ result = prime * result
+ + ((nextState == null) ? 0 : nextState.hashCode());
+ result = prime * result
+ + ((previousPhase == null) ? 0 : previousPhase.hashCode());
+ result = prime * result
+ + ((previousPlan == null) ? 0 : previousPlan.hashCode());
+ result = prime
+ * result
+ + ((previousPriceList == null) ? 0 : previousPriceList
+ .hashCode());
+ result = prime * result
+ + ((previousState == null) ? 0 : previousState.hashCode());
+ result = prime
+ * result
+ + ((remainingEventsForUserOperation == null) ? 0
+ : remainingEventsForUserOperation.hashCode());
+ result = prime
+ * result
+ + ((requestedTransitionTime == null) ? 0
+ : requestedTransitionTime.hashCode());
+ result = prime * result
+ + ((subscriptionId == null) ? 0 : subscriptionId.hashCode());
+ result = prime * result
+ + ((totalOrdering == null) ? 0 : totalOrdering.hashCode());
+ result = prime * result
+ + ((transitionType == null) ? 0 : transitionType.hashCode());
+ result = prime * result
+ + ((userToken == null) ? 0 : userToken.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DefaultSubscriptionEvent other = (DefaultSubscriptionEvent) obj;
+ if (bundleId == null) {
+ if (other.bundleId != null)
+ return false;
+ } else if (!bundleId.equals(other.bundleId))
+ return false;
+ if (effectiveTransitionTime == null) {
+ if (other.effectiveTransitionTime != null)
+ return false;
+ } else if (effectiveTransitionTime
+ .compareTo(other.effectiveTransitionTime) != 0)
+ return false;
+ if (eventId == null) {
+ if (other.eventId != null)
+ return false;
+ } else if (!eventId.equals(other.eventId))
+ return false;
+ if (nextPhase == null) {
+ if (other.nextPhase != null)
+ return false;
+ } else if (!nextPhase.equals(other.nextPhase))
+ return false;
+ if (nextPlan == null) {
+ if (other.nextPlan != null)
+ return false;
+ } else if (!nextPlan.equals(other.nextPlan))
+ return false;
+ if (nextPriceList == null) {
+ if (other.nextPriceList != null)
+ return false;
+ } else if (!nextPriceList.equals(other.nextPriceList))
+ return false;
+ if (nextState != other.nextState)
+ return false;
+ if (previousPhase == null) {
+ if (other.previousPhase != null)
+ return false;
+ } else if (!previousPhase.equals(other.previousPhase))
+ return false;
+ if (previousPlan == null) {
+ if (other.previousPlan != null)
+ return false;
+ } else if (!previousPlan.equals(other.previousPlan))
+ return false;
+ if (previousPriceList == null) {
+ if (other.previousPriceList != null)
+ return false;
+ } else if (!previousPriceList.equals(other.previousPriceList))
+ return false;
+ if (previousState != other.previousState)
+ return false;
+ if (remainingEventsForUserOperation == null) {
+ if (other.remainingEventsForUserOperation != null)
+ return false;
+ } else if (!remainingEventsForUserOperation
+ .equals(other.remainingEventsForUserOperation))
+ return false;
+ if (requestedTransitionTime == null) {
+ if (other.requestedTransitionTime != null)
+ return false;
+ } else if (requestedTransitionTime
+ .compareTo(other.requestedTransitionTime) != 0)
+ return false;
+ if (subscriptionId == null) {
+ if (other.subscriptionId != null)
+ return false;
+ } else if (!subscriptionId.equals(other.subscriptionId))
+ return false;
+ if (totalOrdering == null) {
+ if (other.totalOrdering != null)
+ return false;
+ } else if (!totalOrdering.equals(other.totalOrdering))
+ return false;
+ if (transitionType != other.transitionType)
+ return false;
+ if (userToken == null) {
+ if (other.userToken != null)
+ return false;
+ } else if (!userToken.equals(other.userToken))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "DefaultSubscriptionEvent [transitionType=" + transitionType
+ + ", effectiveTransitionTime=" + effectiveTransitionTime
+ + ", totalOrdering=" + totalOrdering
+ + ", subscriptionId=" + subscriptionId + ", bundleId="
+ + bundleId + ", eventId=" + eventId
+ + ", requestedTransitionTime=" + requestedTransitionTime
+ + ", previousState=" + previousState + ", previousPriceList="
+ + previousPriceList + ", previousPlan=" + previousPlan
+ + ", previousPhase=" + previousPhase + ", nextState="
+ + nextState + ", nextPriceList=" + nextPriceList
+ + ", nextPlan=" + nextPlan + ", nextPhase=" + nextPhase
+ + ", remainingEventsForUserOperation="
+ + remainingEventsForUserOperation + ", userToken=" + userToken
+ + ", startDate=" + startDate + "]";
+
+ }
+
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionStatusDryRun.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionStatusDryRun.java
new file mode 100644
index 0000000..9971f41
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionStatusDryRun.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.entitlement.api.user;
+
+import java.util.UUID;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+
+public class DefaultSubscriptionStatusDryRun implements SubscriptionStatusDryRun {
+
+ private final UUID id;
+ private final String productName;
+ private final PhaseType phaseType;
+ private final BillingPeriod billingPeriod;
+ private final String priceList;
+ private final DryRunChangeReason reason;
+
+
+ public DefaultSubscriptionStatusDryRun(final UUID id, final String productName,
+ final PhaseType phaseType, final BillingPeriod billingPeriod, final String priceList,
+ final DryRunChangeReason reason) {
+ super();
+ this.id = id;
+ this.productName = productName;
+ this.phaseType = phaseType;
+ this.billingPeriod = billingPeriod;
+ this.priceList = priceList;
+ this.reason = reason;
+ }
+
+ @Override
+ public UUID getId() {
+ return id;
+ }
+
+ @Override
+ public String getProductName() {
+ return productName;
+ }
+
+ @Override
+ public PhaseType getPhaseType() {
+ return phaseType;
+ }
+
+
+ @Override
+ public BillingPeriod getBillingPeriod() {
+ return billingPeriod;
+ }
+
+ @Override
+ public String getPriceList() {
+ return priceList;
+ }
+
+ @Override
+ public DryRunChangeReason getReason() {
+ return reason;
+ }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java
index 8cc2573..4da1fe0 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java
@@ -16,9 +16,12 @@
package com.ning.billing.entitlement.api.user;
+import java.util.UUID;
+
import org.joda.time.DateTime;
-import java.util.UUID;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.overdue.OverdueState;
public class SubscriptionBundleData implements SubscriptionBundle {
@@ -26,17 +29,25 @@ public class SubscriptionBundleData implements SubscriptionBundle {
private final String key;
private final UUID accountId;
private final DateTime startDate;
+ private final DateTime lastSysTimeUpdate;
+ private final OverdueState<SubscriptionBundle> overdueState;
+
+ public SubscriptionBundleData(String name, UUID accountId, DateTime startDate) {
+ this(UUID.randomUUID(), name, accountId, startDate, startDate);
+ }
- public SubscriptionBundleData(String name, UUID accountId) {
- this(UUID.randomUUID(), name, accountId, null);
+ public SubscriptionBundleData(UUID id, String key, UUID accountId, DateTime startDate, DateTime lastSysUpdate) {
+ this(id, key, accountId, startDate, lastSysUpdate, null);
}
- public SubscriptionBundleData(UUID id, String key, UUID accountId, DateTime startDate) {
+ public SubscriptionBundleData(UUID id, String key, UUID accountId, DateTime startDate, DateTime lastSysUpdate, OverdueState<SubscriptionBundle> overdueState) {
super();
this.id = id;
this.key = key;
this.accountId = accountId;
this.startDate = startDate;
+ this.lastSysTimeUpdate = lastSysUpdate;
+ this.overdueState = overdueState;
}
@Override
@@ -54,10 +65,23 @@ public class SubscriptionBundleData implements SubscriptionBundle {
return accountId;
}
-
// STEPH do we need it ? and should we return that and when is that populated/updated?
@Override
public DateTime getStartDate() {
return startDate;
}
+
+ public DateTime getLastSysUpdateTime() {
+ return lastSysTimeUpdate;
+ }
+
+ @Override
+ public OverdueState<SubscriptionBundle> getOverdueState() {
+ return overdueState;
+ }
+
+ @Override
+ public BlockingState getBlockingState() {
+ throw new UnsupportedOperationException();
+ }
}
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 a67c502..815410a 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
@@ -16,6 +16,19 @@
package com.ning.billing.entitlement.api.user;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import com.ning.billing.util.dao.ObjectType;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import com.ning.billing.catalog.api.ActionPolicy;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Catalog;
@@ -23,9 +36,11 @@ 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.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceList;
import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.SubscriptionApiService;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
import com.ning.billing.entitlement.api.user.SubscriptionTransitionDataIterator.Kind;
import com.ning.billing.entitlement.api.user.SubscriptionTransitionDataIterator.Order;
import com.ning.billing.entitlement.api.user.SubscriptionTransitionDataIterator.TimeLimit;
@@ -35,58 +50,51 @@ import com.ning.billing.entitlement.events.phase.PhaseEvent;
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.junction.api.BlockingState;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.customfield.CustomField;
-
import com.ning.billing.util.entity.ExtendedEntityBase;
-import org.joda.time.DateTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.annotation.Nullable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.UUID;
public class SubscriptionData extends ExtendedEntityBase implements Subscription {
private final static Logger log = LoggerFactory.getLogger(SubscriptionData.class);
- private final Clock clock;
- private final SubscriptionApiService apiService;
+
+ protected final Clock clock;
+ protected final SubscriptionApiService apiService;
//
// Final subscription fields
//
- private final UUID bundleId;
- private final DateTime startDate;
- private final DateTime bundleStartDate;
- private final ProductCategory category;
+ protected final UUID bundleId;
+ protected final DateTime startDate;
+ protected final DateTime bundleStartDate;
+ protected final ProductCategory category;
//
- // Those can be modified through non User APIs, and a new Subscription object would be created
+ // Those can be modified through non User APIs, and a new Subscription
+ // object would be created
//
- private final long activeVersion;
- private final DateTime chargedThroughDate;
- private final DateTime paidThroughDate;
+ protected final long activeVersion;
+ protected final DateTime chargedThroughDate;
+ protected final DateTime paidThroughDate;
+
//
// User APIs (create, change, cancel,...) will recompute those each time,
// so the user holding that subscription object get the correct state when
// the call completes
//
- private LinkedList<SubscriptionTransitionData> transitions;
+ protected LinkedList<SubscriptionTransitionData> transitions;
// Transient object never returned at the API
public SubscriptionData(SubscriptionBuilder builder) {
this(builder, null, null);
}
- public SubscriptionData(SubscriptionBuilder builder, @Nullable SubscriptionApiService apiService,
- @Nullable Clock clock) {
- super(builder.getId(), null, null);
+ public SubscriptionData(SubscriptionBuilder builder,
+ @Nullable SubscriptionApiService apiService, @Nullable Clock clock) {
+ super(builder.getId());
this.apiService = apiService;
this.clock = clock;
this.bundleId = builder.getBundleId();
@@ -99,12 +107,13 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
}
@Override
- public String getObjectName() {
- return "Subscription";
+ public ObjectType getObjectType() {
+ return ObjectType.SUBSCRIPTION;
}
@Override
- public void saveFieldValue(String fieldName, @Nullable String fieldValue, CallContext context) {
+ public void saveFieldValue(String fieldName, @Nullable String fieldValue,
+ CallContext context) {
super.setFieldValue(fieldName, fieldValue);
apiService.commitCustomFields(this, context);
}
@@ -133,107 +142,105 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
@Override
public SubscriptionState getState() {
- return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextState();
+ return (getPreviousTransition() == null) ? null
+ : getPreviousTransition().getNextState();
}
@Override
public PlanPhase getCurrentPhase() {
- return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPhase();
+ return (getPreviousTransitionData() == null) ? null
+ : getPreviousTransitionData().getNextPhase();
}
-
@Override
public Plan getCurrentPlan() {
- return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPlan();
+ return (getPreviousTransitionData() == null) ? null
+ : getPreviousTransitionData().getNextPlan();
}
@Override
- public String getCurrentPriceList() {
- return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPriceList();
- }
+ public PriceList getCurrentPriceList() {
+ return (getPreviousTransitionData() == null) ? null :
+ getPreviousTransitionData().getNextPriceList();
+ }
@Override
public DateTime getEndDate() {
- SubscriptionTransition latestTransition = getPreviousTransition();
+ SubscriptionEvent latestTransition = getPreviousTransition();
if (latestTransition.getNextState() == SubscriptionState.CANCELLED) {
return latestTransition.getEffectiveTransitionTime();
}
return null;
}
-
@Override
- public void cancel(DateTime requestedDate, boolean eot, CallContext context) throws EntitlementUserApiException {
- apiService.cancel(this, requestedDate, eot, context);
+ public boolean cancel(DateTime requestedDate, boolean eot,
+ CallContext context) throws EntitlementUserApiException {
+ return apiService.cancel(this, requestedDate, eot, context);
}
@Override
- public void uncancel(CallContext context) throws EntitlementUserApiException {
- apiService.uncancel(this, context);
+ public boolean uncancel(CallContext context)
+ throws EntitlementUserApiException {
+ return apiService.uncancel(this, context);
}
@Override
- public void changePlan(String productName, BillingPeriod term,
- String priceList, DateTime requestedDate, CallContext context) throws EntitlementUserApiException {
- apiService.changePlan(this, productName, term, priceList, requestedDate, context);
+ public boolean changePlan(String productName, BillingPeriod term,
+ String priceList, DateTime requestedDate, CallContext context)
+ throws EntitlementUserApiException {
+ return apiService.changePlan(this, productName, term, priceList,
+ requestedDate, context);
}
@Override
- public void recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
- throws EntitlementUserApiException {
- apiService.recreatePlan(this, spec, requestedDate, context);
+ public boolean recreate(PlanPhaseSpecifier spec, DateTime requestedDate,
+ CallContext context) throws EntitlementUserApiException {
+ return apiService.recreatePlan(this, spec, requestedDate, context);
}
- public List<SubscriptionTransition> getBillingTransitions() {
-
- if (transitions == null) {
- return Collections.emptyList();
- }
- List<SubscriptionTransition> result = new ArrayList<SubscriptionTransition>();
- SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
- Order.ASC_FROM_PAST, Kind.BILLING, Visibility.ALL, TimeLimit.ALL);
- while (it.hasNext()) {
- result.add(it.next());
+ @Override
+ public SubscriptionEvent getPendingTransition() {
+ SubscriptionTransitionData data = getPendingTransitionData();
+ if (data == null) {
+ return null;
}
- return result;
+ return new DefaultSubscriptionEvent(data, startDate);
}
-
+
@Override
- public SubscriptionTransition getPendingTransition() {
+ public BlockingState getBlockingState() {
+ throw new UnsupportedOperationException();
+ }
+ protected SubscriptionTransitionData getPendingTransitionData() {
if (transitions == null) {
return null;
}
- SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
- Order.ASC_FROM_PAST, Kind.ENTITLEMENT, Visibility.ALL, TimeLimit.FUTURE_ONLY);
+ SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+ clock, transitions, Order.ASC_FROM_PAST, Kind.ENTITLEMENT,
+ Visibility.ALL, TimeLimit.FUTURE_ONLY);
return it.hasNext() ? it.next() : null;
}
-
+
@Override
- public SubscriptionTransition getPreviousTransition() {
- if (transitions == null) {
+ public SubscriptionEvent getPreviousTransition() {
+ SubscriptionTransitionData data = getPreviousTransitionData();
+ if (data == null) {
return null;
}
- SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
- Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT, Visibility.FROM_DISK_ONLY, TimeLimit.PAST_OR_PRESENT_ONLY);
- return it.hasNext() ? it.next() : null;
+ return new DefaultSubscriptionEvent(data, startDate);
}
- public SubscriptionTransition getTransitionFromEvent(EntitlementEvent event) {
- if (transitions == null || event == null) {
+ protected SubscriptionTransitionData getPreviousTransitionData() {
+ if (transitions == null) {
return null;
}
- for (SubscriptionTransition cur : transitions) {
- if (cur.getId().equals(event.getId())) {
- return cur;
- }
- }
- return null;
- }
-
- public long getActiveVersion() {
- return activeVersion;
+ SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+ clock, transitions, Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT,
+ Visibility.FROM_DISK_ONLY, TimeLimit.PAST_OR_PRESENT_ONLY);
+ return it.hasNext() ? it.next() : null;
}
@Override
@@ -255,32 +262,107 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
return paidThroughDate;
}
- public SubscriptionTransitionData getInitialTransitionForCurrentPlan() {
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((id == null) ? 0 : id.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SubscriptionData other = (SubscriptionData) obj;
+ if (id == null) {
+ if (other.id != null)
+ return false;
+ } else if (!id.equals(other.id))
+ return false;
+ return true;
+ }
+
+ public List<SubscriptionEvent> getBillingTransitions() {
+
if (transitions == null) {
- throw new EntitlementError(String.format("No transitions for subscription %s", getId()));
+ return Collections.emptyList();
}
+ List<SubscriptionEvent> result = new ArrayList<SubscriptionEvent>();
+ SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+ clock, transitions, Order.ASC_FROM_PAST, Kind.BILLING,
+ Visibility.ALL, TimeLimit.ALL);
+ while (it.hasNext()) {
+ result.add(new DefaultSubscriptionEvent(it.next(), startDate));
+ }
+ return result;
+ }
- SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
- Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT, Visibility.ALL, TimeLimit.PAST_OR_PRESENT_ONLY);
+ public SubscriptionEvent getTransitionFromEvent(final EntitlementEvent event, final int seqId) {
+ if (transitions == null || event == null) {
+ return null;
+ }
+ for (SubscriptionTransitionData cur : transitions) {
+ if (cur.getId().equals(event.getId())) {
+ SubscriptionTransitionData withSeq = new SubscriptionTransitionData((SubscriptionTransitionData) cur, seqId);
+ return new DefaultSubscriptionEvent(withSeq, startDate);
+ }
+ }
+ return null;
+ }
+
+ public long getLastEventOrderedId() {
+ SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+ clock, transitions, Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT,
+ Visibility.FROM_DISK_ONLY, TimeLimit.ALL);
+ return it.hasNext() ? it.next().getTotalOrdering() : -1L;
+ }
+
+ public long getActiveVersion() {
+ return activeVersion;
+ }
+
+
+ public List<SubscriptionTransitionData> getAllTransitions() {
+ return transitions;
+ }
+
+ public SubscriptionTransitionData getInitialTransitionForCurrentPlan() {
+ if (transitions == null) {
+ throw new EntitlementError(String.format(
+ "No transitions for subscription %s", getId()));
+ }
+
+ SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+ clock, transitions, Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT,
+ Visibility.ALL, TimeLimit.PAST_OR_PRESENT_ONLY);
while (it.hasNext()) {
SubscriptionTransitionData cur = it.next();
- if (cur.getTransitionType() == SubscriptionTransitionType.CREATE ||
- cur.getTransitionType() == SubscriptionTransitionType.RE_CREATE ||
- cur.getTransitionType() == SubscriptionTransitionType.CHANGE ||
- cur.getTransitionType() == SubscriptionTransitionType.MIGRATE_ENTITLEMENT) {
+ if (cur.getTransitionType() == SubscriptionTransitionType.CREATE
+ || cur.getTransitionType() == SubscriptionTransitionType.RE_CREATE
+ || cur.getTransitionType() == SubscriptionTransitionType.CHANGE
+ || cur.getTransitionType() == SubscriptionTransitionType.MIGRATE_ENTITLEMENT) {
return cur;
}
}
- throw new EntitlementError(String.format("Failed to find InitialTransitionForCurrentPlan id = %s", getId().toString()));
+ throw new EntitlementError(String.format(
+ "Failed to find InitialTransitionForCurrentPlan id = %s",
+ getId().toString()));
}
public boolean isSubscriptionFutureCancelled() {
if (transitions == null) {
return false;
}
- SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
- Order.ASC_FROM_PAST, Kind.ENTITLEMENT, Visibility.ALL, TimeLimit.FUTURE_ONLY);
+ SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+ clock, transitions, Order.ASC_FROM_PAST, Kind.ENTITLEMENT,
+ Visibility.ALL, TimeLimit.FUTURE_ONLY);
while (it.hasNext()) {
SubscriptionTransitionData cur = it.next();
if (cur.getTransitionType() == SubscriptionTransitionType.CANCEL) {
@@ -290,62 +372,70 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
return false;
}
- public DateTime getPlanChangeEffectiveDate(ActionPolicy policy, DateTime requestedDate) {
+ public DateTime getPlanChangeEffectiveDate(ActionPolicy policy,
+ DateTime requestedDate) {
if (policy == ActionPolicy.IMMEDIATE) {
return requestedDate;
}
if (policy != ActionPolicy.END_OF_TERM) {
- throw new EntitlementError(String.format("Unexpected policy type %s", policy.toString()));
+ throw new EntitlementError(String.format(
+ "Unexpected policy type %s", policy.toString()));
}
if (chargedThroughDate == null) {
return requestedDate;
} else {
- return chargedThroughDate.isBefore(requestedDate) ? requestedDate : chargedThroughDate;
+ return chargedThroughDate.isBefore(requestedDate) ? requestedDate
+ : chargedThroughDate;
}
}
public DateTime getCurrentPhaseStart() {
if (transitions == null) {
- throw new EntitlementError(String.format("No transitions for subscription %s", getId()));
+ throw new EntitlementError(String.format(
+ "No transitions for subscription %s", getId()));
}
- SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(clock, transitions,
- Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT, Visibility.ALL, TimeLimit.PAST_OR_PRESENT_ONLY);
+ SubscriptionTransitionDataIterator it = new SubscriptionTransitionDataIterator(
+ clock, transitions, Order.DESC_FROM_FUTURE, Kind.ENTITLEMENT,
+ Visibility.ALL, TimeLimit.PAST_OR_PRESENT_ONLY);
while (it.hasNext()) {
SubscriptionTransitionData cur = it.next();
- if (cur.getTransitionType() == SubscriptionTransitionType.PHASE ||
- cur.getTransitionType() == SubscriptionTransitionType.CREATE ||
- cur.getTransitionType() == SubscriptionTransitionType.RE_CREATE ||
- cur.getTransitionType() == SubscriptionTransitionType.CHANGE ||
- cur.getTransitionType() == SubscriptionTransitionType.MIGRATE_ENTITLEMENT) {
+ if (cur.getTransitionType() == SubscriptionTransitionType.PHASE
+ || cur.getTransitionType() == SubscriptionTransitionType.CREATE
+ || cur.getTransitionType() == SubscriptionTransitionType.RE_CREATE
+ || cur.getTransitionType() == SubscriptionTransitionType.CHANGE
+ || cur.getTransitionType() == SubscriptionTransitionType.MIGRATE_ENTITLEMENT) {
return cur.getEffectiveTransitionTime();
}
}
- throw new EntitlementError(String.format("Failed to find CurrentPhaseStart id = %s", getId().toString()));
+ throw new EntitlementError(String.format(
+ "Failed to find CurrentPhaseStart id = %s", getId().toString()));
}
- public void rebuildTransitions(final List<EntitlementEvent> events, final Catalog catalog) {
+ public void rebuildTransitions(final List<EntitlementEvent> inputEvents,
+ final Catalog catalog) {
- if (events == null) {
+ if (inputEvents == null) {
return;
}
SubscriptionState nextState = null;
String nextPlanName = null;
String nextPhaseName = null;
- String nextPriceList = null;
+ String nextPriceListName = null;
+ UUID nextUserToken = null;
SubscriptionState previousState = null;
- String previousPriceList = null;
+ PriceList previousPriceList = null;
transitions = new LinkedList<SubscriptionTransitionData>();
Plan previousPlan = null;
PlanPhase previousPhase = null;
- for (final EntitlementEvent cur : events) {
+ for (final EntitlementEvent cur : inputEvents) {
if (!cur.isActive() || cur.getActiveVersion() < activeVersion) {
continue;
@@ -366,7 +456,9 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
ApiEvent userEV = (ApiEvent) cur;
apiEventType = userEV.getEventType();
isFromDisk = userEV.isFromDisk();
- switch(apiEventType) {
+ nextUserToken = userEV.getUserToken();
+
+ switch (apiEventType) {
case MIGRATE_BILLING:
case MIGRATE_ENTITLEMENT:
case CREATE:
@@ -378,12 +470,12 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
nextState = SubscriptionState.ACTIVE;
nextPlanName = userEV.getEventPlan();
nextPhaseName = userEV.getEventPlanPhase();
- nextPriceList = userEV.getPriceList();
+ nextPriceListName = userEV.getPriceList();
break;
case CHANGE:
nextPlanName = userEV.getEventPlan();
nextPhaseName = userEV.getEventPlanPhase();
- nextPriceList = userEV.getPriceList();
+ nextPriceListName = userEV.getPriceList();
break;
case CANCEL:
nextState = SubscriptionState.CANCELLED;
@@ -393,42 +485,37 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
case UNCANCEL:
break;
default:
- throw new EntitlementError(String.format("Unexpected UserEvent type = %s",
- userEV.getEventType().toString()));
+ throw new EntitlementError(String.format(
+ "Unexpected UserEvent type = %s", userEV
+ .getEventType().toString()));
}
break;
default:
- throw new EntitlementError(String.format("Unexpected Event type = %s",
- cur.getType()));
+ throw new EntitlementError(String.format(
+ "Unexpected Event type = %s", cur.getType()));
}
-
Plan nextPlan = null;
PlanPhase nextPhase = null;
+ PriceList nextPriceList = null;
+
try {
nextPlan = (nextPlanName != null) ? catalog.findPlan(nextPlanName, cur.getRequestedDate(), getStartDate()) : null;
nextPhase = (nextPhaseName != null) ? catalog.findPhase(nextPhaseName, cur.getRequestedDate(), getStartDate()) : null;
+ nextPriceList = (nextPriceListName != null) ? catalog.findPriceList(nextPriceListName, cur.getRequestedDate()) : null;
} catch (CatalogApiException e) {
- log.error(String.format("Failed to build transition for subscription %s", id), e);
+ log.error(String.format(
+ "Failed to build transition for subscription %s", id),
+ e);
}
- SubscriptionTransitionData transition =
- new SubscriptionTransitionData(cur.getId(),
- id,
- bundleId,
- cur.getType(),
- apiEventType,
- cur.getRequestedDate(),
- cur.getEffectiveDate(),
- previousState,
- previousPlan,
- previousPhase,
- previousPriceList,
- nextState,
- nextPlan,
- nextPhase,
- nextPriceList,
- cur.getTotalOrdering(),
- isFromDisk);
+ SubscriptionTransitionData transition = new SubscriptionTransitionData(
+ cur.getId(), id, bundleId, cur.getType(), apiEventType,
+ cur.getRequestedDate(), cur.getEffectiveDate(),
+ previousState, previousPlan, previousPhase,
+ previousPriceList, nextState, nextPlan, nextPhase,
+ nextPriceList, cur.getTotalOrdering(), nextUserToken,
+ isFromDisk);
+
transitions.add(transition);
previousState = nextState;
@@ -437,5 +524,4 @@ public class SubscriptionData extends ExtendedEntityBase implements Subscription
previousPriceList = nextPriceList;
}
}
-
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
index f03193b..d5885d5 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
@@ -16,20 +16,25 @@
package com.ning.billing.entitlement.api.user;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.catalog.api.PriceList;
import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
import com.ning.billing.entitlement.events.user.ApiEventType;
import com.ning.billing.entitlement.exceptions.EntitlementError;
-import org.joda.time.DateTime;
-
-import java.util.UUID;
-public class SubscriptionTransitionData implements SubscriptionTransition {
+public class SubscriptionTransitionData /* implements SubscriptionEvent */ {
- private final long totalOrdering;
+ private final Long totalOrdering;
private final UUID subscriptionId;
private final UUID bundleId;
private final UUID eventId;
@@ -38,20 +43,36 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
private final DateTime requestedTransitionTime;
private final DateTime effectiveTransitionTime;
private final SubscriptionState previousState;
- private final String previousPriceList;
+ private final PriceList previousPriceList;
private final Plan previousPlan;
private final PlanPhase previousPhase;
private final SubscriptionState nextState;
- private final String nextPriceList;
+ private final PriceList nextPriceList;
private final Plan nextPlan;
private final PlanPhase nextPhase;
- private final boolean isFromDisk;
-
- public SubscriptionTransitionData(UUID eventId, UUID subscriptionId, UUID bundleId, EventType eventType,
- ApiEventType apiEventType, DateTime requestedTransitionTime, DateTime effectiveTransitionTime,
- SubscriptionState previousState, Plan previousPlan, PlanPhase previousPhase, String previousPriceList,
- SubscriptionState nextState, Plan nextPlan, PlanPhase nextPhase, String nextPriceList,
- long totalOrdering, boolean isFromDisk) {
+ private final Boolean isFromDisk;
+ private final Integer remainingEventsForUserOperation;
+ private final UUID userToken;
+
+
+ public SubscriptionTransitionData(UUID eventId,
+ UUID subscriptionId,
+ UUID bundleId,
+ EventType eventType,
+ ApiEventType apiEventType,
+ DateTime requestedTransitionTime,
+ DateTime effectiveTransitionTime,
+ SubscriptionState previousState,
+ Plan previousPlan,
+ PlanPhase previousPhase,
+ PriceList previousPriceList,
+ SubscriptionState nextState,
+ Plan nextPlan,
+ PlanPhase nextPhase,
+ PriceList nextPriceList,
+ Long totalOrdering,
+ UUID userToken,
+ Boolean isFromDisk) {
super();
this.eventId = eventId;
this.subscriptionId = subscriptionId;
@@ -70,67 +91,93 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
this.nextPhase = nextPhase;
this.totalOrdering = totalOrdering;
this.isFromDisk = isFromDisk;
+ this.userToken = userToken;
+ this.remainingEventsForUserOperation = 0;
+ }
+
+ public SubscriptionTransitionData(final SubscriptionTransitionData input, final int remainingEventsForUserOperation) {
+ super();
+ this.eventId = input.getId();
+ this.subscriptionId = input.getSubscriptionId();
+ this.bundleId = input.getBundleId();
+ this.eventType = input.getEventType();
+ this.apiEventType = input.getApiEventType();
+ this.requestedTransitionTime = input.getRequestedTransitionTime();
+ this.effectiveTransitionTime = input.getEffectiveTransitionTime();
+ this.previousState = input.getPreviousState();
+ this.previousPriceList = input.getPreviousPriceList();
+ this.previousPlan = input.getPreviousPlan();
+ this.previousPhase = input.getPreviousPhase();
+ this.nextState = input.getNextState();
+ this.nextPlan = input.getNextPlan();
+ this.nextPriceList = input.getNextPriceList();
+ this.nextPhase = input.getNextPhase();
+ this.totalOrdering = input.getTotalOrdering();
+ this.isFromDisk = input.isFromDisk();
+ this.userToken = input.getUserToken();
+ this.remainingEventsForUserOperation = remainingEventsForUserOperation;
}
- @Override
+
public UUID getId() {
return eventId;
}
- @Override
public UUID getSubscriptionId() {
return subscriptionId;
}
- @Override
public UUID getBundleId() {
return bundleId;
}
-
- @Override
public SubscriptionState getPreviousState() {
return previousState;
}
- @Override
public Plan getPreviousPlan() {
return previousPlan;
}
- @Override
public PlanPhase getPreviousPhase() {
return previousPhase;
}
- @Override
public Plan getNextPlan() {
return nextPlan;
}
- @Override
public PlanPhase getNextPhase() {
return nextPhase;
}
- @Override
public SubscriptionState getNextState() {
return nextState;
}
- @Override
- public String getPreviousPriceList() {
+ public PriceList getPreviousPriceList() {
return previousPriceList;
}
- @Override
- public String getNextPriceList() {
+ public PriceList getNextPriceList() {
return nextPriceList;
}
+
+ public UUID getUserToken() {
+ return userToken;
+ }
+
+ public Integer getRemainingEventsForUserOperation() {
+ return remainingEventsForUserOperation;
+ }
+
- @Override
public SubscriptionTransitionType getTransitionType() {
+ return toSubscriptionTransitionType(eventType, apiEventType);
+ }
+
+ public static SubscriptionTransitionType toSubscriptionTransitionType(EventType eventType, ApiEventType apiEventType) {
switch(eventType) {
case API_USER:
return apiEventType.getSubscriptionTransitionType();
@@ -141,21 +188,20 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
}
}
- @Override
public DateTime getRequestedTransitionTime() {
return requestedTransitionTime;
}
- @Override
public DateTime getEffectiveTransitionTime() {
return effectiveTransitionTime;
}
- public long getTotalOrdering() {
+
+ public Long getTotalOrdering() {
return totalOrdering;
}
- public boolean isFromDisk() {
+ public Boolean isFromDisk() {
return isFromDisk;
}
@@ -168,7 +214,6 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
}
-
@Override
public String toString() {
return "SubscriptionTransition [eventId=" + eventId
@@ -185,5 +230,4 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
+ ", nextPriceList " + nextPriceList
+ ", nextPhase=" + ((nextPhase != null) ? nextPhase.getName() : null) + "]";
}
-
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java
index fbab9b2..355628d 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionDataIterator.java
@@ -19,7 +19,7 @@ package com.ning.billing.entitlement.api.user;
import java.util.Iterator;
import java.util.LinkedList;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
import com.ning.billing.entitlement.exceptions.EntitlementError;
import com.ning.billing.util.clock.Clock;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
index b2c9405..2a4a550 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
@@ -28,8 +28,9 @@ import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.Product;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+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.SubscriptionEvent;
import com.ning.billing.entitlement.exceptions.EntitlementError;
public class AddonUtils {
@@ -43,8 +44,35 @@ public class AddonUtils {
this.catalogService = catalogService;
}
+ public void checkAddonCreationRights(SubscriptionData baseSubscription, Plan targetAddOnPlan)
+ throws EntitlementUserApiException, CatalogApiException {
- public boolean isAddonAvailable(final String basePlanName, final DateTime requestedDate, final Plan targetAddOnPlan) {
+ if (baseSubscription.getState() != SubscriptionState.ACTIVE) {
+ throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_BP_NON_ACTIVE, targetAddOnPlan.getName());
+ }
+
+ Product baseProduct = baseSubscription.getCurrentPlan().getProduct();
+ if (isAddonIncluded(baseProduct, targetAddOnPlan)) {
+ throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_ALREADY_INCLUDED,
+ targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
+ }
+
+ if (!isAddonAvailable(baseProduct, targetAddOnPlan)) {
+ throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_NOT_AVAILABLE,
+ targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
+ }
+ }
+
+ public boolean isAddonAvailableFromProdName(final String baseProductName, final DateTime requestedDate, final Plan targetAddOnPlan) {
+ try {
+ Product product = catalogService.getFullCatalog().findProduct(baseProductName, requestedDate);
+ return isAddonAvailable(product, targetAddOnPlan);
+ } catch (CatalogApiException e) {
+ throw new EntitlementError(e);
+ }
+ }
+
+ public boolean isAddonAvailableFromPlanName(final String basePlanName, final DateTime requestedDate, final Plan targetAddOnPlan) {
try {
Plan plan = catalogService.getFullCatalog().findPlan(basePlanName, requestedDate);
Product product = plan.getProduct();
@@ -65,9 +93,19 @@ public class AddonUtils {
}
return false;
}
+
+ public boolean isAddonIncludedFromProdName(final String baseProductName, final DateTime requestedDate, final Plan targetAddOnPlan) {
+ try {
+ Product product = catalogService.getFullCatalog().findProduct(baseProductName, requestedDate);
+ return isAddonIncluded(product, targetAddOnPlan);
+ } catch (CatalogApiException e) {
+ throw new EntitlementError(e);
+ }
+
+ }
- public boolean isAddonIncluded(final String basePlanName, final DateTime requestedDate, final Plan targetAddOnPlan) {
- try {
+ public boolean isAddonIncludedFromPlanName(final String basePlanName, final DateTime requestedDate, final Plan targetAddOnPlan) {
+ try {
Plan plan = catalogService.getFullCatalog().findPlan(basePlanName, requestedDate);
Product product = plan.getProduct();
return isAddonIncluded(product, targetAddOnPlan);
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 32ffb90..344f47e 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
@@ -18,36 +18,29 @@ package com.ning.billing.entitlement.engine.core;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
-
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallContextFactory;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.UserType;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
+
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.Product;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.config.EntitlementConfig;
+import com.ning.billing.config.NotificationConfig;
import com.ning.billing.entitlement.alignment.PlanAligner;
import com.ning.billing.entitlement.alignment.TimedPhase;
import com.ning.billing.entitlement.api.EntitlementService;
-import com.ning.billing.entitlement.api.billing.DefaultEntitlementBillingApi;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
-import com.ning.billing.entitlement.api.migration.DefaultEntitlementMigrationApi;
-import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
-import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
-import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
import com.ning.billing.entitlement.api.user.SubscriptionData;
import com.ning.billing.entitlement.engine.addon.AddonUtils;
@@ -62,10 +55,13 @@ import com.ning.billing.entitlement.events.user.ApiEventCancel;
import com.ning.billing.entitlement.exceptions.EntitlementError;
import com.ning.billing.lifecycle.LifecycleHandlerType;
import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
-import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.bus.Bus;
import com.ning.billing.util.bus.Bus.EventBusException;
-import com.ning.billing.util.notificationq.NotificationConfig;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.notificationq.NotificationQueue;
import com.ning.billing.util.notificationq.NotificationQueueService;
import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists;
@@ -73,6 +69,7 @@ import com.ning.billing.util.notificationq.NotificationQueueService.Notification
public class Engine implements EventListener, EntitlementService {
+
public static final String NOTIFICATION_QUEUE_NAME = "subscription-events";
public static final String ENTITLEMENT_SERVICE_NAME = "entitlement-service";
@@ -81,9 +78,6 @@ public class Engine implements EventListener, EntitlementService {
private final Clock clock;
private final EntitlementDao dao;
private final PlanAligner planAligner;
- private final EntitlementUserApi userApi;
- private final EntitlementBillingApi billingApi;
- private final EntitlementMigrationApi migrationApi;
private final AddonUtils addonUtils;
private final Bus eventBus;
@@ -95,9 +89,8 @@ public class Engine implements EventListener, EntitlementService {
@Inject
public Engine(Clock clock, EntitlementDao dao, PlanAligner planAligner,
- EntitlementConfig config, DefaultEntitlementUserApi userApi,
- DefaultEntitlementBillingApi billingApi,
- DefaultEntitlementMigrationApi migrationApi, AddonUtils addonUtils, Bus eventBus,
+ EntitlementConfig config,
+ AddonUtils addonUtils, Bus eventBus,
NotificationQueueService notificationQueueService,
SubscriptionFactory subscriptionFactory,
CallContextFactory factory) {
@@ -105,9 +98,6 @@ public class Engine implements EventListener, EntitlementService {
this.clock = clock;
this.dao = dao;
this.planAligner = planAligner;
- this.userApi = userApi;
- this.billingApi = billingApi;
- this.migrationApi = migrationApi;
this.addonUtils = addonUtils;
this.config = config;
this.eventBus = eventBus;
@@ -129,32 +119,29 @@ public class Engine implements EventListener, EntitlementService {
NOTIFICATION_QUEUE_NAME,
new NotificationQueueHandler() {
@Override
- public void handleReadyNotification(String notificationKey, DateTime eventDateTime) {
- EntitlementEvent event = dao.getEventById(UUID.fromString(notificationKey));
+ public void handleReadyNotification(final String inputKey, final DateTime eventDateTime) {
+
+ EntitlementNotificationKey key = new EntitlementNotificationKey(inputKey);
+ final EntitlementEvent event = dao.getEventById(key.getEventId());
if (event == null) {
- log.warn("Failed to extract event for notification key {}", notificationKey);
- } else {
- final CallContext context = factory.createCallContext("SubscriptionEventQueue", CallOrigin.INTERNAL, UserType.SYSTEM);
- processEventReady(event, context);
+ log.warn("Failed to extract event for notification key {}", inputKey);
+ return;
}
+ final UUID userToken = (event.getType() == EventType.API_USER) ? ((ApiEvent) event).getUserToken() : null;
+ final CallContext context = factory.createCallContext("SubscriptionEventQueue", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
+ processEventReady(event, key.getSeqId(), context);
}
},
new NotificationConfig() {
+
@Override
- public boolean isNotificationProcessingOff() {
- return config.isEventProcessingOff();
- }
- @Override
- public long getNotificationSleepTimeMs() {
- return config.getNotificationSleepTimeMs();
- }
- @Override
- public int getDaoMaxReadyEvents() {
- return config.getDaoMaxReadyEvents();
+ public long getSleepTimeMs() {
+ return config.getSleepTimeMs();
}
+
@Override
- public long getDaoClaimTimeMs() {
- return config.getDaoClaimTimeMs();
+ public boolean isNotificationProcessingOff() {
+ return config.isNotificationProcessingOff();
}
});
} catch (NotificationQueueAlreadyExists e) {
@@ -175,24 +162,7 @@ public class Engine implements EventListener, EntitlementService {
}
@Override
- public EntitlementUserApi getUserApi() {
- return userApi;
- }
-
- @Override
- public EntitlementBillingApi getBillingApi() {
- return billingApi;
- }
-
-
- @Override
- public EntitlementMigrationApi getMigrationApi() {
- return migrationApi;
- }
-
-
- @Override
- public void processEventReady(EntitlementEvent event, CallContext context) {
+ public void processEventReady(final EntitlementEvent event, final int seqId, final CallContext context) {
if (!event.isActive()) {
return;
}
@@ -201,17 +171,24 @@ public class Engine implements EventListener, EntitlementService {
log.warn("Failed to retrieve subscription for id %s", event.getSubscriptionId());
return;
}
+ if (subscription.getActiveVersion() > event.getActiveVersion()) {
+ // Skip repaired events
+ return;
+ }
+
//
// Do any internal processing on that event before we send the event to the bus
//
+
+ int theRealSeqId = seqId;
if (event.getType() == EventType.PHASE) {
onPhaseEvent(subscription, context);
} else if (event.getType() == EventType.API_USER &&
subscription.getCategory() == ProductCategory.BASE) {
- onBasePlanEvent(subscription, (ApiEvent) event, context);
+ theRealSeqId = onBasePlanEvent(subscription, (ApiEvent) event, context);
}
try {
- eventBus.post(subscription.getTransitionFromEvent(event));
+ eventBus.post(subscription.getTransitionFromEvent(event, theRealSeqId));
} catch (EventBusException e) {
log.warn("Failed to post entitlement event " + event, e);
}
@@ -233,7 +210,7 @@ public class Engine implements EventListener, EntitlementService {
}
}
- private void onBasePlanEvent(SubscriptionData baseSubscription, ApiEvent event, CallContext context) {
+ private int onBasePlanEvent(SubscriptionData baseSubscription, ApiEvent event, CallContext context) {
DateTime now = clock.getUTCNow();
@@ -242,6 +219,9 @@ public class Engine implements EventListener, EntitlementService {
List<Subscription> subscriptions = dao.getSubscriptions(subscriptionFactory, baseSubscription.getBundleId());
+
+ Map<UUID, EntitlementEvent> addOnCancellations = new HashMap<UUID, EntitlementEvent>();
+
Iterator<Subscription> it = subscriptions.iterator();
while (it.hasNext()) {
SubscriptionData cur = (SubscriptionData) it.next();
@@ -262,9 +242,18 @@ public class Engine implements EventListener, EntitlementService {
.setProcessedDate(now)
.setEffectiveDate(event.getEffectiveDate())
.setRequestedDate(now)
+ .setUserToken(context.getUserToken())
.setFromDisk(true));
- dao.cancelSubscription(cur.getId(), cancelEvent, context);
+
+ addOnCancellations.put(cur.getId(), cancelEvent);
}
}
+ final int addOnSize = addOnCancellations.size();
+ int cancelSeq = addOnSize - 1;
+ for (final UUID key : addOnCancellations.keySet()) {
+ dao.cancelSubscription(key, addOnCancellations.get(key), context, cancelSeq);
+ cancelSeq--;
+ }
+ return addOnSize;
}
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EntitlementNotificationKey.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EntitlementNotificationKey.java
new file mode 100644
index 0000000..a4d19b0
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EntitlementNotificationKey.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.entitlement.engine.core;
+
+import java.util.UUID;
+import com.ning.billing.util.notificationq.NotificationKey;
+
+public class EntitlementNotificationKey implements NotificationKey {
+
+ private static final String DELIMITER = ":";
+
+ private final UUID eventId;
+ private final int seqId;
+
+ public EntitlementNotificationKey(final UUID eventId, int seqId) {
+ this.eventId = eventId;
+ this.seqId = seqId;
+ }
+
+ public EntitlementNotificationKey(final UUID eventId) {
+ this(eventId, 0);
+ }
+
+ public EntitlementNotificationKey(final String input) {
+
+ String [] parts = input.split(DELIMITER);
+ eventId = UUID.fromString(parts[0]);
+ if (parts.length == 2) {
+ seqId = Integer.valueOf(parts[1]);
+ } else {
+ seqId = 0;
+ }
+ }
+
+ public UUID getEventId() {
+ return eventId;
+ }
+
+ public int getSeqId() {
+ return seqId;
+ }
+
+ public String toString() {
+ if (seqId == 0) {
+ return eventId.toString();
+ } else {
+ return eventId.toString() + ":" + seqId;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((eventId == null) ? 0 : eventId.hashCode());
+ result = prime * result + seqId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ EntitlementNotificationKey other = (EntitlementNotificationKey) obj;
+ if (eventId == null) {
+ if (other.eventId != null)
+ return false;
+ } else if (!eventId.equals(other.eventId))
+ return false;
+ if (seqId != other.seqId)
+ return false;
+ return true;
+ }
+}
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 f6b13f9..1272dba 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
@@ -22,6 +22,6 @@ import com.ning.billing.util.callcontext.CallContext;
public interface EventListener {
- public void processEventReady(EntitlementEvent event, CallContext context);
+ public void processEventReady(final EntitlementEvent event, final int seqId, final CallContext context);
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
index bccd559..b6becbb 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/BundleSqlDao.java
@@ -16,12 +16,14 @@
package com.ning.billing.entitlement.engine.dao;
-import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.dao.BinderBase;
-import com.ning.billing.util.dao.MapperBase;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.util.dao.AuditSqlDao;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
import org.joda.time.DateTime;
import org.skife.jdbi.v2.SQLStatement;
import org.skife.jdbi.v2.StatementContext;
@@ -36,52 +38,60 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
import org.skife.jdbi.v2.tweak.ResultSetMapper;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.List;
-import java.util.UUID;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
+import com.ning.billing.util.dao.BinderBase;
+import com.ning.billing.util.dao.MapperBase;
+
@ExternalizedSqlViaStringTemplate3()
-public interface BundleSqlDao extends Transactional<BundleSqlDao>, CloseMe, Transmogrifier {
+public interface BundleSqlDao extends Transactional<BundleSqlDao>, EntitySqlDao<SubscriptionBundle>,
+ AuditSqlDao, CloseMe, Transmogrifier {
@SqlUpdate
public void insertBundle(@Bind(binder = SubscriptionBundleBinder.class) SubscriptionBundleData bundle,
@CallContextBinder final CallContext context);
+ @SqlUpdate
+ public void updateBundleLastSysTime(@Bind("id") String id, @Bind("lastSysUpdateDate") Date lastSysUpdate);
+
@SqlQuery
@Mapper(ISubscriptionBundleSqlMapper.class)
public SubscriptionBundle getBundleFromId(@Bind("id") String id);
@SqlQuery
@Mapper(ISubscriptionBundleSqlMapper.class)
- public SubscriptionBundle getBundleFromKey(@Bind("name") String name);
+ public SubscriptionBundle getBundleFromKey(@Bind("externalKey") String externalKey);
@SqlQuery
@Mapper(ISubscriptionBundleSqlMapper.class)
- public List<SubscriptionBundle> getBundleFromAccount(@Bind("account_id") String accountId);
+ public List<SubscriptionBundle> getBundleFromAccount(@Bind("accountId") String accountId);
public static class SubscriptionBundleBinder extends BinderBase implements Binder<Bind, SubscriptionBundleData> {
@Override
public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, SubscriptionBundleData bundle) {
stmt.bind("id", bundle.getId().toString());
- stmt.bind("start_dt", getDate(bundle.getStartDate()));
- stmt.bind("name", bundle.getKey());
- stmt.bind("account_id", bundle.getAccountId().toString());
+ stmt.bind("startDate", getDate(bundle.getStartDate()));
+ stmt.bind("externalKey", bundle.getKey());
+ stmt.bind("accountId", bundle.getAccountId().toString());
+ stmt.bind("lastSysUpdateDate", getDate(bundle.getLastSysUpdateTime()));
}
}
public static class ISubscriptionBundleSqlMapper extends MapperBase implements ResultSetMapper<SubscriptionBundle> {
+
@Override
public SubscriptionBundle map(int arg, ResultSet r,
StatementContext ctx) throws SQLException {
-
UUID id = UUID.fromString(r.getString("id"));
- String name = r.getString("name");
+ String key = r.getString("external_key");
UUID accountId = UUID.fromString(r.getString("account_id"));
- DateTime startDate = getDate(r, "start_dt");
- SubscriptionBundleData bundle = new SubscriptionBundleData(id, name, accountId, startDate);
+ DateTime startDate = getDate(r, "start_date");
+ DateTime lastSysUpdateDate = getDate(r, "last_sys_update_date");
+ SubscriptionBundleData bundle = new SubscriptionBundleData(id, key, accountId, startDate, lastSysUpdateDate);
return bundle;
}
-
}
}
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 d79444a..5e95c88 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
@@ -17,16 +17,18 @@
package com.ning.billing.entitlement.engine.dao;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.timeline.SubscriptionDataRepair;
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.api.user.SubscriptionFactory;
import com.ning.billing.entitlement.events.EntitlementEvent;
public interface EntitlementDao {
@@ -41,7 +43,7 @@ public interface EntitlementDao {
public Subscription getSubscriptionFromId(final SubscriptionFactory factory, final UUID subscriptionId);
- // Account retrieval
+ // ACCOUNT retrieval
public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId);
// Subscription retrieval
@@ -52,13 +54,15 @@ public interface EntitlementDao {
public List<Subscription> getSubscriptionsForKey(final SubscriptionFactory factory, final String bundleKey);
// Update
- public void updateSubscription(final SubscriptionData subscription, final CallContext context);
+ public void updateChargedThroughDate(final SubscriptionData subscription, final CallContext context);
// Event apis
public void createNextPhaseEvent(final UUID subscriptionId, final EntitlementEvent nextPhase, final CallContext context);
public EntitlementEvent getEventById(final UUID eventId);
+ public Map<UUID, List<EntitlementEvent>> getEventsForBundle(final UUID bundleId);
+
public List<EntitlementEvent> getEventsForSubscription(final UUID subscriptionId);
public List<EntitlementEvent> getPendingEventsForSubscription(final UUID subscriptionId);
@@ -68,14 +72,17 @@ public interface EntitlementDao {
public void recreateSubscription(final UUID subscriptionId, final List<EntitlementEvent> recreateEvents, final CallContext context);
- public void cancelSubscription(final UUID subscriptionId, final EntitlementEvent cancelEvent, final CallContext context);
-
+ public void cancelSubscription(final UUID subscriptionId, final EntitlementEvent cancelEvent, final CallContext context, final int cancelSeq);
+
public void uncancelSubscription(final UUID subscriptionId, final List<EntitlementEvent> uncancelEvents, final CallContext context);
public void changePlan(final UUID subscriptionId, final List<EntitlementEvent> changeEvents, final CallContext context);
public void migrate(final UUID accountId, final AccountMigrationData data, final CallContext context);
+ // Repair
+ public void repair(final UUID accountId, final UUID bundleId, final List<SubscriptionDataRepair> inRepair, final CallContext context);
+
// Custom Fields
public void saveCustomFields(final SubscriptionData subscription, final CallContext context);
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/RepairEntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/RepairEntitlementDao.java
new file mode 100644
index 0000000..cdfc221
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/RepairEntitlementDao.java
@@ -0,0 +1,249 @@
+/*
+ * 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.engine.dao;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import com.ning.billing.entitlement.api.SubscriptionFactory;
+import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementLifecycleDao;
+import com.ning.billing.entitlement.api.timeline.SubscriptionDataRepair;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.exceptions.EntitlementError;
+import com.ning.billing.util.callcontext.CallContext;
+
+public class RepairEntitlementDao implements EntitlementDao, RepairEntitlementLifecycleDao {
+
+ private final ThreadLocal<Map<UUID, SubscriptionRepairEvent>> preThreadsInRepairSubscriptions = new ThreadLocal<Map<UUID, SubscriptionRepairEvent>>();
+
+ private final static class SubscriptionRepairEvent {
+
+ private final Set<EntitlementEvent> events;
+
+ public SubscriptionRepairEvent(List<EntitlementEvent> initialEvents) {
+ events = new TreeSet<EntitlementEvent>(new Comparator<EntitlementEvent>() {
+ @Override
+ public int compare(EntitlementEvent o1, EntitlementEvent o2) {
+ return o1.compareTo(o2);
+ }
+ });
+ if (initialEvents != null) {
+ events.addAll(initialEvents);
+ }
+ }
+
+ public Set<EntitlementEvent> getEvents() {
+ return events;
+ }
+
+ public void addEvents(List<EntitlementEvent> newEvents) {
+ events.addAll(newEvents);
+ }
+ }
+
+ private Map<UUID, SubscriptionRepairEvent> getRepairMap() {
+ if (preThreadsInRepairSubscriptions.get() == null) {
+ preThreadsInRepairSubscriptions.set(new HashMap<UUID, SubscriptionRepairEvent>());
+ }
+ return preThreadsInRepairSubscriptions.get();
+ }
+
+ private SubscriptionRepairEvent getRepairSubscriptionEvents(UUID subscriptionId) {
+ Map<UUID, SubscriptionRepairEvent> map = getRepairMap();
+ return map.get(subscriptionId);
+ }
+
+ @Override
+ public List<EntitlementEvent> getEventsForSubscription(UUID subscriptionId) {
+ SubscriptionRepairEvent target = getRepairSubscriptionEvents(subscriptionId);
+ return new LinkedList<EntitlementEvent>(target.getEvents());
+ }
+
+ @Override
+ public void createSubscription(SubscriptionData subscription,
+ List<EntitlementEvent> createEvents, CallContext context) {
+ addEvents(subscription.getId(), createEvents);
+ }
+
+ @Override
+ public void recreateSubscription(UUID subscriptionId,
+ List<EntitlementEvent> recreateEvents, CallContext context) {
+ addEvents(subscriptionId, recreateEvents);
+ }
+
+ @Override
+ public void cancelSubscription(UUID subscriptionId,
+ EntitlementEvent cancelEvent, CallContext context, int cancelSeq) {
+ long activeVersion = cancelEvent.getActiveVersion();
+ addEvents(subscriptionId, Collections.singletonList(cancelEvent));
+ SubscriptionRepairEvent target = getRepairSubscriptionEvents(subscriptionId);
+ boolean foundCancelEvent = false;
+ for (EntitlementEvent cur : target.getEvents()) {
+ if (cur.getId().equals(cancelEvent.getId())) {
+ foundCancelEvent = true;
+ } else if (foundCancelEvent) {
+ cur.setActiveVersion(activeVersion - 1);
+ }
+ }
+ }
+
+
+ @Override
+ public void changePlan(UUID subscriptionId,
+ List<EntitlementEvent> changeEvents, CallContext context) {
+ addEvents(subscriptionId, changeEvents);
+ }
+
+ @Override
+ public void initializeRepair(UUID subscriptionId, List<EntitlementEvent> initialEvents) {
+ Map<UUID, SubscriptionRepairEvent> map = getRepairMap();
+ if (map.get(subscriptionId) == null) {
+ SubscriptionRepairEvent value = new SubscriptionRepairEvent(initialEvents);
+ map.put(subscriptionId, value);
+ } else {
+ throw new EntitlementError(String.format("Unexpected SubscriptionRepairEvent %s for thread %s", subscriptionId, Thread.currentThread().getName()));
+ }
+ }
+
+ @Override
+ public void cleanup() {
+ Map<UUID, SubscriptionRepairEvent> map = getRepairMap();
+ map.clear();
+ }
+
+
+ private void addEvents(UUID subscriptionId, List<EntitlementEvent> events) {
+ SubscriptionRepairEvent target = getRepairSubscriptionEvents(subscriptionId);
+ target.addEvents(events);
+ }
+
+
+ @Override
+ public void uncancelSubscription(UUID subscriptionId,
+ List<EntitlementEvent> uncancelEvents, CallContext context) {
+ throw new EntitlementError("Not implemented");
+ }
+
+ @Override
+ public List<SubscriptionBundle> getSubscriptionBundleForAccount(UUID accountId) {
+ throw new EntitlementError("Not implemented");
+ }
+
+ @Override
+ public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
+ throw new EntitlementError("Not implemented");
+ }
+
+ @Override
+ public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
+ throw new EntitlementError("Not implemented");
+ }
+
+ @Override
+ public SubscriptionBundle createSubscriptionBundle(
+ SubscriptionBundleData bundle, CallContext context) {
+ throw new EntitlementError("Not implemented");
+ }
+
+ @Override
+ public Subscription getSubscriptionFromId(SubscriptionFactory factory,
+ UUID subscriptionId) {
+ throw new EntitlementError("Not implemented");
+ }
+
+ @Override
+ public UUID getAccountIdFromSubscriptionId(UUID subscriptionId) {
+ throw new EntitlementError("Not implemented");
+ }
+
+ @Override
+ public Subscription getBaseSubscription(SubscriptionFactory factory,
+ UUID bundleId) {
+ throw new EntitlementError("Not implemented");
+ }
+
+ @Override
+ public List<Subscription> getSubscriptions(SubscriptionFactory factory,
+ UUID bundleId) {
+ throw new EntitlementError("Not implemented");
+ }
+
+ @Override
+ public List<Subscription> getSubscriptionsForKey(
+ SubscriptionFactory factory, String bundleKey) {
+ throw new EntitlementError("Not implemented");
+ }
+
+ @Override
+ public void updateChargedThroughDate(SubscriptionData subscription,
+ CallContext context) {
+ throw new EntitlementError("Not implemented");
+ }
+
+ @Override
+ public void createNextPhaseEvent(UUID subscriptionId,
+ EntitlementEvent nextPhase, CallContext context) {
+ throw new EntitlementError("Not implemented");
+ }
+
+ @Override
+ public EntitlementEvent getEventById(UUID eventId) {
+ throw new EntitlementError("Not implemented");
+ }
+
+ @Override
+ public Map<UUID, List<EntitlementEvent>> getEventsForBundle(UUID bundleId) {
+ throw new EntitlementError("Not implemented");
+ }
+
+
+ @Override
+ public List<EntitlementEvent> getPendingEventsForSubscription(
+ UUID subscriptionId) {
+ throw new EntitlementError("Not implemented");
+ }
+
+
+ @Override
+ public void migrate(UUID accountId, AccountMigrationData data,
+ CallContext context) {
+ throw new EntitlementError("Not implemented");
+ }
+
+ @Override
+ public void saveCustomFields(SubscriptionData subscription,
+ CallContext context) {
+ throw new EntitlementError("Not implemented");
+ }
+
+ @Override
+ public void repair(UUID accountId, UUID bundleId, List<SubscriptionDataRepair> inRepair,
+ CallContext context) {
+ throw new EntitlementError("Not implemented");
+ }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java
index 04f2965..a43117d 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.java
@@ -19,7 +19,8 @@ package com.ning.billing.entitlement.engine.dao;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.util.dao.AuditSqlDao;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.CallContextBinder;
import com.ning.billing.util.dao.BinderBase;
@@ -45,7 +46,7 @@ import java.util.List;
import java.util.UUID;
@ExternalizedSqlViaStringTemplate3()
-public interface SubscriptionSqlDao extends Transactional<SubscriptionSqlDao>, CloseMe, Transmogrifier {
+public interface SubscriptionSqlDao extends Transactional<SubscriptionSqlDao>, AuditSqlDao, CloseMe, Transmogrifier {
@SqlUpdate
public void insertSubscription(@Bind(binder = SubscriptionBinder.class) SubscriptionData sub,
@CallContextBinder final CallContext context);
@@ -56,24 +57,32 @@ public interface SubscriptionSqlDao extends Transactional<SubscriptionSqlDao>, C
@SqlQuery
@Mapper(SubscriptionMapper.class)
- public List<Subscription> getSubscriptionsFromBundleId(@Bind("bundle_id") String bundleId);
+ public List<Subscription> getSubscriptionsFromBundleId(@Bind("bundleId") String bundleId);
@SqlUpdate
- public void updateSubscription(@Bind("id") String id, @Bind("active_version") long activeVersion,
- @Bind("ctd_dt") Date ctd, @Bind("ptd_dt") Date ptd,
- @CallContextBinder final CallContext context);
-
+ public void updateChargedThroughDate(@Bind("id") String id, @Bind("chargedThroughDate") Date chargedThroughDate,
+ @CallContextBinder final CallContext context);
+
+ @SqlUpdate void updateActiveVersion(@Bind("id") String id, @Bind("activeVersion") long activeVersion,
+ @CallContextBinder final CallContext context);
+
+ @SqlUpdate
+ public void updateForRepair(@Bind("id") String id, @Bind("activeVersion") long activeVersion,
+ @Bind("startDate") Date startDate,
+ @Bind("bundleStartDate") Date bundleStartDate,
+ @CallContextBinder final CallContext context);
+
public static class SubscriptionBinder extends BinderBase implements Binder<Bind, SubscriptionData> {
@Override
public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, SubscriptionData sub) {
stmt.bind("id", sub.getId().toString());
- stmt.bind("bundle_id", sub.getBundleId().toString());
+ stmt.bind("bundleId", sub.getBundleId().toString());
stmt.bind("category", sub.getCategory().toString());
- stmt.bind("start_dt", getDate(sub.getStartDate()));
- stmt.bind("bundle_start_dt", getDate(sub.getBundleStartDate()));
- stmt.bind("active_version", sub.getActiveVersion());
- stmt.bind("ctd_dt", getDate(sub.getChargedThroughDate()));
- stmt.bind("ptd_dt", getDate(sub.getPaidThroughDate()));
+ stmt.bind("startDate", getDate(sub.getStartDate()));
+ stmt.bind("bundleStartDate", getDate(sub.getBundleStartDate()));
+ stmt.bind("activeVersion", sub.getActiveVersion());
+ stmt.bind("chargedThroughDate", getDate(sub.getChargedThroughDate()));
+ stmt.bind("paidThroughDate", getDate(sub.getPaidThroughDate()));
}
}
@@ -85,10 +94,10 @@ public interface SubscriptionSqlDao extends Transactional<SubscriptionSqlDao>, C
UUID id = UUID.fromString(r.getString("id"));
UUID bundleId = UUID.fromString(r.getString("bundle_id"));
ProductCategory category = ProductCategory.valueOf(r.getString("category"));
- DateTime bundleStartDate = getDate(r, "bundle_start_dt");
- DateTime startDate = getDate(r, "start_dt");
- DateTime ctd = getDate(r, "ctd_dt");
- DateTime ptd = getDate(r, "ptd_dt");
+ DateTime bundleStartDate = getDate(r, "bundle_start_date");
+ DateTime startDate = getDate(r, "start_date");
+ DateTime ctd = getDate(r, "charged_through_date");
+ DateTime ptd = getDate(r, "paid_through_date");
long activeVersion = r.getLong("active_version");
return new SubscriptionData(new SubscriptionBuilder()
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 d3895c0..8e7688b 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
@@ -16,12 +16,13 @@
package com.ning.billing.entitlement.events;
+import com.ning.billing.util.entity.Entity;
import org.joda.time.DateTime;
import java.util.UUID;
-public interface EntitlementEvent extends Comparable<EntitlementEvent> {
+public interface EntitlementEvent extends Comparable<EntitlementEvent>, Entity {
public enum EventType {
API_USER,
@@ -32,8 +33,6 @@ public interface EntitlementEvent extends Comparable<EntitlementEvent> {
public long getTotalOrdering();
- public UUID getId();
-
public long getActiveVersion();
public void setActiveVersion(long activeVersion);
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 5861e5d..4afc75d 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
@@ -17,7 +17,6 @@
package com.ning.billing.entitlement.events;
import com.ning.billing.entitlement.events.user.ApiEvent;
-import com.ning.billing.entitlement.exceptions.EntitlementError;
import org.joda.time.DateTime;
import java.util.UUID;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/PhaseEventData.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/PhaseEventData.java
index 47546ea..23f7b2b 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/PhaseEventData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/phase/PhaseEventData.java
@@ -17,7 +17,6 @@
package com.ning.billing.entitlement.events.phase;
-import com.ning.billing.entitlement.alignment.TimedPhase;
import com.ning.billing.entitlement.api.user.SubscriptionData;
import com.ning.billing.entitlement.events.EventBase;
import org.joda.time.DateTime;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEvent.java
index c26b168..20a6569 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEvent.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEvent.java
@@ -16,6 +16,8 @@
package com.ning.billing.entitlement.events.user;
+import java.util.UUID;
+
import com.ning.billing.entitlement.events.EntitlementEvent;
@@ -30,5 +32,7 @@ public interface ApiEvent extends EntitlementEvent {
public String getPriceList();
public boolean isFromDisk();
+
+ public UUID getUserToken();
}
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 f67c6b7..d2ea706 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
@@ -17,7 +17,6 @@
package com.ning.billing.entitlement.events.user;
import com.ning.billing.entitlement.events.EventBase;
-import org.joda.time.DateTime;
import java.util.UUID;
@@ -28,6 +27,7 @@ public class ApiEventBase extends EventBase implements ApiEvent {
private final String eventPlan;
private final String eventPlanPhase;
private final String eventPriceList;
+ private final UUID userToken;
private final boolean fromDisk;
public ApiEventBase(ApiEventBuilder builder) {
@@ -37,28 +37,9 @@ public class ApiEventBase extends EventBase implements ApiEvent {
this.eventPlan = builder.getEventPlan();
this.eventPlanPhase = builder.getEventPlanPhase();
this.fromDisk = builder.isFromDisk();
+ this.userToken = builder.getUserToken();
}
-/*
- public ApiEventBase(UUID subscriptionId, DateTime bundleStartDate, DateTime processed, String planName, String phaseName,
- String priceList, DateTime requestedDate, ApiEventType eventType, DateTime effectiveDate, long activeVersion) {
- super(subscriptionId, requestedDate, effectiveDate, processed, activeVersion, true);
- this.eventType = eventType;
- this.eventPriceList = priceList;
- this.eventPlan = planName;
- this.eventPlanPhase = phaseName;
- }
-
- public ApiEventBase(UUID subscriptionId, DateTime bundleStartDate, DateTime processed,
- DateTime requestedDate, ApiEventType eventType, DateTime effectiveDate, long activeVersion) {
- super(subscriptionId, requestedDate, effectiveDate, processed, activeVersion, true);
- this.eventType = eventType;
- this.eventPriceList = null;
- this.eventPlan = null;
- this.eventPlanPhase = null;
- }
-*/
-
@Override
public ApiEventType getEventType() {
return eventType;
@@ -83,6 +64,12 @@ public class ApiEventBase extends EventBase implements ApiEvent {
public String getPriceList() {
return eventPriceList;
}
+
+ @Override
+ public UUID getUserToken() {
+ return userToken;
+ }
+
@Override
public boolean isFromDisk() {
@@ -105,6 +92,7 @@ public class ApiEventBase extends EventBase implements ApiEvent {
+ ", getActiveVersion()=" + getActiveVersion()
+ ", getProcessedDate()=" + getProcessedDate()
+ ", getSubscriptionId()=" + getSubscriptionId()
+ + ", evetnToken()=" + getUserToken()
+ ", isActive()=" + isActive() + "]";
}
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBuilder.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBuilder.java
index b7e9764..b6be427 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBuilder.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventBuilder.java
@@ -16,6 +16,8 @@
package com.ning.billing.entitlement.events.user;
+import java.util.UUID;
+
import com.ning.billing.entitlement.events.EventBaseBuilder;
public class ApiEventBuilder extends EventBaseBuilder<ApiEventBuilder> {
@@ -24,6 +26,7 @@ public class ApiEventBuilder extends EventBaseBuilder<ApiEventBuilder> {
private String eventPlan;
private String eventPlanPhase;
private String eventPriceList;
+ private UUID userToken;
private boolean fromDisk;
@@ -50,11 +53,21 @@ public class ApiEventBuilder extends EventBaseBuilder<ApiEventBuilder> {
public String getEventPriceList() {
return eventPriceList;
}
+
+ public UUID getUserToken() {
+ return userToken;
+ }
public boolean isFromDisk() {
return fromDisk;
}
+ public ApiEventBuilder setUserToken(UUID userToken) {
+ this.userToken = userToken;
+ return this;
+ }
+
+
public ApiEventBuilder setFromDisk(boolean fromDisk) {
this.fromDisk = fromDisk;
return this;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java
index a279f52..d1eae92 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/events/user/ApiEventType.java
@@ -16,7 +16,7 @@
package com.ning.billing.entitlement.events.user;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
public enum ApiEventType {
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 bef27aa..f86f170 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
@@ -1,49 +1,56 @@
DROP TABLE IF EXISTS events;
DROP TABLE IF EXISTS entitlement_events;
CREATE TABLE entitlement_events (
- id int(11) unsigned NOT NULL AUTO_INCREMENT,
- event_id char(36) NOT NULL,
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ id char(36) NOT NULL,
event_type varchar(9) NOT NULL,
user_type varchar(25) DEFAULT NULL,
- requested_dt datetime NOT NULL,
- effective_dt datetime NOT NULL,
+ requested_date datetime NOT NULL,
+ effective_date datetime NOT NULL,
subscription_id char(36) NOT NULL,
plan_name varchar(64) DEFAULT NULL,
phase_name varchar(128) DEFAULT NULL,
- plist_name varchar(64) DEFAULT NULL,
+ price_list_name varchar(64) DEFAULT NULL,
+ user_token char(36),
current_version int(11) DEFAULT 1,
is_active bool DEFAULT 1,
created_by varchar(50) NOT NULL,
created_date datetime NOT NULL,
updated_by varchar(50) NOT NULL,
updated_date datetime NOT NULL,
- PRIMARY KEY(id)
+ PRIMARY KEY(record_id)
) ENGINE=innodb;
-CREATE INDEX idx_ent_1 ON entitlement_events(subscription_id,is_active,effective_dt);
-CREATE INDEX idx_ent_2 ON entitlement_events(subscription_id,effective_dt,created_date,requested_dt,id);
+CREATE UNIQUE INDEX entitlement_events_id ON entitlement_events(id);
+CREATE INDEX idx_ent_1 ON entitlement_events(subscription_id,is_active,effective_date);
+CREATE INDEX idx_ent_2 ON entitlement_events(subscription_id,effective_date,created_date,requested_date,id);
DROP TABLE IF EXISTS subscriptions;
CREATE TABLE subscriptions (
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
id char(36) NOT NULL,
bundle_id char(36) NOT NULL,
category varchar(32) NOT NULL,
- start_dt datetime NOT NULL,
- bundle_start_dt datetime NOT NULL,
+ start_date datetime NOT NULL,
+ bundle_start_date datetime NOT NULL,
active_version int(11) DEFAULT 1,
- ctd_dt datetime DEFAULT NULL,
- ptd_dt datetime DEFAULT NULL,
+ charged_through_date datetime DEFAULT NULL,
+ paid_through_date datetime DEFAULT NULL,
created_by varchar(50) NOT NULL,
created_date datetime NOT NULL,
updated_by varchar(50) NOT NULL,
updated_date datetime NOT NULL,
- PRIMARY KEY(id)
+ PRIMARY KEY(record_id)
) ENGINE=innodb;
+CREATE UNIQUE INDEX subscriptions_id ON subscriptions(id);
DROP TABLE IF EXISTS bundles;
CREATE TABLE bundles (
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
id char(36) NOT NULL,
- start_dt datetime, /*NOT NULL*/
- name varchar(64) NOT NULL,
+ start_date datetime, /*NOT NULL*/
+ external_key varchar(64) NOT NULL,
account_id char(36) NOT NULL,
- PRIMARY KEY(id)
+ last_sys_update_date datetime,
+ PRIMARY KEY(record_id)
) ENGINE=innodb;
+ CREATE UNIQUE INDEX bundles_id ON bundles(id);
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg
index 4f0e4db..2b0467d 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/BundleSqlDao.sql.stg
@@ -1,53 +1,68 @@
group BundleSqlDao;
+fields(prefix) ::= <<
+ <prefix>id,
+ <prefix>start_date,
+ <prefix>external_key,
+ <prefix>account_id,
+ <prefix>last_sys_update_date
+>>
+
insertBundle() ::= <<
- insert into bundles (
- id
- , start_dt
- , name
- , account_id
- ) values (
- :id
- , :start_dt
- , :name
- , :account_id
- );
->>
-
-getBundleFromId(id) ::= <<
- select
- id
- , start_dt
- , name
- , account_id
+ insert into bundles (<fields()>)
+ values (:id, :startDate, :externalKey, :accountId, :lastSysUpdateDate);
+>>
+
+updateBundleLastSysTime() ::= <<
+ update bundles
+ set
+ last_sys_update_date = :lastSysUpdateDate
+ where id = :id
+ ;
+>>
+
+getBundleFromId() ::= <<
+ select <fields()>
from bundles
where
id = :id
;
>>
-getBundleFromKey(name) ::= <<
- select
- id
- , start_dt
- , name
- , account_id
+getBundleFromKey() ::= <<
+ select <fields()>
from bundles
where
- name = :name
+ external_key = :externalKey
;
>>
-
-getBundleFromAccount(account_id) ::= <<
- select
- id
- , start_dt
- , name
- , account_id
+getBundleFromAccount() ::= <<
+ select <fields()>
from bundles
where
- account_id = :account_id
+ account_id = :accountId
;
>>
+getRecordId() ::= <<
+ SELECT record_id
+ FROM bundles
+ WHERE id = :id;
+>>
+
+auditFields(prefix) ::= <<
+ <prefix>table_name,
+ <prefix>record_id,
+ <prefix>change_type,
+ <prefix>change_date,
+ <prefix>changed_by,
+ <prefix>reason_code,
+ <prefix>comments,
+ <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+ INSERT INTO audit_log(<auditFields()>)
+ VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
\ No newline at end of file
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg
index 780c06a..1b16b00 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/SubscriptionSqlDao.sql.stg
@@ -5,24 +5,24 @@ insertSubscription() ::= <<
id
, bundle_id
, category
- , start_dt
- , bundle_start_dt
+ , start_date
+ , bundle_start_date
, active_version
- , ctd_dt
- , ptd_dt
+ , charged_through_date
+ , paid_through_date
, created_by
, created_date
, updated_by
, updated_date
) values (
:id
- , :bundle_id
+ , :bundleId
, :category
- , :start_dt
- , :bundle_start_dt
- , :active_version
- , :ctd_dt
- , :ptd_dt
+ , :startDate
+ , :bundleStartDate
+ , :activeVersion
+ , :chargedThroughDate
+ , :paidThroughDate
, :userName
, :createdDate
, :userName
@@ -30,44 +30,86 @@ insertSubscription() ::= <<
);
>>
-getSubscriptionFromId(id) ::= <<
+getSubscriptionFromId() ::= <<
select
id
, bundle_id
, category
- , start_dt
- , bundle_start_dt
+ , start_date
+ , bundle_start_date
, active_version
- , ctd_dt
- , ptd_dt
+ , charged_through_date
+ , paid_through_date
from subscriptions
where id = :id
;
>>
-getSubscriptionsFromBundleId(bundle_id) ::= <<
+getSubscriptionsFromBundleId() ::= <<
select
id
, bundle_id
, category
- , start_dt
- , bundle_start_dt
+ , start_date
+ , bundle_start_date
, active_version
- , ctd_dt
- , ptd_dt
+ , charged_through_date
+ , paid_through_date
from subscriptions
- where bundle_id = :bundle_id
+ where bundle_id = :bundleId
;
>>
-updateSubscription(id, active_version, ctd_dt, ptd_dt) ::= <<
+updateChargedThroughDate() ::= <<
update subscriptions
set
- active_version = :active_version
- , ctd_dt = :ctd_dt
- , ptd_dt = :ptd_dt
+ charged_through_date = :chargedThroughDate
, updated_by = :userName
, updated_date = :updatedDate
where id = :id
;
>>
+
+updateActiveVersion() ::= <<
+ update subscriptions
+ set
+ active_version = :activeVersion
+ , updated_by = :userName
+ , updated_date = :updatedDate
+ where id = :id
+ ;
+>>
+
+updateForRepair() ::= <<
+ update subscriptions
+ set
+ active_version = :activeVersion
+ , start_date = :startDate
+ , bundle_start_date = :bundleStartDate
+ , updated_by = :userName
+ , updated_date = :updatedDate
+ where id = :id
+ ;
+>>
+
+getRecordId() ::= <<
+ SELECT record_id
+ FROM subscriptions
+ WHERE id = :id;
+>>
+
+auditFields(prefix) ::= <<
+ <prefix>table_name,
+ <prefix>record_id,
+ <prefix>change_type,
+ <prefix>change_date,
+ <prefix>changed_by,
+ <prefix>reason_code,
+ <prefix>comments,
+ <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+ INSERT INTO audit_log(<auditFields()>)
+ VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
index 1d679a9..2681a0b 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
@@ -27,15 +27,15 @@ import java.util.List;
import java.util.UUID;
import org.joda.time.DateTime;
+import org.joda.time.Interval;
import org.testng.Assert;
+import com.ning.billing.api.TestApiListener.NextEvent;
import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.catalog.api.Duration;
import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.PlanPhaseSpecifier;
import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
import com.ning.billing.entitlement.api.TestApiBase;
import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementAccountMigration;
import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementBundleMigration;
@@ -44,15 +44,16 @@ import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.Entitl
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 org.testng.annotations.Test;
-@Test(groups = {"slow"})
public abstract class TestMigration extends TestApiBase {
public void testSingleBasePlan() {
try {
+
+ log.info("Starting testSingleBasePlan");
+
final DateTime startDate = clock.getUTCNow().minusMonths(2);
- DateTime beforeMigration = clock.getUTCNow();
+ DateTime beforeMigration = clock.getUTCNow();
EntitlementAccountMigration toBeMigrated = createAccountWithRegularBasePlan(startDate);
DateTime afterMigration = clock.getUTCNow();
@@ -69,11 +70,13 @@ public abstract class TestMigration extends TestApiBase {
Subscription subscription = subscriptions.get(0);
assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
assertEquals(subscription.getEndDate(), null);
- assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+ assertEquals(subscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-annual");
assertEquals(subscription.getChargedThroughDate(), startDate.plusYears(1));
+
+ assertListenerStatus();
} catch (EntitlementMigrationApiException e) {
Assert.fail("", e);
}
@@ -81,6 +84,7 @@ public abstract class TestMigration extends TestApiBase {
public void testPlanWithAddOn() {
try {
+ log.info("Starting testPlanWithAddOn");
DateTime beforeMigration = clock.getUTCNow();
final DateTime initalBPStart = clock.getUTCNow().minusMonths(3);
final DateTime initalAddonStart = clock.getUTCNow().minusMonths(1).plusDays(7);
@@ -103,7 +107,7 @@ public abstract class TestMigration extends TestApiBase {
subscriptions.get(0) : subscriptions.get(1);
assertDateWithin(baseSubscription.getStartDate(), beforeMigration, afterMigration);
assertEquals(baseSubscription.getEndDate(), null);
- assertEquals(baseSubscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+ assertEquals(baseSubscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
assertEquals(baseSubscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
assertEquals(baseSubscription.getState(), SubscriptionState.ACTIVE);
assertEquals(baseSubscription.getCurrentPlan().getName(), "shotgun-annual");
@@ -111,14 +115,17 @@ public abstract class TestMigration extends TestApiBase {
Subscription aoSubscription = (subscriptions.get(0).getCurrentPlan().getProduct().getCategory() == ProductCategory.ADD_ON) ?
subscriptions.get(0) : subscriptions.get(1);
- assertEquals(aoSubscription.getStartDate(), initalAddonStart);
+ // initalAddonStart.plusMonths(1).minusMonths(1) may be different from initalAddonStart, depending on exact date
+ // e.g : March 31 + 1 month => April 30 and April 30 - 1 month = March 30 which is != March 31 !!!!
+ assertEquals(aoSubscription.getStartDate(), initalAddonStart.plusMonths(1).minusMonths(1));
assertEquals(aoSubscription.getEndDate(), null);
- assertEquals(aoSubscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+ assertEquals(aoSubscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
assertEquals(aoSubscription.getCurrentPhase().getPhaseType(), PhaseType.DISCOUNT);
assertEquals(aoSubscription.getState(), SubscriptionState.ACTIVE);
assertEquals(aoSubscription.getCurrentPlan().getName(), "telescopic-scope-monthly");
assertEquals(aoSubscription.getChargedThroughDate(), initalAddonStart.plusMonths(1));
+ assertListenerStatus();
} catch (EntitlementMigrationApiException e) {
Assert.fail("", e);
}
@@ -127,7 +134,7 @@ public abstract class TestMigration extends TestApiBase {
public void testSingleBasePlanFutureCancelled() {
try {
-
+ log.info("Starting testSingleBasePlanFutureCancelled");
final DateTime startDate = clock.getUTCNow().minusMonths(1);
DateTime beforeMigration = clock.getUTCNow();
EntitlementAccountMigration toBeMigrated = createAccountWithRegularBasePlanFutreCancelled(startDate);
@@ -146,26 +153,30 @@ public abstract class TestMigration extends TestApiBase {
assertEquals(subscriptions.size(), 1);
Subscription subscription = subscriptions.get(0);
assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
- assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+ assertEquals(subscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-annual");
assertEquals(subscription.getChargedThroughDate(), startDate.plusYears(1));
+
testListener.pushExpectedEvent(NextEvent.MIGRATE_BILLING);
testListener.pushExpectedEvent(NextEvent.CANCEL);
- Duration oneYear = getDurationYear(1);
- clock.setDeltaFromReality(oneYear, 0);
+
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusYears(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
assertTrue(testListener.isCompleted(5000));
assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
assertNotNull(subscription.getEndDate());
assertTrue(subscription.getEndDate().isAfterNow());
- assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+ assertEquals(subscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
assertEquals(subscription.getCurrentPhase(), null);
assertEquals(subscription.getState(), SubscriptionState.CANCELLED);
assertNull(subscription.getCurrentPlan());
+ assertListenerStatus();
+
} catch (EntitlementMigrationApiException e) {
Assert.fail("", e);
}
@@ -174,6 +185,8 @@ public abstract class TestMigration extends TestApiBase {
public void testSingleBasePlanWithPendingPhase() {
try {
+
+ log.info("Starting testSingleBasePlanWithPendingPhase");
final DateTime trialDate = clock.getUTCNow().minusDays(10);
EntitlementAccountMigration toBeMigrated = createAccountFuturePendingPhase(trialDate);
@@ -191,7 +204,7 @@ public abstract class TestMigration extends TestApiBase {
assertEquals(subscription.getStartDate(), trialDate);
assertEquals(subscription.getEndDate(), null);
- assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+ assertEquals(subscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.TRIAL);
assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-monthly");
@@ -199,18 +212,21 @@ public abstract class TestMigration extends TestApiBase {
testListener.pushExpectedEvent(NextEvent.MIGRATE_BILLING);
testListener.pushExpectedEvent(NextEvent.PHASE);
- Duration thirtyDays = getDurationDay(30);
- clock.setDeltaFromReality(thirtyDays, 0);
+
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(30));
+ clock.addDeltaFromReality(it.toDurationMillis());
assertTrue(testListener.isCompleted(5000));
assertEquals(subscription.getStartDate(), trialDate);
assertEquals(subscription.getEndDate(), null);
- assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+ assertEquals(subscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-monthly");
assertEquals(subscription.getCurrentPhase().getName(), "assault-rifle-monthly-evergreen");
+ assertListenerStatus();
+
} catch (EntitlementMigrationApiException e) {
Assert.fail("", e);
}
@@ -219,6 +235,7 @@ public abstract class TestMigration extends TestApiBase {
public void testSingleBasePlanWithPendingChange() {
try {
+ log.info("Starting testSingleBasePlanWithPendingChange");
DateTime beforeMigration = clock.getUTCNow();
EntitlementAccountMigration toBeMigrated = createAccountFuturePendingChange();
DateTime afterMigration = clock.getUTCNow();
@@ -236,24 +253,27 @@ public abstract class TestMigration extends TestApiBase {
Subscription subscription = subscriptions.get(0);
assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
assertEquals(subscription.getEndDate(), null);
- assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+ assertEquals(subscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-monthly");
testListener.pushExpectedEvent(NextEvent.CHANGE);
- Duration oneMonth = getDurationMonth(1);
- clock.setDeltaFromReality(oneMonth, 0);
+
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
assertTrue(testListener.isCompleted(5000));
assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
assertEquals(subscription.getEndDate(), null);
- assertEquals(subscription.getCurrentPriceList(), PriceListSet.DEFAULT_PRICELIST_NAME);
+ assertEquals(subscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
assertEquals(subscription.getCurrentPlan().getName(), "shotgun-annual");
+ assertListenerStatus();
+
} catch (EntitlementMigrationApiException e) {
Assert.fail("", e);
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationMemory.java
index 6ea53bc..0195d69 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationMemory.java
@@ -31,26 +31,26 @@ public class TestMigrationMemory extends TestMigration {
}
@Override
- @Test(enabled=false, groups="fast")
+ @Test(enabled=true, groups="fast")
public void testSingleBasePlan() {
super.testSingleBasePlan();
}
@Override
- @Test(enabled=false, groups="fast")
+ @Test(enabled=true, groups="fast")
public void testSingleBasePlanFutureCancelled() {
super.testSingleBasePlanFutureCancelled();
}
@Override
- @Test(enabled=false, groups="fast")
+ @Test(enabled=true, groups="fast")
public void testPlanWithAddOn() {
super.testPlanWithAddOn();
}
@Override
- @Test(enabled=false, groups="fast")
+ @Test(enabled=true, groups="fast")
public void testSingleBasePlanWithPendingPhase() {
super.testSingleBasePlanWithPendingPhase();
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationSql.java
index 587ca46..34b2266 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationSql.java
@@ -23,9 +23,7 @@ import com.google.inject.Injector;
import com.google.inject.Stage;
import com.ning.billing.entitlement.glue.MockEngineModuleSql;
-@Test(groups = "slow")
public class TestMigrationSql extends TestMigration {
-
@Override
protected Injector getInjector() {
return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
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 8379269..7d1389e 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
@@ -26,24 +26,26 @@ import java.net.URL;
import java.util.List;
import java.util.UUID;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.TestCallContext;
+import javax.annotation.Nullable;
+
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
+import org.joda.time.Period;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
-import org.testng.ITestResult;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
-import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.BeforeSuite;
import com.google.inject.Injector;
+import com.google.inject.Key;
import com.ning.billing.account.api.AccountData;
+import com.ning.billing.api.TestApiListener;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.api.TestListenerStatus;
import com.ning.billing.catalog.DefaultCatalogService;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Catalog;
@@ -56,14 +58,14 @@ import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.catalog.api.TimeUnit;
import com.ning.billing.config.EntitlementConfig;
import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
+import com.ning.billing.entitlement.api.billing.ChargeThruApi;
import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
+import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
import com.ning.billing.entitlement.api.user.EntitlementUserApi;
import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
import com.ning.billing.entitlement.api.user.SubscriptionData;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
import com.ning.billing.entitlement.engine.core.Engine;
import com.ning.billing.entitlement.engine.dao.EntitlementDao;
import com.ning.billing.entitlement.engine.dao.MockEntitlementDao;
@@ -72,24 +74,27 @@ import com.ning.billing.entitlement.events.EntitlementEvent;
import com.ning.billing.entitlement.events.phase.PhaseEvent;
import com.ning.billing.entitlement.events.user.ApiEvent;
import com.ning.billing.entitlement.events.user.ApiEventType;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.TestCallContext;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.bus.DefaultBusService;
-import com.ning.billing.util.bus.BusService;
-
-import javax.annotation.Nullable;
+import com.ning.billing.util.glue.RealImplementation;
-public abstract class TestApiBase {
+public abstract class TestApiBase implements TestListenerStatus {
+
protected static final Logger log = LoggerFactory.getLogger(TestApiBase.class);
protected static final long DAY_IN_MS = (24 * 3600 * 1000);
protected EntitlementService entitlementService;
protected EntitlementUserApi entitlementApi;
- protected EntitlementBillingApi billingApi;
+ protected ChargeThruApi billingApi;
protected EntitlementMigrationApi migrationApi;
+ protected EntitlementTimelineApi repairApi;
protected CatalogService catalogService;
protected EntitlementConfig config;
@@ -99,12 +104,23 @@ public abstract class TestApiBase {
protected AccountData accountData;
protected Catalog catalog;
- protected ApiTestListener testListener;
+ protected TestApiListener testListener;
protected SubscriptionBundle bundle;
private MysqlTestingHelper helper;
protected CallContext context = new TestCallContext("Api Test");
+ private boolean isListenerFailed;
+ private String listenerFailedMsg;
+
+ //
+ // The date on which we make our test start; just to ensure that running tests at different dates does not
+ // produce different results. nothing specific about that date; we could change it to anything.
+ //
+ protected DateTime testStartDate = new DateTime(2012, 5, 7, 0, 3, 42, 0);
+
+
+
public static void loadSystemPropertiesFromClasspath(final String resource) {
final URL url = TestApiBase.class.getResource(resource);
assertNotNull(url);
@@ -128,37 +144,57 @@ public abstract class TestApiBase {
} catch (Exception e) {
log.warn("Failed to tearDown test properly ", e);
}
- //if(helper != null) { helper.stopMysql(); }
}
+
+ @Override
+ public void failed(final String msg) {
+ this.isListenerFailed = true;
+ this.listenerFailedMsg = msg;
+ }
+
+ @Override
+ public void resetTestListenerStatus() {
+ this.isListenerFailed = false;
+ this.listenerFailedMsg = null;
+ }
+
@BeforeClass(alwaysRun = true)
- public void setup() {
+ public void setup() throws Exception {
loadSystemPropertiesFromClasspath("/entitlement.properties");
final Injector g = getInjector();
entitlementService = g.getInstance(EntitlementService.class);
+ EntitlementUserApi entApi = (EntitlementUserApi)g.getInstance(Key.get(EntitlementUserApi.class, RealImplementation.class));
+ entitlementApi = entApi;
+ billingApi = g.getInstance(ChargeThruApi.class);
+ migrationApi = g.getInstance(EntitlementMigrationApi.class);
+ repairApi = g.getInstance(EntitlementTimelineApi.class);
catalogService = g.getInstance(CatalogService.class);
busService = g.getInstance(BusService.class);
config = g.getInstance(EntitlementConfig.class);
dao = g.getInstance(EntitlementDao.class);
clock = (ClockMock) g.getInstance(Clock.class);
helper = (isSqlTest(dao)) ? g.getInstance(MysqlTestingHelper.class) : null;
-
- try {
- ((DefaultCatalogService) catalogService).loadCatalog();
- ((DefaultBusService) busService).startBus();
- ((Engine) entitlementService).initialize();
- init();
- } catch (Exception e) {
- }
+ init();
}
- private static boolean isSqlTest(EntitlementDao theDao) {
- return (! (theDao instanceof MockEntitlementDaoMemory));
+ private void init() throws Exception {
+
+ setupDao();
+
+ ((DefaultCatalogService) catalogService).loadCatalog();
+ ((Engine) entitlementService).initialize();
+
+ accountData = getAccountData();
+ assertNotNull(accountData);
+ catalog = catalogService.getFullCatalog();
+ assertNotNull(catalog);
+ testListener = new TestApiListener(this);
}
- private void setupMySQL() throws IOException {
+ private void setupDao() throws IOException {
if (helper != null) {
final String entitlementDdl = IOUtils.toString(TestApiBase.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
final String utilDdl = IOUtils.toString(TestApiBase.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
@@ -168,68 +204,87 @@ public abstract class TestApiBase {
}
}
- private void init() throws Exception {
-
- setupMySQL();
-
- accountData = getAccountData();
- assertNotNull(accountData);
-
- catalog = catalogService.getFullCatalog();
- assertNotNull(catalog);
-
-
- testListener = new ApiTestListener(busService.getBus());
- entitlementApi = entitlementService.getUserApi();
- billingApi = entitlementService.getBillingApi();
- migrationApi = entitlementService.getMigrationApi();
+ private static boolean isSqlTest(EntitlementDao theDao) {
+ return (! (theDao instanceof MockEntitlementDaoMemory));
}
-
+
@BeforeMethod(alwaysRun = true)
- public void setupTest() {
+ public void setupTest() throws Exception {
log.warn("RESET TEST FRAMEWORK\n\n");
+ // CLEANUP ALL DB TABLES OR IN MEMORY STRUCTURES
+ cleanupDao();
+
+ // RESET LIST OF EXPECTED EVENTS
if (testListener != null) {
testListener.reset();
+ resetTestListenerStatus();
}
-
+
+ // RESET CLOCK
clock.resetDeltaFromReality();
- ((MockEntitlementDao) dao).reset();
-
- try {
- busService.getBus().register(testListener);
- UUID accountId = UUID.randomUUID();
- bundle = entitlementApi.createBundleForAccount(accountId, "myDefaultBundle", context);
- } catch (Exception e) {
- Assert.fail(e.getMessage());
- }
- assertNotNull(bundle);
+ // START BUS AND REGISTER LISTENER
+ busService.getBus().start();
+ busService.getBus().register(testListener);
+
+ // START NOTIFICATION QUEUE FOR ENTITLEMENT
((Engine)entitlementService).start();
+
+ // SETUP START DATE
+ clock.setDeltaFromReality(testStartDate.getMillis() - clock.getUTCNow().getMillis());
+
+ // CREATE NEW BUNDLE FOR TEST
+ UUID accountId = UUID.randomUUID();
+ bundle = entitlementApi.createBundleForAccount(accountId, "myDefaultBundle", context);
+ assertNotNull(bundle);
}
@AfterMethod(alwaysRun = true)
- public void cleanupTest() {
- try {
- busService.getBus().unregister(testListener);
- ((Engine)entitlementService).stop();
- } catch (Exception e) {
- Assert.fail(e.getMessage());
- }
+ public void cleanupTest() throws Exception {
+
+ // UNREGISTER TEST LISTENER AND STOP BUS
+ busService.getBus().unregister(testListener);
+ busService.getBus().stop();
+
+ // STOP NOTIFICATION QUEUE
+ ((Engine)entitlementService).stop();
+
log.warn("DONE WITH TEST\n");
}
+
+ protected void assertListenerStatus() {
+ if (isListenerFailed) {
+ log.error(listenerFailedMsg);
+ Assert.fail(listenerFailedMsg);
+ }
+ }
+
+ private void cleanupDao() {
+ if (helper != null) {
+ helper.cleanupAllTables();
+ } else {
+ ((MockEntitlementDao) dao).reset();
+ }
+ }
- protected SubscriptionData createSubscription(final String productName, final BillingPeriod term, final String planSet) throws EntitlementUserApiException {
- return createSubscriptionWithBundle(bundle.getId(), productName, term, planSet);
+ protected SubscriptionData createSubscription(final String productName, final BillingPeriod term, final String planSet, final DateTime requestedDate)
+ throws EntitlementUserApiException {
+ return createSubscriptionWithBundle(bundle.getId(), productName, term, planSet, requestedDate);
+ }
+ protected SubscriptionData createSubscription(final String productName, final BillingPeriod term, final String planSet)
+ throws EntitlementUserApiException {
+ return createSubscriptionWithBundle(bundle.getId(), productName, term, planSet, null);
}
- protected SubscriptionData createSubscriptionWithBundle(final UUID bundleId, final String productName, final BillingPeriod term, final String planSet) throws EntitlementUserApiException {
+ protected SubscriptionData createSubscriptionWithBundle(final UUID bundleId, final String productName, final BillingPeriod term, final String planSet, final DateTime requestedDate)
+ throws EntitlementUserApiException {
+
testListener.pushExpectedEvent(NextEvent.CREATE);
-
SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundleId,
new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSet, null),
- clock.getUTCNow(), context);
+ requestedDate == null ? clock.getUTCNow() : requestedDate, context);
assertNotNull(subscription);
assertTrue(testListener.isCompleted(5000));
return subscription;
@@ -262,7 +317,6 @@ public abstract class TestApiBase {
}
}
-
protected void assertDateWithin(DateTime in, DateTime lower, DateTime upper) {
assertTrue(in.isEqual(lower) || in.isAfter(lower));
assertTrue(in.isEqual(upper) || in.isBefore(upper));
@@ -283,6 +337,10 @@ public abstract class TestApiBase {
public DateTime addToDateTime(DateTime dateTime) {
return null;
}
+ @Override
+ public Period toJodaPeriod() {
+ throw new UnsupportedOperationException();
+ }
};
return result;
}
@@ -302,6 +360,10 @@ public abstract class TestApiBase {
public DateTime addToDateTime(DateTime dateTime) {
return null; //To change body of implemented methods use File | Settings | File Templates.
}
+ @Override
+ public Period toJodaPeriod() {
+ throw new UnsupportedOperationException();
+ }
};
return result;
}
@@ -320,7 +382,11 @@ public abstract class TestApiBase {
@Override
public DateTime addToDateTime(DateTime dateTime) {
- return null; //To change body of implemented methods use File | Settings | File Templates.
+ return dateTime.plusYears(years);
+ }
+ @Override
+ public Period toJodaPeriod() {
+ throw new UnsupportedOperationException();
}
};
return result;
@@ -349,6 +415,16 @@ public abstract class TestApiBase {
}
@Override
+ public boolean isMigrated() {
+ return false;
+ }
+
+ @Override
+ public boolean isNotifiedForInvoices() {
+ return false;
+ }
+
+ @Override
public String getExternalKey() {
return "k123456";
}
@@ -427,8 +503,8 @@ public abstract class TestApiBase {
}
}
- protected void printSubscriptionTransitions(List<SubscriptionTransition> transitions) {
- for (SubscriptionTransition cur : transitions) {
+ protected void printSubscriptionTransitions(List<SubscriptionEvent> transitions) {
+ for (SubscriptionEvent cur : transitions) {
log.debug("Transition " + cur);
}
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestEventJson.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestEventJson.java
new file mode 100644
index 0000000..49e9ed7
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestEventJson.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import java.util.UUID;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.ning.billing.entitlement.api.timeline.DefaultRepairEntitlementEvent;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementEvent;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionEvent;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+
+public class TestEventJson {
+
+
+ private ObjectMapper mapper = new ObjectMapper();
+
+ @BeforeTest(groups= {"fast"})
+ public void setup() {
+ mapper = new ObjectMapper();
+ mapper.disable(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS);
+ }
+
+ @Test(groups= {"fast"})
+ public void testSubscriptionEvent() throws Exception {
+
+
+ SubscriptionEvent e = new DefaultSubscriptionEvent(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), new DateTime(), new DateTime(),
+ SubscriptionState.ACTIVE, "pro", "TRIAL", "DEFAULT", SubscriptionState.CANCELLED, null, null, null, 3L, UUID.randomUUID(), SubscriptionTransitionType.CANCEL, 0, new DateTime());
+
+ String json = mapper.writeValueAsString(e);
+
+ Class<?> claz = Class.forName(DefaultSubscriptionEvent.class.getName());
+ Object obj = mapper.readValue(json, claz);
+ Assert.assertTrue(obj.equals(e));
+
+ }
+
+ @Test(groups= {"fast"})
+ public void testRepairEntitlementEvent() throws Exception {
+ RepairEntitlementEvent e = new DefaultRepairEntitlementEvent(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), new DateTime());
+
+ String json = mapper.writeValueAsString(e);
+
+ Class<?> claz = Class.forName(DefaultRepairEntitlementEvent.class.getName());
+ Object obj = mapper.readValue(json, claz);
+ Assert.assertTrue(obj.equals(e));
+ }
+
+
+
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestApiBaseRepair.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestApiBaseRepair.java
new file mode 100644
index 0000000..4fc11ab
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestApiBaseRepair.java
@@ -0,0 +1,222 @@
+/*
+ * 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.timeline;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.TestApiBase;
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.entitlement.api.timeline.EntitlementRepairException;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+
+
+public abstract class TestApiBaseRepair extends TestApiBase {
+
+ protected final static Logger log = LoggerFactory.getLogger(TestApiBaseRepair.class);
+
+ public interface TestWithExceptionCallback {
+ public void doTest() throws EntitlementRepairException, EntitlementUserApiException;
+ }
+
+ public static class TestWithException {
+ public void withException(TestWithExceptionCallback callback, ErrorCode code) throws Exception {
+ try {
+ callback.doTest();
+ Assert.fail("Failed to catch exception " + code);
+ } catch (EntitlementRepairException e) {
+ assertEquals(e.getCode(), code.getCode());
+ }
+ }
+ }
+
+
+ protected SubscriptionTimeline createSubscriptionReapir(final UUID id, final List<DeletedEvent> deletedEvents, final List<NewEvent> newEvents) {
+ return new SubscriptionTimeline() {
+ @Override
+ public UUID getId() {
+ return id;
+ }
+ @Override
+ public List<NewEvent> getNewEvents() {
+ return newEvents;
+ }
+ @Override
+ public List<ExistingEvent> getExistingEvents() {
+ return null;
+ }
+ @Override
+ public List<DeletedEvent> getDeletedEvents() {
+ return deletedEvents;
+ }
+ };
+ }
+
+ protected BundleTimeline createBundleRepair(final UUID bundleId, final String viewId, final List<SubscriptionTimeline> subscriptionRepair) {
+ return new BundleTimeline() {
+ @Override
+ public String getViewId() {
+ return viewId;
+ }
+ @Override
+ public List<SubscriptionTimeline> getSubscriptions() {
+ return subscriptionRepair;
+ }
+ @Override
+ public UUID getBundleId() {
+ return bundleId;
+ }
+ @Override
+ public String getExternalKey() {
+ return null;
+ }
+ };
+ }
+
+ protected ExistingEvent createExistingEventForAssertion(final SubscriptionTransitionType type,
+ final String productName, final PhaseType phaseType, final ProductCategory category, final String priceListName, final BillingPeriod billingPeriod,
+ final DateTime effectiveDateTime) {
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
+ ExistingEvent ev = new ExistingEvent() {
+ @Override
+ public SubscriptionTransitionType getSubscriptionTransitionType() {
+ return type;
+ }
+ @Override
+ public DateTime getRequestedDate() {
+ return null;
+ }
+ @Override
+ public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+ return spec;
+ }
+ @Override
+ public UUID getEventId() {
+ return null;
+ }
+ @Override
+ public DateTime getEffectiveDate() {
+ return effectiveDateTime;
+ }
+ };
+ return ev;
+ }
+
+ protected SubscriptionTimeline getSubscriptionRepair(final UUID id, final BundleTimeline bundleRepair) {
+ for (SubscriptionTimeline cur : bundleRepair.getSubscriptions()) {
+ if (cur.getId().equals(id)) {
+ return cur;
+ }
+ }
+ Assert.fail("Failed to find SubscriptionReapir " + id);
+ return null;
+ }
+ protected void validateExistingEventForAssertion(final ExistingEvent expected, final ExistingEvent input) {
+
+ log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName()));
+ assertEquals(input.getPlanPhaseSpecifier().getProductName(), expected.getPlanPhaseSpecifier().getProductName());
+ log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType()));
+ assertEquals(input.getPlanPhaseSpecifier().getPhaseType(), expected.getPlanPhaseSpecifier().getPhaseType());
+ log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory()));
+ assertEquals(input.getPlanPhaseSpecifier().getProductCategory(), expected.getPlanPhaseSpecifier().getProductCategory());
+ log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName()));
+ assertEquals(input.getPlanPhaseSpecifier().getPriceListName(), expected.getPlanPhaseSpecifier().getPriceListName());
+ log.info(String.format("Got %s -> Expected %s", input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod()));
+ assertEquals(input.getPlanPhaseSpecifier().getBillingPeriod(), expected.getPlanPhaseSpecifier().getBillingPeriod());
+ log.info(String.format("Got %s -> Expected %s", input.getEffectiveDate(), expected.getEffectiveDate()));
+ assertEquals(input.getEffectiveDate(), expected.getEffectiveDate());
+ }
+
+ protected DeletedEvent createDeletedEvent(final UUID eventId) {
+ return new DeletedEvent() {
+ @Override
+ public UUID getEventId() {
+ return eventId;
+ }
+ };
+ }
+
+ protected NewEvent createNewEvent(final SubscriptionTransitionType type, final DateTime requestedDate, final PlanPhaseSpecifier spec) {
+
+ return new NewEvent() {
+ @Override
+ public SubscriptionTransitionType getSubscriptionTransitionType() {
+ return type;
+ }
+ @Override
+ public DateTime getRequestedDate() {
+ return requestedDate;
+ }
+ @Override
+ public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+ return spec;
+ }
+ };
+ }
+
+ protected void sortEventsOnBundle(final BundleTimeline bundle) {
+ if (bundle.getSubscriptions() == null) {
+ return;
+ }
+ for (SubscriptionTimeline cur : bundle.getSubscriptions()) {
+ if (cur.getExistingEvents() != null) {
+ sortExistingEvent(cur.getExistingEvents());
+ }
+ if (cur.getNewEvents() != null) {
+ sortNewEvent(cur.getNewEvents());
+ }
+ }
+ }
+
+ protected void sortExistingEvent(final List<ExistingEvent> events) {
+ Collections.sort(events, new Comparator<ExistingEvent>() {
+ @Override
+ public int compare(ExistingEvent arg0, ExistingEvent arg1) {
+ return arg0.getEffectiveDate().compareTo(arg1.getEffectiveDate());
+ }
+ });
+ }
+ protected void sortNewEvent(final List<NewEvent> events) {
+ Collections.sort(events, new Comparator<NewEvent>() {
+ @Override
+ public int compare(NewEvent arg0, NewEvent arg1) {
+ return arg0.getRequestedDate().compareTo(arg1.getRequestedDate());
+ }
+ });
+ }
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairBP.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairBP.java
new file mode 100644
index 0000000..9e864cd
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairBP.java
@@ -0,0 +1,752 @@
+/*
+ * 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.timeline;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionEvents;
+import com.ning.billing.entitlement.glue.MockEngineModuleSql;
+
+public class TestRepairBP extends TestApiBaseRepair {
+
+ @Override
+ public Injector getInjector() {
+ return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
+ }
+
+ @Test(groups={"slow"})
+ public void testFetchBundleRepair() throws Exception {
+
+ log.info("Starting testFetchBundleRepair");
+
+ String baseProduct = "Shotgun";
+ BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+ String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ // CREATE BP
+ Subscription baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+ String aoProduct = "Telescopic-Scope";
+ BillingPeriod aoTerm = BillingPeriod.MONTHLY;
+ String aoPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ List<SubscriptionTimeline> subscriptionRepair = bundleRepair.getSubscriptions();
+ assertEquals(subscriptionRepair.size(), 2);
+
+ for (SubscriptionTimeline cur : subscriptionRepair) {
+ assertNull(cur.getDeletedEvents());
+ assertNull(cur.getNewEvents());
+
+ List<ExistingEvent> events = cur.getExistingEvents();
+ assertEquals(events.size(), 2);
+ sortExistingEvent(events);
+
+ assertEquals(events.get(0).getSubscriptionTransitionType(), SubscriptionTransitionType.CREATE);
+ assertEquals(events.get(1).getSubscriptionTransitionType(), SubscriptionTransitionType.PHASE);
+ final boolean isBP = cur.getId().equals(baseSubscription.getId());
+ if (isBP) {
+ assertEquals(cur.getId(), baseSubscription.getId());
+
+ assertEquals(events.get(0).getPlanPhaseSpecifier().getProductName(), baseProduct);
+ assertEquals(events.get(0).getPlanPhaseSpecifier().getPhaseType(), PhaseType.TRIAL);
+ assertEquals(events.get(0).getPlanPhaseSpecifier().getProductCategory(),ProductCategory.BASE);
+ assertEquals(events.get(0).getPlanPhaseSpecifier().getPriceListName(), basePriceList);
+ assertEquals(events.get(0).getPlanPhaseSpecifier().getBillingPeriod(), BillingPeriod.NO_BILLING_PERIOD);
+
+ assertEquals(events.get(1).getPlanPhaseSpecifier().getProductName(), baseProduct);
+ assertEquals(events.get(1).getPlanPhaseSpecifier().getPhaseType(), PhaseType.EVERGREEN);
+ assertEquals(events.get(1).getPlanPhaseSpecifier().getProductCategory(),ProductCategory.BASE);
+ assertEquals(events.get(1).getPlanPhaseSpecifier().getPriceListName(), basePriceList);
+ assertEquals(events.get(1).getPlanPhaseSpecifier().getBillingPeriod(), baseTerm);
+ } else {
+ assertEquals(cur.getId(), aoSubscription.getId());
+
+ assertEquals(events.get(0).getPlanPhaseSpecifier().getProductName(), aoProduct);
+ assertEquals(events.get(0).getPlanPhaseSpecifier().getPhaseType(), PhaseType.DISCOUNT);
+ assertEquals(events.get(0).getPlanPhaseSpecifier().getProductCategory(),ProductCategory.ADD_ON);
+ assertEquals(events.get(0).getPlanPhaseSpecifier().getPriceListName(), aoPriceList);
+ assertEquals(events.get(1).getPlanPhaseSpecifier().getBillingPeriod(), aoTerm);
+
+ assertEquals(events.get(1).getPlanPhaseSpecifier().getProductName(), aoProduct);
+ assertEquals(events.get(1).getPlanPhaseSpecifier().getPhaseType(), PhaseType.EVERGREEN);
+ assertEquals(events.get(1).getPlanPhaseSpecifier().getProductCategory(),ProductCategory.ADD_ON);
+ assertEquals(events.get(1).getPlanPhaseSpecifier().getPriceListName(), aoPriceList);
+ assertEquals(events.get(1).getPlanPhaseSpecifier().getBillingPeriod(), aoTerm);
+ }
+ }
+ assertListenerStatus();
+ }
+
+ @Test(groups={"slow"})
+ public void testBPRepairWithCancellationOnstart() throws Exception {
+
+ log.info("Starting testBPRepairWithCancellationOnstart");
+
+ String baseProduct = "Shotgun";
+ DateTime startDate = clock.getUTCNow();
+
+ // CREATE BP
+ Subscription baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+
+ // Stays in trial-- for instance
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(10));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+
+ List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+ des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, baseSubscription.getStartDate(), null);
+
+
+ SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+ // FIRST ISSUE DRY RUN
+ BundleTimeline bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+ boolean dryRun = true;
+ BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+ sortEventsOnBundle(dryRunBundleRepair);
+ List<SubscriptionTimeline> subscriptionRepair = dryRunBundleRepair.getSubscriptions();
+ assertEquals(subscriptionRepair.size(), 1);
+ SubscriptionTimeline cur = subscriptionRepair.get(0);
+ int index = 0;
+ List<ExistingEvent> events = subscriptionRepair.get(0).getExistingEvents();
+ assertEquals(events.size(), 2);
+ List<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, baseProduct, PhaseType.TRIAL,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate()));
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, baseProduct, PhaseType.TRIAL,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD,baseSubscription.getStartDate()));
+
+ for (ExistingEvent e : expected) {
+ validateExistingEventForAssertion(e, events.get(index++));
+ }
+
+ SubscriptionData dryRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+
+ assertEquals(dryRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+ assertEquals(dryRunBaseSubscription.getBundleId(), bundle.getId());
+ assertEquals(dryRunBaseSubscription.getStartDate(), baseSubscription.getStartDate());
+
+ Plan currentPlan = dryRunBaseSubscription.getCurrentPlan();
+ assertNotNull(currentPlan);
+ assertEquals(currentPlan.getProduct().getName(), baseProduct);
+ assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+ assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+ PlanPhase currentPhase = dryRunBaseSubscription.getCurrentPhase();
+ assertNotNull(currentPhase);
+ assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
+
+ // SECOND RE-ISSUE CALL-- NON DRY RUN
+ dryRun = false;
+ testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+ BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+ assertTrue(testListener.isCompleted(5000));
+
+ subscriptionRepair = realRunBundleRepair.getSubscriptions();
+ assertEquals(subscriptionRepair.size(), 1);
+ cur = subscriptionRepair.get(0);
+ assertEquals(cur.getId(), baseSubscription.getId());
+ index = 0;
+ for (ExistingEvent e : expected) {
+ validateExistingEventForAssertion(e, events.get(index++));
+ }
+ SubscriptionData realRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+ assertEquals(realRunBaseSubscription.getAllTransitions().size(), 2);
+
+
+ assertEquals(realRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+ assertEquals(realRunBaseSubscription.getBundleId(), bundle.getId());
+ assertEquals(realRunBaseSubscription.getStartDate(), startDate);
+
+ assertEquals(realRunBaseSubscription.getState(), SubscriptionState.CANCELLED);
+
+ assertListenerStatus();
+ }
+
+ @Test(groups={"slow"})
+ public void testBPRepairReplaceCreateBeforeTrial() throws Exception {
+
+ log.info("Starting testBPRepairReplaceCreateBeforeTrial");
+
+ String baseProduct = "Shotgun";
+ String newBaseProduct = "Assault-Rifle";
+
+ DateTime startDate = clock.getUTCNow();
+ int clockShift = -1;
+ DateTime restartDate = startDate.plusDays(clockShift).minusDays(1);
+ LinkedList<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, newBaseProduct, PhaseType.TRIAL,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, restartDate));
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, newBaseProduct, PhaseType.EVERGREEN,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, restartDate.plusDays(30)));
+
+ testBPRepairCreate(true, startDate, clockShift, baseProduct, newBaseProduct, expected);
+ assertListenerStatus();
+ }
+
+ @Test(groups={"slow"}, enabled=true)
+ public void testBPRepairReplaceCreateInTrial() throws Exception {
+
+ log.info("Starting testBPRepairReplaceCreateInTrial");
+
+ String baseProduct = "Shotgun";
+ String newBaseProduct = "Assault-Rifle";
+
+ DateTime startDate = clock.getUTCNow();
+ int clockShift = 10;
+ DateTime restartDate = startDate.plusDays(clockShift).minusDays(1);
+ LinkedList<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, newBaseProduct, PhaseType.TRIAL,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, restartDate));
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, newBaseProduct, PhaseType.EVERGREEN,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, restartDate.plusDays(30)));
+
+ UUID baseSubscriptionId = testBPRepairCreate(true, startDate, clockShift, baseProduct, newBaseProduct, expected);
+
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(32));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
+
+ // CHECK WHAT"S GOING ON AFTER WE MOVE CLOCK-- FUTURE MOTIFICATION SHOULD KICK IN
+ SubscriptionData subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscriptionId);
+
+ assertEquals(subscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+ assertEquals(subscription.getBundleId(), bundle.getId());
+ assertEquals(subscription.getStartDate(), restartDate);
+ assertEquals(subscription.getBundleStartDate(), restartDate);
+
+ Plan currentPlan = subscription.getCurrentPlan();
+ assertNotNull(currentPlan);
+ assertEquals(currentPlan.getProduct().getName(), newBaseProduct);
+ assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+ assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+ PlanPhase currentPhase = subscription.getCurrentPhase();
+ assertNotNull(currentPhase);
+ assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+
+ assertListenerStatus();
+ }
+
+
+ @Test(groups={"slow"})
+ public void testBPRepairReplaceCreateAfterTrial() throws Exception {
+
+ log.info("Starting testBPRepairReplaceCreateAfterTrial");
+
+ String baseProduct = "Shotgun";
+ String newBaseProduct = "Assault-Rifle";
+
+ DateTime startDate = clock.getUTCNow();
+ int clockShift = 40;
+ DateTime restartDate = startDate.plusDays(clockShift).minusDays(1);
+ LinkedList<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, newBaseProduct, PhaseType.TRIAL,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, restartDate));
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, newBaseProduct, PhaseType.EVERGREEN,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, restartDate.plusDays(30)));
+
+ testBPRepairCreate(false, startDate, clockShift, baseProduct, newBaseProduct, expected);
+ assertListenerStatus();
+ }
+
+
+ private UUID testBPRepairCreate(boolean inTrial, DateTime startDate, int clockShift,
+ String baseProduct, String newBaseProduct, List<ExistingEvent> expectedEvents) throws Exception {
+
+ log.info("Starting testBPRepairCreate");
+
+ // CREATE BP
+ Subscription baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+
+ // MOVE CLOCK
+ if (clockShift > 0) {
+ if (!inTrial) {
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ }
+
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(clockShift));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ if (!inTrial) {
+ assertTrue(testListener.isCompleted(5000));
+ }
+ }
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+
+ DateTime newCreateTime = baseSubscription.getStartDate().plusDays(clockShift - 1);
+
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier(newBaseProduct, ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, newCreateTime, spec);
+ List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+ des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));
+ des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+
+ SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+ // FIRST ISSUE DRY RUN
+ BundleTimeline bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+ boolean dryRun = true;
+ BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+ List<SubscriptionTimeline> subscriptionRepair = dryRunBundleRepair.getSubscriptions();
+ assertEquals(subscriptionRepair.size(), 1);
+ SubscriptionTimeline cur = subscriptionRepair.get(0);
+ assertEquals(cur.getId(), baseSubscription.getId());
+
+ List<ExistingEvent> events = cur.getExistingEvents();
+ assertEquals(expectedEvents.size(), events.size());
+ int index = 0;
+ for (ExistingEvent e : expectedEvents) {
+ validateExistingEventForAssertion(e, events.get(index++));
+ }
+ SubscriptionData dryRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+
+ assertEquals(dryRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+ assertEquals(dryRunBaseSubscription.getBundleId(), bundle.getId());
+ assertEquals(dryRunBaseSubscription.getStartDate(), baseSubscription.getStartDate());
+
+ Plan currentPlan = dryRunBaseSubscription.getCurrentPlan();
+ assertNotNull(currentPlan);
+ assertEquals(currentPlan.getProduct().getName(), baseProduct);
+ assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+ assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+ PlanPhase currentPhase = dryRunBaseSubscription.getCurrentPhase();
+ assertNotNull(currentPhase);
+ if (inTrial) {
+ assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
+ } else {
+ assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+ }
+
+ // SECOND RE-ISSUE CALL-- NON DRY RUN
+ dryRun = false;
+ testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+ BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+ assertTrue(testListener.isCompleted(5000));
+ subscriptionRepair = realRunBundleRepair.getSubscriptions();
+ assertEquals(subscriptionRepair.size(), 1);
+ cur = subscriptionRepair.get(0);
+ assertEquals(cur.getId(), baseSubscription.getId());
+
+ events = cur.getExistingEvents();
+ for (ExistingEvent e : events) {
+ log.info(String.format("%s, %s, %s, %s", e.getSubscriptionTransitionType(), e.getEffectiveDate(), e.getPlanPhaseSpecifier().getProductName(), e.getPlanPhaseSpecifier().getPhaseType()));
+ }
+ assertEquals(events.size(), expectedEvents.size());
+ index = 0;
+ for (ExistingEvent e : expectedEvents) {
+ validateExistingEventForAssertion(e, events.get(index++));
+ }
+ SubscriptionData realRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+ assertEquals(realRunBaseSubscription.getAllTransitions().size(), 2);
+
+
+ assertEquals(realRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+ assertEquals(realRunBaseSubscription.getBundleId(), bundle.getId());
+ assertEquals(realRunBaseSubscription.getStartDate(), newCreateTime);
+
+ currentPlan = realRunBaseSubscription.getCurrentPlan();
+ assertNotNull(currentPlan);
+ assertEquals(currentPlan.getProduct().getName(), newBaseProduct);
+ assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+ assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+ currentPhase = realRunBaseSubscription.getCurrentPhase();
+ assertNotNull(currentPhase);
+ assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
+
+ return baseSubscription.getId();
+ }
+
+ @Test(groups={"slow"})
+ public void testBPRepairAddChangeInTrial() throws Exception {
+
+ log.info("Starting testBPRepairAddChangeInTrial");
+
+ String baseProduct = "Shotgun";
+ String newBaseProduct = "Assault-Rifle";
+
+ DateTime startDate = clock.getUTCNow();
+ int clockShift = 10;
+ DateTime changeDate = startDate.plusDays(clockShift).minusDays(1);
+ LinkedList<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, baseProduct, PhaseType.TRIAL,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, startDate));
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CHANGE, newBaseProduct, PhaseType.TRIAL,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, changeDate));
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, newBaseProduct, PhaseType.EVERGREEN,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, startDate.plusDays(30)));
+
+ UUID baseSubscriptionId = testBPRepairAddChange(true, startDate, clockShift, baseProduct, newBaseProduct, expected, 3);
+
+ // CHECK WHAT"S GOING ON AFTER WE MOVE CLOCK-- FUTURE MOTIFICATION SHOULD KICK IN
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(32));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
+ SubscriptionData subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscriptionId);
+
+ assertEquals(subscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+ assertEquals(subscription.getBundleId(), bundle.getId());
+ assertEquals(subscription.getStartDate(), startDate);
+ assertEquals(subscription.getBundleStartDate(), startDate);
+
+ Plan currentPlan = subscription.getCurrentPlan();
+ assertNotNull(currentPlan);
+ assertEquals(currentPlan.getProduct().getName(), newBaseProduct);
+ assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+ assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+ PlanPhase currentPhase = subscription.getCurrentPhase();
+ assertNotNull(currentPhase);
+ assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+
+ assertListenerStatus();
+ }
+
+ @Test(groups={"slow"})
+ public void testBPRepairAddChangeAfterTrial() throws Exception {
+
+ log.info("Starting testBPRepairAddChangeAfterTrial");
+
+ String baseProduct = "Shotgun";
+ String newBaseProduct = "Assault-Rifle";
+
+ DateTime startDate = clock.getUTCNow();
+ int clockShift = 40;
+ DateTime changeDate = startDate.plusDays(clockShift).minusDays(1);
+
+ LinkedList<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, baseProduct, PhaseType.TRIAL,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, startDate));
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, baseProduct, PhaseType.EVERGREEN,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, startDate.plusDays(30)));
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CHANGE, newBaseProduct, PhaseType.EVERGREEN,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, changeDate));
+ testBPRepairAddChange(false, startDate, clockShift, baseProduct, newBaseProduct, expected, 3);
+
+ assertListenerStatus();
+ }
+
+
+ private UUID testBPRepairAddChange(boolean inTrial, DateTime startDate, int clockShift,
+ String baseProduct, String newBaseProduct, List<ExistingEvent> expectedEvents, int expectedTransitions) throws Exception {
+
+
+ // CREATE BP
+ Subscription baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+
+ // MOVE CLOCK
+ if (!inTrial) {
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ }
+
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(clockShift));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ if (!inTrial) {
+ assertTrue(testListener.isCompleted(5000));
+ }
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+
+ DateTime changeTime = baseSubscription.getStartDate().plusDays(clockShift - 1);
+
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier(newBaseProduct, ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, changeTime, spec);
+ List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+ if (inTrial) {
+ des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+ }
+ SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+ // FIRST ISSUE DRY RUN
+ BundleTimeline bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+ boolean dryRun = true;
+ BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+
+ List<SubscriptionTimeline> subscriptionRepair = dryRunBundleRepair.getSubscriptions();
+ assertEquals(subscriptionRepair.size(), 1);
+ SubscriptionTimeline cur = subscriptionRepair.get(0);
+ assertEquals(cur.getId(), baseSubscription.getId());
+
+ List<ExistingEvent> events = cur.getExistingEvents();
+ assertEquals(expectedEvents.size(), events.size());
+ int index = 0;
+ for (ExistingEvent e : expectedEvents) {
+ validateExistingEventForAssertion(e, events.get(index++));
+ }
+ SubscriptionData dryRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+
+ assertEquals(dryRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+ assertEquals(dryRunBaseSubscription.getBundleId(), bundle.getId());
+ assertEquals(dryRunBaseSubscription.getStartDate(), baseSubscription.getStartDate());
+
+ Plan currentPlan = dryRunBaseSubscription.getCurrentPlan();
+ assertNotNull(currentPlan);
+ assertEquals(currentPlan.getProduct().getName(), baseProduct);
+ assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+ assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+ PlanPhase currentPhase = dryRunBaseSubscription.getCurrentPhase();
+ assertNotNull(currentPhase);
+ if (inTrial) {
+ assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
+ } else {
+ assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+ }
+
+
+ // SECOND RE-ISSUE CALL-- NON DRY RUN
+ dryRun = false;
+ testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+ BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+ assertTrue(testListener.isCompleted(5000));
+
+ subscriptionRepair = realRunBundleRepair.getSubscriptions();
+ assertEquals(subscriptionRepair.size(), 1);
+ cur = subscriptionRepair.get(0);
+ assertEquals(cur.getId(), baseSubscription.getId());
+
+ events = cur.getExistingEvents();
+ assertEquals(expectedEvents.size(), events.size());
+ index = 0;
+ for (ExistingEvent e : expectedEvents) {
+ validateExistingEventForAssertion(e, events.get(index++));
+ }
+ SubscriptionData realRunBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+ assertEquals(realRunBaseSubscription.getAllTransitions().size(), expectedTransitions);
+
+
+ assertEquals(realRunBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+ assertEquals(realRunBaseSubscription.getBundleId(), bundle.getId());
+ assertEquals(realRunBaseSubscription.getStartDate(), baseSubscription.getStartDate());
+
+ currentPlan = realRunBaseSubscription.getCurrentPlan();
+ assertNotNull(currentPlan);
+ assertEquals(currentPlan.getProduct().getName(), newBaseProduct);
+ assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+ assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+ currentPhase = realRunBaseSubscription.getCurrentPhase();
+ assertNotNull(currentPhase);
+ if (inTrial) {
+ assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
+ } else {
+ assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+ }
+ return baseSubscription.getId();
+ }
+
+ @Test(groups={"slow"})
+ public void testRepairWithFurureCancelEvent() throws Exception {
+
+ log.info("Starting testRepairWithFurureCancelEvent");
+
+ DateTime startDate = clock.getUTCNow();
+
+ // CREATE BP
+ Subscription baseSubscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+
+ // MOVE CLOCK -- OUT OF TRIAL
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(35));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
+
+ // SET CTD to BASE SUBSCRIPTION SP CANCEL OCCURS EOT
+ DateTime newChargedThroughDate = baseSubscription.getStartDate().plusDays(30).plusMonths(1);
+ billingApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, context);
+ baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+
+
+ DateTime requestedChange = clock.getUTCNow();
+ baseSubscription.changePlan("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, requestedChange, context);
+
+
+ // CHECK CHANGE DID NOT OCCUR YET
+ Plan currentPlan = baseSubscription.getCurrentPlan();
+ assertNotNull(currentPlan);
+ assertEquals(currentPlan.getProduct().getName(), "Shotgun");
+ assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+ assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+
+ DateTime repairTime = clock.getUTCNow().minusDays(1);
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, repairTime, spec);
+ List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+ des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(2).getEventId()));
+
+ SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+ // SKIP DRY RUN AND DO REPAIR...
+ BundleTimeline bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+ boolean dryRun = false;
+ testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+ repairApi.repairBundle(bRepair, dryRun, context);
+ assertTrue(testListener.isCompleted(5000));
+
+ baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+
+ assertEquals(((SubscriptionData) baseSubscription).getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+ assertEquals(baseSubscription.getBundleId(), bundle.getId());
+ assertEquals(baseSubscription.getStartDate(), baseSubscription.getStartDate());
+
+ currentPlan = baseSubscription.getCurrentPlan();
+ assertNotNull(currentPlan);
+ assertEquals(currentPlan.getProduct().getName(), "Assault-Rifle");
+ assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.BASE);
+ assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+ PlanPhase currentPhase = baseSubscription.getCurrentPhase();
+ assertNotNull(currentPhase);
+ assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+
+ assertListenerStatus();
+ }
+
+
+ // Needs real SQL backend to be tested properly
+ @Test(groups={"slow"})
+ public void testENT_REPAIR_VIEW_CHANGED_newEvent() throws Exception {
+
+ log.info("Starting testENT_REPAIR_VIEW_CHANGED_newEvent");
+
+ TestWithException test = new TestWithException();
+ DateTime startDate = clock.getUTCNow();
+
+ final Subscription baseSubscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+
+ test.withException(new TestWithExceptionCallback() {
+ @Override
+ public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+ List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+ des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));
+ des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+ SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+ BundleTimeline bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+ testListener.pushExpectedEvent(NextEvent.CHANGE);
+ DateTime changeTime = clock.getUTCNow();
+ baseSubscription.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, changeTime, context);
+ assertTrue(testListener.isCompleted(5000));
+
+ repairApi.repairBundle(bRepair, true, context);
+ assertListenerStatus();
+ }
+ }, ErrorCode.ENT_REPAIR_VIEW_CHANGED);
+ }
+
+ @Test(groups={"slow"}, enabled=false)
+ public void testENT_REPAIR_VIEW_CHANGED_ctd() throws Exception {
+
+ log.info("Starting testENT_REPAIR_VIEW_CHANGED_ctd");
+
+ TestWithException test = new TestWithException();
+ DateTime startDate = clock.getUTCNow();
+
+ final Subscription baseSubscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+
+ test.withException(new TestWithExceptionCallback() {
+ @Override
+ public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+ List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+ des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));
+ des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+ SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+ BundleTimeline bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+ DateTime newChargedThroughDate = baseSubscription.getStartDate().plusDays(30).plusMonths(1);
+ billingApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, context);
+ entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+
+ repairApi.repairBundle(bRepair, true, context);
+
+ assertListenerStatus();
+ }
+ }, ErrorCode.ENT_REPAIR_VIEW_CHANGED);
+ }
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithAO.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithAO.java
new file mode 100644
index 0000000..d7fd642
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithAO.java
@@ -0,0 +1,782 @@
+/*
+ * 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.timeline;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionEvents;
+import com.ning.billing.entitlement.glue.MockEngineModuleSql;
+
+public class TestRepairWithAO extends TestApiBaseRepair {
+
+ @Override
+ public Injector getInjector() {
+ return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
+ }
+
+ @Test(groups={"slow"})
+ public void testRepairChangeBPWithAddonIncluded() throws Exception {
+
+ log.info("Starting testRepairChangeBPWithAddonIncluded");
+
+ String baseProduct = "Shotgun";
+ BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+ String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ // CREATE BP
+ SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+ // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ SubscriptionData aoSubscription2 = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+
+ // Quick check
+ SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+ assertEquals(bpRepair.getExistingEvents().size(), 2);
+
+ SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+ SubscriptionTimeline aoRepair2 = getSubscriptionRepair(aoSubscription2.getId(), bundleRepair);
+ assertEquals(aoRepair2.getExistingEvents().size(), 2);
+
+ DateTime bpChangeDate = clock.getUTCNow().minusDays(1);
+
+ List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+ des.add(createDeletedEvent(bpRepair.getExistingEvents().get(1).getEventId()));
+
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, bpChangeDate, spec);
+
+ bpRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+ bundleRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(bpRepair));
+
+ boolean dryRun = true;
+ BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+
+ aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+ aoRepair2 = getSubscriptionRepair(aoSubscription2.getId(), dryRunBundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+ bpRepair = getSubscriptionRepair(baseSubscription.getId(), dryRunBundleRepair);
+ assertEquals(bpRepair.getExistingEvents().size(), 3);
+
+ // Check expected for AO
+ List<ExistingEvent> expectedAO = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+ expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
+ expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Telescopic-Scope", PhaseType.DISCOUNT,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, bpChangeDate));
+ int index = 0;
+ for (ExistingEvent e : expectedAO) {
+ validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));
+ }
+
+ List<ExistingEvent> expectedAO2 = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+ expectedAO2.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Laser-Scope", PhaseType.DISCOUNT,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription2.getStartDate()));
+ expectedAO2.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Laser-Scope", PhaseType.EVERGREEN,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription2.getStartDate().plusMonths(1)));
+ index = 0;
+ for (ExistingEvent e : expectedAO2) {
+ validateExistingEventForAssertion(e, aoRepair2.getExistingEvents().get(index++));
+ }
+
+ // Check expected for BP
+ List<ExistingEvent> expectedBP = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+ expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Shotgun", PhaseType.TRIAL,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate()));
+ expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CHANGE, "Assault-Rifle", PhaseType.TRIAL,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, bpChangeDate));
+ expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Assault-Rifle", PhaseType.EVERGREEN,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusDays(30)));
+ index = 0;
+ for (ExistingEvent e : expectedBP) {
+ validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));
+ }
+
+
+ SubscriptionData newAoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+ assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+ assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+ SubscriptionData newAoSubscription2 = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription2.getId());
+ assertEquals(newAoSubscription2.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newAoSubscription2.getAllTransitions().size(), 2);
+ assertEquals(newAoSubscription2.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+
+ SubscriptionData newBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+ assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newBaseSubscription.getAllTransitions().size(), 2);
+ assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+ dryRun = false;
+ testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+ BundleTimeline realRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+ assertTrue(testListener.isCompleted(5000));
+
+
+
+ aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+ bpRepair = getSubscriptionRepair(baseSubscription.getId(), realRunBundleRepair);
+ assertEquals(bpRepair.getExistingEvents().size(), 3);
+
+ index = 0;
+ for (ExistingEvent e : expectedAO) {
+ validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));
+ }
+
+ index = 0;
+ for (ExistingEvent e : expectedAO2) {
+ validateExistingEventForAssertion(e, aoRepair2.getExistingEvents().get(index++));
+ }
+
+ index = 0;
+ for (ExistingEvent e : expectedBP) {
+ validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));
+ }
+
+ newAoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+ assertEquals(newAoSubscription.getState(), SubscriptionState.CANCELLED);
+ assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+ assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+ newAoSubscription2 = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription2.getId());
+ assertEquals(newAoSubscription2.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newAoSubscription2.getAllTransitions().size(), 2);
+ assertEquals(newAoSubscription2.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+
+ newBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+ assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newBaseSubscription.getAllTransitions().size(), 3);
+ assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+ }
+
+ @Test(groups={"slow"})
+ public void testRepairChangeBPWithAddonNonAvailable() throws Exception {
+
+ log.info("Starting testRepairChangeBPWithAddonNonAvailable");
+
+ String baseProduct = "Shotgun";
+ BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+ String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ // CREATE BP
+ SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+ // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ // MOVE CLOCK A LITTLE BIT MORE -- AFTER TRIAL
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(32));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(7000));
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+
+ // Quick check
+ SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+ assertEquals(bpRepair.getExistingEvents().size(), 2);
+
+ SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+ DateTime bpChangeDate = clock.getUTCNow().minusDays(1);
+
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, bpChangeDate, spec);
+
+ bpRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.<SubscriptionTimeline.DeletedEvent>emptyList(), Collections.singletonList(ne));
+
+ bundleRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(bpRepair));
+
+ boolean dryRun = true;
+ BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+
+ aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 3);
+
+ bpRepair = getSubscriptionRepair(baseSubscription.getId(), dryRunBundleRepair);
+ assertEquals(bpRepair.getExistingEvents().size(), 3);
+
+ // Check expected for AO
+ List<ExistingEvent> expectedAO = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+ expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
+ expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.EVERGREEN,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusMonths(1)));
+ expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Telescopic-Scope", PhaseType.EVERGREEN,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, bpChangeDate));
+ int index = 0;
+ for (ExistingEvent e : expectedAO) {
+ validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));
+ }
+
+ // Check expected for BP
+ List<ExistingEvent> expectedBP = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+ expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Shotgun", PhaseType.TRIAL,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate()));
+ expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Shotgun", PhaseType.EVERGREEN,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusDays(30)));
+ expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CHANGE, "Pistol", PhaseType.EVERGREEN,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, bpChangeDate));
+ index = 0;
+ for (ExistingEvent e : expectedBP) {
+ validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));
+ }
+
+ SubscriptionData newAoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+ assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+ assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+ SubscriptionData newBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+ assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newBaseSubscription.getAllTransitions().size(), 2);
+ assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+ dryRun = false;
+ testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+ BundleTimeline realRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+ assertTrue(testListener.isCompleted(5000));
+
+ aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 3);
+
+ bpRepair = getSubscriptionRepair(baseSubscription.getId(), realRunBundleRepair);
+ assertEquals(bpRepair.getExistingEvents().size(), 3);
+
+ index = 0;
+ for (ExistingEvent e : expectedAO) {
+ validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));
+ }
+
+ index = 0;
+ for (ExistingEvent e : expectedBP) {
+ validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));
+ }
+
+ newAoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+ assertEquals(newAoSubscription.getState(), SubscriptionState.CANCELLED);
+ assertEquals(newAoSubscription.getAllTransitions().size(), 3);
+ assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+ newBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+ assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newBaseSubscription.getAllTransitions().size(), 3);
+ assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+ }
+
+ @Test(groups={"slow"})
+ public void testRepairCancelBP_EOT_WithAddons() throws Exception {
+
+ log.info("Starting testRepairCancelBP_EOT_WithAddons");
+
+ String baseProduct = "Shotgun";
+ BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+ String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ // CREATE BP
+ SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+ // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+
+ SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ // MOVE CLOCK A LITTLE BIT MORE -- AFTER TRIAL
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(7000));
+
+ // SET CTD to BASE SUBSCRIPTION SP CANCEL OCCURS EOT
+ DateTime newChargedThroughDate = baseSubscription.getStartDate().plusDays(30).plusMonths(1);
+ billingApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, context);
+ baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+
+ // Quick check
+ SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+ assertEquals(bpRepair.getExistingEvents().size(), 2);
+
+ SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+ DateTime bpCancelDate = clock.getUTCNow().minusDays(1);
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, bpCancelDate, null);
+ bpRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.<SubscriptionTimeline.DeletedEvent>emptyList(), Collections.singletonList(ne));
+ bundleRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(bpRepair));
+
+ boolean dryRun = true;
+ BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+
+ aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 3);
+
+ bpRepair = getSubscriptionRepair(baseSubscription.getId(), dryRunBundleRepair);
+ assertEquals(bpRepair.getExistingEvents().size(), 3);
+
+ // Check expected for AO
+ List<ExistingEvent> expectedAO = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+ expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
+ expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Telescopic-Scope", PhaseType.EVERGREEN,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusMonths(1)));
+ expectedAO.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Telescopic-Scope", PhaseType.EVERGREEN,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, newChargedThroughDate));
+
+ int index = 0;
+ for (ExistingEvent e : expectedAO) {
+ validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));
+ }
+
+ // Check expected for BP
+ List<ExistingEvent> expectedBP = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+ expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Shotgun", PhaseType.TRIAL,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.NO_BILLING_PERIOD, baseSubscription.getStartDate()));
+ expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Shotgun", PhaseType.EVERGREEN,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusDays(30)));
+ expectedBP.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Shotgun", PhaseType.EVERGREEN,
+ ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, newChargedThroughDate));
+ index = 0;
+ for (ExistingEvent e : expectedBP) {
+ validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));
+ }
+
+ SubscriptionData newAoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+ assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+ assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+ SubscriptionData newBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+ assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newBaseSubscription.getAllTransitions().size(), 2);
+ assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+ dryRun = false;
+ testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+ BundleTimeline realRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+ assertTrue(testListener.isCompleted(5000));
+
+ aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 3);
+
+ bpRepair = getSubscriptionRepair(baseSubscription.getId(), realRunBundleRepair);
+ assertEquals(bpRepair.getExistingEvents().size(), 3);
+
+ index = 0;
+ for (ExistingEvent e : expectedAO) {
+ validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));
+ }
+
+ index = 0;
+ for (ExistingEvent e : expectedBP) {
+ validateExistingEventForAssertion(e, bpRepair.getExistingEvents().get(index++));
+ }
+
+ newAoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+ assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newAoSubscription.getAllTransitions().size(), 3);
+ assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+ newBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+ assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newBaseSubscription.getAllTransitions().size(), 3);
+ assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+ // MOVE CLOCK AFTER CANCEL DATE
+ testListener.pushExpectedEvent(NextEvent.CANCEL);
+ testListener.pushExpectedEvent(NextEvent.CANCEL);
+
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(32));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(7000));
+
+ newAoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+ assertEquals(newAoSubscription.getState(), SubscriptionState.CANCELLED);
+ assertEquals(newAoSubscription.getAllTransitions().size(), 3);
+ assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+ newBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+ assertEquals(newBaseSubscription.getState(), SubscriptionState.CANCELLED);
+ assertEquals(newBaseSubscription.getAllTransitions().size(), 3);
+ assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+ }
+
+
+
+ @Test(groups={"slow"})
+ public void testRepairCancelAO() throws Exception {
+
+ log.info("Starting testRepairCancelAO");
+
+ String baseProduct = "Shotgun";
+ BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+ String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ // CREATE BP
+ SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+ // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+
+ // Quick check
+ SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+ assertEquals(bpRepair.getExistingEvents().size(), 2);
+
+ SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+
+ List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+ des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));
+ DateTime aoCancelDate = aoSubscription.getStartDate().plusDays(1);
+
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, aoCancelDate, null);
+
+ SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
+
+ BundleTimeline bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
+
+ boolean dryRun = true;
+ BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+
+ aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+ bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+ assertEquals(bpRepair.getExistingEvents().size(), 2);
+
+ List<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CANCEL, "Telescopic-Scope", PhaseType.DISCOUNT,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoCancelDate));
+ int index = 0;
+ for (ExistingEvent e : expected) {
+ validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));
+ }
+ SubscriptionData newAoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+ assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+ assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+ SubscriptionData newBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+ assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newBaseSubscription.getAllTransitions().size(), 2);
+ assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+ dryRun = false;
+ testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+ BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+ assertTrue(testListener.isCompleted(5000));
+
+
+ aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 2);
+ index = 0;
+ for (ExistingEvent e : expected) {
+ validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));
+ }
+
+ newAoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+ assertEquals(newAoSubscription.getState(), SubscriptionState.CANCELLED);
+ assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+ assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+ newBaseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+ assertEquals(newBaseSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newBaseSubscription.getAllTransitions().size(), 2);
+ assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+ }
+
+
+ @Test(groups={"slow"})
+ public void testRepairRecreateAO() throws Exception {
+
+ log.info("Starting testRepairRecreateAO");
+
+ String baseProduct = "Shotgun";
+ BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+ String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ // CREATE BP
+ SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+ // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+
+ // Quick check
+ SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+ assertEquals(bpRepair.getExistingEvents().size(), 2);
+
+ SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+
+ List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+ des.add(createDeletedEvent(aoRepair.getExistingEvents().get(0).getEventId()));
+ des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));
+
+ DateTime aoRecreateDate = aoSubscription.getStartDate().plusDays(1);
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.DISCOUNT);
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, aoRecreateDate, spec);
+
+ SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
+
+ BundleTimeline bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
+
+ boolean dryRun = true;
+ BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+
+ aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+
+ List<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoRecreateDate));
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Telescopic-Scope", PhaseType.EVERGREEN,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, baseSubscription.getStartDate().plusMonths(1) /* Bundle align */));
+ int index = 0;
+ for (ExistingEvent e : expected) {
+ validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));
+ }
+ SubscriptionData newAoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+ assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+ assertEquals(newAoSubscription.getStartDate(), aoSubscription.getStartDate());
+ assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
+
+ // NOW COMMIT
+ dryRun = false;
+ testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+ BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+ assertTrue(testListener.isCompleted(5000));
+
+ aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 2);
+ index = 0;
+ for (ExistingEvent e : expected) {
+ validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));
+ }
+
+ newAoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+ assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+ assertEquals(newAoSubscription.getStartDate(), aoRecreateDate);
+ assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+
+ }
+
+ // Fasten your seatbelt here:
+ //
+ // We are doing repair for multi-phase tiered-addon with different alignment:
+ // Telescopic-Scope -> Laser-Scope
+ // Tiered ADON logic
+ // . Both multi phase
+ // . Telescopic-Scope (bundle align) and Laser-Scope is Subscription align
+ //
+ @Test(groups={"slow"})
+ public void testRepairChangeAOOK() throws Exception {
+
+ log.info("Starting testRepairChangeAOOK");
+
+ String baseProduct = "Shotgun";
+ BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+ String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ // CREATE BP
+ SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+ // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+
+ // Quick check
+ SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+ assertEquals(bpRepair.getExistingEvents().size(), 2);
+
+ SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+ List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+ des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));
+ DateTime aoChangeDate = aoSubscription.getStartDate().plusDays(1);
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, aoChangeDate, spec);
+
+ SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
+
+ BundleTimeline bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
+
+ boolean dryRun = true;
+ BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+
+ aoRepair = getSubscriptionRepair(aoSubscription.getId(), dryRunBundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 3);
+
+
+ List<ExistingEvent> expected = new LinkedList<SubscriptionTimeline.ExistingEvent>();
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CREATE, "Telescopic-Scope", PhaseType.DISCOUNT,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoSubscription.getStartDate()));
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CHANGE, "Laser-Scope", PhaseType.DISCOUNT,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, aoChangeDate));
+ expected.add(createExistingEventForAssertion(SubscriptionTransitionType.PHASE, "Laser-Scope", PhaseType.EVERGREEN,
+ ProductCategory.ADD_ON, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY,
+ aoSubscription.getStartDate().plusMonths(1) /* Subscription alignment */));
+
+ int index = 0;
+ for (ExistingEvent e : expected) {
+ validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));
+ }
+ SubscriptionData newAoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+ assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newAoSubscription.getAllTransitions().size(), 2);
+
+ // AND NOW COMMIT
+ dryRun = false;
+ testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
+ BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+ assertTrue(testListener.isCompleted(5000));
+
+ aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 3);
+ index = 0;
+ for (ExistingEvent e : expected) {
+ validateExistingEventForAssertion(e, aoRepair.getExistingEvents().get(index++));
+ }
+
+ newAoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+ assertEquals(newAoSubscription.getState(), SubscriptionState.ACTIVE);
+ assertEquals(newAoSubscription.getAllTransitions().size(), 3);
+
+
+ assertEquals(newAoSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
+ assertEquals(newAoSubscription.getBundleId(), bundle.getId());
+ assertEquals(newAoSubscription.getStartDate(), aoSubscription.getStartDate());
+
+ Plan currentPlan = newAoSubscription.getCurrentPlan();
+ assertNotNull(currentPlan);
+ assertEquals(currentPlan.getProduct().getName(), "Laser-Scope");
+ assertEquals(currentPlan.getProduct().getCategory(), ProductCategory.ADD_ON);
+ assertEquals(currentPlan.getBillingPeriod(), BillingPeriod.MONTHLY);
+
+ PlanPhase currentPhase = newAoSubscription.getCurrentPhase();
+ assertNotNull(currentPhase);
+ assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
+
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(60));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
+
+ newAoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+ currentPhase = newAoSubscription.getCurrentPhase();
+ assertNotNull(currentPhase);
+ assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+ }
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java
new file mode 100644
index 0000000..54fda89
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java
@@ -0,0 +1,464 @@
+/*
+ * 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.timeline;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.NewEvent;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
+
+public class TestRepairWithError extends TestApiBaseRepair {
+
+ private static final String baseProduct = "Shotgun";
+ private TestWithException test;
+ private Subscription baseSubscription;
+ private DateTime startDate;
+ @Override
+ public Injector getInjector() {
+ return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleMemory());
+ }
+
+ @BeforeMethod(alwaysRun = true)
+ public void setupTest() throws Exception {
+ super.setupTest();
+ test = new TestWithException();
+ startDate = clock.getUTCNow();
+ baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
+ }
+
+ @Test(groups={"fast"})
+ public void testENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING() throws Exception {
+
+ log.info("Starting testENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING");
+
+ test.withException(new TestWithExceptionCallback() {
+ @Override
+ public void doTest() throws EntitlementRepairException {
+
+ // MOVE AFTER TRIAL
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ assertTrue(testListener.isCompleted(5000));
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+
+ SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.<DeletedEvent>emptyList(), Collections.singletonList(ne));
+
+ BundleTimeline bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+ repairApi.repairBundle(bRepair, true, context);
+ }
+ }, ErrorCode.ENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING);
+ }
+
+ @Test(groups={"fast"})
+ public void testENT_REPAIR_INVALID_DELETE_SET() throws Exception {
+
+ log.info("Starting testENT_REPAIR_INVALID_DELETE_SET");
+
+ test.withException(new TestWithExceptionCallback() {
+ @Override
+ public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ testListener.pushExpectedEvent(NextEvent.CHANGE);
+ DateTime changeTime = clock.getUTCNow();
+ baseSubscription.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, changeTime, context);
+ assertTrue(testListener.isCompleted(5000));
+
+ // MOVE AFTER TRIAL
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+ DeletedEvent de = createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId());
+
+ SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.singletonList(de), Collections.singletonList(ne));
+ BundleTimeline bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+ repairApi.repairBundle(bRepair, true, context);
+ }
+ }, ErrorCode.ENT_REPAIR_INVALID_DELETE_SET);
+ }
+
+ @Test(groups={"fast"})
+ public void testENT_REPAIR_NON_EXISTENT_DELETE_EVENT() throws Exception {
+
+ log.info("Starting testENT_REPAIR_NON_EXISTENT_DELETE_EVENT");
+
+ test.withException(new TestWithExceptionCallback() {
+ @Override
+ public void doTest() throws EntitlementRepairException {
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+ DeletedEvent de = createDeletedEvent(UUID.randomUUID());
+ SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), Collections.singletonList(de), Collections.singletonList(ne));
+
+ BundleTimeline bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+ repairApi.repairBundle(bRepair, true, context);
+ }
+ }, ErrorCode.ENT_REPAIR_NON_EXISTENT_DELETE_EVENT);
+ }
+
+ @Test(groups={"fast"})
+ public void testENT_REPAIR_SUB_RECREATE_NOT_EMPTY() throws Exception {
+
+ log.info("Starting testENT_REPAIR_SUB_RECREATE_NOT_EMPTY");
+
+ test.withException(new TestWithExceptionCallback() {
+ @Override
+ public void doTest() throws EntitlementRepairException {
+
+ // MOVE AFTER TRIAL
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, baseSubscription.getStartDate().plusDays(10), spec);
+ List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+ des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+ SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+ BundleTimeline bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+ repairApi.repairBundle(bRepair, true, context);
+
+ }
+ }, ErrorCode.ENT_REPAIR_SUB_RECREATE_NOT_EMPTY);
+ }
+
+ @Test(groups={"fast"})
+ public void testENT_REPAIR_SUB_EMPTY() throws Exception {
+
+ log.info("Starting testENT_REPAIR_SUB_EMPTY");
+
+ test.withException(new TestWithExceptionCallback() {
+
+ @Override
+ public void doTest() throws EntitlementRepairException {
+
+ // MOVE AFTER TRIAL
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN);
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CHANGE, baseSubscription.getStartDate().plusDays(10), spec);
+ List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+ des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));
+ des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+ SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+ BundleTimeline bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+ repairApi.repairBundle(bRepair, true, context);
+ }
+ }, ErrorCode.ENT_REPAIR_SUB_EMPTY);
+ }
+
+ @Test(groups={"fast"})
+ public void testENT_REPAIR_AO_CREATE_BEFORE_BP_START() throws Exception {
+
+ log.info("Starting testENT_REPAIR_AO_CREATE_BEFORE_BP_START");
+
+ test.withException(new TestWithExceptionCallback() {
+ @Override
+ public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+
+ // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+
+ // Quick check
+ SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+ assertEquals(bpRepair.getExistingEvents().size(), 2);
+
+ SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+
+ List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+ des.add(createDeletedEvent(aoRepair.getExistingEvents().get(0).getEventId()));
+ des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));
+
+ DateTime aoRecreateDate = aoSubscription.getStartDate().minusDays(5);
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.DISCOUNT);
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, aoRecreateDate, spec);
+
+ SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
+
+ BundleTimeline bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
+
+ boolean dryRun = true;
+ repairApi.repairBundle(bRepair, dryRun, context);
+ }
+ }, ErrorCode.ENT_REPAIR_AO_CREATE_BEFORE_BP_START);
+ }
+
+ @Test(groups={"fast"})
+ public void testENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING() throws Exception {
+
+ log.info("Starting testENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING");
+
+ test.withException(new TestWithExceptionCallback() {
+ @Override
+ public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+
+ // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+
+ // Quick check
+ SubscriptionTimeline bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+ assertEquals(bpRepair.getExistingEvents().size(), 2);
+
+ SubscriptionTimeline aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+ assertEquals(aoRepair.getExistingEvents().size(), 2);
+
+
+ List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+ //des.add(createDeletedEvent(aoRepair.getExistingEvents().get(1).getEventId()));
+ DateTime aoCancelDate = aoSubscription.getStartDate().plusDays(10);
+
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, aoCancelDate, null);
+
+ SubscriptionTimeline saoRepair = createSubscriptionReapir(aoSubscription.getId(), des, Collections.singletonList(ne));
+
+ bundleRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(saoRepair));
+
+ boolean dryRun = true;
+ repairApi.repairBundle(bundleRepair, dryRun, context);
+ }
+ }, ErrorCode.ENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING);
+ }
+
+
+ @Test(groups={"fast"})
+ public void testENT_REPAIR_BP_RECREATE_MISSING_AO() throws Exception {
+
+ log.info("Starting testENT_REPAIR_BP_RECREATE_MISSING_AO");
+
+ test.withException(new TestWithExceptionCallback() {
+ @Override
+ public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+ //testListener.pushExpectedEvent(NextEvent.PHASE);
+
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ //assertTrue(testListener.isCompleted(5000));
+
+ SubscriptionData aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+
+ DateTime newCreateTime = baseSubscription.getStartDate().plusDays(3);
+
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, newCreateTime, spec);
+ List<DeletedEvent> des = new LinkedList<SubscriptionTimeline.DeletedEvent>();
+ des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));
+ des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+
+ SubscriptionTimeline sRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+ // FIRST ISSUE DRY RUN
+ BundleTimeline bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
+
+ boolean dryRun = true;
+ repairApi.repairBundle(bRepair, dryRun, context);
+ }
+ }, ErrorCode.ENT_REPAIR_BP_RECREATE_MISSING_AO);
+ }
+
+ //
+ // CAN'T seem to trigger such case easily, other errors trigger before...
+ //
+ @Test(groups={"fast"}, enabled=false)
+ public void testENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE() throws Exception {
+
+ log.info("Starting testENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE");
+
+ test.withException(new TestWithExceptionCallback() {
+ @Override
+ public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+ /*
+ //testListener.pushExpectedEvent(NextEvent.PHASE);
+
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+
+ SubscriptionData aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ BundleRepair bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+
+ DateTime newCreateTime = baseSubscription.getStartDate().plusDays(3);
+
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL);
+
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CREATE, newCreateTime, spec);
+ List<DeletedEvent> des = new LinkedList<SubscriptionRepair.DeletedEvent>();
+ des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(0).getEventId()));
+ des.add(createDeletedEvent(bundleRepair.getSubscriptions().get(0).getExistingEvents().get(1).getEventId()));
+
+ SubscriptionRepair bpRepair = createSubscriptionReapir(baseSubscription.getId(), des, Collections.singletonList(ne));
+
+ ne = createNewEvent(SubscriptionTransitionType.CANCEL, clock.getUTCNow().minusDays(1), null);
+ SubscriptionRepair aoRepair = createSubscriptionReapir(aoSubscription.getId(), Collections.<SubscriptionRepair.DeletedEvent>emptyList(), Collections.singletonList(ne));
+
+
+ List<SubscriptionRepair> allRepairs = new LinkedList<SubscriptionRepair>();
+ allRepairs.add(bpRepair);
+ allRepairs.add(aoRepair);
+ bundleRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), allRepairs);
+ // FIRST ISSUE DRY RUN
+ BundleRepair bRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), allRepairs);
+
+ boolean dryRun = true;
+ repairApi.repairBundle(bRepair, dryRun, context);
+ */
+ }
+ }, ErrorCode.ENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE);
+ }
+
+ @Test(groups={"fast"}, enabled=false)
+ public void testENT_REPAIR_MISSING_AO_DELETE_EVENT() throws Exception {
+
+ log.info("Starting testENT_REPAIR_MISSING_AO_DELETE_EVENT");
+
+ test.withException(new TestWithExceptionCallback() {
+ @Override
+ public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
+
+
+ /*
+ // MOVE CLOCK -- JUST BEFORE END OF TRIAL
+ *
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(29));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ clock.setDeltaFromReality(getDurationDay(29), 0);
+
+ SubscriptionData aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+
+ // MOVE CLOCK -- RIGHT OUT OF TRIAL
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ clock.addDeltaFromReality(getDurationDay(5));
+ assertTrue(testListener.isCompleted(5000));
+
+ DateTime requestedChange = clock.getUTCNow();
+ baseSubscription.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, requestedChange, context);
+
+ DateTime reapairTime = clock.getUTCNow().minusDays(1);
+
+ BundleRepair bundleRepair = repairApi.getBundleRepair(bundle.getId());
+ sortEventsOnBundle(bundleRepair);
+
+ SubscriptionRepair bpRepair = getSubscriptionRepair(baseSubscription.getId(), bundleRepair);
+ SubscriptionRepair aoRepair = getSubscriptionRepair(aoSubscription.getId(), bundleRepair);
+
+ List<DeletedEvent> bpdes = new LinkedList<SubscriptionRepair.DeletedEvent>();
+ bpdes.add(createDeletedEvent(bpRepair.getExistingEvents().get(2).getEventId()));
+ bpRepair = createSubscriptionReapir(baseSubscription.getId(), bpdes, Collections.<NewEvent>emptyList());
+
+ NewEvent ne = createNewEvent(SubscriptionTransitionType.CANCEL, reapairTime, null);
+ aoRepair = createSubscriptionReapir(aoSubscription.getId(), Collections.<SubscriptionRepair.DeletedEvent>emptyList(), Collections.singletonList(ne));
+
+ List<SubscriptionRepair> allRepairs = new LinkedList<SubscriptionRepair>();
+ allRepairs.add(bpRepair);
+ allRepairs.add(aoRepair);
+ bundleRepair = createBundleRepair(bundle.getId(), bundleRepair.getViewId(), allRepairs);
+
+ boolean dryRun = false;
+ repairApi.repairBundle(bundleRepair, dryRun, context);
+ */
+ }
+ }, ErrorCode.ENT_REPAIR_MISSING_AO_DELETE_EVENT);
+ }
+
+}
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 927ea08..7990084 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
@@ -21,13 +21,17 @@ import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
+import java.util.List;
+
import org.joda.time.DateTime;
+import org.joda.time.Interval;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
+import com.ning.billing.api.TestApiListener.NextEvent;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.CatalogApiException;
import com.ning.billing.catalog.api.Duration;
@@ -39,8 +43,8 @@ import com.ning.billing.catalog.api.PlanSpecifier;
import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.TestApiBase;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.SubscriptionStatusDryRun.DryRunChangeReason;
import com.ning.billing.entitlement.glue.MockEngineModuleSql;
import com.ning.billing.util.clock.DefaultClock;
@@ -54,6 +58,8 @@ public class TestUserApiAddOn extends TestApiBase {
@Test(enabled=true, groups={"slow"})
public void testCreateCancelAddon() {
+ log.info("Starting testCreateCancelAddon");
+
try {
String baseProduct = "Shotgun";
BillingPeriod baseTerm = BillingPeriod.MONTHLY;
@@ -77,13 +83,17 @@ public class TestUserApiAddOn extends TestApiBase {
assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
+ assertListenerStatus();
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
@Test(enabled=true, groups={"slow"})
- public void testCancelBPWthAddon() {
+ public void testCancelBPWithAddon() {
+
+ log.info("Starting testCancelBPWithAddon");
+
try {
String baseProduct = "Shotgun";
@@ -104,13 +114,14 @@ public class TestUserApiAddOn extends TestApiBase {
testListener.pushExpectedEvent(NextEvent.PHASE);
// MOVE CLOCK AFTER TRIAL + AO DISCOUNT
- Duration twoMonths = getDurationMonth(2);
- clock.setDeltaFromReality(twoMonths, DAY_IN_MS);
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(2));
+ clock.addDeltaFromReality(it.toDurationMillis());
assertTrue(testListener.isCompleted(5000));
// SET CTD TO CANCEL IN FUTURE
DateTime now = clock.getUTCNow();
Duration ctd = getDurationMonth(1);
+ // Why not just use clock.getUTCNow().plusMonths(1) ?
DateTime newChargedThroughDate = DefaultClock.addDuration(now, ctd);
billingApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, context);
baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
@@ -123,18 +134,21 @@ public class TestUserApiAddOn extends TestApiBase {
assertEquals(aoSubscription.getState(), SubscriptionState.ACTIVE);
assertTrue(aoSubscription.isSubscriptionFutureCancelled());
-
// MOVE AFTER CANCELLATION
testListener.reset();
testListener.pushExpectedEvent(NextEvent.CANCEL);
testListener.pushExpectedEvent(NextEvent.CANCEL);
- clock.addDeltaFromReality(ctd);
+
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
assertTrue(testListener.isCompleted(5000));
// REFETCH AO SUBSCRIPTION AND CHECK THIS IS CANCELLED
aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
+ assertListenerStatus();
+
} catch (Exception e) {
Assert.fail(e.getMessage());
}
@@ -142,7 +156,10 @@ public class TestUserApiAddOn extends TestApiBase {
@Test(enabled=true, groups={"slow"})
- public void testChangeBPWthAddonNonIncluded() {
+ public void testChangeBPWithAddonIncluded() {
+
+ log.info("Starting testChangeBPWithAddonIncluded");
+
try {
String baseProduct = "Shotgun";
@@ -163,8 +180,8 @@ public class TestUserApiAddOn extends TestApiBase {
testListener.pushExpectedEvent(NextEvent.PHASE);
// MOVE CLOCK AFTER TRIAL + AO DISCOUNT
- Duration twoMonths = getDurationMonth(2);
- clock.setDeltaFromReality(twoMonths, DAY_IN_MS);
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(2));
+ clock.addDeltaFromReality(it.toDurationMillis());
assertTrue(testListener.isCompleted(5000));
// SET CTD TO CHANGE IN FUTURE
@@ -179,6 +196,15 @@ public class TestUserApiAddOn extends TestApiBase {
BillingPeriod newBaseTerm = BillingPeriod.MONTHLY;
String newBasePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+ List<SubscriptionStatusDryRun> aoStatus = entitlementApi.getDryRunChangePlanStatus(baseSubscription.getId(), newBaseProduct, now);
+ assertEquals(aoStatus.size(), 1);
+ assertEquals(aoStatus.get(0).getId(), aoSubscription.getId());
+ assertEquals(aoStatus.get(0).getProductName(), aoProduct);
+ assertEquals(aoStatus.get(0).getBillingPeriod(), aoTerm);
+ assertEquals(aoStatus.get(0).getPhaseType(), aoSubscription.getCurrentPhase().getPhaseType());
+ assertEquals(aoStatus.get(0).getPriceList(), aoSubscription.getCurrentPriceList().getName());
+ assertEquals(aoStatus.get(0).getReason(), DryRunChangeReason.AO_INCLUDED_IN_NEW_PLAN);
+
testListener.reset();
testListener.pushExpectedEvent(NextEvent.CHANGE);
testListener.pushExpectedEvent(NextEvent.CANCEL);
@@ -189,13 +215,17 @@ public class TestUserApiAddOn extends TestApiBase {
aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
+ assertListenerStatus();
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
@Test(enabled=true, groups={"slow"})
- public void testChangeBPWthAddonNonAvailable() {
+ public void testChangeBPWithAddonNonAvailable() {
+
+ log.info("Starting testChangeBPWithAddonNonAvailable");
+
try {
String baseProduct = "Shotgun";
@@ -216,8 +246,8 @@ public class TestUserApiAddOn extends TestApiBase {
testListener.pushExpectedEvent(NextEvent.PHASE);
// MOVE CLOCK AFTER TRIAL + AO DISCOUNT
- Duration twoMonths = getDurationMonth(2);
- clock.setDeltaFromReality(twoMonths, DAY_IN_MS);
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(2));
+ clock.addDeltaFromReality(it.toDurationMillis());
assertTrue(testListener.isCompleted(5000));
// SET CTD TO CANCEL IN FUTURE
@@ -232,9 +262,17 @@ public class TestUserApiAddOn extends TestApiBase {
BillingPeriod newBaseTerm = BillingPeriod.MONTHLY;
String newBasePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+ List<SubscriptionStatusDryRun> aoStatus = entitlementApi.getDryRunChangePlanStatus(baseSubscription.getId(), newBaseProduct, now);
+ assertEquals(aoStatus.size(), 1);
+ assertEquals(aoStatus.get(0).getId(), aoSubscription.getId());
+ assertEquals(aoStatus.get(0).getProductName(), aoProduct);
+ assertEquals(aoStatus.get(0).getBillingPeriod(), aoTerm);
+ assertEquals(aoStatus.get(0).getPhaseType(), aoSubscription.getCurrentPhase().getPhaseType());
+ assertEquals(aoStatus.get(0).getPriceList(), aoSubscription.getCurrentPriceList().getName());
+ assertEquals(aoStatus.get(0).getReason(), DryRunChangeReason.AO_NOT_AVAILABLE_IN_NEW_PLAN);
+
baseSubscription.changePlan(newBaseProduct, newBaseTerm, newBasePriceList, now, context);
-
// REFETCH AO SUBSCRIPTION AND CHECK THIS IS ACTIVE
aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
assertEquals(aoSubscription.getState(), SubscriptionState.ACTIVE);
@@ -244,7 +282,8 @@ public class TestUserApiAddOn extends TestApiBase {
testListener.reset();
testListener.pushExpectedEvent(NextEvent.CHANGE);
testListener.pushExpectedEvent(NextEvent.CANCEL);
- clock.addDeltaFromReality(ctd);
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
assertTrue(testListener.isCompleted(5000));
@@ -252,6 +291,7 @@ public class TestUserApiAddOn extends TestApiBase {
aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
+ assertListenerStatus();
} catch (Exception e) {
Assert.fail(e.getMessage());
}
@@ -260,6 +300,9 @@ public class TestUserApiAddOn extends TestApiBase {
@Test(enabled=true, groups={"slow"})
public void testAddonCreateWithBundleAlign() {
+
+ log.info("Starting testAddonCreateWithBundleAlign");
+
try {
String aoProduct = "Telescopic-Scope";
BillingPeriod aoTerm = BillingPeriod.MONTHLY;
@@ -275,14 +318,18 @@ public class TestUserApiAddOn extends TestApiBase {
testAddonCreateInternal(aoProduct, aoTerm, aoPriceList, alignement);
+ assertListenerStatus();
} catch (CatalogApiException e) {
Assert.fail(e.getMessage());
}
}
+ //TODO MDW - debugging reenable if you find this
@Test(enabled=true, groups={"slow"})
public void testAddonCreateWithSubscriptionAlign() {
+ log.info("Starting testAddonCreateWithSubscriptionAlign");
+
try {
String aoProduct = "Laser-Scope";
BillingPeriod aoTerm = BillingPeriod.MONTHLY;
@@ -298,13 +345,15 @@ public class TestUserApiAddOn extends TestApiBase {
testAddonCreateInternal(aoProduct, aoTerm, aoPriceList, alignement);
- } catch (CatalogApiException e) {
- Assert.fail(e.getMessage());
- }
+ assertListenerStatus();
+ } catch (CatalogApiException e) {
+ Assert.fail(e.getMessage());
+ }
}
private void testAddonCreateInternal(String aoProduct, BillingPeriod aoTerm, String aoPriceList, PlanAlignmentCreate expAlignement) {
+
try {
String baseProduct = "Shotgun";
@@ -315,9 +364,9 @@ public class TestUserApiAddOn extends TestApiBase {
SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
// MOVE CLOCK 14 DAYS LATER
- Duration someTimeLater = getDurationDay(13);
- clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
-
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(14));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
// CREATE ADDON
DateTime beforeAOCreation = clock.getUTCNow();
SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
@@ -334,46 +383,46 @@ public class TestUserApiAddOn extends TestApiBase {
assertNotNull(aoCurrentPhase);
assertEquals(aoCurrentPhase.getPhaseType(), PhaseType.DISCOUNT);
- assertDateWithin(aoSubscription.getStartDate(), beforeAOCreation, afterAOCreation);
- assertEquals(aoSubscription.getBundleStartDate(), baseSubscription.getBundleStartDate());
+ assertDateWithin(aoSubscription.getStartDate(), beforeAOCreation, afterAOCreation);
+ assertEquals(aoSubscription.getBundleStartDate(), baseSubscription.getBundleStartDate());
- // CHECK next AO PHASE EVENT IS INDEED A MONTH AFTER BP STARTED => BUNDLE ALIGNMENT
- SubscriptionTransition aoPendingTranstion = aoSubscription.getPendingTransition();
+ // CHECK next AO PHASE EVENT IS INDEED A MONTH AFTER BP STARTED => BUNDLE ALIGNMENT
+ SubscriptionEvent aoPendingTranstion = aoSubscription.getPendingTransition();
- if (expAlignement == PlanAlignmentCreate.START_OF_BUNDLE) {
- assertEquals(aoPendingTranstion.getEffectiveTransitionTime(), baseSubscription.getStartDate().plusMonths(1));
- } else {
- assertEquals(aoPendingTranstion.getEffectiveTransitionTime(), aoSubscription.getStartDate().plusMonths(1));
- }
+ if (expAlignement == PlanAlignmentCreate.START_OF_BUNDLE) {
+ assertEquals(aoPendingTranstion.getEffectiveTransitionTime(), baseSubscription.getStartDate().plusMonths(1));
+ } else {
+ assertEquals(aoPendingTranstion.getEffectiveTransitionTime(), aoSubscription.getStartDate().plusMonths(1));
+ }
- // ADD TWO PHASE EVENTS (BP + AO)
- testListener.reset();
- testListener.pushExpectedEvent(NextEvent.PHASE);
- testListener.pushExpectedEvent(NextEvent.PHASE);
+ // ADD TWO PHASE EVENTS (BP + AO)
+ testListener.reset();
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ testListener.pushExpectedEvent(NextEvent.PHASE);
- // MOVE THROUGH TIME TO GO INTO EVERGREEN
- someTimeLater = aoCurrentPhase.getDuration();
- clock.addDeltaFromReality(someTimeLater);
- assertTrue(testListener.isCompleted(5000));
+ // MOVE THROUGH TIME TO GO INTO EVERGREEN
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(33));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
- // CHECK EVERYTHING AGAIN
- aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+ // CHECK EVERYTHING AGAIN
+ aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
- aoCurrentPlan = aoSubscription.getCurrentPlan();
- assertNotNull(aoCurrentPlan);
- assertEquals(aoCurrentPlan.getProduct().getName(),aoProduct);
- assertEquals(aoCurrentPlan.getProduct().getCategory(), ProductCategory.ADD_ON);
- assertEquals(aoCurrentPlan.getBillingPeriod(), aoTerm);
+ aoCurrentPlan = aoSubscription.getCurrentPlan();
+ assertNotNull(aoCurrentPlan);
+ assertEquals(aoCurrentPlan.getProduct().getName(),aoProduct);
+ assertEquals(aoCurrentPlan.getProduct().getCategory(), ProductCategory.ADD_ON);
+ assertEquals(aoCurrentPlan.getBillingPeriod(), aoTerm);
- aoCurrentPhase = aoSubscription.getCurrentPhase();
- assertNotNull(aoCurrentPhase);
- assertEquals(aoCurrentPhase.getPhaseType(), PhaseType.EVERGREEN);
+ aoCurrentPhase = aoSubscription.getCurrentPhase();
+ assertNotNull(aoCurrentPhase);
+ assertEquals(aoCurrentPhase.getPhaseType(), PhaseType.EVERGREEN);
- aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
- aoPendingTranstion = aoSubscription.getPendingTransition();
- assertNull(aoPendingTranstion);
+ aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+ aoPendingTranstion = aoSubscription.getPendingTransition();
+ assertNull(aoPendingTranstion);
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
index 55ad5e8..7fb06e0 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
@@ -17,25 +17,24 @@
package com.ning.billing.entitlement.api.user;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.assertFalse;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
-import com.ning.billing.util.clock.DefaultClock;
import org.joda.time.DateTime;
+import org.joda.time.Interval;
import org.testng.Assert;
-
+import com.ning.billing.api.TestApiListener.NextEvent;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.PriceListSet;
-import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.entitlement.api.TestApiBase;
-import java.util.List;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
+import com.ning.billing.util.clock.DefaultClock;
public abstract class TestUserApiCancel extends TestApiBase {
@@ -57,8 +56,8 @@ public abstract class TestUserApiCancel extends TestApiBase {
assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
// ADVANCE TIME still in trial
- Duration moveALittleInTime = getDurationDay(3);
- clock.setDeltaFromReality(moveALittleInTime, 0);
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+ clock.addDeltaFromReality(it.toDurationMillis());
DateTime future = clock.getUTCNow();
testListener.pushExpectedEvent(NextEvent.CANCEL);
@@ -66,11 +65,12 @@ public abstract class TestUserApiCancel extends TestApiBase {
// CANCEL in trial period to get IMM policy
subscription.cancel(clock.getUTCNow(), false, context);
currentPhase = subscription.getCurrentPhase();
- testListener.isCompleted(1000);
+ testListener.isCompleted(3000);
assertNull(currentPhase);
checkNextPhaseChange(subscription, 0, null);
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
}
@@ -97,8 +97,10 @@ public abstract class TestUserApiCancel extends TestApiBase {
// MOVE TO NEXT PHASE
testListener.pushExpectedEvent(NextEvent.PHASE);
- clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
- assertTrue(testListener.isCompleted(2000));
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ assertTrue(testListener.isCompleted(5000));
trialPhase = subscription.getCurrentPhase();
assertEquals(trialPhase.getPhaseType(), PhaseType.EVERGREEN);
@@ -108,21 +110,25 @@ public abstract class TestUserApiCancel extends TestApiBase {
billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, context);
subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
- testListener.pushExpectedEvent(NextEvent.CANCEL);
-
// CANCEL
+ testListener.setNonExpectedMode();
+ testListener.pushExpectedEvent(NextEvent.CANCEL);
subscription.cancel(clock.getUTCNow(), false, context);
- assertFalse(testListener.isCompleted(2000));
+ assertFalse(testListener.isCompleted(3000));
+ testListener.reset();
// MOVE TO EOT + RECHECK
- clock.addDeltaFromReality(ctd);
+ testListener.pushExpectedEvent(NextEvent.CANCEL);
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
DateTime future = clock.getUTCNow();
- assertTrue(testListener.isCompleted(2000));
+ assertTrue(testListener.isCompleted(5000));
PlanPhase currentPhase = subscription.getCurrentPhase();
assertNull(currentPhase);
checkNextPhaseChange(subscription, 0, null);
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
}
@@ -150,8 +156,10 @@ public abstract class TestUserApiCancel extends TestApiBase {
// MOVE TO NEXT PHASE
testListener.pushExpectedEvent(NextEvent.PHASE);
- clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
- assertTrue(testListener.isCompleted(2000));
+
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
trialPhase = subscription.getCurrentPhase();
assertEquals(trialPhase.getPhaseType(), PhaseType.EVERGREEN);
@@ -159,12 +167,13 @@ public abstract class TestUserApiCancel extends TestApiBase {
// CANCEL
subscription.cancel(clock.getUTCNow(), false, context);
- assertTrue(testListener.isCompleted(2000));
+ assertTrue(testListener.isCompleted(5000));
PlanPhase currentPhase = subscription.getCurrentPhase();
assertNull(currentPhase);
checkNextPhaseChange(subscription, 0, null);
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
}
@@ -194,8 +203,9 @@ public abstract class TestUserApiCancel extends TestApiBase {
// MOVE TO NEXT PHASE
testListener.pushExpectedEvent(NextEvent.PHASE);
- clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
- assertTrue(testListener.isCompleted(2000));
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
PlanPhase currentPhase = subscription.getCurrentPhase();
assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
@@ -205,27 +215,25 @@ public abstract class TestUserApiCancel extends TestApiBase {
billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, context);
subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
- testListener.pushExpectedEvent(NextEvent.CANCEL);
-
- // CANCEL
+ // CANCEL EOT
subscription.cancel(clock.getUTCNow(), false, context);
- assertFalse(testListener.isCompleted(2000));
subscription.uncancel(context);
-
+
// MOVE TO EOT + RECHECK
- clock.addDeltaFromReality(ctd);
- DateTime future = clock.getUTCNow();
- assertFalse(testListener.isCompleted(2000));
+ testListener.pushExpectedEvent(NextEvent.UNCANCEL);
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
Plan currentPlan = subscription.getCurrentPlan();
assertEquals(currentPlan.getProduct().getName(), prod);
currentPhase = subscription.getCurrentPhase();
assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
}
}
-
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
index 57e34e9..625002a 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
@@ -16,12 +16,13 @@
package com.ning.billing.entitlement.api.user;
+import org.testng.annotations.Test;
+
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
-import org.testng.annotations.Test;
public class TestUserApiCancelMemory extends TestUserApiCancel {
@@ -32,25 +33,25 @@ public class TestUserApiCancelMemory extends TestUserApiCancel {
}
@Override
- @Test(enabled=true, groups={"fast-disabled"})
+ @Test(enabled=true, groups={"fast"})
public void testCancelSubscriptionIMM() {
super.testCancelSubscriptionIMM();
}
@Override
- @Test(enabled=true, groups={"fast-disabled"})
+ @Test(enabled=true, groups={"fast"})
public void testCancelSubscriptionEOTWithChargeThroughDate() throws EntitlementBillingApiException {
super.testCancelSubscriptionEOTWithChargeThroughDate();
}
@Override
- @Test(enabled=true, groups={"fast-disabled"})
+ @Test(enabled=true, groups={"fast"})
public void testCancelSubscriptionEOTWithNoChargeThroughDate() {
super.testCancelSubscriptionEOTWithNoChargeThroughDate();
}
@Override
- @Test(enabled=true, groups={"fast-disabled"})
+ @Test(enabled=true, groups={"fast"})
public void testUncancel() throws EntitlementBillingApiException {
super.testUncancel();
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java
index 469d374..6d02736 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
@@ -16,12 +16,13 @@
package com.ning.billing.entitlement.api.user;
+import org.testng.annotations.Test;
+
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleSql;
-import org.testng.annotations.Test;
public class TestUserApiCancelSql extends TestUserApiCancel {
@@ -34,7 +35,7 @@ public class TestUserApiCancelSql extends TestUserApiCancel {
}
@Test(enabled= false, groups={"stress"})
- public void stressTest() throws EntitlementBillingApiException {
+ public void stressTest() throws Exception {
for (int i = 0; i < MAX_STRESS_ITERATIONS; i++) {
cleanupTest();
setupTest();
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 f7f8c41..b1d2c0b 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
@@ -25,18 +25,18 @@ import java.util.ArrayList;
import java.util.List;
import org.joda.time.DateTime;
+import org.joda.time.Interval;
import org.testng.Assert;
+import com.ning.billing.api.TestApiListener.NextEvent;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.PriceListSet;
-import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.TestApiBase;
-
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.events.EntitlementEvent;
import com.ning.billing.entitlement.events.user.ApiEvent;
@@ -70,7 +70,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
private void tChangePlanBundleAlignEOTWithNoChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet,
String toProd, BillingPeriod toTerm, String toPlanSet) {
- log.info("Starting testChangePlanBundleAlignEOTWithNoChargeThroughDateReal");
+ log.info("Starting testChangePlanBundleAlignEOTWithNoChargeThroughDate");
try {
@@ -80,21 +80,25 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
// MOVE TO NEXT PHASE
PlanPhase currentPhase = subscription.getCurrentPhase();
testListener.pushExpectedEvent(NextEvent.PHASE);
- clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
+
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
DateTime futureNow = clock.getUTCNow();
DateTime nextExpectedPhaseChange = DefaultClock.addDuration(subscription.getStartDate(), currentPhase.getDuration());
assertTrue(futureNow.isAfter(nextExpectedPhaseChange));
- assertTrue(testListener.isCompleted(3000));
+ assertTrue(testListener.isCompleted(5000));
// CHANGE PLAN
testListener.pushExpectedEvent(NextEvent.CHANGE);
subscription.changePlan(toProd, toTerm, toPlanSet, clock.getUTCNow(), context);
- assertTrue(testListener.isCompleted(2000));
+ assertTrue(testListener.isCompleted(5000));
// CHECK CHANGE PLAN
currentPhase = subscription.getCurrentPhase();
checkChangePlan(subscription, toProd, ProductCategory.BASE, toTerm, PhaseType.EVERGREEN);
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
}
@@ -102,13 +106,14 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
protected void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
+ log.info("Starting testChangePlanBundleAlignEOTWithChargeThroughDate");
testChangePlanBundleAlignEOTWithChargeThroughDate("Shotgun", BillingPeriod.ANNUAL, "gunclubDiscount", "Pistol", BillingPeriod.ANNUAL, "gunclubDiscount");
}
private void testChangePlanBundleAlignEOTWithChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet,
String toProd, BillingPeriod toTerm, String toPlanSet) throws EntitlementBillingApiException {
- log.info("Starting testChangeSubscriptionEOTWithChargeThroughDate");
+
try {
// CREATE
@@ -120,8 +125,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
// MOVE TO NEXT PHASE
testListener.pushExpectedEvent(NextEvent.PHASE);
- clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
- assertTrue(testListener.isCompleted(2000));
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
PlanPhase currentPhase = subscription.getCurrentPhase();
assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
@@ -132,10 +138,11 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, context);
// RE READ SUBSCRIPTION + CHANGE PLAN
+ testListener.setNonExpectedMode();
testListener.pushExpectedEvent(NextEvent.CHANGE);
subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
subscription.changePlan(toProd, toTerm, toPlanSet, clock.getUTCNow(), context);
- assertFalse(testListener.isCompleted(2000));
+ assertFalse(testListener.isCompleted(3000));
testListener.reset();
// CHECK CHANGE PLAN
@@ -153,13 +160,15 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
// MOVE TO EOT
testListener.pushExpectedEvent(NextEvent.CHANGE);
- clock.addDeltaFromReality(ctd);
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
assertTrue(testListener.isCompleted(5000));
subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
currentPhase = subscription.getCurrentPhase();
checkChangePlan(subscription, toProd, ProductCategory.BASE, toTerm, PhaseType.DISCOUNT);
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
}
@@ -182,14 +191,14 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
testListener.pushExpectedEvent(NextEvent.CHANGE);
- Duration moveALittleInTime = getDurationDay(3);
- clock.setDeltaFromReality(moveALittleInTime, 0);
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+ clock.addDeltaFromReality(it.toDurationMillis());
// CHANGE PLAN IMM
subscription.changePlan(toProd, toTerm, toPlanSet, clock.getUTCNow(), context);
checkChangePlan(subscription, toProd, ProductCategory.BASE, toTerm, PhaseType.TRIAL);
- assertTrue(testListener.isCompleted(2000));
+ assertTrue(testListener.isCompleted(5000));
PlanPhase currentPhase = subscription.getCurrentPhase();
DateTime nextExpectedPhaseChange = DefaultClock.addDuration(subscription.getStartDate(), currentPhase.getDuration());
@@ -197,20 +206,14 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
// NEXT PHASE
testListener.pushExpectedEvent(NextEvent.PHASE);
- clock.addDeltaFromReality(currentPhase.getDuration());
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(30));
+ clock.addDeltaFromReality(it.toDurationMillis());
DateTime futureNow = clock.getUTCNow();
- /*
- try {
- Thread.sleep(1000 * 3000);
- } catch (Exception e) {
-
- }
- */
-
assertTrue(futureNow.isAfter(nextExpectedPhaseChange));
- assertTrue(testListener.isCompleted(3000));
+ assertTrue(testListener.isCompleted(5000));
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
}
@@ -218,14 +221,13 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
protected void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
+ log.info("Starting testChangePlanChangePlanAlignEOTWithChargeThroughDate");
tChangePlanChangePlanAlignEOTWithChargeThroughDate("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, "Assault-Rifle", BillingPeriod.ANNUAL, "rescue");
}
private void tChangePlanChangePlanAlignEOTWithChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet,
String toProd, BillingPeriod toTerm, String toPlanSet) throws EntitlementBillingApiException {
- log.info("Starting testChangePlanBundleAlignEOTWithChargeThroughDate");
-
try {
DateTime currentTime = clock.getUTCNow();
@@ -238,9 +240,10 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
// MOVE TO NEXT PHASE
testListener.pushExpectedEvent(NextEvent.PHASE);
currentTime = clock.getUTCNow();
- clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+ clock.addDeltaFromReality(it.toDurationMillis());
currentTime = clock.getUTCNow();
- assertTrue(testListener.isCompleted(2000));
+ assertTrue(testListener.isCompleted(5000));
// SET CTD
Duration ctd = getDurationMonth(1);
@@ -255,19 +258,22 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
// CHANGE PLAN
currentTime = clock.getUTCNow();
-
- testListener.pushExpectedEvent(NextEvent.CHANGE);
subscription.changePlan(toProd, toTerm, toPlanSet, clock.getUTCNow(), context);
checkChangePlan(subscription, fromProd, ProductCategory.BASE, fromTerm, PhaseType.EVERGREEN);
// CHECK CHANGE DID NOT KICK IN YET
- assertFalse(testListener.isCompleted(2000));
+ testListener.setNonExpectedMode();
+ testListener.pushExpectedEvent(NextEvent.CHANGE);
+ assertFalse(testListener.isCompleted(3000));
+ testListener.reset();
// MOVE TO AFTER CTD
- clock.addDeltaFromReality(ctd);
+ testListener.pushExpectedEvent(NextEvent.CHANGE);
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
currentTime = clock.getUTCNow();
- assertTrue(testListener.isCompleted(2000));
+ assertTrue(testListener.isCompleted(5000));
// CHECK CORRECT PRODUCT, PHASE, PLAN SET
String currentProduct = subscription.getCurrentPlan().getProduct().getName();
@@ -277,22 +283,27 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
assertNotNull(currentPhase);
assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
- testListener.pushExpectedEvent(NextEvent.PHASE);
-
// MOVE TIME ABOUT ONE MONTH BEFORE NEXT EXPECTED PHASE CHANGE
- clock.addDeltaFromReality(getDurationMonth(11));
-
+ testListener.setNonExpectedMode();
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(11));
+ clock.addDeltaFromReality(it.toDurationMillis());
currentTime = clock.getUTCNow();
- assertFalse(testListener.isCompleted(2000));
+ assertFalse(testListener.isCompleted(3000));
+ testListener.reset();
DateTime nextExpectedPhaseChange = DefaultClock.addDuration(newChargedThroughDate, currentPhase.getDuration());
checkNextPhaseChange(subscription, 1, nextExpectedPhaseChange);
// MOVE TIME RIGHT AFTER NEXT EXPECTED PHASE CHANGE
- clock.addDeltaFromReality(getDurationMonth(1));
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
currentTime = clock.getUTCNow();
- assertTrue(testListener.isCompleted(2000));
+ assertTrue(testListener.isCompleted(5000));
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
}
@@ -300,6 +311,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
protected void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
+ log.info("Starting testMultipleChangeLastIMM");
try {
SubscriptionData subscription = createSubscription("Assault-Rifle", BillingPeriod.MONTHLY, "gunclubDiscount");
PlanPhase trialPhase = subscription.getCurrentPhase();
@@ -307,8 +319,10 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
// MOVE TO NEXT PHASE
testListener.pushExpectedEvent(NextEvent.PHASE);
- clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
- assertTrue(testListener.isCompleted(2000));
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ assertTrue(testListener.isCompleted(5000));
// SET CTD
List<Duration> durationList = new ArrayList<Duration>();
@@ -321,14 +335,16 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
// CHANGE EOT
+ testListener.setNonExpectedMode();
testListener.pushExpectedEvent(NextEvent.CHANGE);
subscription.changePlan("Pistol", BillingPeriod.MONTHLY, "gunclubDiscount", clock.getUTCNow(), context);
- assertFalse(testListener.isCompleted(2000));
+ assertFalse(testListener.isCompleted(3000));
+ testListener.reset();
// CHANGE
testListener.pushExpectedEvent(NextEvent.CHANGE);
subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
- assertFalse(testListener.isCompleted(2000));
+ assertTrue(testListener.isCompleted(5000));
Plan currentPlan = subscription.getCurrentPlan();
assertNotNull(currentPlan);
@@ -339,7 +355,8 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
PlanPhase currentPhase = subscription.getCurrentPhase();
assertNotNull(currentPhase);
assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
-
+
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
}
@@ -347,6 +364,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
protected void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
+ log.info("Starting testMultipleChangeLastEOT");
try {
SubscriptionData subscription = createSubscription("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount");
@@ -354,8 +372,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
testListener.pushExpectedEvent(NextEvent.PHASE);
- clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
- assertTrue(testListener.isCompleted(2000));
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
// SET CTD
List<Duration> durationList = new ArrayList<Duration>();
@@ -367,15 +386,17 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
// CHANGE EOT
+ testListener.setNonExpectedMode();
testListener.pushExpectedEvent(NextEvent.CHANGE);
subscription.changePlan("Shotgun", BillingPeriod.MONTHLY, "gunclubDiscount", clock.getUTCNow(), context);
- assertFalse(testListener.isCompleted(2000));
+ assertFalse(testListener.isCompleted(3000));
testListener.reset();
// CHANGE EOT
+ testListener.setNonExpectedMode();
testListener.pushExpectedEvent(NextEvent.CHANGE);
subscription.changePlan("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
- assertFalse(testListener.isCompleted(2000));
+ assertFalse(testListener.isCompleted(3000));
testListener.reset();
// CHECK NO CHANGE OCCURED YET
@@ -391,8 +412,10 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
// ACTIVATE CHNAGE BY MOVING AFTER CTD
testListener.pushExpectedEvent(NextEvent.CHANGE);
- clock.addDeltaFromReality(ctd);
- assertTrue(testListener.isCompleted(3000));
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ assertTrue(testListener.isCompleted(5000));
currentPlan = subscription.getCurrentPlan();
assertNotNull(currentPlan);
@@ -408,8 +431,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
// MOVE TO NEXT PHASE
testListener.pushExpectedEvent(NextEvent.PHASE);
- clock.addDeltaFromReality(currentPhase.getDuration());
- assertTrue(testListener.isCompleted(3000));
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(6));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
currentPlan = subscription.getCurrentPlan();
@@ -422,7 +446,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
assertNotNull(currentPhase);
assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
-
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
}
@@ -430,6 +454,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
protected void testCorrectPhaseAlignmentOnChange() {
+
+ log.info("Starting testCorrectPhaseAlignmentOnChange");
+
try {
SubscriptionData subscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
@@ -437,13 +464,15 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
// MOVE 2 DAYS AHEAD
- clock.setDeltaFromReality(getDurationDay(1), DAY_IN_MS);
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(2));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
// CHANGE IMMEDIATE TO A 3 PHASES PLAN
testListener.reset();
testListener.pushExpectedEvent(NextEvent.CHANGE);
subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
- assertTrue(testListener.isCompleted(3000));
+ assertTrue(testListener.isCompleted(5000));
testListener.reset();
// CHECK EVERYTHING LOOKS CORRECT
@@ -458,8 +487,10 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
// MOVE AFTER TRIAL PERIOD -> DISCOUNT
testListener.pushExpectedEvent(NextEvent.PHASE);
- clock.addDeltaFromReality(trialPhase.getDuration());
- assertTrue(testListener.isCompleted(3000));
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(30));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ assertTrue(testListener.isCompleted(5000));
trialPhase = subscription.getCurrentPhase();
assertEquals(trialPhase.getPhaseType(), PhaseType.DISCOUNT);
@@ -467,11 +498,12 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
DateTime expectedNextPhaseDate = subscription.getStartDate().plusDays(30).plusMonths(6);
- SubscriptionTransition nextPhase = subscription.getPendingTransition();
+ SubscriptionEvent nextPhase = subscription.getPendingTransition();
DateTime nextPhaseEffectiveDate = nextPhase.getEffectiveTransitionTime();
assertEquals(nextPhaseEffectiveDate, expectedNextPhaseDate);
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
index 0351ea1..0da1169 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
@@ -16,12 +16,13 @@
package com.ning.billing.entitlement.api.user;
+import org.testng.annotations.Test;
+
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
-import org.testng.annotations.Test;
public class TestUserApiChangePlanMemory extends TestUserApiChangePlan {
@@ -32,46 +33,38 @@ public class TestUserApiChangePlanMemory extends TestUserApiChangePlan {
@Override
- @Test(enabled=true, groups={"fast-disabled"})
+ @Test(enabled=true, groups={"fast"})
public void testChangePlanBundleAlignEOTWithNoChargeThroughDate() {
super.testChangePlanBundleAlignEOTWithNoChargeThroughDate();
}
@Override
- @Test(enabled=true, groups={"fast-disabled"})
+ @Test(enabled=true, groups={"fast"})
public void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
super.testChangePlanBundleAlignEOTWithChargeThroughDate();
}
@Override
- @Test(enabled=true, groups={"fast-disabled"})
+ @Test(enabled=true, groups={"fast"})
public void testChangePlanBundleAlignIMM() {
super.testChangePlanBundleAlignIMM();
}
@Override
- @Test(enabled=true, groups={"fast-disabled"})
+ @Test(enabled=true, groups={"fast"})
public void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
super.testMultipleChangeLastIMM();
}
@Override
- @Test(enabled=true, groups={"fast-disabled"})
+ @Test(enabled=true, groups={"fast"})
public void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
super.testMultipleChangeLastEOT();
}
- /*
- // Set to false until we implement rescue example.
- @Override
- @Test(enabled=false, groups={"fast"})
- public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
- super.testChangePlanChangePlanAlignEOTWithChargeThroughDate();
- }
- */
@Override
- @Test(enabled=true, groups={"fast-disabled"})
+ @Test(enabled=true, groups={"fast"})
public void testCorrectPhaseAlignmentOnChange() {
super.testCorrectPhaseAlignmentOnChange();
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
index 735099c..1039b85 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
@@ -16,12 +16,13 @@
package com.ning.billing.entitlement.api.user;
+import org.testng.annotations.Test;
+
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleSql;
-import org.testng.annotations.Test;
public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
@@ -32,8 +33,8 @@ public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
}
- @Test(enabled= true, groups={"stress"})
- public void stressTest() throws EntitlementBillingApiException {
+ @Test(enabled= false, groups={"stress"})
+ public void stressTest() throws Exception {
for (int i = 0; i < MAX_STRESS_ITERATIONS; i++) {
cleanupTest();
setupTest();
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 a8d6e0c..6517044 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
@@ -22,19 +22,21 @@ import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import java.util.List;
+
import org.joda.time.DateTime;
+import org.joda.time.Interval;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
+import com.ning.billing.api.TestApiListener.NextEvent;
import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.PriceListSet;
-import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.TestApiBase;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
import com.ning.billing.entitlement.events.EntitlementEvent;
import com.ning.billing.entitlement.events.phase.PhaseEvent;
import com.ning.billing.util.clock.DefaultClock;
@@ -70,6 +72,8 @@ public abstract class TestUserApiCreate extends TestApiBase {
assertTrue(testListener.isCompleted(5000));
+ assertListenerStatus();
+
} catch (EntitlementUserApiException e) {
log.error("Unexpected exception",e);
Assert.fail(e.getMessage());
@@ -110,6 +114,8 @@ public abstract class TestUserApiCreate extends TestApiBase {
assertNotNull(currentPhase);
assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+ assertListenerStatus();
+
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
}
@@ -160,14 +166,15 @@ public abstract class TestUserApiCreate extends TestApiBase {
assertEquals(nextPhaseChange, nextExpectedPhaseChange);
testListener.pushExpectedEvent(NextEvent.PHASE);
-
- clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+ clock.addDeltaFromReality(it.toDurationMillis());
DateTime futureNow = clock.getUTCNow();
assertTrue(futureNow.isAfter(nextPhaseChange));
assertTrue(testListener.isCompleted(5000));
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
}
@@ -197,23 +204,26 @@ public abstract class TestUserApiCreate extends TestApiBase {
// MOVE TO DISCOUNT PHASE
testListener.pushExpectedEvent(NextEvent.PHASE);
- clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
currentPhase = subscription.getCurrentPhase();
assertNotNull(currentPhase);
assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
- assertTrue(testListener.isCompleted(2000));
+
// MOVE TO EVERGREEN PHASE + RE-READ SUBSCRIPTION
testListener.pushExpectedEvent(NextEvent.PHASE);
- clock.addDeltaFromReality(currentPhase.getDuration());
- assertTrue(testListener.isCompleted(2000));
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusYears(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
currentPhase = subscription.getCurrentPhase();
assertNotNull(currentPhase);
assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
-
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
}
@@ -234,11 +244,9 @@ public abstract class TestUserApiCreate extends TestApiBase {
getProductSpecifier(productName, planSetName, term, null), clock.getUTCNow(), context);
assertNotNull(subscription);
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
}
}
-
-
-
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateMemory.java
index b674e34..e4969f1 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateMemory.java
@@ -16,11 +16,12 @@
package com.ning.billing.entitlement.api.user;
+import org.testng.annotations.Test;
+
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
-import org.testng.annotations.Test;
public class TestUserApiCreateMemory extends TestUserApiCreate {
@@ -31,31 +32,31 @@ public class TestUserApiCreateMemory extends TestUserApiCreate {
}
@Override
- @Test(enabled=true, groups={"fast-disabled"})
+ @Test(enabled=true, groups={"fast"})
public void testCreateWithRequestedDate() {
super.testCreateWithRequestedDate();
}
@Override
- @Test(enabled=true, groups={"fast-disabled"})
+ @Test(enabled=true, groups={"fast"})
public void testCreateWithInitialPhase() {
super.testSimpleSubscriptionThroughPhases();
}
@Override
- @Test(enabled=true, groups={"fast-disabled"})
+ @Test(enabled=true, groups={"fast"})
public void testSimpleCreateSubscription() {
super.testSimpleCreateSubscription();
}
@Override
- @Test(enabled=true, groups={"fast-disabled"})
+ @Test(enabled=true, groups={"fast"})
protected void testSimpleSubscriptionThroughPhases() {
super.testSimpleSubscriptionThroughPhases();
}
@Override
- @Test(enabled=true, groups={"fast-disabled"})
+ @Test(enabled=true, groups={"fast"})
protected void testSubscriptionWithAddOn() {
super.testSubscriptionWithAddOn();
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateSql.java
index 6820fec..c890da7 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateSql.java
@@ -16,11 +16,12 @@
package com.ning.billing.entitlement.api.user;
+import org.testng.annotations.Test;
+
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
import com.ning.billing.entitlement.glue.MockEngineModuleSql;
-import org.testng.annotations.Test;
public class TestUserApiCreateSql extends TestUserApiCreate {
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
index 74fbef7..0973666 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
@@ -16,31 +16,34 @@
package com.ning.billing.entitlement.api.user;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
-
+import com.ning.billing.api.TestApiListener.NextEvent;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
-import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.TestApiBase;
-
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleSql;
import com.ning.billing.util.clock.DefaultClock;
-import org.joda.time.DateTime;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import static org.testng.Assert.*;
public class TestUserApiDemos extends TestApiBase {
@@ -67,6 +70,7 @@ public class TestUserApiDemos extends TestApiBase {
@Test(enabled=true, groups="demos")
public void testDemo1() throws EntitlementBillingApiException {
+ log.info("Starting testSubscriptionWithAddOn");
try {
System.out.println("DEMO 1 START");
@@ -80,14 +84,15 @@ public class TestUserApiDemos extends TestApiBase {
/* STEP 2. CHANGE PLAN WHILE IN TRIAL */
testListener.pushExpectedEvent(NextEvent.CHANGE);
subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
- assertTrue(testListener.isCompleted(3000));
+ assertTrue(testListener.isCompleted(5000));
displayState(subscription.getId(), "STEP 2. CHANGED PLAN WHILE IN TRIAL");
/* STEP 3. MOVE TO DISCOUNT PHASE */
testListener.pushExpectedEvent(NextEvent.PHASE);
- clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
- assertTrue(testListener.isCompleted(3000));
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
displayState(subscription.getId(), "STEP 3. MOVE TO DISCOUNT PHASE");
@@ -101,25 +106,28 @@ public class TestUserApiDemos extends TestApiBase {
billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, context);
subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
+ testListener.setNonExpectedMode();
testListener.pushExpectedEvent(NextEvent.CHANGE);
subscription.changePlan("Shotgun", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
- assertFalse(testListener.isCompleted(2000));
+ assertFalse(testListener.isCompleted(3000));
testListener.reset();
displayState(subscription.getId(), "STEP 4. SET CTD AND CHANGE PLAN EOT (Shotgun)");
/* STEP 5. CHANGE AGAIN */
+ testListener.setNonExpectedMode();
testListener.pushExpectedEvent(NextEvent.CHANGE);
subscription.changePlan("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
- assertFalse(testListener.isCompleted(2000));
+ assertFalse(testListener.isCompleted(3000));
testListener.reset();
displayState(subscription.getId(), "STEP 5. CHANGE AGAIN EOT (Pistol)");
/* STEP 6. MOVE TO EOT AND CHECK CHANGE OCCURED */
testListener.pushExpectedEvent(NextEvent.CHANGE);
- clock.addDeltaFromReality(ctd);
- assertTrue(testListener.isCompleted(2000));
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
Plan currentPlan = subscription.getCurrentPlan();
assertNotNull(currentPlan);
@@ -135,7 +143,8 @@ public class TestUserApiDemos extends TestApiBase {
/* STEP 7. MOVE TO NEXT PHASE */
testListener.pushExpectedEvent(NextEvent.PHASE);
- clock.addDeltaFromReality(currentPhase.getDuration());
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(6));
+ clock.addDeltaFromReality(it.toDurationMillis());
assertTrue(testListener.isCompleted(5000));
subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
@@ -157,6 +166,7 @@ public class TestUserApiDemos extends TestApiBase {
displayState(subscription.getId(), "STEP 8. CANCELLATION");
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
}
@@ -168,29 +178,33 @@ public class TestUserApiDemos extends TestApiBase {
System.out.println("");
System.out.println("******\t STEP " + stepMsg + " **************");
-
- SubscriptionData subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscriptionId);
+ try {
+ SubscriptionData subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscriptionId);
- Plan currentPlan = subscription.getCurrentPlan();
- PlanPhase currentPhase = subscription.getCurrentPhase();
- String priceList = subscription.getCurrentPriceList();
- System.out.println("");
- System.out.println("\t CURRENT TIME = " + clock.getUTCNow());
- System.out.println("");
- System.out.println("\t CURRENT STATE = " + subscription.getState());
- System.out.println("\t CURRENT PRODUCT = " + ((currentPlan == null) ? "NONE" : currentPlan.getProduct().getName()));
- System.out.println("\t CURRENT TERM = " + ((currentPlan == null) ? "NONE" : currentPlan.getBillingPeriod().toString()));
- System.out.println("\t CURRENT PHASE = " + ((currentPhase == null) ? "NONE" : currentPhase.getPhaseType()));
- System.out.println("\t CURRENT PRICE LIST = " + ((priceList == null) ? "NONE" : priceList));
- System.out.println("\t CURRENT \'SLUG\' = " + ((currentPhase == null) ? "NONE" : currentPhase.getName()));
+ Plan currentPlan = subscription.getCurrentPlan();
+ PlanPhase currentPhase = subscription.getCurrentPhase();
+ String priceList = subscription.getCurrentPriceList().getName();
+
+ System.out.println("");
+ System.out.println("\t CURRENT TIME = " + clock.getUTCNow());
+ System.out.println("");
+ System.out.println("\t CURRENT STATE = " + subscription.getState());
+ System.out.println("\t CURRENT PRODUCT = " + ((currentPlan == null) ? "NONE" : currentPlan.getProduct().getName()));
+ System.out.println("\t CURRENT TERM = " + ((currentPlan == null) ? "NONE" : currentPlan.getBillingPeriod().toString()));
+ System.out.println("\t CURRENT PHASE = " + ((currentPhase == null) ? "NONE" : currentPhase.getPhaseType()));
+ System.out.println("\t CURRENT PRICE LIST = " + ((priceList == null) ? "NONE" : priceList));
+ System.out.println("\t CURRENT \'SLUG\' = " + ((currentPhase == null) ? "NONE" : currentPhase.getName()));
+ } catch (EntitlementUserApiException e) {
+ System.out.println("No subscription found for id:" + subscriptionId );
+ }
System.out.println("");
}
@Test(enabled= true, groups={"stress"})
- public void stressTest() throws EntitlementBillingApiException {
+ public void stressTest() throws Exception {
for (int i = 0; i < 100; i++) {
cleanupTest();
setupTest();
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 11103ba..c56e0e0 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
@@ -16,28 +16,31 @@
package com.ning.billing.entitlement.api.user;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
import com.ning.billing.ErrorCode;
+import com.ning.billing.api.TestApiListener.NextEvent;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Duration;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.entitlement.api.TestApiBase;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
import com.ning.billing.util.clock.DefaultClock;
-import org.joda.time.DateTime;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import javax.annotation.Nullable;
-import java.util.UUID;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
public class TestUserApiError extends TestApiBase {
@@ -50,6 +53,9 @@ public class TestUserApiError extends TestApiBase {
@Test(enabled=true, groups={"fast"})
public void testCreateSubscriptionBadCatalog() {
+
+ log.info("Starting testCreateSubscriptionBadCatalog");
+
// WRONG PRODUCTS
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);
@@ -66,16 +72,19 @@ public class TestUserApiError extends TestApiBase {
@Test(enabled=true, groups={"fast"})
public void testCreateSubscriptionNoBundle() {
+ log.info("Starting testCreateSubscriptionNoBundle");
tCreateSubscriptionInternal(null, "Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.ENT_CREATE_NO_BUNDLE);
}
@Test(enabled=true, groups={"fast"})
public void testCreateSubscriptionNoBP() {
+ log.info("Starting testCreateSubscriptionNoBP");
tCreateSubscriptionInternal(bundle.getId(), "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.ENT_CREATE_NO_BP);
}
@Test(enabled=true, groups={"fast"})
public void testCreateSubscriptionBPExists() {
+ log.info("Starting testCreateSubscriptionBPExists");
try {
createSubscription("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME);
tCreateSubscriptionInternal(bundle.getId(), "Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.ENT_CREATE_BP_EXISTS);
@@ -87,6 +96,7 @@ public class TestUserApiError extends TestApiBase {
@Test(enabled=true, groups={"fast"})
public void testRecreateSubscriptionBPNotCancelled() {
+ log.info("Starting testRecreateSubscriptionBPNotCancelled");
try {
SubscriptionData subscription = createSubscription("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME);
try {
@@ -103,10 +113,11 @@ public class TestUserApiError extends TestApiBase {
@Test(enabled=true, groups={"fast"})
public void testCreateSubscriptionAddOnNotAvailable() {
+ log.info("Starting testCreateSubscriptionAddOnNotAvailable");
try {
UUID accountId = UUID.randomUUID();
SubscriptionBundle aoBundle = entitlementApi.createBundleForAccount(accountId, "myAOBundle", context);
- createSubscriptionWithBundle(aoBundle.getId(), "Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+ createSubscriptionWithBundle(aoBundle.getId(), "Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
tCreateSubscriptionInternal(aoBundle.getId(), "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.ENT_CREATE_AO_NOT_AVAILABLE);
} catch (Exception e) {
e.printStackTrace();
@@ -116,10 +127,11 @@ public class TestUserApiError extends TestApiBase {
@Test(enabled=true, groups={"fast"})
public void testCreateSubscriptionAddOnIncluded() {
+ log.info("Starting testCreateSubscriptionAddOnIncluded");
try {
UUID accountId = UUID.randomUUID();
SubscriptionBundle aoBundle = entitlementApi.createBundleForAccount(accountId, "myAOBundle", context);
- createSubscriptionWithBundle(aoBundle.getId(), "Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+ createSubscriptionWithBundle(aoBundle.getId(), "Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
tCreateSubscriptionInternal(aoBundle.getId(), "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.ENT_CREATE_AO_ALREADY_INCLUDED);
} catch (Exception e) {
e.printStackTrace();
@@ -148,6 +160,7 @@ public class TestUserApiError extends TestApiBase {
@Test(enabled=true, groups={"fast"})
public void testChangeSubscriptionNonActive() {
+ log.info("Starting testChangeSubscriptionNonActive");
try {
Subscription subscription = createSubscription("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME);
@@ -172,6 +185,7 @@ public class TestUserApiError extends TestApiBase {
@Test(enabled=true, groups={"fast"})
public void testChangeSubscriptionFutureCancelled() {
+ log.info("Starting testChangeSubscriptionFutureCancelled");
try {
Subscription subscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
PlanPhase trialPhase = subscription.getCurrentPhase();
@@ -179,8 +193,9 @@ public class TestUserApiError extends TestApiBase {
// MOVE TO NEXT PHASE
PlanPhase currentPhase = subscription.getCurrentPhase();
testListener.pushExpectedEvent(NextEvent.PHASE);
- clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
- assertTrue(testListener.isCompleted(3000));
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
// SET CTD TO CANCEL IN FUTURE
@@ -202,6 +217,8 @@ public class TestUserApiError extends TestApiBase {
assertFalse(true);
}
}
+
+ assertListenerStatus();
} catch (Exception e) {
e.printStackTrace();
Assert.assertFalse(true);
@@ -211,10 +228,12 @@ public class TestUserApiError extends TestApiBase {
@Test(enabled=false, groups={"fast"})
public void testCancelBadState() {
+ log.info("Starting testCancelBadState");
}
@Test(enabled=true, groups={"fast"})
public void testUncancelBadState() {
+ log.info("Starting testUncancelBadState");
try {
Subscription subscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
@@ -228,7 +247,7 @@ public class TestUserApiError extends TestApiBase {
assertFalse(true);
}
}
-
+ assertListenerStatus();
} catch (Exception e) {
e.printStackTrace();
Assert.assertFalse(true);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiRecreate.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiRecreate.java
index 7dbc802..389ac5f 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiRecreate.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiRecreate.java
@@ -25,11 +25,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
-import com.google.inject.Injector;
+import com.ning.billing.api.TestApiListener.NextEvent;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.entitlement.api.TestApiBase;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
public abstract class TestUserApiRecreate extends TestApiBase {
@@ -37,9 +36,10 @@ public abstract class TestUserApiRecreate extends TestApiBase {
protected void testRecreateWithBPCanceledThroughSubscription() {
- log.info("Starting testRecreateWithBPCanceled");
+ log.info("Starting testRecreateWithBPCanceledThroughSubscription");
try {
testCreateAndRecreate(false);
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
log.error("Unexpected exception",e);
Assert.fail(e.getMessage());
@@ -47,9 +47,10 @@ public abstract class TestUserApiRecreate extends TestApiBase {
}
protected void testCreateWithBPCanceledFromUserApi() {
- log.info("Starting testCreateWithBPCanceled");
+ log.info("Starting testCreateWithBPCanceledFromUserApi");
try {
testCreateAndRecreate(true);
+ assertListenerStatus();
} 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/TestUserApiScenarios.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
index 1ece406..7f874de 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
@@ -16,24 +16,27 @@
package com.ning.billing.entitlement.api.user;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
+import com.ning.billing.api.TestApiListener.NextEvent;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Duration;
import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.PlanPhase;
-
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
import com.ning.billing.entitlement.api.TestApiBase;
import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleSql;
import com.ning.billing.util.clock.DefaultClock;
-import org.joda.time.DateTime;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import static org.testng.Assert.*;
public class TestUserApiScenarios extends TestApiBase {
@@ -42,7 +45,7 @@ public class TestUserApiScenarios extends TestApiBase {
return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
}
- @Test(enabled=true)
+ @Test(groups={"slow"}, enabled=true)
public void testChangeIMMCancelUncancelChangeEOT() throws EntitlementBillingApiException {
log.info("Starting testChangeIMMCancelUncancelChangeEOT");
@@ -54,12 +57,13 @@ public class TestUserApiScenarios extends TestApiBase {
testListener.pushExpectedEvent(NextEvent.CHANGE);
subscription.changePlan("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
- testListener.isCompleted(3000);
+ testListener.isCompleted(5000);
// MOVE TO NEXT PHASE
testListener.pushExpectedEvent(NextEvent.PHASE);
- clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
- assertTrue(testListener.isCompleted(2000));
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
// SET CTD
Duration ctd = getDurationMonth(1);
@@ -69,22 +73,28 @@ public class TestUserApiScenarios extends TestApiBase {
subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
// CANCEL EOT
+ testListener.setNonExpectedMode();
testListener.pushExpectedEvent(NextEvent.CANCEL);
subscription.cancel(clock.getUTCNow(), false, context);
- assertFalse(testListener.isCompleted(2000));
+ assertFalse(testListener.isCompleted(5000));
testListener.reset();
// UNCANCEL
subscription.uncancel(context);
// CHANGE EOT
+ testListener.setNonExpectedMode();
testListener.pushExpectedEvent(NextEvent.CHANGE);
subscription.changePlan("Pistol", BillingPeriod.MONTHLY, "gunclubDiscount", clock.getUTCNow(), context);
- assertFalse(testListener.isCompleted(2000));
-
- clock.addDeltaFromReality(ctd);
- assertTrue(testListener.isCompleted(3000));
+ assertFalse(testListener.isCompleted(5000));
+ testListener.reset();
+
+ testListener.pushExpectedEvent(NextEvent.CHANGE);
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(testListener.isCompleted(5000));
+ assertListenerStatus();
} catch (EntitlementUserApiException e) {
Assert.fail(e.getMessage());
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java
index bbfd42a..c208521 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java
@@ -16,16 +16,11 @@
package com.ning.billing.entitlement.api.user;
-import java.util.List;
-
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.clock.DefaultClock;
+import java.util.List;
+
import org.joda.time.DateTime;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -33,11 +28,16 @@ import org.testng.annotations.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
+import com.ning.billing.api.TestApiListener.NextEvent;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.entitlement.api.TestApiBase;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
import com.ning.billing.entitlement.glue.MockEngineModuleSql;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.DefaultClock;
import com.ning.billing.util.customfield.CustomField;
@@ -51,7 +51,7 @@ public class TestUserCustomFieldsSql extends TestApiBase {
}
@Test(enabled=false, groups={"slow"})
- public void stress() {
+ public void stress() throws Exception {
cleanupTest();
for (int i = 0; i < 20; i++) {
setupTest();
@@ -66,7 +66,7 @@ public class TestUserCustomFieldsSql extends TestApiBase {
@Test(enabled=true, groups={"slow"})
public void testOverwriteCustomFields() {
- log.info("Starting testCreateWithRequestedDate");
+ log.info("Starting testOverwriteCustomFields");
try {
DateTime init = clock.getUTCNow();
@@ -122,7 +122,7 @@ public class TestUserCustomFieldsSql extends TestApiBase {
@Test(enabled=true, groups={"slow"})
public void testBasicCustomFields() {
- log.info("Starting testCreateWithRequestedDate");
+ log.info("Starting testBasicCustomFields");
try {
DateTime init = clock.getUTCNow();
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementNotificationKey.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementNotificationKey.java
new file mode 100644
index 0000000..dbd7098
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementNotificationKey.java
@@ -0,0 +1,45 @@
+/*
+ * 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.engine.core;
+
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestEntitlementNotificationKey {
+
+ @Test(groups="fast")
+ public void testKeyWithSeqId() {
+ UUID id = UUID.randomUUID();
+ int seq = 4;
+ EntitlementNotificationKey input = new EntitlementNotificationKey(id, seq);
+ Assert.assertEquals(id.toString() + ":" + seq, input.toString());
+ EntitlementNotificationKey output = new EntitlementNotificationKey(input.toString());
+ Assert.assertEquals(output, input);
+ }
+
+ @Test(groups="fast")
+ public void testKeyWithoutSeqId() {
+ UUID id = UUID.randomUUID();
+ int seq = 0;
+ EntitlementNotificationKey input = new EntitlementNotificationKey(id, seq);
+ Assert.assertEquals(input.toString(), id.toString());
+ EntitlementNotificationKey output = new EntitlementNotificationKey(input.toString());
+ Assert.assertEquals(output, input);
+ }
+
+}
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 94836a8..5eb760c 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
@@ -21,10 +21,10 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.TreeSet;
import java.util.UUID;
-import com.ning.billing.util.callcontext.CallContext;
import org.apache.commons.lang.NotImplementedException;
import org.joda.time.DateTime;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
@@ -36,20 +36,22 @@ import com.ning.billing.catalog.api.CatalogService;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.catalog.api.TimeUnit;
import com.ning.billing.config.EntitlementConfig;
+import com.ning.billing.entitlement.api.SubscriptionFactory;
import com.ning.billing.entitlement.api.migration.AccountMigrationData;
import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
import com.ning.billing.entitlement.api.migration.AccountMigrationData.SubscriptionMigrationData;
+import com.ning.billing.entitlement.api.timeline.SubscriptionDataRepair;
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.api.user.SubscriptionFactory;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.DefaultSubscriptionFactory.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.util.callcontext.CallContext;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.notificationq.NotificationKey;
import com.ning.billing.util.notificationq.NotificationQueue;
@@ -156,7 +158,6 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
@Override
public void createSubscription(final SubscriptionData subscription, final List<EntitlementEvent> initialEvents,
final CallContext context) {
-
synchronized(events) {
events.addAll(initialEvents);
for (final EntitlementEvent cur : initialEvents) {
@@ -263,7 +264,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public void updateSubscription(final SubscriptionData subscription, final CallContext context) {
+ public void updateChargedThroughDate(final SubscriptionData subscription, final CallContext context) {
boolean found = false;
Iterator<Subscription> it = subscriptions.iterator();
@@ -282,8 +283,8 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
@Override
public void cancelSubscription(final UUID subscriptionId, final EntitlementEvent cancelEvent,
- final CallContext context) {
- synchronized (cancelEvent) {
+ final CallContext context, final int seqId) {
+ synchronized(events) {
cancelNextPhaseEvent(subscriptionId);
insertEvent(cancelEvent);
}
@@ -446,4 +447,15 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
public void saveCustomFields(SubscriptionData subscription, CallContext context) {
throw new NotImplementedException();
}
+
+ @Override
+ public Map<UUID, List<EntitlementEvent>> getEventsForBundle(UUID bundleId) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void repair(UUID accountId, UUID bundleId, List<SubscriptionDataRepair> inRepair,
+ CallContext context) {
+ }
}
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 4733f4a..d52c55a 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
@@ -16,8 +16,9 @@
package com.ning.billing.entitlement.engine.dao;
+
+import com.ning.billing.util.bus.Bus;
import com.ning.billing.util.customfield.dao.CustomFieldDao;
-import com.ning.billing.util.tag.dao.TagDao;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.Transaction;
import org.skife.jdbi.v2.TransactionStatus;
@@ -26,19 +27,19 @@ import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
import com.google.inject.Inject;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
import com.ning.billing.entitlement.engine.addon.AddonUtils;
import com.ning.billing.util.clock.Clock;
+
import com.ning.billing.util.notificationq.NotificationQueueService;
-public class MockEntitlementDaoSql extends EntitlementSqlDao implements MockEntitlementDao {
+public class MockEntitlementDaoSql extends AuditedEntitlementDao implements MockEntitlementDao {
private final ResetSqlDao resetDao;
@Inject
public MockEntitlementDaoSql(IDBI dbi, Clock clock, AddonUtils addonUtils, NotificationQueueService notificationQueueService,
- CustomFieldDao customFieldDao) {
- super(dbi, clock, addonUtils, notificationQueueService, customFieldDao);
+ CustomFieldDao customFieldDao, final Bus eventBus) {
+ super(dbi, clock, addonUtils, notificationQueueService, customFieldDao, eventBus);
this.resetDao = dbi.onDemand(ResetSqlDao.class);
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java
index acb1a2a..31e0da3 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
@@ -23,12 +23,12 @@ import com.ning.billing.util.clock.MockClockModule;
import com.ning.billing.util.glue.BusModule;
import com.ning.billing.util.glue.CallContextModule;
-public class MockEngineModule extends EntitlementModule {
+public class MockEngineModule extends DefaultEntitlementModule {
+
@Override
protected void configure() {
super.configure();
- install(new BusModule());
install(new CatalogModule());
bind(AccountUserApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class));
install(new MockClockModule());
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 8b78c2d..148b62d 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,8 +17,13 @@
package com.ning.billing.entitlement.glue;
+import com.google.inject.name.Names;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementLifecycleDao;
import com.ning.billing.entitlement.engine.dao.EntitlementDao;
import com.ning.billing.entitlement.engine.dao.MockEntitlementDaoMemory;
+import com.ning.billing.entitlement.engine.dao.RepairEntitlementDao;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.BusModule.BusType;
import com.ning.billing.util.notificationq.MockNotificationQueueService;
import com.ning.billing.util.notificationq.NotificationQueueService;
@@ -27,6 +32,9 @@ public class MockEngineModuleMemory extends MockEngineModule {
@Override
protected void installEntitlementDao() {
bind(EntitlementDao.class).to(MockEntitlementDaoMemory.class).asEagerSingleton();
+ bind(EntitlementDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairEntitlementDao.class);
+ bind(RepairEntitlementLifecycleDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairEntitlementDao.class);
+ bind(RepairEntitlementDao.class).asEagerSingleton();
}
private void installNotificationQueue() {
@@ -36,6 +44,7 @@ public class MockEngineModuleMemory extends MockEngineModule {
@Override
protected void configure() {
super.configure();
+ install(new BusModule(BusType.MEMORY));
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 f774e58..e1b3742 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
@@ -16,18 +16,26 @@
package com.ning.billing.entitlement.glue;
+
+import com.google.inject.name.Names;
+
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+
+
import com.ning.billing.dbi.DBIProvider;
import com.ning.billing.dbi.DbiConfig;
import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementLifecycleDao;
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.entitlement.engine.dao.RepairEntitlementDao;
+
+import com.ning.billing.util.glue.BusModule;
import com.ning.billing.util.glue.FieldStoreModule;
import com.ning.billing.util.glue.NotificationQueueModule;
-
-import org.skife.config.ConfigurationObjectFactory;
-import org.skife.jdbi.v2.IDBI;
+import com.ning.billing.util.glue.BusModule.BusType;
public class MockEngineModuleSql extends MockEngineModule {
@@ -35,7 +43,11 @@ public class MockEngineModuleSql extends MockEngineModule {
@Override
protected void installEntitlementDao() {
bind(EntitlementDao.class).to(MockEntitlementDaoSql.class).asEagerSingleton();
+ bind(EntitlementDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairEntitlementDao.class);
+ bind(RepairEntitlementLifecycleDao.class).annotatedWith(Names.named(REPAIR_NAMED)).to(RepairEntitlementDao.class);
+ bind(RepairEntitlementDao.class).asEagerSingleton();
}
+
protected void installDBI() {
final MysqlTestingHelper helper = new MysqlTestingHelper();
@@ -55,6 +67,7 @@ public class MockEngineModuleSql extends MockEngineModule {
installDBI();
install(new NotificationQueueModule());
install(new FieldStoreModule());
+ install(new BusModule(BusType.PERSISTENT));
super.configure();
}
}
diff --git a/entitlement/src/test/resources/entitlement.properties b/entitlement/src/test/resources/entitlement.properties
index af1c3fc..58f4f95 100644
--- a/entitlement/src/test/resources/entitlement.properties
+++ b/entitlement/src/test/resources/entitlement.properties
@@ -1,6 +1,8 @@
killbill.catalog.uri=file:src/test/resources/testInput.xml
killbill.entitlement.dao.claim.time=60000
killbill.entitlement.dao.ready.max=1
-killbill.entitlement.engine.notifications.sleep=500
+killbill.entitlement.engine.notifications.sleep=100
+killbill.billing.util.persistent.bus.sleep=100
+killbill.billing.util.persistent.bus.nbThreads=1
user.timezone=UTC
entitlement/src/test/resources/testInput.xml 515(+351 -164)
diff --git a/entitlement/src/test/resources/testInput.xml b/entitlement/src/test/resources/testInput.xml
index 8a97d57..bdc73db 100644
--- a/entitlement/src/test/resources/testInput.xml
+++ b/entitlement/src/test/resources/testInput.xml
@@ -1,41 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!--
- ~ Copyright 2010-2011 Ning, Inc.
- ~
- ~ Ning licenses this file to you under the Apache License, version 2.0
- ~ (the "License"); you may not use this file except in compliance with the
- ~ License. You may obtain a copy of the License at:
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- ~ License for the specific language governing permissions and limitations
- ~ under the License.
- -->
+<!-- ~ Copyright 2010-2011 Ning, Inc. ~ ~ Ning licenses this file to you
+ under the Apache License, version 2.0 ~ (the "License"); you may not use
+ this file except in compliance with the ~ License. You may obtain a copy
+ of the License at: ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless
+ required by applicable law or agreed to in writing, software ~ distributed
+ under the License is distributed on an "AS IS" BASIS, WITHOUT ~ WARRANTIES
+ OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for
+ the specific language governing permissions and limitations ~ under the License. -->
-<!--
-Use cases covered so far:
- Tiered Product (Pistol/Shotgun/Assault-Rifle)
- Multiple changeEvent plan policies
- Multiple PlanAlignment (see below, trial add-on alignments and rescue discount package)
- Product transition rules
- Add on (Scopes, Hoster)
- Multi-pack addon (Extra-Ammo)
- Addon Trial aligned to base plan (holster-monthly-regular)
- Addon Trial aligned to creation (holster-monthly-special)
- Rescue discount package (assault-rifle-annual-rescue)
- Plan phase with a reccurring and a one off (refurbish-maintenance)
- Phan with more than 2 phase (gunclub discount plans)
-
-Use Cases to do:
- Tiered Add On
- Riskfree period
-
-
-
- -->
+<!-- Use cases covered so far: Tiered Product (Pistol/Shotgun/Assault-Rifle)
+ Multiple changeEvent plan policies Multiple PlanAlignment (see below, trial
+ add-on alignments and rescue discount package) Product transition rules Add
+ on (Scopes, Hoster) Multi-pack addon (Extra-Ammo) Addon Trial aligned to
+ base plan (holster-monthly-regular) Addon Trial aligned to creation (holster-monthly-special)
+ Rescue discount package (assault-rifle-annual-rescue) Plan phase with a reccurring
+ and a one off (refurbish-maintenance) Phan with more than 2 phase (gunclub
+ discount plans) Use Cases to do: Tiered Add On Riskfree period -->
<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
@@ -47,21 +27,21 @@ Use Cases to do:
<currency>EUR</currency>
<currency>GBP</currency>
</currencies>
-
+
<products>
<product name="Pistol">
<category>BASE</category>
</product>
<product name="Shotgun">
<category>BASE</category>
- <available>
- <addonProduct>Telescopic-Scope</addonProduct>
- <addonProduct>Laser-Scope</addonProduct>
- </available>
+ <available>
+ <addonProduct>Telescopic-Scope</addonProduct>
+ <addonProduct>Laser-Scope</addonProduct>
+ </available>
</product>
<product name="Assault-Rifle">
<category>BASE</category>
- <included>
+ <included>
<addonProduct>Telescopic-Scope</addonProduct>
</included>
<available>
@@ -84,37 +64,37 @@ Use Cases to do:
<category>ADD_ON</category>
</product>
</products>
-
+
<rules>
<changePolicy>
- <changePolicyCase>
+ <changePolicyCase>
<phaseType>TRIAL</phaseType>
<policy>IMMEDIATE</policy>
</changePolicyCase>
- <changePolicyCase>
- <toProduct>Assault-Rifle</toProduct>
- <policy>IMMEDIATE</policy>
- </changePolicyCase>
- <changePolicyCase>
- <fromProduct>Pistol</fromProduct>
- <toProduct>Shotgun</toProduct>
- <policy>IMMEDIATE</policy>
- </changePolicyCase>
- <changePolicyCase>
+ <changePolicyCase>
+ <toProduct>Assault-Rifle</toProduct>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromProduct>Pistol</fromProduct>
+ <toProduct>Shotgun</toProduct>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
<toPriceList>rescue</toPriceList>
<policy>END_OF_TERM</policy>
</changePolicyCase>
- <changePolicyCase>
+ <changePolicyCase>
<fromBillingPeriod>MONTHLY</fromBillingPeriod>
<toBillingPeriod>ANNUAL</toBillingPeriod>
<policy>IMMEDIATE</policy>
</changePolicyCase>
- <changePolicyCase>
+ <changePolicyCase>
<fromBillingPeriod>ANNUAL</fromBillingPeriod>
<toBillingPeriod>MONTHLY</toBillingPeriod>
<policy>END_OF_TERM</policy>
</changePolicyCase>
- <changePolicyCase>
+ <changePolicyCase>
<policy>END_OF_TERM</policy>
</changePolicyCase>
</changePolicy>
@@ -128,27 +108,27 @@ Use Cases to do:
<toPriceList>rescue</toPriceList>
<alignment>CHANGE_OF_PRICELIST</alignment>
</changeAlignmentCase>
- <changeAlignmentCase>
- <alignment>START_OF_SUBSCRIPTION</alignment>
- </changeAlignmentCase>
+ <changeAlignmentCase>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </changeAlignmentCase>
</changeAlignment>
<cancelPolicy>
<cancelPolicyCase>
<phaseType>TRIAL</phaseType>
<policy>IMMEDIATE</policy>
</cancelPolicyCase>
- <cancelPolicyCase>
- <policy>END_OF_TERM</policy>
- </cancelPolicyCase>
+ <cancelPolicyCase>
+ <policy>END_OF_TERM</policy>
+ </cancelPolicyCase>
</cancelPolicy>
<createAlignment>
- <createAlignmentCase>
- <product>Laser-Scope</product>
- <alignment>START_OF_SUBSCRIPTION</alignment>
- </createAlignmentCase>
- <createAlignmentCase>
- <alignment>START_OF_BUNDLE</alignment>
- </createAlignmentCase>
+ <createAlignmentCase>
+ <product>Laser-Scope</product>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </createAlignmentCase>
+ <createAlignmentCase>
+ <alignment>START_OF_BUNDLE</alignment>
+ </createAlignmentCase>
</createAlignment>
<billingAlignment>
<billingAlignmentCase>
@@ -191,9 +171,18 @@ Use Cases to do:
</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>29.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>USD</currency>
+ <value>29.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -207,7 +196,7 @@ Use Cases to do:
</duration>
<billingPeriod>NO_BILLING_PERIOD</billingPeriod>
<fixedPrice></fixedPrice>
- <!-- no price implies $0 -->
+ <!-- no price implies $0 -->
</phase>
</initialPhases>
<finalPhase type="EVERGREEN">
@@ -217,9 +206,18 @@ Use Cases to do:
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>249.95</value></price>
- <price><currency>EUR</currency><value>149.95</value></price>
- <price><currency>GBP</currency><value>169.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>249.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>149.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>169.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -242,9 +240,18 @@ Use Cases to do:
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>599.95</value></price>
- <price><currency>EUR</currency><value>349.95</value></price>
- <price><currency>GBP</currency><value>399.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>599.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>349.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>399.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -267,9 +274,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>199.95</value></price>
- <price><currency>EUR</currency><value>199.95</value></price>
- <price><currency>GBP</currency><value>199.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>199.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -292,9 +308,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>2399.95</value></price>
- <price><currency>EUR</currency><value>1499.95</value></price>
- <price><currency>GBP</currency><value>1699.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>2399.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>1499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>1699.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -317,9 +342,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>5999.95</value></price>
- <price><currency>EUR</currency><value>3499.95</value></price>
- <price><currency>GBP</currency><value>3999.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>5999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>3499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>3999.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -342,9 +376,18 @@ Use Cases to do:
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>9.95</value></price>
- <price><currency>EUR</currency><value>9.95</value></price>
- <price><currency>GBP</currency><value>9.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>9.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>9.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>9.95</value>
+ </price>
</recurringPrice>
</phase>
</initialPhases>
@@ -354,9 +397,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>199.95</value></price>
- <price><currency>EUR</currency><value>199.95</value></price>
- <price><currency>GBP</currency><value>199.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>199.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -379,9 +431,18 @@ Use Cases to do:
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>19.95</value></price>
- <price><currency>EUR</currency><value>49.95</value></price>
- <price><currency>GBP</currency><value>69.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>19.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>49.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>69.95</value>
+ </price>
</recurringPrice>
</phase>
</initialPhases>
@@ -391,9 +452,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>2399.95</value></price>
- <price><currency>EUR</currency><value>1499.95</value></price>
- <price><currency>GBP</currency><value>1699.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>2399.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>1499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>1699.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -416,10 +486,19 @@ Use Cases to do:
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>99.95</value></price>
- <price><currency>EUR</currency><value>99.95</value></price>
- <price><currency>GBP</currency><value>99.95</value></price>
- </recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>99.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>99.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>99.95</value>
+ </price>
+ </recurringPrice>
</phase>
</initialPhases>
<finalPhase type="EVERGREEN">
@@ -428,65 +507,110 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>5999.95</value></price>
- <price><currency>EUR</currency><value>3499.95</value></price>
- <price><currency>GBP</currency><value>3999.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>5999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>3499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>3999.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
<plan name="laser-scope-monthly">
- <product>Laser-Scope</product>
- <initialPhases>
- <phase type="DISCOUNT">
- <duration>
- <unit>MONTHS</unit>
- <number>1</number>
- </duration>
- <billingPeriod>MONTHLY</billingPeriod>
- <recurringPrice>
- <price><currency>USD</currency><value>999.95</value></price>
- <price><currency>EUR</currency><value>499.95</value></price>
- <price><currency>GBP</currency><value>999.95</value></price>
- </recurringPrice>
- </phase>
- </initialPhases>
+ <product>Laser-Scope</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>1</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>999.95</value>
+ </price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
<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>
+ <price>
+ <currency>USD</currency>
+ <value>1999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>1499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>1999.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
<plan name="telescopic-scope-monthly">
<product>Telescopic-Scope</product>
<initialPhases>
- <phase type="DISCOUNT">
- <duration>
- <unit>MONTHS</unit>
- <number>1</number>
- </duration>
- <billingPeriod>MONTHLY</billingPeriod>
- <recurringPrice>
- <price><currency>USD</currency><value>399.95</value></price>
- <price><currency>EUR</currency><value>299.95</value></price>
- <price><currency>GBP</currency><value>399.95</value></price>
- </recurringPrice>
- </phase>
- </initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>1</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>399.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>299.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>399.95</value>
+ </price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
<finalPhase type="EVERGREEN">
<duration>
<unit>UNLIMITED</unit>
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>999.95</value></price>
- <price><currency>EUR</currency><value>499.95</value></price>
- <price><currency>GBP</currency><value>999.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>999.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -498,9 +622,18 @@ Use Cases to do:
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>999.95</value></price>
- <price><currency>EUR</currency><value>499.95</value></price>
- <price><currency>GBP</currency><value>999.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>999.95</value>
+ </price>
</recurringPrice>
</finalPhase>
<plansAllowedInBundle>-1</plansAllowedInBundle> <!-- arbitrary number of these (multipack) -->
@@ -524,9 +657,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>199.95</value></price>
- <price><currency>EUR</currency><value>199.95</value></price>
- <price><currency>GBP</currency><value>199.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>199.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -549,9 +691,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>199.95</value></price>
- <price><currency>EUR</currency><value>199.95</value></price>
- <price><currency>GBP</currency><value>199.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>199.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -565,9 +716,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>5999.95</value></price>
- <price><currency>EUR</currency><value>3499.95</value></price>
- <price><currency>GBP</currency><value>3999.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>5999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>3499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>3999.95</value>
+ </price>
</recurringPrice>
</phase>
</initialPhases>
@@ -577,9 +737,18 @@ Use Cases to do:
</duration>
<billingPeriod>ANNUAL</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>5999.95</value></price>
- <price><currency>EUR</currency><value>3499.95</value></price>
- <price><currency>GBP</currency><value>3999.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>5999.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>3499.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>3999.95</value>
+ </price>
</recurringPrice>
</finalPhase>
</plan>
@@ -592,20 +761,38 @@ Use Cases to do:
</duration>
<billingPeriod>MONTHLY</billingPeriod>
<recurringPrice>
- <price><currency>USD</currency><value>199.95</value></price>
- <price><currency>EUR</currency><value>199.95</value></price>
- <price><currency>GBP</currency><value>199.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>199.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>199.95</value>
+ </price>
</recurringPrice>
<fixedPrice>
- <price><currency>USD</currency><value>599.95</value></price>
- <price><currency>EUR</currency><value>599.95</value></price>
- <price><currency>GBP</currency><value>599.95</value></price>
+ <price>
+ <currency>USD</currency>
+ <value>599.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>599.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>599.95</value>
+ </price>
</fixedPrice>
</finalPhase>
</plan>
</plans>
<priceLists>
- <defaultPriceList name="DEFAULT">
+ <defaultPriceList name="DEFAULT">
<plans>
<plan>pistol-monthly</plan>
<plan>shotgun-monthly</plan>
index.html 120(+120 -0)
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..f907b08
--- /dev/null
+++ b/index.html
@@ -0,0 +1,120 @@
+<!-- ~ Copyright 2010-2011 Ning, Inc. ~ ~ Ning licenses this file to you
+ under the Apache License, version 2.0 ~ (the "License"); you may not use
+ this file except in compliance with the ~ License. You may obtain a copy
+ of the License at: ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless
+ required by applicable law or agreed to in writing, software ~ distributed
+ under the License is distributed on an "AS IS" BASIS, WITHOUT ~ WARRANTIES
+ OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for
+ the specific language governing permissions and limitations ~ under the License. -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>Killbill: Subscription Management/Billing System</title>
+ <meta name="description" content="Killbill is a new open source subscription management/billing system">
+ <meta name="author" content="Stephane Brossier">
+
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js" type="text/javascript"></script>
+ <![endif]-->
+
+ <link rel=stylesheet type="text/css" href="doc/css/bootstrap.min.css">
+ <link rel=stylesheet type="text/css" href="doc/css/prettify.css">
+ <link rel=stylesheet type="text/css" href="doc/css/killbill.css">
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+ <script src="js/prettify.js"></script>
+ <script src="js/bootstrap-dropdown.js"></script>
+
+ <script type="text/javascript">
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-19297396-1']);
+ _gaq.push(['_trackPageview']);
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+ </script>
+</head>
+
+<body>
+
+<div class="topbar" data-dropdown="dropdown">
+ <div class="topbar-inner">
+ <div class="container-fluid">
+ <a class="brand" href="index.html">Killbill</a>
+ <ul class="nav">
+ <li><a href="doc/user.html">User Guide</a></li>
+ <li><a href="doc/setup.html">Setup/Installation</a></li>
+ <li><a href="doc/api.html">APIs</a></li>
+ <li><a href="doc/design.html">Design</a></li>
+ <li><a href="http://groups.google.com/group/killbill-users">Mailing List</a></li>
+ </ul>
+ <ul class="nav secondary-nav">
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle">Maven sites</a>
+ <ul class="dropdown-menu">
+ <li><a href="http://sbrossie.github.com/killbill" target="_blank">Killbill</a></li>
+ </ul>
+ </li>
+ </ul>
+ <ul class="nav secondary-nav">
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle">Github projects</a>
+ <ul class="dropdown-menu">
+ <li><a href="http://github.com/ning/killbill" target="_blank">Killbill</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ </div>
+</div>
+
+
+<div class="container-fluid">
+ <div class="sidebar">
+ <div class="well">
+ <h5>Quick Presentation</h5>
+ <ul>
+ <li><a href="#motivation">Motivation</a></li>
+ <li><a href="#overview">Overview</a></li>
+ <li><a href="#goals">Goals</a></li>
+ <li><a href="#feature-list">Feature List</a></li>
+ <li><a href="#dependencies">Dependencies</a></li>
+ <li><a href="#state-project">State of the Project</a></li>
+ </ul>
+ </div>
+ </div>
+
+ <div class="content">
+
+ <h2 id="motivation">Motivation</h2>
+
+ This is the motivation; why we are building it
+
+ <h2 id="overview">Overview</h2>
+
+ This is the overview; describes what it does / (does not ?)
+
+
+ <h2 id="goals">Goals</h2>
+
+ This is the goals
+
+ <h2 id="feature-list">Feature List</h2>
+
+ This is the feature list
+
+ <h2 id="dependencies">Dependencies</h2>
+
+ This is the dependencies
+
+ <h2 id="state-project">State of the Project</h2>
+
+ This is the state of the project
+
+ </div>
+</div>
+</body>
+</html>
invoice/pom.xml 42(+13 -29)
diff --git a/invoice/pom.xml b/invoice/pom.xml
index 43f45c9..e03ae9a 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -33,20 +33,24 @@
<groupId>com.ning.billing</groupId>
<artifactId>killbill-util</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.jdbi</groupId>
+ <artifactId>jdbi</artifactId>
+ </dependency>
<dependency>
- <groupId>com.ning.billing</groupId>
- <artifactId>killbill-util</artifactId>
- <type>test-jar</type>
- <scope>test</scope>
+ <groupId>org.antlr</groupId>
+ <artifactId>stringtemplate</artifactId>
+ <scope>runtime</scope>
</dependency>
- <dependency>
- <groupId>com.ning.billing</groupId>
- <artifactId>killbill-entitlement</artifactId>
- <scope>test</scope>
+ <dependency>
+ <groupId>com.google.inject</groupId>
+ <artifactId>guice</artifactId>
+ <scope>provided</scope>
</dependency>
+ <!-- TEST SCOPE -->
<dependency>
<groupId>com.ning.billing</groupId>
- <artifactId>killbill-entitlement</artifactId>
+ <artifactId>killbill-util</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
@@ -62,12 +66,6 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>com.ning.billing</groupId>
- <artifactId>killbill-account</artifactId>
- <type>test-jar</type>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
@@ -78,20 +76,6 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.jdbi</groupId>
- <artifactId>jdbi</artifactId>
- </dependency>
- <dependency>
- <groupId>org.antlr</groupId>
- <artifactId>stringtemplate</artifactId>
- <scope>runtime</scope>
- </dependency>
- <dependency>
- <groupId>com.google.inject</groupId>
- <artifactId>guice</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
<groupId>com.jayway.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java b/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
index 6f033db..da41e77 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
@@ -26,18 +26,14 @@ import com.ning.billing.util.bus.Bus;
public class DefaultInvoiceService implements InvoiceService {
public static final String INVOICE_SERVICE_NAME = "invoice-service";
- private final InvoiceUserApi userApi;
- private final InvoicePaymentApi paymentApi;
private final NextBillingDateNotifier dateNotifier;
private final InvoiceListener invoiceListener;
private final Bus eventBus;
@Inject
- public DefaultInvoiceService(InvoiceListener invoiceListener, Bus eventBus, InvoiceUserApi userApi, InvoicePaymentApi paymentApi, NextBillingDateNotifier dateNotifier) {
+ public DefaultInvoiceService(InvoiceListener invoiceListener, Bus eventBus, NextBillingDateNotifier dateNotifier) {
this.invoiceListener = invoiceListener;
this.eventBus = eventBus;
- this.userApi = userApi;
- this.paymentApi = paymentApi;
this.dateNotifier = dateNotifier;
}
@@ -47,16 +43,6 @@ public class DefaultInvoiceService implements InvoiceService {
return INVOICE_SERVICE_NAME;
}
- @Override
- public InvoiceUserApi getUserApi() {
- return userApi;
- }
-
- @Override
- public InvoicePaymentApi getPaymentApi() {
- return paymentApi;
- }
-
@LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.INIT_SERVICE)
public void initialize() {
dateNotifier.initialize();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/migration/MigrationInvoice.java b/invoice/src/main/java/com/ning/billing/invoice/api/migration/MigrationInvoice.java
index 84877eb..6627982 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/migration/MigrationInvoice.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/migration/MigrationInvoice.java
@@ -24,6 +24,6 @@ import java.util.UUID;
public class MigrationInvoice extends DefaultInvoice {
public MigrationInvoice(UUID accountId, DateTime invoiceDate, DateTime targetDate, Currency currency) {
- super(UUID.randomUUID(), accountId, null, invoiceDate, targetDate, currency, true, null, null);
+ super(UUID.randomUUID(), accountId, null, invoiceDate, targetDate, currency, true);
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultEmptyInvoiceEvent.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultEmptyInvoiceEvent.java
new file mode 100644
index 0000000..6ce30f6
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultEmptyInvoiceEvent.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.invoice.api.user;
+
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
+import com.ning.billing.invoice.api.EmptyInvoiceEvent;
+
+public class DefaultEmptyInvoiceEvent implements EmptyInvoiceEvent {
+
+ private final UUID accountId;
+ private final DateTime processingDate;
+ private final UUID userToken;
+
+
+ @JsonCreator
+ public DefaultEmptyInvoiceEvent(@JsonProperty("accountId") final UUID accountId,
+ @JsonProperty("processingDate") final DateTime processingDate,
+ @JsonProperty("userToken") final UUID userToken) {
+ super();
+ this.accountId = accountId;
+ this.processingDate = processingDate;
+ this.userToken = userToken;
+ }
+
+ @JsonIgnore
+ @Override
+ public BusEventType getBusEventType() {
+ return BusEventType.INVOICE_EMPTY;
+ }
+
+ @Override
+ public UUID getUserToken() {
+ return userToken;
+ }
+
+ public UUID getAccountId() {
+ return accountId;
+ }
+
+ public DateTime getProcessingDate() {
+ return processingDate;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((accountId == null) ? 0 : accountId.hashCode());
+ result = prime * result
+ + ((processingDate == null) ? 0 : processingDate.hashCode());
+ result = prime * result
+ + ((userToken == null) ? 0 : userToken.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DefaultEmptyInvoiceEvent other = (DefaultEmptyInvoiceEvent) obj;
+ if (accountId == null) {
+ if (other.accountId != null)
+ return false;
+ } else if (!accountId.equals(other.accountId))
+ return false;
+ if (processingDate == null) {
+ if (other.processingDate != null)
+ return false;
+ } else if (processingDate.compareTo(other.processingDate) != 0)
+ return false;
+ if (userToken == null) {
+ if (other.userToken != null)
+ return false;
+ } else if (!userToken.equals(other.userToken))
+ return false;
+ return true;
+ }
+
+
+}
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 cf46d15..5178a29 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
@@ -18,61 +18,46 @@ package com.ning.billing.invoice.dao;
import java.math.BigDecimal;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import java.util.UUID;
-import com.ning.billing.util.ChangeType;
-import com.ning.billing.util.audit.dao.AuditSqlDao;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.customfield.dao.CustomFieldSqlDao;
-import com.ning.billing.util.tag.ControlTagType;
-import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.dao.TagDao;
import org.joda.time.DateTime;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.Transaction;
import org.skife.jdbi.v2.TransactionStatus;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.api.InvoiceCreationNotification;
import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.api.InvoicePayment;
-import com.ning.billing.invoice.api.user.DefaultInvoiceCreationNotification;
import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
import com.ning.billing.invoice.model.RecurringInvoiceItem;
import com.ning.billing.invoice.notification.NextBillingDatePoster;
-import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.ChangeType;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.customfield.dao.CustomFieldSqlDao;
+import com.ning.billing.util.dao.EntityAudit;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.TableName;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.dao.TagDao;
public class DefaultInvoiceDao implements InvoiceDao {
- private final static Logger log = LoggerFactory.getLogger(DefaultInvoiceDao.class);
-
private final InvoiceSqlDao invoiceSqlDao;
private final InvoicePaymentSqlDao invoicePaymentSqlDao;
- private final EntitlementBillingApi entitlementBillingApi;
private final TagDao tagDao;
- private final Bus eventBus;
-
private final NextBillingDatePoster nextBillingDatePoster;
@Inject
- public DefaultInvoiceDao(final IDBI dbi, final Bus eventBus,
- final EntitlementBillingApi entitlementBillingApi,
+ public DefaultInvoiceDao(final IDBI dbi,
final NextBillingDatePoster nextBillingDatePoster,
final TagDao tagDao) {
this.invoiceSqlDao = dbi.onDemand(InvoiceSqlDao.class);
this.invoicePaymentSqlDao = dbi.onDemand(InvoicePaymentSqlDao.class);
- this.eventBus = eventBus;
- this.entitlementBillingApi = entitlementBillingApi;
this.nextBillingDatePoster = nextBillingDatePoster;
this.tagDao = tagDao;
}
@@ -151,75 +136,58 @@ public class DefaultInvoiceDao implements InvoiceDao {
@Override
public void create(final Invoice invoice, final CallContext context) {
+
invoiceSqlDao.inTransaction(new Transaction<Void, InvoiceSqlDao>() {
@Override
- public Void inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+ public Void inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
// STEPH this seems useless
- Invoice currentInvoice = invoiceDao.getById(invoice.getId().toString());
+ Invoice currentInvoice = transactional.getById(invoice.getId().toString());
if (currentInvoice == null) {
- invoiceDao.create(invoice, context);
+ List<EntityAudit> audits = new ArrayList<EntityAudit>();
+
+ transactional.create(invoice, context);
+ Long recordId = transactional.getRecordId(invoice.getId().toString());
+ audits.add(new EntityAudit(TableName.INVOICES, recordId, ChangeType.INSERT));
+
+ List<Long> recordIdList;
List<InvoiceItem> recurringInvoiceItems = invoice.getInvoiceItems(RecurringInvoiceItem.class);
- RecurringInvoiceItemSqlDao recurringInvoiceItemDao = invoiceDao.become(RecurringInvoiceItemSqlDao.class);
+ RecurringInvoiceItemSqlDao recurringInvoiceItemDao = transactional.become(RecurringInvoiceItemSqlDao.class);
recurringInvoiceItemDao.batchCreateFromTransaction(recurringInvoiceItems, context);
+ recordIdList = recurringInvoiceItemDao.getRecordIds(invoice.getId().toString());
+ audits.addAll(createAudits(TableName.RECURRING_INVOICE_ITEMS, recordIdList));
- notifyOfFutureBillingEvents(invoiceSqlDao, recurringInvoiceItems);
+ notifyOfFutureBillingEvents(transactional, recurringInvoiceItems);
List<InvoiceItem> fixedPriceInvoiceItems = invoice.getInvoiceItems(FixedPriceInvoiceItem.class);
- FixedPriceInvoiceItemSqlDao fixedPriceInvoiceItemDao = invoiceDao.become(FixedPriceInvoiceItemSqlDao.class);
+ FixedPriceInvoiceItemSqlDao fixedPriceInvoiceItemDao = transactional.become(FixedPriceInvoiceItemSqlDao.class);
fixedPriceInvoiceItemDao.batchCreateFromTransaction(fixedPriceInvoiceItems, context);
+ recordIdList = fixedPriceInvoiceItemDao.getRecordIds(invoice.getId().toString());
+ audits.addAll(createAudits(TableName.FIXED_INVOICE_ITEMS, recordIdList));
- setChargedThroughDates(invoiceSqlDao, fixedPriceInvoiceItems, recurringInvoiceItems, context);
-
- // STEPH Why do we need that? Are the payments not always null at this point?
List<InvoicePayment> invoicePayments = invoice.getPayments();
- InvoicePaymentSqlDao invoicePaymentSqlDao = invoiceDao.become(InvoicePaymentSqlDao.class);
+ InvoicePaymentSqlDao invoicePaymentSqlDao = transactional.become(InvoicePaymentSqlDao.class);
invoicePaymentSqlDao.batchCreateFromTransaction(invoicePayments, context);
+ recordIdList = invoicePaymentSqlDao.getRecordIds(invoice.getId().toString());
+ audits.addAll(createAudits(TableName.INVOICE_PAYMENTS, recordIdList));
- AuditSqlDao auditSqlDao = invoiceDao.become(AuditSqlDao.class);
- auditSqlDao.insertAuditFromTransaction("invoices", invoice.getId().toString(), ChangeType.INSERT, context);
- auditSqlDao.insertAuditFromTransaction("recurring_invoice_items", getIdsFromInvoiceItems(recurringInvoiceItems), ChangeType.INSERT, context);
- auditSqlDao.insertAuditFromTransaction("fixed_invoice_items", getIdsFromInvoiceItems(fixedPriceInvoiceItems), ChangeType.INSERT, context);
- auditSqlDao.insertAuditFromTransaction("invoice_payments", getIdsFromInvoicePayments(invoicePayments), ChangeType.INSERT, context);
-
+ transactional.insertAuditFromTransaction(audits, context);
}
return null;
}
});
-
- // TODO: move this inside the transaction once the bus is persistent
- InvoiceCreationNotification event;
- event = new DefaultInvoiceCreationNotification(invoice.getId(), invoice.getAccountId(),
- invoice.getBalance(), invoice.getCurrency(),
- invoice.getInvoiceDate());
- try {
- eventBus.post(event);
- } catch (Bus.EventBusException e) {
- throw new RuntimeException(e);
- }
- }
-
- private List<String> getIdsFromInvoiceItems(List<InvoiceItem> invoiceItems) {
- List<String> ids = new ArrayList<String>();
-
- for (InvoiceItem item : invoiceItems) {
- ids.add(item.getId().toString());
- }
-
- return ids;
}
- private List<String> getIdsFromInvoicePayments(List<InvoicePayment> invoicePayments) {
- List<String> ids = new ArrayList<String>();
-
- for (InvoicePayment payment : invoicePayments) {
- ids.add(payment.getId().toString());
+ private List<EntityAudit> createAudits(TableName tableName, List<Long> recordIdList) {
+ List<EntityAudit> entityAuditList = new ArrayList<EntityAudit>();
+ for (Long recordId : recordIdList) {
+ entityAuditList.add(new EntityAudit(tableName, recordId, ChangeType.INSERT));
}
- return ids;
+ return entityAuditList;
}
@Override
@@ -248,9 +216,10 @@ public class DefaultInvoiceDao implements InvoiceDao {
public Void inTransaction(InvoicePaymentSqlDao transactional, TransactionStatus status) throws Exception {
transactional.notifyOfPaymentAttempt(invoicePayment, context);
- AuditSqlDao auditSqlDao = transactional.become(AuditSqlDao.class);
String invoicePaymentId = invoicePayment.getId().toString();
- auditSqlDao.insertAuditFromTransaction("invoice_payments", invoicePaymentId, ChangeType.INSERT, context);
+ Long recordId = transactional.getRecordId(invoicePaymentId);
+ EntityAudit audit = new EntityAudit(TableName.INVOICE_PAYMENTS, recordId, ChangeType.INSERT);
+ transactional.insertAuditFromTransaction(audit, context);
return null;
}
@@ -283,12 +252,12 @@ public class DefaultInvoiceDao implements InvoiceDao {
@Override
public void addControlTag(ControlTagType controlTagType, UUID invoiceId, CallContext context) {
- tagDao.addTag(controlTagType.toString(), invoiceId, Invoice.ObjectType, context);
+ tagDao.addTag(controlTagType.toString(), invoiceId, ObjectType.INVOICE, context);
}
@Override
public void removeControlTag(ControlTagType controlTagType, UUID invoiceId, CallContext context) {
- tagDao.removeTag(controlTagType.toString(), invoiceId, Invoice.ObjectType, context);
+ tagDao.removeTag(controlTagType.toString(), invoiceId, ObjectType.INVOICE, context);
}
@Override
@@ -346,7 +315,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
}
private void getTagsWithinTransaction(final Invoice invoice, final Transmogrifier dao) {
- List<Tag> tags = tagDao.loadTagsFromTransaction(dao, invoice.getId(), Invoice.ObjectType);
+ List<Tag> tags = tagDao.loadEntitiesFromTransaction(dao, invoice.getId(), ObjectType.INVOICE);
invoice.addTags(tags);
}
@@ -359,7 +328,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
private void getFieldsWithinTransaction(final Invoice invoice, final InvoiceSqlDao invoiceSqlDao) {
CustomFieldSqlDao customFieldSqlDao = invoiceSqlDao.become(CustomFieldSqlDao.class);
String invoiceId = invoice.getId().toString();
- List<CustomField> customFields = customFieldSqlDao.load(invoiceId, Invoice.ObjectType);
+ List<CustomField> customFields = customFieldSqlDao.load(invoiceId, ObjectType.INVOICE);
invoice.setFields(customFields);
}
@@ -375,32 +344,4 @@ public class DefaultInvoiceDao implements InvoiceDao {
}
}
}
-
- private void setChargedThroughDates(final InvoiceSqlDao dao, final Collection<InvoiceItem> fixedPriceItems,
- final Collection<InvoiceItem> recurringItems, CallContext context) {
- Map<UUID, DateTime> chargeThroughDates = new HashMap<UUID, DateTime>();
- addInvoiceItemsToChargeThroughDates(chargeThroughDates, fixedPriceItems);
- addInvoiceItemsToChargeThroughDates(chargeThroughDates, recurringItems);
-
- for (UUID subscriptionId : chargeThroughDates.keySet()) {
- DateTime chargeThroughDate = chargeThroughDates.get(subscriptionId);
- log.info("Setting CTD for subscription {} to {}", subscriptionId.toString(), chargeThroughDate.toString());
- entitlementBillingApi.setChargedThroughDateFromTransaction(dao, subscriptionId, chargeThroughDate, context);
- }
- }
-
- private void addInvoiceItemsToChargeThroughDates(Map<UUID, DateTime> chargeThroughDates, Collection<InvoiceItem> items) {
- for (InvoiceItem item : items) {
- UUID subscriptionId = item.getSubscriptionId();
- DateTime endDate = item.getEndDate();
-
- if (chargeThroughDates.containsKey(subscriptionId)) {
- if (chargeThroughDates.get(subscriptionId).isBefore(endDate)) {
- chargeThroughDates.put(subscriptionId, endDate);
- }
- } else {
- chargeThroughDates.put(subscriptionId, endDate);
- }
- }
- }
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java
index f564658..d4fa5ee 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java
@@ -16,12 +16,18 @@
package com.ning.billing.invoice.dao;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.entity.EntityDao;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.util.entity.dao.EntitySqlDao;
import org.joda.time.DateTime;
import org.skife.jdbi.v2.SQLStatement;
import org.skife.jdbi.v2.StatementContext;
@@ -36,20 +42,18 @@ import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
import org.skife.jdbi.v2.tweak.ResultSetMapper;
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.math.BigDecimal;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.List;
-import java.util.UUID;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
@ExternalizedSqlViaStringTemplate3()
@RegisterMapper(FixedPriceInvoiceItemSqlDao.FixedPriceInvoiceItemMapper.class)
-public interface FixedPriceInvoiceItemSqlDao extends EntityDao<InvoiceItem> {
+public interface FixedPriceInvoiceItemSqlDao extends EntitySqlDao<InvoiceItem> {
+ @SqlQuery
+ List<Long> getRecordIds(@Bind("invoiceId") final String invoiceId);
+
@SqlQuery
List<InvoiceItem> getInvoiceItemsByInvoice(@Bind("invoiceId") final String invoiceId);
@@ -78,9 +82,10 @@ public interface FixedPriceInvoiceItemSqlDao extends EntityDao<InvoiceItem> {
return new Binder<FixedPriceInvoiceItemBinder, FixedPriceInvoiceItem>() {
public void bind(SQLStatement q, FixedPriceInvoiceItemBinder bind, FixedPriceInvoiceItem item) {
q.bind("id", item.getId().toString());
- q.bind("invoiceId", item.getInvoiceId().toString());
q.bind("accountId", item.getAccountId().toString());
- q.bind("subscriptionId", item.getSubscriptionId().toString());
+ q.bind("invoiceId", item.getInvoiceId().toString());
+ q.bind("bundleId", item.getBundleId() == null ? null : item.getBundleId().toString());
+ q.bind("subscriptionId", item.getSubscriptionId() == null ? null : item.getSubscriptionId().toString());
q.bind("planName", item.getPlanName());
q.bind("phaseName", item.getPhaseName());
q.bind("startDate", item.getStartDate().toDate());
@@ -99,18 +104,17 @@ public interface FixedPriceInvoiceItemSqlDao extends EntityDao<InvoiceItem> {
UUID id = UUID.fromString(result.getString("id"));
UUID invoiceId = UUID.fromString(result.getString("invoice_id"));
UUID accountId = UUID.fromString(result.getString("account_id"));
- UUID subscriptionId = UUID.fromString(result.getString("subscription_id"));
+ UUID bundleId = result.getString("bundle_id") == null ? null :UUID.fromString(result.getString("bundle_id"));
+ UUID subscriptionId = result.getString("subscription_id") == null ? null : UUID.fromString(result.getString("subscription_id"));
String planName = result.getString("plan_name");
String phaseName = result.getString("phase_name");
DateTime startDate = new DateTime(result.getTimestamp("start_date"));
DateTime endDate = new DateTime(result.getTimestamp("end_date"));
BigDecimal amount = result.getBigDecimal("amount");
Currency currency = Currency.valueOf(result.getString("currency"));
- String createdBy = result.getString("created_by");
- DateTime createdDate = new DateTime(result.getTimestamp("created_date"));
- return new FixedPriceInvoiceItem(id, invoiceId, accountId, subscriptionId, planName, phaseName,
- startDate, endDate, amount, currency, createdBy, createdDate);
+ return new FixedPriceInvoiceItem(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName,
+ startDate, endDate, amount, currency);
}
}
}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
index c51d65d..a11c8e0 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
@@ -28,9 +28,11 @@ import java.util.List;
import java.util.UUID;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.dao.AuditSqlDao;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.CallContextBinder;
import com.ning.billing.util.dao.MapperBase;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
import org.joda.time.DateTime;
import org.skife.jdbi.v2.SQLStatement;
import org.skife.jdbi.v2.StatementContext;
@@ -51,7 +53,10 @@ import com.ning.billing.invoice.api.InvoicePayment;
@ExternalizedSqlViaStringTemplate3
@RegisterMapper(InvoicePaymentSqlDao.InvoicePaymentMapper.class)
-public interface InvoicePaymentSqlDao extends Transactional<InvoicePaymentSqlDao>, Transmogrifier {
+public interface InvoicePaymentSqlDao extends EntitySqlDao<InvoicePayment>, Transactional<InvoicePaymentSqlDao>, AuditSqlDao, Transmogrifier {
+ @SqlQuery
+ List<Long> getRecordIds(@Bind("invoiceId") final String invoiceId);
+
@SqlQuery
public InvoicePayment getByPaymentAttemptId(@Bind("paymentAttempt") final String paymentAttemptId);
@@ -81,15 +86,13 @@ public interface InvoicePaymentSqlDao extends Transactional<InvoicePaymentSqlDao
public static class InvoicePaymentMapper extends MapperBase implements ResultSetMapper<InvoicePayment> {
@Override
public InvoicePayment map(int index, ResultSet result, StatementContext context) throws SQLException {
- final UUID id = UUID.fromString(result.getString("id"));
- final UUID paymentAttemptId = UUID.fromString(result.getString("payment_attempt_id"));
- final UUID invoiceId = UUID.fromString(result.getString("invoice_id"));
+ final UUID id = getUUID(result, "id");
+ final UUID paymentAttemptId = getUUID(result, "payment_attempt_id");
+ final UUID invoiceId = getUUID(result, "invoice_id");
final DateTime paymentAttemptDate = getDate(result, "payment_attempt_date");
final BigDecimal amount = result.getBigDecimal("amount");
final String currencyString = result.getString("currency");
final Currency currency = (currencyString == null) ? null : Currency.valueOf(currencyString);
- final String createdBy = result.getString("created_by");
- final DateTime createdDate = getDate(result, "created_date");
return new InvoicePayment() {
@Override
@@ -116,14 +119,6 @@ public interface InvoicePaymentSqlDao extends Transactional<InvoicePaymentSqlDao
public Currency getCurrency() {
return currency;
}
- @Override
- public String getCreatedBy() {
- return createdBy;
- }
- @Override
- public DateTime getCreatedDate() {
- return createdDate ;
- }
};
}
}
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 dc8e303..af611bc 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
@@ -20,9 +20,10 @@ import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.model.DefaultInvoice;
import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.UuidMapper;
+import com.ning.billing.util.dao.AuditSqlDao;
+import com.ning.billing.util.dao.UuidMapper;
import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.entity.EntityDao;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
import org.joda.time.DateTime;
import org.skife.jdbi.v2.SQLStatement;
import org.skife.jdbi.v2.StatementContext;
@@ -53,7 +54,7 @@ import java.util.UUID;
@ExternalizedSqlViaStringTemplate3()
@RegisterMapper(InvoiceSqlDao.InvoiceMapper.class)
-public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<InvoiceSqlDao>, Transmogrifier, CloseMe {
+public interface InvoiceSqlDao extends EntitySqlDao<Invoice>, AuditSqlDao, Transactional<InvoiceSqlDao>, Transmogrifier, CloseMe {
@Override
@SqlUpdate
void create(@InvoiceBinder Invoice invoice, @CallContextBinder final CallContext context);
@@ -117,10 +118,8 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
DateTime targetDate = new DateTime(result.getTimestamp("target_date"));
Currency currency = Currency.valueOf(result.getString("currency"));
boolean isMigrationInvoice = result.getBoolean("migrated");
- String createdBy = result.getString("created_by");
- DateTime createdDate = new DateTime(result.getTimestamp("created_date"));
- return new DefaultInvoice(id, accountId, invoiceNumber, invoiceDate, targetDate, currency, isMigrationInvoice, createdBy, createdDate);
+ return new DefaultInvoice(id, accountId, invoiceNumber, invoiceDate, targetDate, currency, isMigrationInvoice);
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.java
index 29dfb9e..4daa560 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.java
@@ -16,12 +16,18 @@
package com.ning.billing.invoice.dao;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.model.RecurringInvoiceItem;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.entity.EntityDao;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.util.dao.MapperBase;
import org.joda.time.DateTime;
import org.skife.jdbi.v2.SQLStatement;
import org.skife.jdbi.v2.StatementContext;
@@ -36,20 +42,19 @@ import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
import org.skife.jdbi.v2.tweak.ResultSetMapper;
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.math.BigDecimal;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.List;
-import java.util.UUID;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
@ExternalizedSqlViaStringTemplate3()
@RegisterMapper(RecurringInvoiceItemSqlDao.RecurringInvoiceItemMapper.class)
-public interface RecurringInvoiceItemSqlDao extends EntityDao<InvoiceItem> {
+public interface RecurringInvoiceItemSqlDao extends EntitySqlDao<InvoiceItem> {
+ @SqlQuery
+ List<Long> getRecordIds(@Bind("invoiceId") final String invoiceId);
+
@SqlQuery
List<InvoiceItem> getInvoiceItemsByInvoice(@Bind("invoiceId") final String invoiceId);
@@ -79,7 +84,8 @@ public interface RecurringInvoiceItemSqlDao extends EntityDao<InvoiceItem> {
q.bind("id", item.getId().toString());
q.bind("invoiceId", item.getInvoiceId().toString());
q.bind("accountId", item.getAccountId().toString());
- q.bind("subscriptionId", item.getSubscriptionId().toString());
+ q.bind("bundleId", item.getBundleId() == null ? null : item.getBundleId().toString());
+ q.bind("subscriptionId", item.getSubscriptionId() == null ? null : item.getSubscriptionId().toString());
q.bind("planName", item.getPlanName());
q.bind("phaseName", item.getPhaseName());
q.bind("startDate", item.getStartDate().toDate());
@@ -94,27 +100,26 @@ public interface RecurringInvoiceItemSqlDao extends EntityDao<InvoiceItem> {
}
}
- public static class RecurringInvoiceItemMapper implements ResultSetMapper<InvoiceItem> {
+ public static class RecurringInvoiceItemMapper extends MapperBase implements ResultSetMapper<InvoiceItem> {
@Override
public InvoiceItem map(int index, ResultSet result, StatementContext context) throws SQLException {
- UUID id = UUID.fromString(result.getString("id"));
- UUID invoiceId = UUID.fromString(result.getString("invoice_id"));
- UUID accountId = UUID.fromString(result.getString("account_id"));
- UUID subscriptionId = UUID.fromString(result.getString("subscription_id"));
+ UUID id = getUUID(result, "id");
+ UUID invoiceId = getUUID(result, "invoice_id");
+ UUID accountId = getUUID(result, "account_id");
+ UUID subscriptionId = getUUID(result, "subscription_id");
+ UUID bundleId = getUUID(result, "bundle_id");
String planName = result.getString("plan_name");
String phaseName = result.getString("phase_name");
- DateTime startDate = new DateTime(result.getTimestamp("start_date"));
- DateTime endDate = new DateTime(result.getTimestamp("end_date"));
+ DateTime startDate = getDate(result, "start_date");
+ DateTime endDate = getDate(result, "end_date");
BigDecimal amount = result.getBigDecimal("amount");
BigDecimal rate = result.getBigDecimal("rate");
Currency currency = Currency.valueOf(result.getString("currency"));
- String reversedItemString = result.getString("reversed_item_id");
- UUID reversedItemId = (reversedItemString == null) ? null : UUID.fromString(reversedItemString);
- String createdBy = result.getString("created_by");
- DateTime createdDate = new DateTime(result.getTimestamp("created_date"));
+ UUID reversedItemId = getUUID(result, "reversed_item_id");
+
+ return new RecurringInvoiceItem(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate,
+ amount, rate, currency, reversedItemId);
- return new RecurringInvoiceItem(id, invoiceId, accountId, subscriptionId, planName, phaseName, startDate, endDate,
- amount, rate, currency, reversedItemId, createdBy, createdDate);
}
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
index 32c625c..32d444d 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
@@ -17,11 +17,12 @@
package com.ning.billing.invoice;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.SortedSet;
import java.util.UUID;
-import com.ning.billing.util.callcontext.CallContext;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,51 +30,73 @@ 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.AccountApiException;
import com.ning.billing.account.api.AccountUserApi;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.entitlement.api.billing.BillingEvent;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.invoice.api.InvoiceNotifier;
import com.ning.billing.invoice.api.InvoiceApiException;
import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.user.DefaultEmptyInvoiceEvent;
+import com.ning.billing.invoice.api.user.DefaultInvoiceCreationEvent;
import com.ning.billing.invoice.dao.InvoiceDao;
import com.ning.billing.invoice.model.BillingEventSet;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.junction.api.BillingApi;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+import com.ning.billing.util.bus.BusEvent;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.globallocker.GlobalLock;
import com.ning.billing.util.globallocker.GlobalLocker;
-import com.ning.billing.util.globallocker.LockFailedException;
import com.ning.billing.util.globallocker.GlobalLocker.LockerService;
+import com.ning.billing.util.globallocker.LockFailedException;
public class InvoiceDispatcher {
- private final static Logger log = LoggerFactory.getLogger(InvoiceDispatcher.class);
+ private final static Logger log = LoggerFactory.getLogger(InvoiceDispatcher.class);
private final static int NB_LOCK_TRY = 5;
private final InvoiceGenerator generator;
- private final EntitlementBillingApi entitlementBillingApi;
+ private final BillingApi billingApi;
private final AccountUserApi accountUserApi;
private final InvoiceDao invoiceDao;
+ private final InvoiceNotifier invoiceNotifier;
private final GlobalLocker locker;
+ private final Bus eventBus;
+ private final Clock clock;
private final boolean VERBOSE_OUTPUT;
@Inject
public InvoiceDispatcher(final InvoiceGenerator generator, final AccountUserApi accountUserApi,
- final EntitlementBillingApi entitlementBillingApi,
- final InvoiceDao invoiceDao,
- final GlobalLocker locker) {
+ final BillingApi billingApi,
+ final InvoiceDao invoiceDao,
+ final InvoiceNotifier invoiceNotifier,
+ final GlobalLocker locker,
+ final Bus eventBus,
+ final Clock clock) {
this.generator = generator;
- this.entitlementBillingApi = entitlementBillingApi;
+ this.billingApi = billingApi;
this.accountUserApi = accountUserApi;
this.invoiceDao = invoiceDao;
+ this.invoiceNotifier = invoiceNotifier;
this.locker = locker;
+ this.eventBus = eventBus;
+ this.clock = clock;
String verboseOutputValue = System.getProperty("VERBOSE_OUTPUT");
VERBOSE_OUTPUT = (verboseOutputValue != null) && Boolean.parseBoolean(verboseOutputValue);
}
- public void processSubscription(final SubscriptionTransition transition,
- final CallContext context) throws InvoiceApiException {
+ public void processSubscription(final SubscriptionEvent transition,
+ final CallContext context) throws InvoiceApiException {
UUID subscriptionId = transition.getSubscriptionId();
DateTime targetDate = transition.getEffectiveTransitionTime();
log.info("Got subscription transition from InvoiceListener. id: " + subscriptionId.toString() + "; targetDate: " + targetDate.toString());
@@ -82,25 +105,24 @@ public class InvoiceDispatcher {
}
public void processSubscription(final UUID subscriptionId, final DateTime targetDate,
- final CallContext context) throws InvoiceApiException {
- if (subscriptionId == null) {
- log.error("Failed handling entitlement change.", new InvoiceApiException(ErrorCode.INVOICE_INVALID_TRANSITION));
- return;
- }
-
- UUID accountId = entitlementBillingApi.getAccountIdFromSubscriptionId(subscriptionId);
- if (accountId == null) {
+ final CallContext context) throws InvoiceApiException {
+ try {
+ if (subscriptionId == null) {
+ log.error("Failed handling entitlement change.", new InvoiceApiException(ErrorCode.INVOICE_INVALID_TRANSITION));
+ return;
+ }
+ UUID accountId = billingApi.getAccountIdFromSubscriptionId(subscriptionId);
+ processAccount(accountId, targetDate, false, context);
+ } catch (EntitlementBillingApiException e) {
log.error("Failed handling entitlement change.",
new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, subscriptionId.toString()));
- return;
}
-
- processAccount(accountId, targetDate, false, context);
+ return;
}
-
+
public Invoice processAccount(final UUID accountId, final DateTime targetDate,
- final boolean dryRun, final CallContext context) throws InvoiceApiException {
- GlobalLock lock = null;
+ final boolean dryRun, final CallContext context) throws InvoiceApiException {
+ GlobalLock lock = null;
try {
lock = locker.lockWithNumberOfTries(LockerService.INVOICE, accountId.toString(), NB_LOCK_TRY);
@@ -118,46 +140,103 @@ public class InvoiceDispatcher {
return null;
}
+
private Invoice processAccountWithLock(final UUID accountId, final DateTime targetDate,
- final boolean dryRun, final CallContext context) throws InvoiceApiException {
+ final boolean dryRun, final CallContext context) throws InvoiceApiException {
+ try {
+ Account account = accountUserApi.getAccountById(accountId);
+ SortedSet<BillingEvent> events = billingApi.getBillingEventsForAccountAndUpdateAccountBCD(accountId);
+ BillingEventSet billingEvents = new BillingEventSet(events);
- Account account = accountUserApi.getAccountById(accountId);
- if (account == null) {
- log.error("Failed handling entitlement change.",
- new InvoiceApiException(ErrorCode.INVOICE_ACCOUNT_ID_INVALID, accountId.toString()));
- return null;
- }
+ Currency targetCurrency = account.getCurrency();
- SortedSet<BillingEvent> events = entitlementBillingApi.getBillingEventsForAccount(accountId);
- BillingEventSet billingEvents = new BillingEventSet(events);
+ List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId);
+ Invoice invoice = generator.generateInvoice(accountId, billingEvents, invoices, targetDate, targetCurrency);
- Currency targetCurrency = account.getCurrency();
+ if (invoice == null) {
+ log.info("Generated null invoice.");
+ outputDebugData(events, invoices);
+ if (!dryRun) {
+ BusEvent event = new DefaultEmptyInvoiceEvent(accountId, clock.getUTCNow(), context.getUserToken());
+ postEvent(event, accountId);
+ }
+ } else {
+ log.info("Generated invoice {} with {} items.", invoice.getId().toString(), invoice.getNumberOfItems());
+ if (VERBOSE_OUTPUT) {
+ log.info("New items");
+ for (InvoiceItem item : invoice.getInvoiceItems()) {
+ log.info(item.toString());
+ }
+ }
+ outputDebugData(events, invoices);
+ if (!dryRun) {
+ invoiceDao.create(invoice, context);
- List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId);
- Invoice invoice = generator.generateInvoice(accountId, billingEvents, invoices, targetDate, targetCurrency);
+ List<InvoiceItem> fixedPriceInvoiceItems = invoice.getInvoiceItems(FixedPriceInvoiceItem.class);
+ List<InvoiceItem> recurringInvoiceItems = invoice.getInvoiceItems(RecurringInvoiceItem.class);
+ setChargedThroughDates(fixedPriceInvoiceItems, recurringInvoiceItems, context);
- if (invoice == null) {
- log.info("Generated null invoice.");
- outputDebugData(events, invoices);
- } else {
- log.info("Generated invoice {} with {} items.", invoice.getId().toString(), invoice.getNumberOfItems());
+ final InvoiceCreationEvent event = new DefaultInvoiceCreationEvent(invoice.getId(), invoice.getAccountId(),
+ invoice.getBalance(), invoice.getCurrency(),
+ invoice.getInvoiceDate(),
+ context.getUserToken());
- if (VERBOSE_OUTPUT) {
- log.info("New items");
- for (InvoiceItem item : invoice.getInvoiceItems()) {
- log.info(item.toString());
+ postEvent(event, accountId);
}
}
- outputDebugData(events, invoices);
- if (invoice.getNumberOfItems() > 0 && !dryRun) {
- invoiceDao.create(invoice, context);
+ if (account.isNotifiedForInvoices()) {
+ invoiceNotifier.notify(account, invoice);
}
+
+ return invoice;
+ } catch(AccountApiException e) {
+ log.error("Failed handling entitlement change.",e);
+ return null;
}
-
- return invoice;
}
+ private void setChargedThroughDates(final Collection<InvoiceItem> fixedPriceItems,
+ final Collection<InvoiceItem> recurringItems, CallContext context) {
+
+ Map<UUID, DateTime> chargeThroughDates = new HashMap<UUID, DateTime>();
+ addInvoiceItemsToChargeThroughDates(chargeThroughDates, fixedPriceItems);
+ addInvoiceItemsToChargeThroughDates(chargeThroughDates, recurringItems);
+
+ for (UUID subscriptionId : chargeThroughDates.keySet()) {
+ if(subscriptionId != null) {
+ DateTime chargeThroughDate = chargeThroughDates.get(subscriptionId);
+ log.info("Setting CTD for subscription {} to {}", subscriptionId.toString(), chargeThroughDate.toString());
+ billingApi.setChargedThroughDate(subscriptionId, chargeThroughDate, context);
+ }
+ }
+ }
+
+ private void postEvent(final BusEvent event, final UUID accountId) {
+ try {
+ eventBus.post(event);
+ } catch (EventBusException e){
+ log.error(String.format("Failed to post event {} for account {} ", event.getBusEventType(), accountId), e);
+ }
+ }
+
+
+ private void addInvoiceItemsToChargeThroughDates(Map<UUID, DateTime> chargeThroughDates, Collection<InvoiceItem> items) {
+ for (InvoiceItem item : items) {
+ UUID subscriptionId = item.getSubscriptionId();
+ DateTime endDate = item.getEndDate();
+
+ if (chargeThroughDates.containsKey(subscriptionId)) {
+ if (chargeThroughDates.get(subscriptionId).isBefore(endDate)) {
+ chargeThroughDates.put(subscriptionId, endDate);
+ }
+ } else {
+ chargeThroughDates.put(subscriptionId, endDate);
+ }
+ }
+ }
+
+
private void outputDebugData(Collection<BillingEvent> events, Collection<Invoice> invoices) {
if (VERBOSE_OUTPUT) {
log.info("Events");
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
index ff23965..66f423e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
@@ -28,7 +28,9 @@ import org.slf4j.LoggerFactory;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.timeline.RepairEntitlementEvent;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
import com.ning.billing.invoice.api.InvoiceApiException;
public class InvoiceListener {
@@ -43,9 +45,26 @@ public class InvoiceListener {
}
@Subscribe
- public void handleSubscriptionTransition(final SubscriptionTransition transition) {
+ public void handleRepairEntitlementEvent(final RepairEntitlementEvent repairEvent) {
try {
- CallContext context = factory.createCallContext("Transition", CallOrigin.INTERNAL, UserType.SYSTEM);
+ CallContext context = factory.createCallContext("RepairBundle", CallOrigin.INTERNAL, UserType.SYSTEM, repairEvent.getUserToken());
+ dispatcher.processAccount(repairEvent.getAccountId(), repairEvent.getEffectiveDate(), false, context);
+ } catch (InvoiceApiException e) {
+ log.error(e.getMessage());
+ }
+ }
+
+ @Subscribe
+ public void handleSubscriptionTransition(final SubscriptionEvent transition) {
+ try {
+ // Skip future uncancel event
+ // Skip events which are marked as not being the last one
+ if (transition.getTransitionType() == SubscriptionTransitionType.UNCANCEL
+ || transition.getRemainingEventsForUserOperation() > 0) {
+ return;
+ }
+
+ CallContext context = factory.createCallContext("Transition", CallOrigin.INTERNAL, UserType.SYSTEM, transition.getUserToken());
dispatcher.processSubscription(transition, context);
} catch (InvoiceApiException e) {
log.error(e.getMessage());
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java b/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java
index da95559..8cf1e1e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java
@@ -28,7 +28,10 @@ public class BillingEventSet extends ArrayList<BillingEvent> {
public BillingEventSet(Collection<BillingEvent> events) {
super();
- addAll(events);
+ if(events != null) {
+ addAll(events);
+
+ }
}
public boolean isLast(final BillingEvent event) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/CreditInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/CreditInvoiceItem.java
new file mode 100644
index 0000000..ac800a7
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/CreditInvoiceItem.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.model;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+public class CreditInvoiceItem extends InvoiceItemBase {
+ public CreditInvoiceItem(UUID invoiceId, UUID accountId, DateTime date, BigDecimal amount, Currency currency) {
+ this(UUID.randomUUID(), invoiceId, accountId, date, amount, currency);
+ }
+
+ public CreditInvoiceItem(UUID id, UUID invoiceId, UUID accountId, DateTime date, BigDecimal amount, Currency currency) {
+ super(id, invoiceId, accountId, null, null, null, null, date, date, amount, currency);
+ }
+
+ @Override
+ public InvoiceItem asReversingItem() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Credit";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ CreditInvoiceItem that = (CreditInvoiceItem) o;
+
+ if (accountId.compareTo(that.accountId) != 0) return false;
+ if (amount.compareTo(that.amount) != 0) return false;
+ if (currency != that.currency) return false;
+ if (startDate.compareTo(that.startDate) != 0) return false;
+ if (endDate.compareTo(that.endDate) != 0) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = accountId.hashCode();
+ result = 31 * result + startDate.hashCode();
+ result = 31 * result + endDate.hashCode();
+ result = 31 * result + amount.hashCode();
+ result = 31 * result + currency.hashCode();
+ return result;
+ }
+
+ @Override
+ public int compareTo(InvoiceItem item) {
+ if (!(item instanceof CreditInvoiceItem)) {
+ return 1;
+ }
+
+ CreditInvoiceItem that = (CreditInvoiceItem) item;
+ return id.compareTo(that.getId());
+ }
+}
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 1467415..8f38437 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
@@ -25,6 +25,7 @@ import javax.annotation.Nullable;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.dao.ObjectType;
import com.ning.billing.util.entity.ExtendedEntityBase;
import org.joda.time.DateTime;
@@ -45,13 +46,13 @@ public class DefaultInvoice extends ExtendedEntityBase implements Invoice {
// used to create a new invoice
public DefaultInvoice(UUID accountId, DateTime invoiceDate, DateTime targetDate, Currency currency) {
- this(UUID.randomUUID(), accountId, null, invoiceDate, targetDate, currency, false, null, null);
+ this(UUID.randomUUID(), accountId, null, invoiceDate, targetDate, currency, false);
}
// used to hydrate invoice from persistence layer
public DefaultInvoice(UUID invoiceId, UUID accountId, @Nullable Integer invoiceNumber, DateTime invoiceDate,
- DateTime targetDate, Currency currency, boolean isMigrationInvoice, @Nullable String createdBy, @Nullable DateTime createdDate) {
- super(invoiceId, createdBy, createdDate);
+ DateTime targetDate, Currency currency, boolean isMigrationInvoice) {
+ super(invoiceId);
this.accountId = accountId;
this.invoiceNumber = invoiceNumber;
this.invoiceDate = invoiceDate;
@@ -112,11 +113,6 @@ public class DefaultInvoice extends ExtendedEntityBase implements Invoice {
}
@Override
- public UUID getId() {
- return id;
- }
-
- @Override
public UUID getAccountId() {
return accountId;
}
@@ -196,11 +192,7 @@ public class DefaultInvoice extends ExtendedEntityBase implements Invoice {
}
DateTime lastPaymentAttempt = getLastPaymentAttempt();
- if (lastPaymentAttempt == null) {
- return true;
- }
-
- return !lastPaymentAttempt.plusDays(numberOfDays).isAfter(targetDate);
+ return (lastPaymentAttempt == null) || lastPaymentAttempt.plusDays(numberOfDays).isAfter(targetDate);
}
@Override
@@ -209,8 +201,8 @@ public class DefaultInvoice extends ExtendedEntityBase implements Invoice {
}
@Override
- public String getObjectName() {
- return Invoice.ObjectType;
+ public ObjectType getObjectType() {
+ return ObjectType.INVOICE;
}
@Override
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 389010c..c24215c 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,6 +16,20 @@
package com.ning.billing.invoice.model;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.joda.time.Months;
+
import com.google.inject.Inject;
import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.BillingPeriod;
@@ -28,16 +42,6 @@ import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceApiException;
import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.util.clock.Clock;
-import org.joda.time.DateTime;
-import org.joda.time.Months;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.UUID;
-import javax.annotation.Nullable;
public class DefaultInvoiceGenerator implements InvoiceGenerator {
private static final int ROUNDING_MODE = InvoicingConfiguration.getRoundingMode();
@@ -57,9 +61,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
*/
@Override
public Invoice generateInvoice(final UUID accountId, @Nullable final BillingEventSet events,
- @Nullable final List<Invoice> existingInvoices,
- DateTime targetDate,
- final Currency targetCurrency) throws InvoiceApiException {
+ @Nullable final List<Invoice> existingInvoices,
+ DateTime targetDate,
+ final Currency targetCurrency) throws InvoiceApiException {
if ((events == null) || (events.size() == 0)) {
return null;
}
@@ -89,18 +93,92 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
for (InvoiceItem existingItem : existingItems) {
if (existingItem instanceof RecurringInvoiceItem) {
RecurringInvoiceItem recurringItem = (RecurringInvoiceItem) existingItem;
- proposedItems.add(recurringItem.asCredit());
+ proposedItems.add(recurringItem.asReversingItem());
}
}
+ //addCreditItems(accountId, proposedItems, existingInvoices, targetCurrency);
+
if (proposedItems == null || proposedItems.size() == 0) {
return null;
} else {
invoice.addInvoiceItems(proposedItems);
+
return invoice;
}
}
+ /*
+ * ensures that the balance of all invoices are zero or positive, adding an adjusting credit item if needed
+ */
+ private void addCreditItems(UUID accountId, List<InvoiceItem> invoiceItems, List<Invoice> invoices, Currency currency) {
+ Map<UUID, BigDecimal> invoiceBalances = new HashMap<UUID, BigDecimal>();
+
+ updateInvoiceBalance(invoiceItems, invoiceBalances);
+
+ // add all existing items and payments
+ if (invoices != null) {
+ for (Invoice invoice : invoices) {
+ updateInvoiceBalance(invoice.getInvoiceItems(), invoiceBalances);
+ }
+
+ for (Invoice invoice : invoices) {
+ UUID invoiceId = invoice.getId();
+ invoiceBalances.put(invoiceId, invoiceBalances.get(invoiceId).subtract(invoice.getAmountPaid()));
+ }
+ }
+
+ BigDecimal creditTotal = BigDecimal.ZERO;
+
+ for (UUID invoiceId : invoiceBalances.keySet()) {
+ BigDecimal balance = invoiceBalances.get(invoiceId);
+ if (balance.compareTo(BigDecimal.ZERO) < 0) {
+ creditTotal = creditTotal.add(balance.negate());
+ invoiceItems.add(new CreditInvoiceItem(invoiceId, accountId, clock.getUTCNow(), balance, currency));
+ }
+ }
+
+ if (creditTotal.compareTo(BigDecimal.ZERO) != 0) {
+ // create a single credit item to cover all credits
+ //invoiceItems.add(new CreditInvoiceItem());
+ }
+ }
+
+ private void updateInvoiceBalance(List<InvoiceItem> items, Map<UUID, BigDecimal> invoiceBalances) {
+ for (InvoiceItem item : items) {
+ UUID invoiceId = item.getInvoiceId();
+
+ if (!invoiceBalances.containsKey(invoiceId)) {
+ invoiceBalances.put(invoiceId, BigDecimal.ZERO);
+ }
+
+ invoiceBalances.put(invoiceId, invoiceBalances.get(invoiceId).add(item.getAmount()));
+ }
+ }
+
+ @Override
+ public void distributeItems(List<Invoice> invoices) {
+ Map<UUID, Invoice> invoiceMap = new HashMap<UUID, Invoice>();
+
+ for (Invoice invoice : invoices) {
+ invoiceMap.put(invoice.getId(), invoice);
+ }
+
+ for (final Invoice invoice: invoices) {
+ Iterator<InvoiceItem> itemIterator = invoice.getInvoiceItems().iterator();
+ final UUID invoiceId = invoice.getId();
+
+ while (itemIterator.hasNext()) {
+ InvoiceItem item = itemIterator.next();
+
+ if (!item.getInvoiceId().equals(invoiceId)) {
+ invoiceMap.get(item.getInvoiceId()).addInvoiceItem(item);
+ itemIterator.remove();
+ }
+ }
+ }
+ }
+
private void validateTargetDate(DateTime targetDate) throws InvoiceApiException {
int maximumNumberOfMonths = config.getNumberOfMonthsInFuture();
@@ -211,7 +289,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
if (rate != null) {
BigDecimal amount = itemDatum.getNumberOfCycles().multiply(rate).setScale(NUMBER_OF_DECIMALS, ROUNDING_MODE);
- RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId, accountId,
+ RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId,
+ accountId,
+ thisEvent.getSubscription().getBundleId(),
thisEvent.getSubscription().getId(),
thisEvent.getPlan().getName(),
thisEvent.getPlanPhase().getName(),
@@ -246,7 +326,8 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
Duration duration = thisEvent.getPlanPhase().getDuration();
DateTime endDate = duration.addToDateTime(thisEvent.getEffectiveDate());
- return new FixedPriceInvoiceItem(invoiceId, accountId, thisEvent.getSubscription().getId(),
+ return new FixedPriceInvoiceItem(invoiceId, accountId, thisEvent.getSubscription().getBundleId(),
+ thisEvent.getSubscription().getId(),
thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(),
thisEvent.getEffectiveDate(), endDate, fixedPrice, currency);
} else {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java
index 22eb5e6..ba02039 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java
@@ -33,18 +33,17 @@ public class DefaultInvoicePayment extends EntityBase implements InvoicePayment
private final Currency currency;
public DefaultInvoicePayment(final UUID paymentAttemptId, final UUID invoiceId, final DateTime paymentDate) {
- this(UUID.randomUUID(), paymentAttemptId, invoiceId, paymentDate, null, null, null, null);
+ this(UUID.randomUUID(), paymentAttemptId, invoiceId, paymentDate, null, null);
}
public DefaultInvoicePayment(final UUID paymentAttemptId, final UUID invoiceId, final DateTime paymentDate,
final BigDecimal amount, final Currency currency) {
- this(UUID.randomUUID(), paymentAttemptId, invoiceId, paymentDate, amount, currency, null, null);
+ this(UUID.randomUUID(), paymentAttemptId, invoiceId, paymentDate, amount, currency);
}
public DefaultInvoicePayment(final UUID id, final UUID paymentAttemptId, final UUID invoiceId, final DateTime paymentDate,
- @Nullable final BigDecimal amount, @Nullable final Currency currency,
- @Nullable final String createdBy, @Nullable final DateTime createdDate) {
- super(id, createdBy, createdDate);
+ @Nullable final BigDecimal amount, @Nullable final Currency currency) {
+ super(id);
this.paymentAttemptId = paymentAttemptId;
this.amount = amount;
this.invoiceId = invoiceId;
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
index 91e49fa..b5a79ab 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
@@ -16,27 +16,28 @@
package com.ning.billing.invoice.model;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.InvoiceItem;
-import org.joda.time.DateTime;
-
import java.math.BigDecimal;
import java.util.UUID;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+
public class FixedPriceInvoiceItem extends InvoiceItemBase {
- public FixedPriceInvoiceItem(UUID invoiceId, UUID accountId, UUID subscriptionId, String planName, String phaseName,
+
+ public FixedPriceInvoiceItem(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
- super(invoiceId, accountId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
+ super(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
}
- public FixedPriceInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID subscriptionId, String planName, String phaseName,
- DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency,
- String createdBy, DateTime createdDate) {
- super(id, invoiceId, accountId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, createdBy, createdDate);
+ public FixedPriceInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+ DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
+ super(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
}
@Override
- public InvoiceItem asCredit() {
+ public InvoiceItem asReversingItem() {
throw new UnsupportedOperationException();
}
@@ -49,6 +50,7 @@ public class FixedPriceInvoiceItem extends InvoiceItemBase {
public int hashCode() {
int result = accountId.hashCode();
result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
+ result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
result = 31 * result + (planName != null ? planName.hashCode() : 0);
result = 31 * result + (phaseName != null ? phaseName.hashCode() : 0);
result = 31 * result + (startDate != null ? startDate.hashCode() : 0);
@@ -66,12 +68,17 @@ public class FixedPriceInvoiceItem extends InvoiceItemBase {
FixedPriceInvoiceItem that = (FixedPriceInvoiceItem) item;
int compareAccounts = getAccountId().compareTo(that.getAccountId());
- if (compareAccounts == 0) {
- int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
- if (compareSubscriptions == 0) {
- return getStartDate().compareTo(that.getStartDate());
+ if (compareAccounts == 0 && bundleId != null) {
+ int compareBundles = getBundleId().compareTo(that.getBundleId());
+ if (compareBundles == 0 && subscriptionId != null) {
+ int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
+ if (compareSubscriptions == 0) {
+ return getStartDate().compareTo(that.getStartDate());
+ } else {
+ return compareSubscriptions;
+ }
} else {
- return compareSubscriptions;
+ return compareBundles;
}
} else {
return compareAccounts;
@@ -84,7 +91,8 @@ public class FixedPriceInvoiceItem extends InvoiceItemBase {
sb.append("InvoiceItem = {").append("id = ").append(id.toString()).append(", ");
sb.append("invoiceId = ").append(invoiceId.toString()).append(", ");
sb.append("accountId = ").append(accountId.toString()).append(", ");
- sb.append("subscriptionId = ").append(subscriptionId.toString()).append(", ");
+ sb.append("subscriptionId = ").append(subscriptionId == null ? null : subscriptionId.toString()).append(", ");
+ sb.append("bundleId = ").append(bundleId == null ? null : bundleId.toString()).append(", ");
sb.append("planName = ").append(planName).append(", ");
sb.append("phaseName = ").append(phaseName).append(", ");
sb.append("startDate = ").append(startDate.toString()).append(", ");
@@ -110,6 +118,8 @@ public class FixedPriceInvoiceItem extends InvoiceItemBase {
if (accountId.compareTo(that.accountId) != 0) return false;
if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null)
return false;
+ if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null)
+ return false;
if (amount != null ? amount.compareTo(that.amount) != 0 : that.amount != null) return false;
if (currency != that.currency) return false;
if (startDate != null ? startDate.compareTo(that.startDate) != 0 : that.startDate != null) return false;
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
index 01ebf34..f903087 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
@@ -19,6 +19,7 @@ package com.ning.billing.invoice.model;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceItem;
import org.joda.time.DateTime;
import javax.annotation.Nullable;
@@ -26,5 +27,8 @@ import java.util.List;
import java.util.UUID;
public interface InvoiceGenerator {
- public Invoice generateInvoice(UUID accountId, @Nullable BillingEventSet events, @Nullable List<Invoice> existingInvoices, DateTime targetDate, Currency targetCurrency) throws InvoiceApiException;
+ public Invoice generateInvoice(UUID accountId, @Nullable BillingEventSet events, @Nullable List<Invoice> existingInvoices,
+ DateTime targetDate, Currency targetCurrency) throws InvoiceApiException;
+
+ public void distributeItems(List<Invoice> invoices);
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java
index 2f16468..5ccaa8c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java
@@ -29,6 +29,7 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem
protected final UUID invoiceId;
protected final UUID accountId;
protected final UUID subscriptionId;
+ protected final UUID bundleId;
protected final String planName;
protected final String phaseName;
protected final DateTime startDate;
@@ -36,19 +37,20 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem
protected final BigDecimal amount;
protected final Currency currency;
- public InvoiceItemBase(UUID invoiceId, UUID accountId, @Nullable UUID subscriptionId, String planName, String phaseName,
- DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
- this(UUID.randomUUID(), invoiceId, accountId, subscriptionId, planName, phaseName,
- startDate, endDate, amount, currency, null, null);
+ public InvoiceItemBase(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+ DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
+ this(UUID.randomUUID(), invoiceId, accountId, bundleId, subscriptionId, planName, phaseName,
+ startDate, endDate, amount, currency);
}
- public InvoiceItemBase(UUID id, UUID invoiceId, UUID accountId, @Nullable UUID subscriptionId, String planName, String phaseName,
- DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency,
- @Nullable String createdBy, @Nullable DateTime createdDate) {
- super(id, createdBy, createdDate);
+ public InvoiceItemBase(UUID id, UUID invoiceId, UUID accountId, @Nullable UUID bundleId,
+ @Nullable UUID subscriptionId, String planName, String phaseName,
+ DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
+ super(id);
this.invoiceId = invoiceId;
this.accountId = accountId;
this.subscriptionId = subscriptionId;
+ this.bundleId = bundleId;
this.planName = planName;
this.phaseName = phaseName;
this.startDate = startDate;
@@ -57,21 +59,16 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem
this.currency = currency;
}
- public DateTime getCreatedDate() {
- return createdDate;
- }
-
- @Override
- public UUID getId() {
- return id;
- }
-
@Override
public UUID getInvoiceId() {
return invoiceId;
}
@Override
+ public UUID getBundleId() {
+ return bundleId;
+ }
+
public UUID getAccountId() {
return accountId;
}
@@ -112,7 +109,7 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem
}
@Override
- public abstract InvoiceItem asCredit();
+ public abstract InvoiceItem asReversingItem();
@Override
public abstract String getDescription();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/MigrationInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/MigrationInvoiceItem.java
index 01c1296..ec0962b 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/MigrationInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/MigrationInvoiceItem.java
@@ -25,10 +25,9 @@ import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.MigrationPlan;
public class MigrationInvoiceItem extends FixedPriceInvoiceItem {
- private final static UUID MIGRATION_SUBSCRIPTION_ID = UUID.fromString("ed25f954-3aa2-4422-943b-c3037ad7257c"); //new UUID(0L,0L);
public MigrationInvoiceItem(UUID invoiceId, UUID accountId, DateTime startDate, BigDecimal amount, Currency currency) {
- super(invoiceId, accountId, MIGRATION_SUBSCRIPTION_ID, MigrationPlan.MIGRATION_PLAN_NAME, MigrationPlan.MIGRATION_PLAN_PHASE_NAME,
+ super(invoiceId, accountId, null, null, MigrationPlan.MIGRATION_PLAN_NAME, MigrationPlan.MIGRATION_PLAN_PHASE_NAME,
startDate, startDate, amount, currency);
}
}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
index f63599d..bc813fc 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
@@ -27,51 +27,50 @@ public class RecurringInvoiceItem extends InvoiceItemBase {
private final BigDecimal rate;
private final UUID reversedItemId;
- public RecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID subscriptionId, String planName, String phaseName,
+ public RecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
DateTime startDate, DateTime endDate,
BigDecimal amount, BigDecimal rate,
- Currency currency) {
- super(invoiceId, accountId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
+ Currency currency) {
+ super(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
this.rate = rate;
this.reversedItemId = null;
}
- public RecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID subscriptionId, String planName, String phaseName,
+ public RecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
DateTime startDate, DateTime endDate,
BigDecimal amount, BigDecimal rate,
Currency currency, UUID reversedItemId) {
- super(invoiceId, accountId, subscriptionId, planName, phaseName, startDate, endDate,
+ super(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate,
amount, currency);
this.rate = rate;
this.reversedItemId = reversedItemId;
}
- public RecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID subscriptionId, String planName, String phaseName,
+ public RecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId,
+ String planName, String phaseName,
DateTime startDate, DateTime endDate,
BigDecimal amount, BigDecimal rate,
- Currency currency,
- String createdBy, DateTime createdDate) {
- super(id, invoiceId, accountId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, createdBy, createdDate);
-
+ Currency currency) {
+ super(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
this.rate = rate;
this.reversedItemId = null;
}
- public RecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID subscriptionId, String planName, String phaseName,
+ public RecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId,
+ String planName, String phaseName,
DateTime startDate, DateTime endDate,
BigDecimal amount, BigDecimal rate,
- Currency currency, UUID reversedItemId,
- String createdBy, DateTime createdDate) {
- super(id, invoiceId, accountId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, createdBy, createdDate);
-
+ Currency currency, UUID reversedItemId) {
+ super(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
this.rate = rate;
this.reversedItemId = reversedItemId;
}
@Override
- public InvoiceItem asCredit() {
+ public InvoiceItem asReversingItem() {
BigDecimal amountNegated = amount == null ? null : amount.negate();
- return new RecurringInvoiceItem(invoiceId, accountId, subscriptionId, planName, phaseName, startDate, endDate,
+
+ return new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate,
amountNegated, rate, currency, id);
}
@@ -103,17 +102,23 @@ public class RecurringInvoiceItem extends InvoiceItemBase {
RecurringInvoiceItem that = (RecurringInvoiceItem) item;
int compareAccounts = getAccountId().compareTo(that.getAccountId());
- if (compareAccounts == 0) {
- int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
- if (compareSubscriptions == 0) {
- int compareStartDates = getStartDate().compareTo(that.getStartDate());
- if (compareStartDates == 0) {
- return getEndDate().compareTo(that.getEndDate());
+ if (compareAccounts == 0 && bundleId != null) {
+ int compareBundles = getBundleId().compareTo(that.getBundleId());
+ if (compareBundles == 0 && subscriptionId != null) {
+
+ int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
+ if (compareSubscriptions == 0) {
+ int compareStartDates = getStartDate().compareTo(that.getStartDate());
+ if (compareStartDates == 0) {
+ return getEndDate().compareTo(that.getEndDate());
+ } else {
+ return compareStartDates;
+ }
} else {
- return compareStartDates;
+ return compareSubscriptions;
}
} else {
- return compareSubscriptions;
+ return compareBundles;
}
} else {
return compareAccounts;
@@ -137,7 +142,10 @@ public class RecurringInvoiceItem extends InvoiceItemBase {
if (rate.compareTo(that.rate) != 0) return false;
if (reversedItemId != null ? !reversedItemId.equals(that.reversedItemId) : that.reversedItemId != null)
return false;
- if (!subscriptionId.equals(that.subscriptionId)) return false;
+ if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null)
+ return false;
+ if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null)
+ return false;
return true;
}
@@ -146,6 +154,7 @@ public class RecurringInvoiceItem extends InvoiceItemBase {
public int hashCode() {
int result = accountId.hashCode();
result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
+ result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
result = 31 * result + planName.hashCode();
result = 31 * result + phaseName.hashCode();
result = 31 * result + startDate.hashCode();
@@ -165,6 +174,8 @@ public class RecurringInvoiceItem extends InvoiceItemBase {
sb.append(startDate.toString()).append(", ");
sb.append(endDate.toString()).append(", ");
sb.append(amount.toString()).append(", ");
+ sb.append("subscriptionId = ").append(subscriptionId == null ? null : subscriptionId.toString()).append(", ");
+ sb.append("bundleId = ").append(bundleId == null ? null : bundleId.toString()).append(", ");
return sb.toString();
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
index 03dc211..d61e206 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
@@ -19,19 +19,17 @@ package com.ning.billing.invoice.notification;
import java.util.UUID;
import org.joda.time.DateTime;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.config.NotificationConfig;
import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.invoice.InvoiceListener;
import com.ning.billing.invoice.api.DefaultInvoiceService;
-import com.ning.billing.util.bus.Bus;
-import com.ning.billing.util.notificationq.NotificationConfig;
-import com.ning.billing.util.notificationq.NotificationKey;
import com.ning.billing.util.notificationq.NotificationQueue;
import com.ning.billing.util.notificationq.NotificationQueueService;
import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists;
@@ -68,15 +66,19 @@ public class DefaultNextBillingDateNotifier implements NextBillingDateNotifier
new NotificationQueueHandler() {
@Override
public void handleReadyNotification(String notificationKey, DateTime eventDate) {
- try {
- UUID key = UUID.fromString(notificationKey);
- Subscription subscription = entitlementUserApi.getSubscriptionFromId(key);
- if (subscription == null) {
- log.warn("Next Billing Date Notification Queue handled spurious notification (key: " + key + ")" );
- } else {
- processEvent(key , eventDate);
+ try {
+ UUID key = UUID.fromString(notificationKey);
+ try {
+ Subscription subscription = entitlementUserApi.getSubscriptionFromId(key);
+ if (subscription == null) {
+ log.warn("Next Billing Date Notification Queue handled spurious notification (key: " + key + ")" );
+ } else {
+ processEvent(key , eventDate);
+ }
+ } catch (EntitlementUserApiException e) {
+ log.warn("Next Billing Date Notification Queue handled spurious notification (key: " + key + ")", e );
}
- } catch (IllegalArgumentException e) {
+ } catch (IllegalArgumentException e) {
log.error("The key returned from the NextBillingNotificationQueue is not a valid UUID", e);
return;
}
@@ -84,21 +86,15 @@ public class DefaultNextBillingDateNotifier implements NextBillingDateNotifier
}
},
new NotificationConfig() {
+
@Override
- public boolean isNotificationProcessingOff() {
- return config.isEventProcessingOff();
- }
- @Override
- public long getNotificationSleepTimeMs() {
- return config.getNotificationSleepTimeMs();
+ public long getSleepTimeMs() {
+ return config.getSleepTimeMs();
}
+
@Override
- public int getDaoMaxReadyEvents() {
- return config.getDaoMaxReadyEvents();
- }
- @Override
- public long getDaoClaimTimeMs() {
- return config.getDaoClaimTimeMs();
+ public boolean isNotificationProcessingOff() {
+ return config.isNotificationProcessingOff();
}
});
} catch (NotificationQueueAlreadyExists e) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java
index 8ddb29d..4aad036 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java
@@ -31,7 +31,8 @@ import com.ning.billing.util.notificationq.NotificationQueueService;
import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotificationQueue;
public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
- private final static Logger log = LoggerFactory.getLogger(DefaultNextBillingDateNotifier.class);
+
+ private final static Logger log = LoggerFactory.getLogger(DefaultNextBillingDatePoster.class);
private final NotificationQueueService notificationQueueService;
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/EmailInvoiceNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/EmailInvoiceNotifier.java
new file mode 100644
index 0000000..58026b2
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/EmailInvoiceNotifier.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.notification;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceNotifier;
+import com.ning.billing.invoice.template.HtmlInvoiceGenerator;
+import com.ning.billing.util.email.DefaultEmailSender;
+import com.ning.billing.util.email.EmailApiException;
+import com.ning.billing.util.email.EmailConfig;
+import com.ning.billing.util.email.EmailSender;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class EmailInvoiceNotifier implements InvoiceNotifier {
+ private final AccountUserApi accountUserApi;
+ private final HtmlInvoiceGenerator generator;
+ private final EmailConfig config;
+
+ @Inject
+ public EmailInvoiceNotifier(AccountUserApi accountUserApi, HtmlInvoiceGenerator generator, EmailConfig config) {
+ this.accountUserApi = accountUserApi;
+ this.generator = generator;
+ this.config = config;
+ }
+
+ @Override
+ public void notify(Account account, Invoice invoice) throws InvoiceApiException {
+ List<String> to = new ArrayList<String>();
+ to.add(account.getEmail());
+
+ List<AccountEmail> accountEmailList = accountUserApi.getEmails(account.getId());
+ List<String> cc = new ArrayList<String>();
+ for (AccountEmail email : accountEmailList) {
+ cc.add(email.getEmail());
+ }
+
+ String htmlBody = null;
+ try {
+ htmlBody = generator.generateInvoice(account, invoice, "HtmlInvoiceTemplate");
+ } catch (IOException e) {
+ throw new InvoiceApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
+ }
+
+ // TODO: get subject
+ String subject = "";
+
+ EmailSender sender = new DefaultEmailSender(config);
+ try {
+ sender.sendSecureEmail(to, cc, subject, htmlBody);
+ } catch (EmailApiException e) {
+ throw new InvoiceApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
+ } catch (IOException e) {
+ throw new InvoiceApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
+ }
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/NullInvoiceNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/NullInvoiceNotifier.java
new file mode 100644
index 0000000..460e779
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/NullInvoiceNotifier.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.notification;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceNotifier;
+
+public class NullInvoiceNotifier implements InvoiceNotifier {
+ @Override
+ public void notify(Account account, Invoice invoice) {
+ // deliberate no-op
+ }
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
new file mode 100644
index 0000000..92d1a63
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
@@ -0,0 +1,277 @@
+/*
+ * 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.
+ */
+
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.invoice.template.formatters;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.UUID;
+
+public class DefaultInvoiceFormatter implements InvoiceFormatter {
+ private final TranslatorConfig config;
+ private final Invoice invoice;
+ private final DateTimeFormatter dateFormatter;
+ private final Locale locale;
+
+ public DefaultInvoiceFormatter(TranslatorConfig config, Invoice invoice, Locale locale) {
+ this.config = config;
+ this.invoice = invoice;
+ dateFormatter = DateTimeFormat.mediumDate().withLocale(locale);
+ this.locale = locale;
+ }
+
+ @Override
+ public Integer getInvoiceNumber() {
+ return invoice.getInvoiceNumber();
+ }
+
+ @Override
+ public List<InvoiceItem> getInvoiceItems() {
+ List<InvoiceItem> formatters = new ArrayList<InvoiceItem>();
+ for (InvoiceItem item : invoice.getInvoiceItems()) {
+ formatters.add(new DefaultInvoiceItemFormatter(config, item, dateFormatter, locale));
+ }
+ return formatters;
+ }
+
+ @Override
+ public boolean addInvoiceItem(InvoiceItem item) {
+ return invoice.addInvoiceItem(item);
+ }
+
+ @Override
+ public boolean addInvoiceItems(List<InvoiceItem> items) {
+ return invoice.addInvoiceItems(items);
+ }
+
+ @Override
+ public <T extends InvoiceItem> List<InvoiceItem> getInvoiceItems(Class<T> clazz) {
+ return invoice.getInvoiceItems(clazz);
+ }
+
+ @Override
+ public int getNumberOfItems() {
+ return invoice.getNumberOfItems();
+ }
+
+ @Override
+ public boolean addPayment(InvoicePayment payment) {
+ return invoice.addPayment(payment);
+ }
+
+ @Override
+ public boolean addPayments(List<InvoicePayment> payments) {
+ return invoice.addPayments(payments);
+ }
+
+ @Override
+ public List<InvoicePayment> getPayments() {
+ return invoice.getPayments();
+ }
+
+ @Override
+ public int getNumberOfPayments() {
+ return invoice.getNumberOfPayments();
+ }
+
+ @Override
+ public UUID getAccountId() {
+ return invoice.getAccountId();
+ }
+
+ @Override
+ public BigDecimal getTotalAmount() {
+ return invoice.getTotalAmount();
+ }
+
+ @Override
+ public BigDecimal getBalance() {
+ return invoice.getBalance();
+ }
+
+ @Override
+ public boolean isDueForPayment(DateTime targetDate, int numberOfDays) {
+ return invoice.isDueForPayment(targetDate, numberOfDays);
+ }
+
+ @Override
+ public boolean isMigrationInvoice() {
+ return invoice.isMigrationInvoice();
+ }
+
+ @Override
+ public DateTime getInvoiceDate() {
+ return invoice.getInvoiceDate();
+ }
+
+ @Override
+ public DateTime getTargetDate() {
+ return invoice.getTargetDate();
+ }
+
+ @Override
+ public Currency getCurrency() {
+ return invoice.getCurrency();
+ }
+
+ @Override
+ public DateTime getLastPaymentAttempt() {
+ return invoice.getLastPaymentAttempt();
+ }
+
+ @Override
+ public BigDecimal getAmountPaid() {
+ return invoice.getAmountPaid();
+ }
+
+ @Override
+ public String getFormattedInvoiceDate() {
+ return invoice.getInvoiceDate().toString(dateFormatter);
+ }
+
+ @Override
+ public String getFieldValue(String fieldName) {
+ return invoice.getFieldValue(fieldName);
+ }
+
+ @Override
+ public void setFieldValue(String fieldName, String fieldValue) {
+ invoice.setFieldValue(fieldName, fieldValue);
+ }
+
+ @Override
+ public void saveFieldValue(String fieldName, String fieldValue, CallContext context) {
+ invoice.saveFieldValue(fieldName, fieldValue, context);
+ }
+
+ @Override
+ public List<CustomField> getFieldList() {
+ return invoice.getFieldList();
+ }
+
+ @Override
+ public void setFields(List<CustomField> fields) {
+ invoice.setFields(fields);
+ }
+
+ @Override
+ public void saveFields(List<CustomField> fields, CallContext context) {
+ invoice.saveFields(fields, context);
+ }
+
+ @Override
+ public void clearFields() {
+ invoice.clearFields();
+ }
+
+ @Override
+ public void clearPersistedFields(CallContext context) {
+ invoice.clearPersistedFields(context);
+ }
+
+ @Override
+ public ObjectType getObjectType() {
+ return invoice.getObjectType();
+ }
+
+ @Override
+ public UUID getId() {
+ return invoice.getId();
+ }
+
+ @Override
+ public List<Tag> getTagList() {
+ return invoice.getTagList();
+ }
+
+ @Override
+ public boolean hasTag(TagDefinition tagDefinition) {
+ return invoice.hasTag(tagDefinition);
+ }
+
+ @Override
+ public boolean hasTag(ControlTagType controlTagType) {
+ return invoice.hasTag(controlTagType);
+ }
+
+ @Override
+ public void addTag(TagDefinition definition) {
+ invoice.addTag(definition);
+ }
+
+ @Override
+ public void addTags(List<Tag> tags) {
+ invoice.addTags(tags);
+ }
+
+ @Override
+ public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+ invoice.addTagsFromDefinitions(tagDefinitions);
+ }
+
+ @Override
+ public void clearTags() {
+ invoice.clearTags();
+ }
+
+ @Override
+ public void removeTag(TagDefinition definition) {
+ invoice.removeTag(definition);
+ }
+
+ @Override
+ public boolean generateInvoice() {
+ return invoice.generateInvoice();
+ }
+
+ @Override
+ public boolean processPayment() {
+ return invoice.processPayment();
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java
new file mode 100644
index 0000000..83c0619
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatterFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.template.formatters;
+
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+import java.util.Locale;
+
+public class DefaultInvoiceFormatterFactory implements InvoiceFormatterFactory {
+ @Override
+ public InvoiceFormatter createInvoiceFormatter(TranslatorConfig config, Invoice invoice, Locale locale) {
+ return new DefaultInvoiceFormatter(config, invoice, locale);
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
new file mode 100644
index 0000000..f1a2e8d
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.template.formatters;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.formatters.InvoiceItemFormatter;
+import com.ning.billing.util.template.translation.DefaultCatalogTranslator;
+import com.ning.billing.util.template.translation.Translator;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormatter;
+
+import java.math.BigDecimal;
+import java.util.Locale;
+import java.util.UUID;
+
+public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
+ private final Translator translator;
+
+ private final InvoiceItem item;
+ private final DateTimeFormatter dateFormatter;
+ private final Locale locale;
+
+ public DefaultInvoiceItemFormatter(TranslatorConfig config, InvoiceItem item, DateTimeFormatter dateFormatter, Locale locale) {
+ this.item = item;
+ this.dateFormatter = dateFormatter;
+ this.locale = locale;
+
+ this.translator = new DefaultCatalogTranslator(config);
+ }
+
+ @Override
+ public BigDecimal getAmount() {
+ return item.getAmount();
+ }
+
+ @Override
+ public Currency getCurrency() {
+ return item.getCurrency();
+ }
+
+ @Override
+ public InvoiceItem asReversingItem() {
+ return item.asReversingItem();
+ }
+
+ @Override
+ public String getDescription() {
+ return item.getDescription();
+ }
+
+ @Override
+ public DateTime getStartDate() {
+ return item.getStartDate();
+ }
+
+ @Override
+ public DateTime getEndDate() {
+ return item.getEndDate();
+ }
+
+ @Override
+ public String getFormattedStartDate() {
+ return item.getStartDate().toString(dateFormatter);
+ }
+
+ @Override
+ public String getFormattedEndDate() {
+ return item.getEndDate().toString(dateFormatter);
+ }
+
+ @Override
+ public UUID getInvoiceId() {
+ return item.getInvoiceId();
+ }
+
+ @Override
+ public UUID getAccountId() {
+ return item.getAccountId();
+ }
+
+ @Override
+ public UUID getBundleId() {
+ return item.getBundleId();
+ }
+
+ @Override
+ public UUID getSubscriptionId() {
+ return item.getSubscriptionId();
+ }
+
+ @Override
+ public String getPlanName() {
+ return translator.getTranslation(locale, item.getPlanName());
+ }
+
+ @Override
+ public String getPhaseName() {
+ return translator.getTranslation(locale, item.getPhaseName());
+ }
+
+ @Override
+ public int compareTo(InvoiceItem invoiceItem) {
+ return item.compareTo(invoiceItem);
+ }
+
+ @Override
+ public UUID getId() {
+ return item.getId();
+ }
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.java
new file mode 100644
index 0000000..2a006d8
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/HtmlInvoiceGenerator.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.invoice.template;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
+import com.ning.billing.invoice.template.translator.DefaultInvoiceTranslator;
+import com.ning.billing.util.email.templates.TemplateEngine;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+import java.io.IOException;
+import java.lang.String;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+public class HtmlInvoiceGenerator {
+ private final InvoiceFormatterFactory factory;
+ private final TemplateEngine templateEngine;
+ private final TranslatorConfig config;
+
+ @Inject
+ public HtmlInvoiceGenerator(InvoiceFormatterFactory factory, TemplateEngine templateEngine, TranslatorConfig config) {
+ this.factory = factory;
+ this.templateEngine = templateEngine;
+ this.config = config;
+ }
+
+ public String generateInvoice(Account account, Invoice invoice, String templateName) throws IOException {
+ Map<String, Object> data = new HashMap<String, Object>();
+ DefaultInvoiceTranslator invoiceTranslator = new DefaultInvoiceTranslator(config);
+ Locale locale = new Locale(account.getLocale());
+ invoiceTranslator.setLocale(locale);
+ data.put("text", invoiceTranslator);
+ data.put("account", account);
+
+ InvoiceFormatter formattedInvoice = factory.createInvoiceFormatter(config, invoice, locale);
+ data.put("invoice", formattedInvoice);
+
+ return templateEngine.executeTemplate(templateName, data);
+ }
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/translator/DefaultInvoiceTranslator.java b/invoice/src/main/java/com/ning/billing/invoice/template/translator/DefaultInvoiceTranslator.java
new file mode 100644
index 0000000..f238ea7
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/translator/DefaultInvoiceTranslator.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.template.translator;
+
+import com.google.inject.Inject;
+import com.ning.billing.util.template.translation.DefaultTranslatorBase;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+import java.util.Locale;
+
+public class DefaultInvoiceTranslator extends DefaultTranslatorBase implements InvoiceStrings {
+ private Locale locale;
+
+ @Inject
+ public DefaultInvoiceTranslator(TranslatorConfig config) {
+ super(config);
+ }
+
+ public void setLocale(Locale locale) {
+ this.locale = locale;
+ }
+
+ @Override
+ protected String getBundlePath() {
+ return "com/ning/billing/util/email/translation/InvoiceTranslation";
+ }
+
+ @Override
+ protected String getTranslationType() {
+ return "invoice";
+ }
+
+ @Override
+ public String getInvoiceTitle() {
+ return getTranslation(locale, "invoiceTitle");
+ }
+
+ @Override
+ public String getInvoiceDate() {
+ return getTranslation(locale, "invoiceDate");
+ }
+
+ @Override
+ public String getInvoiceNumber() {
+ return getTranslation(locale, "invoiceNumber");
+ }
+
+ @Override
+ public String getAccountOwnerName() {
+ return getTranslation(locale, "accountOwnerName");
+ }
+
+ @Override
+ public String getAccountOwnerEmail() {
+ return getTranslation(locale, "accountOwnerEmail");
+ }
+
+ @Override
+ public String getAccountOwnerPhone() {
+ return getTranslation(locale, "accountOwnerPhone");
+ }
+
+ @Override
+ public String getCompanyName() {
+ return getTranslation(locale, "companyName");
+ }
+
+ @Override
+ public String getCompanyAddress() {
+ return getTranslation(locale, "companyAddress");
+ }
+
+ @Override
+ public String getCompanyCityProvincePostalCode() {
+ return getTranslation(locale, "");
+ }
+
+ @Override
+ public String getCompanyCountry() {
+ return getTranslation(locale, "companyCountry");
+ }
+
+ @Override
+ public String getCompanyUrl() {
+ return getTranslation(locale, "companyUrl");
+ }
+
+ @Override
+ public String getInvoiceItemBundleName() {
+ return getTranslation(locale, "invoiceItemBundleName");
+ }
+
+ @Override
+ public String getInvoiceItemDescription() {
+ return getTranslation(locale, "invoiceItemDescription");
+ }
+
+ @Override
+ public String getInvoiceItemServicePeriod() {
+ return getTranslation(locale, "invoiceItemServicePeriod");
+ }
+
+ @Override
+ public String getInvoiceItemAmount() {
+ return getTranslation(locale, "invoiceItemAmount");
+ }
+
+ @Override
+ public String getInvoiceAmount() {
+ return getTranslation(locale, "invoiceAmount");
+ }
+
+ @Override
+ public String getInvoiceAmountPaid() {
+ return getTranslation(locale, "invoiceAmountPaid");
+ }
+
+ @Override
+ public String getInvoiceBalance() {
+ return getTranslation(locale, "invoiceBalance");
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/translator/InvoiceStrings.java b/invoice/src/main/java/com/ning/billing/invoice/template/translator/InvoiceStrings.java
new file mode 100644
index 0000000..cc19d5f
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/translator/InvoiceStrings.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.template.translator;
+
+public interface InvoiceStrings {
+ String getInvoiceTitle();
+ String getInvoiceDate();
+ String getInvoiceNumber();
+ String getAccountOwnerName();
+ String getAccountOwnerEmail();
+ String getAccountOwnerPhone();
+
+ // company name and address
+ String getCompanyName();
+ String getCompanyAddress();
+ String getCompanyCityProvincePostalCode();
+ String getCompanyCountry();
+ String getCompanyUrl();
+
+ String getInvoiceItemBundleName();
+ String getInvoiceItemDescription();
+ String getInvoiceItemServicePeriod();
+ String getInvoiceItemAmount();
+
+ String getInvoiceAmount();
+ String getInvoiceAmountPaid();
+ String getInvoiceBalance();
+}
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg
index 100182f..d7aed8c 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg
@@ -4,6 +4,7 @@ fields(prefix) ::= <<
<prefix>id,
<prefix>invoice_id,
<prefix>account_id,
+ <prefix>bundle_id,
<prefix>subscription_id,
<prefix>plan_name,
<prefix>phase_name,
@@ -42,16 +43,38 @@ getInvoiceItemsBySubscription() ::= <<
create() ::= <<
INSERT INTO fixed_invoice_items(<fields()>)
- VALUES(:id, :invoiceId, :accountId, :subscriptionId, :planName, :phaseName,
+ VALUES(:id, :invoiceId, :accountId, :bundleId, :subscriptionId, :planName, :phaseName,
:startDate, :endDate, :amount, :currency, :userName, :createdDate);
>>
batchCreateFromTransaction() ::= <<
INSERT INTO fixed_invoice_items(<fields()>)
- VALUES(:id, :invoiceId, :accountId, :subscriptionId, :planName, :phaseName,
+ VALUES(:id, :invoiceId, :accountId, :bundleId, :subscriptionId, :planName, :phaseName,
:startDate, :endDate, :amount, :currency, :userName, :createdDate);
>>
+getRecordIds() ::= <<
+ SELECT record_id, id
+ FROM fixed_invoice_items
+ WHERE invoice_id = :invoiceId;
+>>
+
+auditFields(prefix) ::= <<
+ <prefix>table_name,
+ <prefix>record_id,
+ <prefix>change_type,
+ <prefix>change_date,
+ <prefix>changed_by,
+ <prefix>reason_code,
+ <prefix>comments,
+ <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+ INSERT INTO audit_log(<auditFields()>)
+ VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
test() ::= <<
SELECT 1
FROM fixed_invoice_items;
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
index a673b81..6967da0 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
@@ -49,6 +49,34 @@ getInvoicePayment() ::= <<
WHERE payment_id = :payment_id;
>>
+getRecordId() ::= <<
+ SELECT record_id
+ FROM invoice_payments
+ WHERE id = :id;
+>>
+
+getRecordIds() ::= <<
+ SELECT record_id, id
+ FROM invoice_payments
+ WHERE invoice_id = :invoiceId;
+>>
+
+auditFields(prefix) ::= <<
+ <prefix>table_name,
+ <prefix>record_id,
+ <prefix>change_type,
+ <prefix>change_date,
+ <prefix>changed_by,
+ <prefix>reason_code,
+ <prefix>comments,
+ <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+ INSERT INTO audit_log(<auditFields()>)
+ VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
test() ::= <<
SELECT 1 FROM invoice_payments;
>>
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 b83dc82..f8efa27 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
@@ -1,18 +1,6 @@
group InvoiceDao;
-invoiceFetchFields(prefix) ::= <<
- <prefix>invoice_number,
- <prefix>id,
- <prefix>account_id,
- <prefix>invoice_date,
- <prefix>target_date,
- <prefix>currency,
- <prefix>migrated,
- <prefix>created_by,
- <prefix>created_date
->>
-
-invoiceSetFields(prefix) ::= <<
+invoiceFields(prefix) ::= <<
<prefix>id,
<prefix>account_id,
<prefix>invoice_date,
@@ -24,42 +12,42 @@ invoiceSetFields(prefix) ::= <<
>>
get() ::= <<
- SELECT <invoiceFetchFields()>
+ SELECT record_id as invoice_number, <invoiceFields()>
FROM invoices
ORDER BY target_date ASC;
>>
getInvoicesByAccount() ::= <<
- SELECT <invoiceFetchFields()>
+ SELECT record_id as invoice_number, <invoiceFields()>
FROM invoices
WHERE account_id = :accountId AND migrated = 'FALSE'
ORDER BY target_date ASC;
>>
getAllInvoicesByAccount() ::= <<
- SELECT <invoiceFetchFields()>
+ SELECT record_id as invoice_number, <invoiceFields()>
FROM invoices
WHERE account_id = :accountId
ORDER BY target_date ASC;
>>
getInvoicesByAccountAfterDate() ::= <<
- SELECT <invoiceFetchFields()>
+ SELECT record_id as invoice_number, <invoiceFields()>
FROM invoices
WHERE account_id = :accountId AND target_date >= :fromDate AND migrated = 'FALSE'
ORDER BY target_date ASC;
>>
getInvoicesBySubscription() ::= <<
- SELECT <invoiceFetchFields("i.")>
+ SELECT record_id as invoice_number, <invoiceFields("i.")>
FROM invoices i
LEFT JOIN recurring_invoice_items rii ON i.id = rii.invoice_id
WHERE rii.subscription_id = :subscriptionId AND migrated = 'FALSE'
- GROUP BY <invoiceFetchFields("i.")>;
+ GROUP BY record_id as invoice_number, <invoiceFields("i.")>;
>>
getById() ::= <<
- SELECT <invoiceFetchFields()>
+ SELECT record_id as invoice_number, <invoiceFields()>
FROM invoices
WHERE id = :id;
>>
@@ -75,7 +63,7 @@ getAccountBalance() ::= <<
>>
create() ::= <<
- INSERT INTO invoices(<invoiceSetFields()>)
+ INSERT INTO invoices(<invoiceFields()>)
VALUES (:id, :accountId, :invoiceDate, :targetDate, :currency, :migrated, :userName, :createdDate);
>>
@@ -87,7 +75,7 @@ getInvoiceIdByPaymentAttemptId() ::= <<
>>
getUnpaidInvoicesByAccountId() ::= <<
- SELECT <invoiceFetchFields("i.")>
+ SELECT record_id as invoice_number, <invoiceFields("i.")>
FROM invoices i
LEFT JOIN invoice_payment_summary ips ON i.id = ips.invoice_id
LEFT JOIN invoice_item_summary iis ON i.id = iis.invoice_id
@@ -97,6 +85,28 @@ getUnpaidInvoicesByAccountId() ::= <<
ORDER BY i.target_date ASC;
>>
+getRecordId() ::= <<
+ SELECT record_id
+ FROM invoices
+ WHERE id = :id;
+>>
+
+auditFields(prefix) ::= <<
+ <prefix>table_name,
+ <prefix>record_id,
+ <prefix>change_type,
+ <prefix>change_date,
+ <prefix>changed_by,
+ <prefix>reason_code,
+ <prefix>comments,
+ <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+ INSERT INTO audit_log(<auditFields()>)
+ VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
test() ::= <<
SELECT 1
FROM invoices;
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.sql.stg
index 6b5f504..8afb199 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.sql.stg
@@ -4,6 +4,7 @@ fields(prefix) ::= <<
<prefix>id,
<prefix>invoice_id,
<prefix>account_id,
+ <prefix>bundle_id,
<prefix>subscription_id,
<prefix>plan_name,
<prefix>phase_name,
@@ -44,16 +45,38 @@ getInvoiceItemsBySubscription() ::= <<
create() ::= <<
INSERT INTO recurring_invoice_items(<fields()>)
- VALUES(:id, :invoiceId, :accountId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
+ VALUES(:id, :invoiceId, :accountId, :bundleId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
:amount, :rate, :currency, :reversedItemId, :userName, :createdDate);
>>
batchCreateFromTransaction() ::= <<
INSERT INTO recurring_invoice_items(<fields()>)
- VALUES(:id, :invoiceId, :accountId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
+ VALUES(:id, :invoiceId, :accountId, :bundleId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
:amount, :rate, :currency, :reversedItemId, :userName, :createdDate);
>>
+getRecordIds() ::= <<
+ SELECT record_id, id
+ FROM recurring_invoice_items
+ WHERE invoice_id = :invoiceId;
+>>
+
+auditFields(prefix) ::= <<
+ <prefix>table_name,
+ <prefix>record_id,
+ <prefix>change_type,
+ <prefix>change_date,
+ <prefix>changed_by,
+ <prefix>reason_code,
+ <prefix>comments,
+ <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+ INSERT INTO audit_log(<auditFields()>)
+ VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
test() ::= <<
SELECT 1
FROM recurring_invoice_items;
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
index 3adb40e..8bda35c 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
@@ -1,41 +1,47 @@
DROP TABLE IF EXISTS invoice_items;
DROP TABLE IF EXISTS recurring_invoice_items;
CREATE TABLE recurring_invoice_items (
- id char(36) NOT NULL,
- invoice_id char(36) NOT NULL,
- account_id char(36) NOT NULL,
- subscription_id char(36) NOT NULL,
- plan_name varchar(50) NOT NULL,
- phase_name varchar(50) NOT NULL,
- start_date datetime NOT NULL,
- end_date datetime NOT NULL,
- amount numeric(10,4) NULL,
- rate numeric(10,4) NULL,
- currency char(3) NOT NULL,
- reversed_item_id char(36),
- created_by varchar(50) NOT NULL,
- created_date datetime NOT NULL,
- PRIMARY KEY(id)
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ id char(36) NOT NULL,
+ invoice_id char(36) NOT NULL,
+ account_id char(36) NOT NULL,
+ bundle_id char(36),
+ subscription_id char(36),
+ plan_name varchar(50) NOT NULL,
+ phase_name varchar(50) NOT NULL,
+ start_date datetime NOT NULL,
+ end_date datetime NOT NULL,
+ amount numeric(10,4) NULL,
+ rate numeric(10,4) NULL,
+ currency char(3) NOT NULL,
+ reversed_item_id char(36),
+ created_by varchar(50) NOT NULL,
+ created_date datetime NOT NULL,
+ PRIMARY KEY(record_id)
) ENGINE=innodb;
+CREATE UNIQUE INDEX recurring_invoice_items_id ON recurring_invoice_items(id);
CREATE INDEX recurring_invoice_items_subscription_id ON recurring_invoice_items(subscription_id ASC);
CREATE INDEX recurring_invoice_items_invoice_id ON recurring_invoice_items(invoice_id ASC);
DROP TABLE IF EXISTS fixed_invoice_items;
CREATE TABLE fixed_invoice_items (
- id char(36) NOT NULL,
- invoice_id char(36) NOT NULL,
- account_id char(36) NOT NULL,
- subscription_id char(36) NOT NULL,
- plan_name varchar(50) NOT NULL,
- phase_name varchar(50) NOT NULL,
- start_date datetime NOT NULL,
- end_date datetime NOT NULL,
- amount numeric(10,4) NULL,
- currency char(3) NOT NULL,
- created_by varchar(50) NOT NULL,
- created_date datetime NOT NULL,
- PRIMARY KEY(id)
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ id char(36) NOT NULL,
+ invoice_id char(36) NOT NULL,
+ account_id char(36) NOT NULL,
+ bundle_id char(36),
+ subscription_id char(36),
+ plan_name varchar(50) NOT NULL,
+ phase_name varchar(50) NOT NULL,
+ start_date datetime NOT NULL,
+ end_date datetime NOT NULL,
+ amount numeric(10,4) NULL,
+ currency char(3) NOT NULL,
+ created_by varchar(50) NOT NULL,
+ created_date datetime NOT NULL,
+ PRIMARY KEY(record_id)
) ENGINE=innodb;
+CREATE UNIQUE INDEX fixed_invoice_items_id ON fixed_invoice_items(id);
CREATE INDEX fixed_invoice_items_subscription_id ON fixed_invoice_items(subscription_id ASC);
CREATE INDEX fixed_invoice_items_invoice_id ON fixed_invoice_items(invoice_id ASC);
@@ -43,33 +49,34 @@ DROP TABLE IF EXISTS invoice_locking;
DROP TABLE IF EXISTS invoices;
CREATE TABLE invoices (
- invoice_number int NOT NULL AUTO_INCREMENT,
- id char(36) NOT NULL,
- account_id char(36) NOT NULL,
- invoice_date datetime NOT NULL,
- target_date datetime NOT NULL,
- currency char(3) NOT NULL,
- migrated bool NOT NULL,
- created_by varchar(50) NOT NULL,
- created_date datetime NOT NULL,
- PRIMARY KEY(invoice_number)
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ id char(36) NOT NULL,
+ account_id char(36) NOT NULL,
+ invoice_date datetime NOT NULL,
+ target_date datetime NOT NULL,
+ currency char(3) NOT NULL,
+ migrated bool NOT NULL,
+ created_by varchar(50) NOT NULL,
+ created_date datetime NOT NULL,
+ PRIMARY KEY(record_id)
) ENGINE=innodb;
-CREATE INDEX invoices_invoice_number ON invoices(invoice_number ASC);
-CREATE INDEX invoices_id ON invoices(id ASC);
+CREATE UNIQUE INDEX invoices_id ON invoices(id);
CREATE INDEX invoices_account_id ON invoices(account_id ASC);
DROP TABLE IF EXISTS invoice_payments;
CREATE TABLE invoice_payments (
- id char(36) NOT NULL,
- invoice_id char(36) NOT NULL,
- payment_attempt_id char(36) COLLATE utf8_bin NOT NULL,
- payment_attempt_date datetime,
- amount numeric(10,4),
- currency char(3),
- created_by varchar(50) NOT NULL,
- created_date datetime NOT NULL,
- PRIMARY KEY(invoice_id, payment_attempt_id)
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ id char(36) NOT NULL,
+ invoice_id char(36) NOT NULL,
+ payment_attempt_id char(36) COLLATE utf8_bin NOT NULL,
+ payment_attempt_date datetime,
+ amount numeric(10,4),
+ currency char(3),
+ created_by varchar(50) NOT NULL,
+ created_date datetime NOT NULL,
+ PRIMARY KEY(record_id)
) ENGINE=innodb;
+CREATE UNIQUE INDEX invoice_payments_id ON invoice_payments(id);
CREATE UNIQUE INDEX invoice_payments_unique ON invoice_payments(invoice_id, payment_attempt_id);
DROP VIEW IF EXISTS invoice_payment_summary;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/migration/MockModuleNoEntitlement.java b/invoice/src/test/java/com/ning/billing/invoice/api/migration/MockModuleNoEntitlement.java
index 4701bc8..fe5d886 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/migration/MockModuleNoEntitlement.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/migration/MockModuleNoEntitlement.java
@@ -16,43 +16,46 @@
package com.ning.billing.invoice.api.migration;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
-import com.ning.billing.entitlement.engine.dao.EntitlementDao;
import com.ning.billing.invoice.MockModule;
-import com.ning.billing.invoice.glue.InvoiceModule;
-import com.ning.billing.invoice.notification.DefaultNextBillingDateNotifier;
-import com.ning.billing.invoice.notification.DefaultNextBillingDatePoster;
+import com.ning.billing.invoice.api.InvoiceNotifier;
+import com.ning.billing.invoice.glue.DefaultInvoiceModule;
import com.ning.billing.invoice.notification.NextBillingDateNotifier;
import com.ning.billing.invoice.notification.NextBillingDatePoster;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
import com.ning.billing.mock.BrainDeadProxyFactory;
import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.util.email.templates.TemplateModule;
public class MockModuleNoEntitlement extends MockModule {
-
- @Override
- protected void installEntitlementModule() {
- EntitlementBillingApi entitlementApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementBillingApi.class);
- ((ZombieControl)entitlementApi).addResult("setChargedThroughDateFromTransaction", BrainDeadProxyFactory.ZOMBIE_VOID);
- bind(EntitlementBillingApi.class).toInstance(entitlementApi);
- bind(EntitlementDao.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementDao.class));
-
- }
+// @Override
+// protected void installEntitlementModule() {
+// BillingApi entitlementApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
+// ((ZombieControl)entitlementApi).addResult("setChargedThroughDateFromTransaction", BrainDeadProxyFactory.ZOMBIE_VOID);
+// ((ZombieControl)entitlementApi).addResult("getBillingEventsForAccountAndUpdateAccountBCD", BrainDeadProxyFactory.ZOMBIE_VOID);
+// //bind(BillingApi.class).toInstance(entitlementApi);
+//// bind(EntitlementUserApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class));
+// ChargeThruApi cta = BrainDeadProxyFactory.createBrainDeadProxyFor(ChargeThruApi.class);
+// bind(ChargeThruApi.class).toInstance(cta);
+// ((ZombieControl)cta).addResult("setChargedThroughDateFromTransaction", BrainDeadProxyFactory.ZOMBIE_VOID);
+// }
@Override
protected void installInvoiceModule() {
- install(new InvoiceModule(){
+ install(new DefaultInvoiceModule(){
@Override
- protected void installNotifier() {
+ protected void installNotifiers() {
bind(NextBillingDateNotifier.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(NextBillingDateNotifier.class));
NextBillingDatePoster poster = BrainDeadProxyFactory.createBrainDeadProxyFor(NextBillingDatePoster.class);
((ZombieControl)poster).addResult("insertNextBillingNotification",BrainDeadProxyFactory.ZOMBIE_VOID);
bind(NextBillingDatePoster.class).toInstance(poster);
+ bind(InvoiceNotifier.class).to(NullInvoiceNotifier.class).asEagerSingleton();
}
});
-
+ install(new TemplateModule());
+
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
index 07c8e54..efedbaf 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
@@ -23,11 +23,7 @@ import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.clock.Clock;
+import com.ning.billing.invoice.tests.InvoicingTestBase;
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
import org.slf4j.Logger;
@@ -48,29 +44,35 @@ 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.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
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.billing.EntitlementBillingApi;
import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
import com.ning.billing.invoice.InvoiceDispatcher;
import com.ning.billing.invoice.TestInvoiceDispatcher;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceMigrationApi;
+import com.ning.billing.invoice.api.InvoiceNotifier;
import com.ning.billing.invoice.api.InvoicePaymentApi;
import com.ning.billing.invoice.api.InvoiceUserApi;
import com.ning.billing.invoice.dao.InvoiceDao;
import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
+import com.ning.billing.junction.api.BillingApi;
import com.ning.billing.mock.BrainDeadProxyFactory;
import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
import com.ning.billing.util.bus.BusService;
import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.ClockMock;
import com.ning.billing.util.globallocker.GlobalLocker;
@Guice(modules = {MockModuleNoEntitlement.class})
-public class TestDefaultInvoiceMigrationApi {
+public class TestDefaultInvoiceMigrationApi extends InvoicingTestBase {
Logger log = LoggerFactory.getLogger(TestDefaultInvoiceMigrationApi.class);
@Inject
@@ -94,8 +96,9 @@ public class TestDefaultInvoiceMigrationApi {
@Inject
private InvoiceMigrationApi migrationApi;
-
-
+
+ @Inject
+ private BillingApi billingApi;
private UUID accountId ;
private UUID subscriptionId ;
@@ -110,7 +113,7 @@ public class TestDefaultInvoiceMigrationApi {
private final Clock clock = new ClockMock();
- @BeforeClass(alwaysRun = true)
+ @BeforeClass(groups={"slow"})
public void setup() throws Exception
{
log.info("Starting set up");
@@ -129,11 +132,13 @@ public class TestDefaultInvoiceMigrationApi {
busService.getBus().start();
+ ((ZombieControl)billingApi).addResult("setChargedThroughDate", BrainDeadProxyFactory.ZOMBIE_VOID);
migrationInvoiceId = createAndCheckMigrationInvoice();
regularInvoiceId = generateRegularInvoice();
+
}
- @AfterClass(alwaysRun = true)
+ @AfterClass(groups={"slow"})
public void tearDown() {
try {
((DefaultBusService) busService).stopBus();
@@ -169,23 +174,26 @@ public class TestDefaultInvoiceMigrationApi {
((ZombieControl)accountUserApi).addResult("getAccountById", account);
((ZombieControl)account).addResult("getCurrency", Currency.USD);
((ZombieControl)account).addResult("getId", accountId);
+ ((ZombieControl)account).addResult("isNotifiedForInvoices", true);
Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
- ((ZombieControl)subscription).addResult("getId", subscriptionId);
+ ((ZombieControl)subscription).addResult("getId", subscriptionId);
+ ((ZombieControl)subscription).addResult("getBundleId", new UUID(0L,0L));
SortedSet<BillingEvent> events = new TreeSet<BillingEvent>();
Plan plan = MockPlan.createBicycleNoTrialEvergreen1USD();
PlanPhase planPhase = MockPlanPhase.create1USDMonthlyEvergreen();
DateTime effectiveDate = new DateTime().minusDays(1);
Currency currency = Currency.USD;
BigDecimal fixedPrice = null;
- events.add(new DefaultBillingEvent(subscription, effectiveDate,plan, planPhase,
- fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
- BillingModeType.IN_ADVANCE, "", 1L, SubscriptionTransitionType.CREATE));
+ events.add(createMockBillingEvent(account, subscription, effectiveDate, plan, planPhase,
+ fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
+ BillingModeType.IN_ADVANCE, "", 1L, SubscriptionTransitionType.CREATE));
- EntitlementBillingApi entitlementBillingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementBillingApi.class);
- ((ZombieControl)entitlementBillingApi).addResult("getBillingEventsForAccount", events);
+ ((ZombieControl)billingApi).addResult("getBillingEventsForAccountAndUpdateAccountBCD", events);
- InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountUserApi, entitlementBillingApi, invoiceDao, locker);
+ InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
+ InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountUserApi, billingApi,
+ invoiceDao, invoiceNotifier, locker, busService.getBus(), clock);
CallContext context = new DefaultCallContextFactory(clock).createCallContext("Migration test", CallOrigin.TEST, UserType.TEST);
Invoice invoice = dispatcher.processAccount(accountId, date_regular, true, context);
@@ -204,7 +212,7 @@ public class TestDefaultInvoiceMigrationApi {
}
// Check migration invoice is NOT returned for all user api invoice calls
- @Test(groups={"slow"},enabled=true)
+ @Test(groups={"slow"}, enabled=true)
public void testUserApiAccess(){
List<Invoice> byAccount = invoiceUserApi.getInvoicesByAccount(accountId);
Assert.assertEquals(byAccount.size(),1);
@@ -231,7 +239,7 @@ public class TestDefaultInvoiceMigrationApi {
}
- // Account balance should reflect total of migration and non-migration invoices
+ // ACCOUNT balance should reflect total of migration and non-migration invoices
@Test(groups={"slow"},enabled=true)
public void testBalance(){
Invoice migrationInvoice = invoiceDao.getById(migrationInvoiceId);
@@ -239,7 +247,7 @@ public class TestDefaultInvoiceMigrationApi {
BigDecimal balanceOfAllInvoices = migrationInvoice.getBalance().add(regularInvoice.getBalance());
BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId);
- System.out.println("Account balance: " + accountBalance + " should equal the Balance Of All Invoices: " + balanceOfAllInvoices);
+ System.out.println("ACCOUNT balance: " + accountBalance + " should equal the Balance Of All Invoices: " + balanceOfAllInvoices);
Assert.assertEquals(accountBalance.compareTo(balanceOfAllInvoices), 0);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
index 3a00c1b..09b809c 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
@@ -22,11 +22,11 @@ import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
-import com.ning.billing.util.callcontext.CallContext;
import org.joda.time.DateTime;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.model.DefaultInvoicePayment;
+import com.ning.billing.util.callcontext.CallContext;
public class MockInvoicePaymentApi implements InvoicePaymentApi
{
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/user/TestEventJson.java b/invoice/src/test/java/com/ning/billing/invoice/api/user/TestEventJson.java
new file mode 100644
index 0000000..eb991dd
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/user/TestEventJson.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.invoice.api.user;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.EmptyInvoiceEvent;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+
+public class TestEventJson {
+
+ private ObjectMapper mapper = new ObjectMapper();
+
+ @BeforeTest(groups= {"fast"})
+ public void setup() {
+ mapper = new ObjectMapper();
+ mapper.disable(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS);
+ }
+
+ @Test(groups= {"fast"})
+ public void testInvoiceCreationEvent() throws Exception {
+
+ InvoiceCreationEvent e = new DefaultInvoiceCreationEvent(UUID.randomUUID(), UUID.randomUUID(), new BigDecimal(12.0), Currency.USD, new DateTime(), UUID.randomUUID());
+
+ String json = mapper.writeValueAsString(e);
+
+ Class<?> claz = Class.forName(DefaultInvoiceCreationEvent.class.getName());
+ Object obj = mapper.readValue(json, claz);
+ Assert.assertTrue(obj.equals(e));
+ }
+
+ @Test(groups= {"fast"})
+ public void testEmptyInvoiceEvent() throws Exception {
+
+ EmptyInvoiceEvent e = new DefaultEmptyInvoiceEvent(UUID.randomUUID(), new DateTime(), UUID.randomUUID());
+
+ String json = mapper.writeValueAsString(e);
+
+ Class<?> claz = Class.forName(DefaultEmptyInvoiceEvent.class.getName());
+ Object obj = mapper.readValue(json, claz);
+ Assert.assertTrue(obj.equals(e));
+ }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
index 60a4313..91dbf7d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
@@ -17,33 +17,32 @@
package com.ning.billing.invoice.dao;
import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
import java.io.IOException;
-import com.ning.billing.config.InvoiceConfig;
-import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
-import com.ning.billing.invoice.model.InvoiceGenerator;
-import com.ning.billing.invoice.tests.InvoicingTestBase;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.clock.Clock;
import org.apache.commons.io.IOUtils;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.TransactionCallback;
import org.skife.jdbi.v2.TransactionStatus;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
+import com.ning.billing.config.InvoiceConfig;
import com.ning.billing.invoice.glue.InvoiceModuleWithEmbeddedDb;
+import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
+import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.invoice.tests.InvoicingTestBase;
import com.ning.billing.util.bus.BusService;
import com.ning.billing.util.bus.DefaultBusService;
-import org.testng.annotations.BeforeMethod;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.Clock;
public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
protected InvoiceDao invoiceDao;
@@ -56,27 +55,22 @@ public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
private final InvoiceConfig invoiceConfig = new InvoiceConfig() {
@Override
- public long getDaoClaimTimeMs() {throw new UnsupportedOperationException();}
- @Override
- public int getDaoMaxReadyEvents() {throw new UnsupportedOperationException();}
- @Override
- public long getNotificationSleepTimeMs() {throw new UnsupportedOperationException();}
+ public long getSleepTimeMs() {throw new UnsupportedOperationException();}
@Override
- public boolean isEventProcessingOff() {throw new UnsupportedOperationException();}
+ public boolean isNotificationProcessingOff() {throw new UnsupportedOperationException();}
@Override
public int getNumberOfMonthsInFuture() {return 36;}
};
@BeforeClass(alwaysRun = true)
protected void setup() throws IOException {
- try {
module = new InvoiceModuleWithEmbeddedDb();
final String invoiceDdl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
- final String entitlementDdl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+ final String utilDdl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
module.startDb();
module.initDb(invoiceDdl);
- module.initDb(entitlementDdl);
+ module.initDb(utilDdl);
final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module);
@@ -94,10 +88,7 @@ public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
((DefaultBusService) busService).startBus();
assertTrue(true);
- }
- catch (Throwable t) {
- fail(t.toString());
- }
+
}
@BeforeMethod(alwaysRun = true)
@@ -106,21 +97,9 @@ public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
@Override
public Void inTransaction(Handle h, TransactionStatus status)
throws Exception {
- h.execute("truncate table accounts");
- //h.execute("truncate table entitlement_events");
- //h.execute("truncate table subscriptions");
- //h.execute("truncate table bundles");
- //h.execute("truncate table notifications");
- //h.execute("truncate table claimed_notifications");
h.execute("truncate table invoices");
h.execute("truncate table fixed_invoice_items");
h.execute("truncate table recurring_invoice_items");
- //h.execute("truncate table tag_definitions");
- //h.execute("truncate table tags");
- //h.execute("truncate table custom_fields");
- //h.execute("truncate table invoice_payments");
- //h.execute("truncate table payment_attempts");
- //h.execute("truncate table payments");
return null;
}
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 ad329bc..bb8313a 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
@@ -16,6 +16,21 @@
package com.ning.billing.invoice.dao;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.testng.annotations.Test;
+
import com.ning.billing.catalog.DefaultPrice;
import com.ning.billing.catalog.MockInternationalPrice;
import com.ning.billing.catalog.MockPlan;
@@ -26,11 +41,10 @@ import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
import com.ning.billing.entitlement.api.billing.BillingEvent;
import com.ning.billing.entitlement.api.billing.BillingModeType;
-import com.ning.billing.entitlement.api.billing.DefaultBillingEvent;
import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceApiException;
import com.ning.billing.invoice.api.InvoiceItem;
@@ -42,22 +56,8 @@ import com.ning.billing.invoice.model.RecurringInvoiceItem;
import com.ning.billing.mock.BrainDeadProxyFactory;
import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
import com.ning.billing.util.tag.ControlTagType;
-import org.joda.time.DateTime;
-import org.testng.annotations.Test;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.UUID;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
-@Test(groups = {"invoicing", "invoicing-invoiceDao"})
+@Test(groups = {"slow", "invoicing", "invoicing-invoiceDao"})
public class InvoiceDaoTests extends InvoiceDaoTestBase {
@Test
public void testCreationAndRetrievalByAccount() {
@@ -84,11 +84,12 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
Invoice invoice = new DefaultInvoice(accountId, clock.getUTCNow(), clock.getUTCNow(), Currency.USD);
UUID invoiceId = invoice.getId();
UUID subscriptionId = UUID.randomUUID();
+ UUID bundleId = UUID.randomUUID();
DateTime startDate = new DateTime(2010, 1, 1, 0, 0, 0, 0);
DateTime endDate = new DateTime(2010, 4, 1, 0, 0, 0, 0);
- InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, accountId, subscriptionId,
- "test plan", "test phase", startDate, endDate,
+ InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId,subscriptionId, "test plan", "test phase", startDate, endDate,
new BigDecimal("21.00"), new BigDecimal("7.00"), Currency.USD);
+
invoice.addInvoiceItem(invoiceItem);
invoiceDao.create(invoice, context);
@@ -155,6 +156,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
@Test
public void testGetInvoicesBySubscription() {
UUID accountId = UUID.randomUUID();
+ UUID bundleId = UUID.randomUUID();
UUID subscriptionId1 = UUID.randomUUID();
BigDecimal rate1 = new BigDecimal("17.0");
@@ -177,19 +179,20 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
DateTime endDate = startDate.plusMonths(1);
- RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoiceId1, accountId, subscriptionId1, "test plan", "test A", startDate, endDate,
+
+ RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId,subscriptionId1, "test plan", "test A", startDate, endDate,
rate1, rate1, Currency.USD);
recurringInvoiceItemDao.create(item1, context);
- RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoiceId1, accountId, subscriptionId2, "test plan", "test B", startDate, endDate,
+ RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId,subscriptionId2, "test plan", "test B", startDate, endDate,
rate2, rate2, Currency.USD);
recurringInvoiceItemDao.create(item2, context);
- RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoiceId1, accountId, subscriptionId3, "test plan", "test C", startDate, endDate,
+ RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId,subscriptionId3, "test plan", "test C", startDate, endDate,
rate3, rate3, Currency.USD);
recurringInvoiceItemDao.create(item3, context);
- RecurringInvoiceItem item4 = new RecurringInvoiceItem(invoiceId1, accountId, subscriptionId4, "test plan", "test D", startDate, endDate,
+ RecurringInvoiceItem item4 = new RecurringInvoiceItem(invoiceId1, accountId, bundleId,subscriptionId4, "test plan", "test D", startDate, endDate,
rate4, rate4, Currency.USD);
recurringInvoiceItemDao.create(item4, context);
@@ -202,15 +205,16 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
startDate = endDate;
endDate = startDate.plusMonths(1);
- RecurringInvoiceItem item5 = new RecurringInvoiceItem(invoiceId2, accountId, subscriptionId1, "test plan", "test phase A", startDate, endDate,
+
+ RecurringInvoiceItem item5 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId,subscriptionId1, "test plan", "test phase A", startDate, endDate,
rate1, rate1, Currency.USD);
recurringInvoiceItemDao.create(item5, context);
- RecurringInvoiceItem item6 = new RecurringInvoiceItem(invoiceId2, accountId, subscriptionId2, "test plan", "test phase B", startDate, endDate,
+ RecurringInvoiceItem item6 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId,subscriptionId2, "test plan", "test phase B", startDate, endDate,
rate2, rate2, Currency.USD);
recurringInvoiceItemDao.create(item6, context);
- RecurringInvoiceItem item7 = new RecurringInvoiceItem(invoiceId2, accountId, subscriptionId3, "test plan", "test phase C", startDate, endDate,
+ RecurringInvoiceItem item7 = new RecurringInvoiceItem(invoiceId2, accountId, bundleId,subscriptionId3, "test plan", "test phase C", startDate, endDate,
rate3, rate3, Currency.USD);
recurringInvoiceItemDao.create(item7, context);
@@ -260,6 +264,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
@Test
public void testAccountBalance() {
UUID accountId = UUID.randomUUID();
+ UUID bundleId = UUID.randomUUID();
DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCNow(), targetDate1, Currency.USD);
invoiceDao.create(invoice1, context);
@@ -270,11 +275,11 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
BigDecimal rate1 = new BigDecimal("17.0");
BigDecimal rate2 = new BigDecimal("42.0");
- RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, UUID.randomUUID(), "test plan", "test phase A", startDate,
+ RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId,UUID.randomUUID(), "test plan", "test phase A", startDate,
endDate, rate1, rate1, Currency.USD);
recurringInvoiceItemDao.create(item1, context);
- RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, UUID.randomUUID(), "test plan", "test phase B", startDate,
+ RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId,UUID.randomUUID(), "test plan", "test phase B", startDate,
endDate, rate2, rate2, Currency.USD);
recurringInvoiceItemDao.create(item2, context);
@@ -289,6 +294,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
@Test
public void testAccountBalanceWithNoPayments() {
UUID accountId = UUID.randomUUID();
+ UUID bundleId = UUID.randomUUID();
DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCNow(), targetDate1, Currency.USD);
invoiceDao.create(invoice1, context);
@@ -299,11 +305,11 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
BigDecimal rate1 = new BigDecimal("17.0");
BigDecimal rate2 = new BigDecimal("42.0");
- RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, UUID.randomUUID(), "test plan", "test phase A", startDate, endDate,
+ RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase A", startDate, endDate,
rate1, rate1, Currency.USD);
recurringInvoiceItemDao.create(item1, context);
- RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, UUID.randomUUID(), "test plan", "test phase B", startDate, endDate,
+ RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase B", startDate, endDate,
rate2, rate2, Currency.USD);
recurringInvoiceItemDao.create(item2, context);
@@ -329,6 +335,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
@Test
public void testGetUnpaidInvoicesByAccountId() {
UUID accountId = UUID.randomUUID();
+ UUID bundleId = UUID.randomUUID();
DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCNow(), targetDate1, Currency.USD);
invoiceDao.create(invoice1, context);
@@ -339,11 +346,12 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
BigDecimal rate1 = new BigDecimal("17.0");
BigDecimal rate2 = new BigDecimal("42.0");
- RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, UUID.randomUUID(), "test plan", "test phase A", startDate, endDate,
+
+ RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase A", startDate, endDate,
rate1, rate1, Currency.USD);
recurringInvoiceItemDao.create(item1, context);
- RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, UUID.randomUUID(), "test plan", "test phase B", startDate, endDate,
+ RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase B", startDate, endDate,
rate2, rate2, Currency.USD);
recurringInvoiceItemDao.create(item2, context);
@@ -367,7 +375,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
BigDecimal rate3 = new BigDecimal("21.0");
- RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoice2.getId(), accountId, UUID.randomUUID(), "test plan", "test phase C", startDate2, endDate2,
+ RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoice2.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase C", startDate2, endDate2,
rate3, rate3, Currency.USD);
recurringInvoiceItemDao.create(item3, context);
@@ -398,11 +406,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
MockPlanPhase phase1 = new MockPlanPhase(recurringPrice, null, BillingPeriod.MONTHLY, PhaseType.TRIAL);
MockPlan plan1 = new MockPlan(phase1);
- Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
- ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+ Subscription subscription = getZombieSubscription();
DateTime effectiveDate1 = new DateTime(2011, 2, 1, 0, 0, 0, 0);
- BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan1, phase1, null,
+ BillingEvent event1 = createMockBillingEvent(null, subscription, effectiveDate1, plan1, phase1, null,
recurringPrice.getPrice(currency), currency, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
"testEvent1", 1L, SubscriptionTransitionType.CREATE);
@@ -420,7 +427,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
MockPlan plan2 = new MockPlan(phase2);
DateTime effectiveDate2 = new DateTime(2011, 2, 15, 0, 0, 0, 0);
- BillingEvent event2 = new DefaultBillingEvent(subscription, effectiveDate2, plan2, phase2, null,
+ BillingEvent event2 = createMockBillingEvent(null, subscription, effectiveDate2, plan2, phase2, null,
recurringPrice2.getPrice(currency), currency, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
"testEvent2", 2L, SubscriptionTransitionType.CREATE);
events.add(event2);
@@ -449,11 +456,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
MockPlanPhase phase = new MockPlanPhase(recurringPrice, null);
MockPlan plan = new MockPlan(phase);
- Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
- ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+ Subscription subscription = getZombieSubscription();
DateTime effectiveDate = buildDateTime(2011, 1, 1);
- BillingEvent event = new DefaultBillingEvent(subscription, effectiveDate, plan, phase, null,
+ BillingEvent event = createMockBillingEvent(null, subscription, effectiveDate, plan, phase, null,
recurringPrice.getPrice(currency), currency, BillingPeriod.MONTHLY, 15, BillingModeType.IN_ADVANCE,
"testEvent", 1L, SubscriptionTransitionType.CREATE);
BillingEventSet events = new BillingEventSet();
@@ -467,6 +473,13 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
assertEquals(invoice.getTotalAmount().compareTo(ZERO), 0);
}
+ private Subscription getZombieSubscription() {
+ Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+ ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+ ((ZombieControl) subscription).addResult("getBundleId", UUID.randomUUID());
+ return subscription;
+ }
+
@Test
public void testInvoiceForFreeTrialWithRecurringDiscount() throws InvoiceApiException, CatalogApiException {
Currency currency = Currency.USD;
@@ -482,11 +495,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
MockPlan plan = new MockPlan();
- Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
- ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+ Subscription subscription = getZombieSubscription();
DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
- BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan, phase1, fixedPrice.getPrice(currency),
+ BillingEvent event1 = createMockBillingEvent(null, subscription, effectiveDate1, plan, phase1, fixedPrice.getPrice(currency),
null, currency, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
"testEvent1", 1L, SubscriptionTransitionType.CREATE);
BillingEventSet events = new BillingEventSet();
@@ -502,7 +514,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
invoiceList.add(invoice1);
DateTime effectiveDate2 = effectiveDate1.plusDays(30);
- BillingEvent event2 = new DefaultBillingEvent(subscription, effectiveDate2, plan, phase2, null,
+ BillingEvent event2 = createMockBillingEvent(null, subscription, effectiveDate2, plan, phase2, null,
recurringPrice.getPrice(currency), currency, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
"testEvent2", 2L, SubscriptionTransitionType.CHANGE);
events.add(event2);
@@ -542,11 +554,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
MockPlan plan = new MockPlan();
- Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
- ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+ Subscription subscription = getZombieSubscription();
DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
- BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan, phase1,
+ BillingEvent event1 = createMockBillingEvent(null, subscription, effectiveDate1, plan, phase1,
fixedPrice.getPrice(currency), null, currency,
BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
"testEvent1", 1L, SubscriptionTransitionType.CREATE);
@@ -554,7 +565,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
events.add(event1);
DateTime effectiveDate2 = effectiveDate1.plusDays(30);
- BillingEvent event2 = new DefaultBillingEvent(subscription, effectiveDate2, plan, phase2, null,
+ BillingEvent event2 = createMockBillingEvent(null, subscription, effectiveDate2, plan, phase2, null,
recurringPrice.getPrice(currency), currency, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
"testEvent2", 2L, SubscriptionTransitionType.CHANGE);
events.add(event2);
@@ -578,8 +589,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
DateTime targetDate1 = DateTime.now().plusMonths(1);
DateTime targetDate2 = DateTime.now().plusMonths(2);
- Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
- ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+ Subscription subscription = getZombieSubscription();
Plan plan = BrainDeadProxyFactory.createBrainDeadProxyFor(Plan.class);
((ZombieControl) plan).addResult("getName", "plan");
@@ -593,10 +603,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
BillingEventSet events = new BillingEventSet();
List<Invoice> invoices = new ArrayList<Invoice>();
- BillingEvent event1 = new DefaultBillingEvent(subscription, targetDate1, plan, phase1, null,
- TEN, currency,
- BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
- "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
+ BillingEvent event1 = createMockBillingEvent(null, subscription, targetDate1, plan, phase1, null,
+ TEN, currency,
+ BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+ "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
events.add(event1);
Invoice invoice1 = generator.generateInvoice(UUID.randomUUID(), events, invoices, targetDate1, Currency.USD);
@@ -605,10 +615,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
invoice1 = invoiceDao.getById(invoice1.getId());
assertNotNull(invoice1.getInvoiceNumber());
- BillingEvent event2 = new DefaultBillingEvent(subscription, targetDate1, plan, phase2, null,
- TWENTY, currency,
- BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
- "testEvent2", 2L, SubscriptionTransitionType.CHANGE);
+ BillingEvent event2 = createMockBillingEvent(null, subscription, targetDate1, plan, phase2, null,
+ TWENTY, currency,
+ BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+ "testEvent2", 2L, SubscriptionTransitionType.CHANGE);
events.add(event2);
Invoice invoice2 = generator.generateInvoice(UUID.randomUUID(), events, invoices, targetDate2, Currency.USD);
invoiceDao.create(invoice2, context);
@@ -618,8 +628,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
@Test
public void testAddingWrittenOffTag() throws InvoiceApiException {
- Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
- ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+ Subscription subscription = getZombieSubscription();
Plan plan = BrainDeadProxyFactory.createBrainDeadProxyFor(Plan.class);
((ZombieControl) plan).addResult("getName", "plan");
@@ -631,10 +640,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
Currency currency = Currency.USD;
// create pseudo-random invoice
- BillingEvent event1 = new DefaultBillingEvent(subscription, targetDate1, plan, phase1, null,
- TEN, currency,
- BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
- "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
+ BillingEvent event1 = createMockBillingEvent(null, subscription, targetDate1, plan, phase1, null,
+ TEN, currency,
+ BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+ "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
BillingEventSet events = new BillingEventSet();
events.add(event1);
@@ -644,13 +653,12 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
invoiceDao.addControlTag(ControlTagType.WRITTEN_OFF, invoice.getId(), context);
Invoice savedInvoice = invoiceDao.getById(invoice.getId());
- assertTrue(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF.toString()));
+ assertTrue(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF));
}
@Test
public void testRemoveWrittenOffTag() throws InvoiceApiException {
- Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
- ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+ Subscription subscription = getZombieSubscription();
Plan plan = BrainDeadProxyFactory.createBrainDeadProxyFor(Plan.class);
((ZombieControl) plan).addResult("getName", "plan");
@@ -662,10 +670,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
Currency currency = Currency.USD;
// create pseudo-random invoice
- BillingEvent event1 = new DefaultBillingEvent(subscription, targetDate1, plan, phase1, null,
- TEN, currency,
- BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
- "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
+ BillingEvent event1 = createMockBillingEvent(null, subscription, targetDate1, plan, phase1, null,
+ TEN, currency,
+ BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+ "testEvent1", 1L, SubscriptionTransitionType.CHANGE);
BillingEventSet events = new BillingEventSet();
events.add(event1);
@@ -675,10 +683,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
invoiceDao.addControlTag(ControlTagType.WRITTEN_OFF, invoice.getId(), context);
Invoice savedInvoice = invoiceDao.getById(invoice.getId());
- assertTrue(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF.toString()));
+ assertTrue(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF));
invoiceDao.removeControlTag(ControlTagType.WRITTEN_OFF, invoice.getId(), context);
savedInvoice = invoiceDao.getById(invoice.getId());
- assertFalse(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF.toString()));
+ assertFalse(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF));
}
}
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 464ef86..9acde8e 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
@@ -16,19 +16,20 @@
package com.ning.billing.invoice.dao;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.model.DefaultInvoice;
-import com.ning.billing.invoice.model.RecurringInvoiceItem;
-import org.joda.time.DateTime;
-import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
+import org.joda.time.DateTime;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
@Test(groups = {"invoicing", "invoicing-invoiceDao"})
public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
@@ -36,12 +37,13 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
public void testInvoiceItemCreation() {
UUID accountId = UUID.randomUUID();
UUID invoiceId = UUID.randomUUID();
+ UUID bundleId = UUID.randomUUID();
UUID subscriptionId = UUID.randomUUID();
DateTime startDate = new DateTime(2011, 10, 1, 0, 0, 0, 0);
DateTime endDate = new DateTime(2011, 11, 1, 0, 0, 0, 0);
BigDecimal rate = new BigDecimal("20.00");
- RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, subscriptionId, "test plan", "test phase", startDate, endDate,
+ RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "test plan", "test phase", startDate, endDate,
rate, rate, Currency.USD);
recurringInvoiceItemDao.create(item, context);
@@ -63,12 +65,14 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
public void testGetInvoiceItemsBySubscriptionId() {
UUID accountId = UUID.randomUUID();
UUID subscriptionId = UUID.randomUUID();
+ UUID bundleId = UUID.randomUUID();
DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
BigDecimal rate = new BigDecimal("20.00");
for (int i = 0; i < 3; i++) {
UUID invoiceId = UUID.randomUUID();
- RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, subscriptionId,
+
+ RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId,
"test plan", "test phase", startDate.plusMonths(i), startDate.plusMonths(i + 1),
rate, rate, Currency.USD);
recurringInvoiceItemDao.create(item, context);
@@ -82,13 +86,15 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
public void testGetInvoiceItemsByInvoiceId() {
UUID accountId = UUID.randomUUID();
UUID invoiceId = UUID.randomUUID();
+ UUID bundleId = UUID.randomUUID();
DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
BigDecimal rate = new BigDecimal("20.00");
for (int i = 0; i < 5; i++) {
UUID subscriptionId = UUID.randomUUID();
BigDecimal amount = rate.multiply(new BigDecimal(i + 1));
- RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, subscriptionId,
+
+ RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId,
"test plan", "test phase", startDate, startDate.plusMonths(1),
amount, amount, Currency.USD);
recurringInvoiceItemDao.create(item, context);
@@ -101,6 +107,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
@Test(groups = "slow")
public void testGetInvoiceItemsByAccountId() {
UUID accountId = UUID.randomUUID();
+ UUID bundleId = UUID.randomUUID();
DateTime targetDate = new DateTime(2011, 5, 23, 0, 0, 0, 0);
DefaultInvoice invoice = new DefaultInvoice(accountId, clock.getUTCNow(), targetDate, Currency.USD);
@@ -111,7 +118,8 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
BigDecimal rate = new BigDecimal("20.00");
UUID subscriptionId = UUID.randomUUID();
- RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, subscriptionId,
+
+ RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId,
"test plan", "test phase", startDate, startDate.plusMonths(1),
rate, rate, Currency.USD);
recurringInvoiceItemDao.create(item, context);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
index dec7697..ea8345d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
@@ -23,16 +23,16 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
-import com.ning.billing.invoice.api.InvoicePayment;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.bus.Bus;
-import com.ning.billing.util.tag.ControlTagType;
import org.joda.time.DateTime;
import com.google.inject.Inject;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.api.user.DefaultInvoiceCreationNotification;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.invoice.api.user.DefaultInvoiceCreationEvent;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.tag.ControlTagType;
public class MockInvoiceDao implements InvoiceDao {
private final Bus eventBus;
@@ -50,9 +50,9 @@ public class MockInvoiceDao implements InvoiceDao {
invoices.put(invoice.getId(), invoice);
}
try {
- eventBus.post(new DefaultInvoiceCreationNotification(invoice.getId(), invoice.getAccountId(),
+ eventBus.post(new DefaultInvoiceCreationEvent(invoice.getId(), invoice.getAccountId(),
invoice.getBalance(), invoice.getCurrency(),
- invoice.getInvoiceDate()));
+ invoice.getInvoiceDate(), null));
}
catch (Bus.EventBusException ex) {
throw new RuntimeException(ex);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
index fd15171..081b21f 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
@@ -16,38 +16,43 @@
package com.ning.billing.invoice.glue;
+import static org.testng.Assert.assertNotNull;
+
import java.io.IOException;
import java.net.URL;
-import com.ning.billing.invoice.api.test.InvoiceTestApi;
+import org.skife.jdbi.v2.IDBI;
+
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.catalog.glue.CatalogModule;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.invoice.api.InvoiceNotifier;
import com.ning.billing.invoice.api.test.DefaultInvoiceTestApi;
+import com.ning.billing.invoice.api.test.InvoiceTestApi;
import com.ning.billing.invoice.dao.InvoicePaymentSqlDao;
import com.ning.billing.invoice.dao.RecurringInvoiceItemSqlDao;
import com.ning.billing.invoice.notification.MockNextBillingDateNotifier;
import com.ning.billing.invoice.notification.MockNextBillingDatePoster;
import com.ning.billing.invoice.notification.NextBillingDateNotifier;
import com.ning.billing.invoice.notification.NextBillingDatePoster;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
+import com.ning.billing.junction.api.BillingApi;
import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.mock.glue.MockEntitlementModule;
import com.ning.billing.util.callcontext.CallContextFactory;
import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.glue.FieldStoreModule;
-import com.ning.billing.util.glue.GlobalLockerModule;
-import com.ning.billing.util.glue.TagStoreModule;
-import org.skife.jdbi.v2.IDBI;
-
-import com.ning.billing.account.api.AccountUserApi;
-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.clock.Clock;
import com.ning.billing.util.clock.DefaultClock;
+import com.ning.billing.util.email.templates.TemplateModule;
import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.FieldStoreModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
+import com.ning.billing.util.glue.TagStoreModule;
import com.ning.billing.util.notificationq.MockNotificationQueueService;
import com.ning.billing.util.notificationq.NotificationQueueService;
-import static org.testng.Assert.assertNotNull;
-
-public class InvoiceModuleWithEmbeddedDb extends InvoiceModule {
+public class InvoiceModuleWithEmbeddedDb extends DefaultInvoiceModule {
private final MysqlTestingHelper helper = new MysqlTestingHelper();
private IDBI dbi;
@@ -80,9 +85,10 @@ public class InvoiceModuleWithEmbeddedDb extends InvoiceModule {
}
@Override
- protected void installNotifier() {
+ protected void installNotifiers() {
bind(NextBillingDateNotifier.class).to(MockNextBillingDateNotifier.class).asEagerSingleton();
bind(NextBillingDatePoster.class).to(MockNextBillingDatePoster.class).asEagerSingleton();
+ bind(InvoiceNotifier.class).to(NullInvoiceNotifier.class).asEagerSingleton();
}
@Override
@@ -94,20 +100,27 @@ public class InvoiceModuleWithEmbeddedDb extends InvoiceModule {
bind(Clock.class).to(DefaultClock.class).asEagerSingleton();
bind(CallContextFactory.class).to(DefaultCallContextFactory.class).asEagerSingleton();
- install(new FieldStoreModule());
+ install(new FieldStoreModule());
install(new TagStoreModule());
installNotificationQueue();
// install(new AccountModule());
bind(AccountUserApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class));
+
+ BillingApi billingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
+ ((ZombieControl) billingApi).addResult("setChargedThroughDateFromTransaction", BrainDeadProxyFactory.ZOMBIE_VOID);
+ bind(BillingApi.class).toInstance(billingApi);
+
install(new CatalogModule());
- install(new EntitlementModule());
+ install(new MockEntitlementModule());
install(new GlobalLockerModule());
super.configure();
bind(InvoiceTestApi.class).to(DefaultInvoiceTestApi.class).asEagerSingleton();
install(new BusModule());
+ install(new TemplateModule());
+
}
private static void loadSystemPropertiesFromClasspath(final String resource) {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
index fe4feb4..521d83d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
@@ -16,18 +16,17 @@
package com.ning.billing.invoice.glue;
+import com.ning.billing.invoice.api.InvoiceNotifier;
import com.ning.billing.invoice.dao.InvoiceDao;
import com.ning.billing.invoice.dao.MockInvoiceDao;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
import com.ning.billing.util.globallocker.GlobalLocker;
import com.ning.billing.util.globallocker.MockGlobalLocker;
-import com.ning.billing.util.glue.CallContextModule;
import com.ning.billing.util.glue.FieldStoreModule;
-import com.ning.billing.util.glue.TagStoreModule;
-import org.skife.jdbi.v2.Call;
-public class InvoiceModuleWithMocks extends InvoiceModule {
- @Override
+public class InvoiceModuleWithMocks extends DefaultInvoiceModule {
+ @Override
protected void installInvoiceDao() {
bind(MockInvoiceDao.class).asEagerSingleton();
bind(InvoiceDao.class).to(MockInvoiceDao.class);
@@ -35,18 +34,13 @@ public class InvoiceModuleWithMocks extends InvoiceModule {
}
@Override
- protected void installGlobalLocker() {
- bind(GlobalLocker.class).to(MockGlobalLocker.class).asEagerSingleton();
- }
-
- @Override
protected void installInvoiceListener() {
}
@Override
- protected void installNotifier() {
-
+ protected void installNotifiers() {
+ bind(InvoiceNotifier.class).to(NullInvoiceNotifier.class).asEagerSingleton();
}
@Override
@@ -55,15 +49,7 @@ public class InvoiceModuleWithMocks extends InvoiceModule {
}
@Override
- protected void installInvoiceMigrationApi() {
-
- }
-
- @Override
- public void configure() {
- super.configure();
+ public void installInvoiceMigrationApi() {
- install(new FieldStoreModule());
- //install(new TagStoreModule());
}
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/HtmlInvoiceGeneratorTest.java b/invoice/src/test/java/com/ning/billing/invoice/HtmlInvoiceGeneratorTest.java
new file mode 100644
index 0000000..95f39c9
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/HtmlInvoiceGeneratorTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
+import com.ning.billing.invoice.template.HtmlInvoiceGenerator;
+import com.ning.billing.invoice.template.formatters.DefaultInvoiceFormatterFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.util.email.templates.MustacheTemplateEngine;
+import com.ning.billing.util.email.templates.TemplateEngine;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+import org.joda.time.DateTime;
+import org.skife.config.ConfigurationObjectFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import static org.testng.Assert.assertNotNull;
+
+@Test(groups = {"fast", "email"})
+public class HtmlInvoiceGeneratorTest {
+ private HtmlInvoiceGenerator g;
+ private final static String TEST_TEMPLATE_NAME = "HtmlInvoiceTemplate";
+
+ @BeforeClass
+ public void setup() {
+ TranslatorConfig config = new ConfigurationObjectFactory(System.getProperties()).build(TranslatorConfig.class);
+ TemplateEngine templateEngine = new MustacheTemplateEngine();
+ InvoiceFormatterFactory factory = new DefaultInvoiceFormatterFactory();
+ g = new HtmlInvoiceGenerator(factory, templateEngine, config);
+ }
+
+ @Test
+ public void testGenerateInvoice() throws Exception {
+ String output = g.generateInvoice(createAccount(), createInvoice(), TEST_TEMPLATE_NAME);
+ assertNotNull(output);
+ System.out.print(output);
+ }
+
+ private Account createAccount() {
+ Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+ ZombieControl zombieControl = (ZombieControl) account;
+ zombieControl.addResult("getExternalKey", "1234abcd");
+ zombieControl.addResult("getName", "Jim Smith");
+ zombieControl.addResult("getFirstNameLength", 3);
+ zombieControl.addResult("getEmail", "jim.smith@mail.com");
+ zombieControl.addResult("getLocale", Locale.US.toString());
+ zombieControl.addResult("getAddress1", "123 Some Street");
+ zombieControl.addResult("getAddress2", "Apt 456");
+ zombieControl.addResult("getCity", "Some City");
+ zombieControl.addResult("getStateOrProvince", "Some State");
+ zombieControl.addResult("getPostalCode", "12345-6789");
+ zombieControl.addResult("getCountry", "USA");
+ zombieControl.addResult("getPhone", "123-456-7890");
+
+ return account;
+ }
+
+ private Invoice createInvoice() {
+ DateTime startDate = new DateTime().minusMonths(1);
+ DateTime endDate = new DateTime();
+
+ BigDecimal price1 = new BigDecimal("29.95");
+ BigDecimal price2 = new BigDecimal("59.95");
+ Invoice dummyInvoice = BrainDeadProxyFactory.createBrainDeadProxyFor(Invoice.class);
+ ZombieControl zombie = (ZombieControl) dummyInvoice;
+ zombie.addResult("getInvoiceDate", startDate);
+ zombie.addResult("getInvoiceNumber", 42);
+ zombie.addResult("getCurrency", Currency.USD);
+ zombie.addResult("getTotalAmount", price1.add(price2));
+ zombie.addResult("getAmountPaid", BigDecimal.ZERO);
+ zombie.addResult("getBalance", price1.add(price2));
+
+ List<InvoiceItem> items = new ArrayList<InvoiceItem>();
+ items.add(createInvoiceItem(price1, "Domain 1", startDate, endDate, "ning-plus"));
+ items.add(createInvoiceItem(price2, "Domain 2", startDate, endDate, "ning-pro"));
+ zombie.addResult("getInvoiceItems", items);
+
+ return dummyInvoice;
+ }
+
+ private InvoiceItem createInvoiceItem(BigDecimal amount, String networkName, DateTime startDate, DateTime endDate, String planName) {
+ InvoiceItem item = BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceItem.class);
+ ZombieControl zombie = (ZombieControl) item;
+ zombie.addResult("getAmount", amount);
+ zombie.addResult("getStartDate", startDate);
+ zombie.addResult("getEndDate", endDate);
+ zombie.addResult("getPlanName", planName);
+ zombie.addResult("getDescription", networkName);
+
+
+ return item;
+ }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/MockModule.java b/invoice/src/test/java/com/ning/billing/invoice/MockModule.java
index 99f12b4..c756020 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/MockModule.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/MockModule.java
@@ -16,32 +16,31 @@
package com.ning.billing.invoice;
-import com.ning.billing.util.callcontext.CallContextFactory;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.glue.FieldStoreModule;
-import com.ning.billing.util.glue.TagStoreModule;
import org.skife.config.ConfigurationObjectFactory;
import org.skife.jdbi.v2.IDBI;
import com.google.inject.AbstractModule;
-import com.ning.billing.account.api.AccountUserApi;
import com.ning.billing.catalog.glue.CatalogModule;
import com.ning.billing.dbi.DBIProvider;
import com.ning.billing.dbi.DbiConfig;
import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.entitlement.glue.EntitlementModule;
-import com.ning.billing.invoice.glue.InvoiceModule;
-import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
+import com.ning.billing.invoice.glue.DefaultInvoiceModule;
+import com.ning.billing.invoice.template.formatters.DefaultInvoiceFormatterFactory;
+import com.ning.billing.mock.glue.MockJunctionModule;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.email.EmailModule;
+import com.ning.billing.util.email.templates.TemplateModule;
import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.FieldStoreModule;
import com.ning.billing.util.glue.GlobalLockerModule;
import com.ning.billing.util.glue.NotificationQueueModule;
-
+import com.ning.billing.util.glue.TagStoreModule;
public class MockModule extends AbstractModule {
- public static final String PLUGIN_NAME = "yoyo";
-
@Override
protected void configure() {
bind(Clock.class).to(ClockMock.class).asEagerSingleton();
@@ -61,24 +60,20 @@ public class MockModule extends AbstractModule {
bind(IDBI.class).toInstance(dbi);
}
+ bind(InvoiceFormatterFactory.class).to(DefaultInvoiceFormatterFactory.class).asEagerSingleton();
+
+ install(new EmailModule());
install(new GlobalLockerModule());
install(new NotificationQueueModule());
-// install(new AccountModule());
- bind(AccountUserApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class));
-
- installEntitlementModule();
install(new CatalogModule());
install(new BusModule());
installInvoiceModule();
+ install(new MockJunctionModule());
+ install(new TemplateModule());
}
-
- protected void installEntitlementModule() {
- install(new EntitlementModule());
- }
protected void installInvoiceModule() {
- install(new InvoiceModule());
+ install(new DefaultInvoiceModule());
}
-
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/notification/MockNextBillingDatePoster.java b/invoice/src/test/java/com/ning/billing/invoice/notification/MockNextBillingDatePoster.java
index 493ba5d..88ff62a 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/notification/MockNextBillingDatePoster.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/MockNextBillingDatePoster.java
@@ -16,11 +16,11 @@
package com.ning.billing.invoice.notification;
+import java.util.UUID;
+
import org.joda.time.DateTime;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
-import java.util.UUID;
-
public class MockNextBillingDatePoster implements NextBillingDatePoster {
@Override
public void insertNextBillingNotification(Transmogrifier transactionalDao, UUID subscriptionId, DateTime futureNotificationTime) {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
index 26df2af..a0eb6d5 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
@@ -24,23 +24,6 @@ import java.sql.SQLException;
import java.util.UUID;
import java.util.concurrent.Callable;
-import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.account.api.MockAccountUserApi;
-import com.ning.billing.entitlement.api.billing.DefaultEntitlementBillingApi;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
-import com.ning.billing.invoice.InvoiceDispatcher;
-import com.ning.billing.invoice.dao.DefaultInvoiceDao;
-import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
-import com.ning.billing.invoice.model.InvoiceGenerator;
-import com.ning.billing.util.callcontext.CallContextFactory;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.customfield.dao.AuditedCustomFieldDao;
-import com.ning.billing.util.customfield.dao.CustomFieldDao;
-import com.ning.billing.util.globallocker.GlobalLocker;
-import com.ning.billing.util.globallocker.MySqlGlobalLocker;
-import com.ning.billing.util.tag.dao.AuditedTagDao;
-import com.ning.billing.util.tag.dao.TagDao;
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
import org.skife.config.ConfigurationObjectFactory;
@@ -50,31 +33,36 @@ import org.skife.jdbi.v2.TransactionStatus;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
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.catalog.DefaultCatalogService;
-import com.ning.billing.catalog.api.CatalogService;
-import com.ning.billing.config.CatalogConfig;
+import com.ning.billing.catalog.MockCatalogModule;
import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.dbi.DBIProvider;
+import com.ning.billing.dbi.DbiConfig;
import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
import com.ning.billing.entitlement.api.user.EntitlementUserApi;
import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.engine.dao.EntitlementDao;
-import com.ning.billing.entitlement.engine.dao.EntitlementSqlDao;
+import com.ning.billing.invoice.InvoiceDispatcher;
import com.ning.billing.invoice.InvoiceListener;
-import com.ning.billing.lifecycle.KillbillService.ServiceException;
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import com.ning.billing.lifecycle.KillbillService;
import com.ning.billing.mock.BrainDeadProxyFactory;
import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.mock.glue.MockJunctionModule;
import com.ning.billing.util.bus.Bus;
-import com.ning.billing.util.bus.InMemoryBus;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.notificationq.DefaultNotificationQueueService;
+import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.BusModule.BusType;
+import com.ning.billing.util.glue.NotificationQueueModule;
import com.ning.billing.util.notificationq.DummySqlTest;
import com.ning.billing.util.notificationq.NotificationQueueService;
import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
@@ -88,6 +76,7 @@ public class TestNextBillingDateNotifier {
private InvoiceListenerMock listener;
private NotificationQueueService notificationQueueService;
+
private static final class InvoiceListenerMock extends InvoiceListener {
int eventCount = 0;
UUID latestSubscriptionId = null;
@@ -113,37 +102,37 @@ public class TestNextBillingDateNotifier {
}
+ @BeforeMethod(groups={"slow"})
+ public void cleanDb() {
+ helper.cleanupAllTables();
+ }
@BeforeClass(groups={"slow"})
- public void setup() throws ServiceException, IOException, ClassNotFoundException, SQLException {
+ public void setup() throws KillbillService.ServiceException, IOException, ClassNotFoundException, SQLException {
//TestApiBase.loadSystemPropertiesFromClasspath("/entitlement.properties");
final Injector g = Guice.createInjector(Stage.PRODUCTION, new AbstractModule() {
- @Override
+
protected void configure() {
- bind(Clock.class).to(ClockMock.class).asEagerSingleton();
- bind(CallContextFactory.class).to(DefaultCallContextFactory.class).asEagerSingleton();
- bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
- bind(NotificationQueueService.class).to(DefaultNotificationQueueService.class).asEagerSingleton();
- final InvoiceConfig invoiceConfig = new ConfigurationObjectFactory(System.getProperties()).build(InvoiceConfig.class);
- bind(InvoiceConfig.class).toInstance(invoiceConfig);
- final CatalogConfig catalogConfig = new ConfigurationObjectFactory(System.getProperties()).build(CatalogConfig.class);
- bind(CatalogConfig.class).toInstance(catalogConfig);
- bind(CatalogService.class).to(DefaultCatalogService.class).asEagerSingleton();
+ install(new MockClockModule());
+ install(new BusModule(BusType.MEMORY));
+ install(new InvoiceModuleWithMocks());
+ install(new MockJunctionModule());
+ install(new MockCatalogModule());
+ install(new NotificationQueueModule());
+
final MysqlTestingHelper helper = new MysqlTestingHelper();
bind(MysqlTestingHelper.class).toInstance(helper);
- IDBI dbi = helper.getDBI();
- bind(IDBI.class).toInstance(dbi);
- bind(TagDao.class).to(AuditedTagDao.class).asEagerSingleton();
- bind(EntitlementDao.class).to(EntitlementSqlDao.class).asEagerSingleton();
- bind(CustomFieldDao.class).to(AuditedCustomFieldDao.class).asEagerSingleton();
- bind(GlobalLocker.class).to(MySqlGlobalLocker.class).asEagerSingleton();
- bind(InvoiceGenerator.class).to(DefaultInvoiceGenerator.class).asEagerSingleton();
- bind(InvoiceDao.class).to(DefaultInvoiceDao.class);
- bind(NextBillingDatePoster.class).to(DefaultNextBillingDatePoster.class).asEagerSingleton();
- bind(AccountUserApi.class).to(MockAccountUserApi.class).asEagerSingleton();
- bind(EntitlementBillingApi.class).to(DefaultEntitlementBillingApi.class).asEagerSingleton();
- bind(EntitlementUserApi.class).to(DefaultEntitlementUserApi.class).asEagerSingleton();
- }
+ if (helper.isUsingLocalInstance()) {
+ bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+ final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
+ bind(DbiConfig.class).toInstance(config);
+ } else {
+ final IDBI dbi = helper.getDBI();
+ bind(IDBI.class).toInstance(dbi);
+ }
+
+
+ }
});
clock = g.getInstance(Clock.class);
@@ -167,16 +156,14 @@ public class TestNextBillingDateNotifier {
private void startMysql() throws IOException, ClassNotFoundException, SQLException {
final String ddl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
final String testDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl_test.sql"));
- final String entitlementDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
helper.startMysql();
helper.initDb(ddl);
helper.initDb(testDdl);
- helper.initDb(entitlementDdl);
}
@Test(enabled=true, groups="slow")
- public void test() throws Exception {
+ public void testInvoiceNotifier() throws Exception {
final UUID subscriptionId = new UUID(0L,1L);
final DateTime now = new DateTime();
final DateTime readyTime = now.plusMillis(2000);
@@ -213,8 +200,9 @@ public class TestNextBillingDateNotifier {
Assert.assertEquals(listener.getLatestSubscriptionId(), subscriptionId);
}
- @AfterClass(alwaysRun = true)
+ @AfterClass(groups="slow")
public void tearDown() {
+ notifier.stop();
helper.stopMysql();
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
index 7008c90..26403d0 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceDispatcher.java
@@ -23,11 +23,9 @@ import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.clock.Clock;
+import com.ning.billing.invoice.api.InvoiceNotifier;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
+import com.ning.billing.invoice.tests.InvoicingTestBase;
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
import org.slf4j.Logger;
@@ -48,33 +46,34 @@ 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.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
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.billing.EntitlementBillingApi;
import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceApiException;
-import com.ning.billing.invoice.api.InvoiceUserApi;
import com.ning.billing.invoice.dao.InvoiceDao;
import com.ning.billing.invoice.model.InvoiceGenerator;
import com.ning.billing.invoice.notification.NextBillingDateNotifier;
+import com.ning.billing.junction.api.BillingApi;
import com.ning.billing.mock.BrainDeadProxyFactory;
import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.mock.glue.MockJunctionModule;
import com.ning.billing.util.bus.BusService;
import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.globallocker.GlobalLocker;
@Test(groups = "slow")
@Guice(modules = {MockModule.class})
-public class TestInvoiceDispatcher {
+public class TestInvoiceDispatcher extends InvoicingTestBase {
private Logger log = LoggerFactory.getLogger(TestInvoiceDispatcher.class);
@Inject
- private InvoiceUserApi invoiceUserApi;
-
- @Inject
private InvoiceGenerator generator;
@Inject
@@ -89,8 +88,11 @@ public class TestInvoiceDispatcher {
@Inject
private NextBillingDateNotifier notifier;
- @Inject
- private BusService busService;
+ @Inject
+ private BusService busService;
+
+ @Inject
+ private BillingApi billingApi;
@Inject
private Clock clock;
@@ -100,13 +102,11 @@ public class TestInvoiceDispatcher {
@BeforeSuite(groups = "slow")
public void setup() throws IOException
{
- final String entitlementDdl = IOUtils.toString(TestInvoiceDispatcher.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
final String invoiceDdl = IOUtils.toString(TestInvoiceDispatcher.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
final String utilDdl = IOUtils.toString(TestInvoiceDispatcher.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
helper.startMysql();
- helper.initDb(entitlementDdl);
helper.initDb(invoiceDdl);
helper.initDb(utilDdl);
notifier.initialize();
@@ -115,6 +115,7 @@ public class TestInvoiceDispatcher {
context = new DefaultCallContextFactory(clock).createCallContext("Miracle Max", CallOrigin.TEST, UserType.TEST);
busService.getBus().start();
+ ((ZombieControl)billingApi).addResult("setChargedThroughDate", BrainDeadProxyFactory.ZOMBIE_VOID);
}
@AfterClass(alwaysRun = true)
@@ -139,25 +140,28 @@ public class TestInvoiceDispatcher {
((ZombieControl)accountUserApi).addResult("getAccountById", account);
((ZombieControl)account).addResult("getCurrency", Currency.USD);
((ZombieControl)account).addResult("getId", accountId);
+ ((ZombieControl)account).addResult(("isNotifiedForInvoices"), true);
Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
- ((ZombieControl)subscription).addResult("getId", subscriptionId);
+ ((ZombieControl)subscription).addResult("getId", subscriptionId);
+ ((ZombieControl)subscription).addResult("getBundleId", new UUID(0L,0L));
SortedSet<BillingEvent> events = new TreeSet<BillingEvent>();
Plan plan = MockPlan.createBicycleNoTrialEvergreen1USD();
PlanPhase planPhase = MockPlanPhase.create1USDMonthlyEvergreen();
DateTime effectiveDate = new DateTime().minusDays(1);
Currency currency = Currency.USD;
BigDecimal fixedPrice = null;
- events.add(new DefaultBillingEvent(subscription, effectiveDate,plan, planPhase,
- fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
- BillingModeType.IN_ADVANCE, "", 1L, SubscriptionTransitionType.CREATE));
+ events.add(createMockBillingEvent(account, subscription, effectiveDate, plan, planPhase,
+ fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
+ BillingModeType.IN_ADVANCE, "", 1L, SubscriptionTransitionType.CREATE));
- EntitlementBillingApi entitlementBillingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementBillingApi.class);
- ((ZombieControl)entitlementBillingApi).addResult("getBillingEventsForAccount", events);
+ ((ZombieControl) billingApi).addResult("getBillingEventsForAccountAndUpdateAccountBCD", events);
DateTime target = new DateTime();
- InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountUserApi, entitlementBillingApi, invoiceDao, locker);
+ InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
+ InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountUserApi, billingApi, invoiceDao,
+ invoiceNotifier, locker, busService.getBus(), clock);
Invoice invoice = dispatcher.processAccount(accountId, target, true, context);
Assert.assertNotNull(invoice);
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 6e6b9b3..6c3dd65 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,6 +16,21 @@
package com.ning.billing.invoice.tests;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
+import org.joda.time.DateTime;
+import org.testng.annotations.Test;
+
import com.ning.billing.catalog.DefaultPrice;
import com.ning.billing.catalog.MockInternationalPrice;
import com.ning.billing.catalog.MockPlan;
@@ -27,13 +42,10 @@ import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
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.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceApiException;
import com.ning.billing.invoice.model.BillingEventSet;
@@ -43,56 +55,33 @@ import com.ning.billing.invoice.model.InvoiceGenerator;
import com.ning.billing.invoice.model.RecurringInvoiceItem;
import com.ning.billing.mock.BrainDeadProxyFactory;
import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
-
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.DefaultClock;
-import org.joda.time.DateTime;
-import org.testng.annotations.Test;
-
-import javax.annotation.Nullable;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
@Test(groups = {"fast", "invoicing", "invoiceGenerator"})
public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
- private final Clock clock = new DefaultClock();
- private final InvoiceConfig invoiceConfig = new InvoiceConfig() {
- @Override
- public long getDaoClaimTimeMs() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getDaoMaxReadyEvents() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public long getNotificationSleepTimeMs() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isEventProcessingOff() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getNumberOfMonthsInFuture() {
- return 36;
- }
- };
-
private final InvoiceGenerator generator;
public DefaultInvoiceGeneratorTests() {
super();
+
+ Clock clock = new DefaultClock();
+ InvoiceConfig invoiceConfig = new InvoiceConfig() {
+ @Override
+ public long getSleepTimeMs() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getNumberOfMonthsInFuture() {
+ return 36;
+ }
+
+ @Override
+ public boolean isNotificationProcessingOff() {
+ throw new UnsupportedOperationException();
+ }
+ };
this.generator = new DefaultInvoiceGenerator(clock, invoiceConfig);
}
@@ -118,7 +107,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
public void testWithSingleMonthlyEvent() throws InvoiceApiException, CatalogApiException {
BillingEventSet events = new BillingEventSet();
- Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+ Subscription sub = createZombieSubscription();
DateTime startDate = buildDateTime(2011, 9, 1);
Plan plan = new MockPlan();
@@ -138,11 +127,23 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
assertEquals(invoice.getInvoiceItems().get(0).getSubscriptionId(), sub.getId());
}
+ private Subscription createZombieSubscription() {
+ return createZombieSubscription(UUID.randomUUID());
+ }
+
+ private Subscription createZombieSubscription(UUID subscriptionId) {
+ Subscription sub = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+ ((ZombieControl) sub).addResult("getId", subscriptionId);
+ ((ZombieControl) sub).addResult("getBundleId", UUID.randomUUID());
+
+ return sub;
+ }
+
@Test
public void testWithSingleMonthlyEventWithLeadingProRation() throws InvoiceApiException, CatalogApiException {
BillingEventSet events = new BillingEventSet();
- Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+ Subscription sub = createZombieSubscription();
DateTime startDate = buildDateTime(2011, 9, 1);
Plan plan = new MockPlan();
@@ -176,7 +177,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
BigDecimal rate2 = TEN;
PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
- Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+ Subscription sub = createZombieSubscription();
BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1, phase1, 1);
events.add(event1);
@@ -201,7 +202,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
BigDecimal rate1 = FIVE;
PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
- Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+ Subscription sub = createZombieSubscription();
BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1,phase1, 1);
events.add(event1);
@@ -238,7 +239,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
BigDecimal rate1 = FIVE;
PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
- Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+ Subscription sub = createZombieSubscription();
BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1, phase1, 1);
events.add(event1);
@@ -265,7 +266,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
public void testSingleEventWithExistingInvoice() throws InvoiceApiException, CatalogApiException {
BillingEventSet events = new BillingEventSet();
- Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
+ Subscription sub = createZombieSubscription();
DateTime startDate = buildDateTime(2011, 9, 1);
Plan plan1 = new MockPlan();
@@ -479,8 +480,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
@Test
public void testFixedPriceLifeCycle() throws InvoiceApiException {
UUID accountId = UUID.randomUUID();
- Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
- ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+ Subscription subscription = createZombieSubscription();
Plan plan = new MockPlan("plan 1");
MockInternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(ZERO, Currency.USD));
@@ -493,17 +493,17 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
BillingEventSet events = new BillingEventSet();
- BillingEvent event1 = new DefaultBillingEvent(subscription, new DateTime("2012-01-1T00:00:00.000-08:00"),
- plan, phase1,
- ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, 1,
- BillingModeType.IN_ADVANCE, "Test Event 1", 1L,
- SubscriptionTransitionType.CREATE);
+ BillingEvent event1 = createMockBillingEvent(null, subscription, new DateTime("2012-01-1T00:00:00.000-08:00"),
+ plan, phase1,
+ ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, 1,
+ BillingModeType.IN_ADVANCE, "Test Event 1", 1L,
+ SubscriptionTransitionType.CREATE);
- BillingEvent event2 = new DefaultBillingEvent(subscription, changeDate,
- plan, phase2,
- ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, 1,
- BillingModeType.IN_ADVANCE, "Test Event 2", 2L,
- SubscriptionTransitionType.PHASE);
+ BillingEvent event2 = createMockBillingEvent(null, subscription, changeDate,
+ plan, phase2,
+ ZERO, null, Currency.USD, BillingPeriod.NO_BILLING_PERIOD, 1,
+ BillingModeType.IN_ADVANCE, "Test Event 2", 2L,
+ SubscriptionTransitionType.PHASE);
events.add(event2);
events.add(event1);
@@ -631,7 +631,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
assertNotNull(invoice2);
assertEquals(invoice2.getNumberOfItems(), 1);
assertEquals(invoice2.getInvoiceItems().get(0).getStartDate().compareTo(trialPhaseEndDate), 0);
- assertEquals(invoice2.getTotalAmount().compareTo(new BigDecimal("3.2097")), 0);
+ assertEquals(invoice2.getTotalAmount().compareTo(new BigDecimal("3.21")), 0);
invoiceList.add(invoice2);
DateTime targetDate = trialPhaseEndDate.toMutableDateTime().dayOfMonth().set(BILL_CYCLE_DAY).toDateTime();
@@ -686,16 +686,16 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
null, BillingPeriod.ANNUAL, phaseType);
}
- private DefaultBillingEvent createBillingEvent(final UUID subscriptionId, final DateTime startDate,
+ private BillingEvent createBillingEvent(final UUID subscriptionId, final DateTime startDate,
final Plan plan, final PlanPhase planPhase, final int billCycleDay) throws CatalogApiException {
- Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(subscriptionId));
+ Subscription sub = createZombieSubscription(subscriptionId);
Currency currency = Currency.USD;
- return new DefaultBillingEvent(sub, startDate, plan, planPhase,
- planPhase.getFixedPrice() == null ? null : planPhase.getFixedPrice().getPrice(currency),
- planPhase.getRecurringPrice() == null ? null : planPhase.getRecurringPrice().getPrice(currency),
- currency, planPhase.getBillingPeriod(),
- billCycleDay, BillingModeType.IN_ADVANCE, "Test", 1L, SubscriptionTransitionType.CREATE);
+ return createMockBillingEvent(null, sub, startDate, plan, planPhase,
+ planPhase.getFixedPrice() == null ? null : planPhase.getFixedPrice().getPrice(currency),
+ planPhase.getRecurringPrice() == null ? null : planPhase.getRecurringPrice().getPrice(currency),
+ currency, planPhase.getBillingPeriod(),
+ billCycleDay, BillingModeType.IN_ADVANCE, "Test", 1L, SubscriptionTransitionType.CREATE);
}
private void testInvoiceGeneration(final UUID accountId, final BillingEventSet events, final List<Invoice> existingInvoices,
@@ -710,5 +710,116 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
assertEquals(invoice.getTotalAmount(), expectedAmount);
}
+ @Test(groups = {"fast", "invoicing"})
+ public void testAddOnInvoiceGeneration() throws CatalogApiException, InvoiceApiException {
+ DateTime april25 = new DateTime(2012, 4, 25, 0, 0, 0, 0);
+
+ // create a base plan on April 25th
+ UUID accountId = UUID.randomUUID();
+ Subscription baseSubscription = createZombieSubscription();
+
+ Plan basePlan = new MockPlan("base Plan");
+ MockInternationalPrice price5 = new MockInternationalPrice(new DefaultPrice(FIVE, Currency.USD));
+ MockInternationalPrice price10 = new MockInternationalPrice(new DefaultPrice(TEN, Currency.USD));
+ MockInternationalPrice price20 = new MockInternationalPrice(new DefaultPrice(TWENTY, Currency.USD));
+ PlanPhase basePlanEvergreen = new MockPlanPhase(price10, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+
+ BillingEventSet events = new BillingEventSet();
+ events.add(createBillingEvent(baseSubscription.getId(), april25, basePlan, basePlanEvergreen, 25));
+
+ // generate invoice
+ Invoice invoice1 = generator.generateInvoice(accountId, events, null, april25, Currency.USD);
+ assertNotNull(invoice1);
+ assertEquals(invoice1.getNumberOfItems(), 1);
+ assertEquals(invoice1.getTotalAmount().compareTo(TEN), 0);
+
+ List<Invoice> invoices = new ArrayList<Invoice>();
+ invoices.add(invoice1);
+
+ // create 2 add ons on April 28th
+ DateTime april28 = new DateTime(2012, 4, 28, 0, 0, 0, 0);
+ Subscription addOnSubscription1 = createZombieSubscription();
+ Plan addOn1Plan = new MockPlan("add on 1");
+ PlanPhase addOn1PlanPhaseEvergreen = new MockPlanPhase(price5, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+ events.add(createBillingEvent(addOnSubscription1.getId(), april28, addOn1Plan, addOn1PlanPhaseEvergreen, 25));
+
+ Subscription addOnSubscription2 = createZombieSubscription();
+ Plan addOn2Plan = new MockPlan("add on 2");
+ PlanPhase addOn2PlanPhaseEvergreen = new MockPlanPhase(price20, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+ events.add(createBillingEvent(addOnSubscription2.getId(), april28, addOn2Plan, addOn2PlanPhaseEvergreen, 25));
+
+ // generate invoice
+ Invoice invoice2 = generator.generateInvoice(accountId, events, invoices, april28, Currency.USD);
+ invoices.add(invoice2);
+ assertNotNull(invoice2);
+ assertEquals(invoice2.getNumberOfItems(), 2);
+ assertEquals(invoice2.getTotalAmount().compareTo(TWENTY_FIVE.multiply(new BigDecimal("0.9")).setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD)), 0);
+
+ // perform a repair (change base plan; remove one add-on)
+ // event stream should include just two plans
+ BillingEventSet newEvents = new BillingEventSet();
+ Plan basePlan2 = new MockPlan("base plan 2");
+ MockInternationalPrice price13 = new MockInternationalPrice(new DefaultPrice(THIRTEEN, Currency.USD));
+ PlanPhase basePlan2Phase = new MockPlanPhase(price13, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+ newEvents.add(createBillingEvent(baseSubscription.getId(), april25, basePlan2, basePlan2Phase, 25));
+ newEvents.add(createBillingEvent(addOnSubscription1.getId(), april28, addOn1Plan, addOn1PlanPhaseEvergreen, 25));
+
+ // generate invoice
+ DateTime may1 = new DateTime(2012, 5, 1, 0, 0, 0, 0);
+ Invoice invoice3 = generator.generateInvoice(accountId, newEvents, invoices, may1, Currency.USD);
+ assertNotNull(invoice3);
+ assertEquals(invoice3.getNumberOfItems(), 5);
+ // -4.50 -18 - 10 (to correct the previous 2 invoices) + 4.50 + 13
+ assertEquals(invoice3.getTotalAmount().compareTo(FIFTEEN.negate()), 0);
+ }
+
+ @Test(enabled = false)
+ public void testRepairForPaidInvoice() throws CatalogApiException, InvoiceApiException {
+ // create an invoice
+ DateTime april25 = new DateTime(2012, 4, 25, 0, 0, 0, 0);
+
+ // create a base plan on April 25th
+ UUID accountId = UUID.randomUUID();
+ Subscription originalSubscription = createZombieSubscription();
+
+ Plan originalPlan = new MockPlan("original plan");
+ MockInternationalPrice price10 = new MockInternationalPrice(new DefaultPrice(TEN, Currency.USD));
+ PlanPhase originalPlanEvergreen = new MockPlanPhase(price10, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+
+ BillingEventSet events = new BillingEventSet();
+ events.add(createBillingEvent(originalSubscription.getId(), april25, originalPlan, originalPlanEvergreen, 25));
+
+ Invoice invoice1 = generator.generateInvoice(accountId, events, null, april25, Currency.USD);
+ List<Invoice> invoices = new ArrayList<Invoice>();
+ invoices.add(invoice1);
+
+ // pay the invoice
+ invoice1.addPayment(new DefaultInvoicePayment(UUID.randomUUID(), invoice1.getId(), april25, TEN, Currency.USD));
+ assertEquals(invoice1.getBalance().compareTo(ZERO), 0);
+
+ // change the plan (i.e. repair) on start date
+ events.clear();
+ Subscription newSubscription = createZombieSubscription();
+ Plan newPlan = new MockPlan("new plan");
+ MockInternationalPrice price5 = new MockInternationalPrice(new DefaultPrice(FIVE, Currency.USD));
+ PlanPhase newPlanEvergreen = new MockPlanPhase(price5, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+ events.add(createBillingEvent(newSubscription.getId(), april25, newPlan, newPlanEvergreen, 25));
+
+ // generate a new invoice
+ Invoice invoice2 = generator.generateInvoice(accountId, events, invoices, april25, Currency.USD);
+ invoices.add(invoice2);
+
+ // move items to the correct invoice (normally, the dao calls will sort that out)
+ generator.distributeItems(invoices);
+
+ // ensure that the original invoice balance is zero
+
+ assertEquals(invoice1.getBalance().compareTo(ZERO), 0);
+
+ // ensure that the account balance is correct
+ assertEquals(invoice2.getBalance().compareTo(FIVE.negate()), 0);
+
+ }
+
// TODO: Jeff C -- how do we ensure that an annual add-on is properly aligned *at the end* with the base plan?
}
\ No newline at end of file
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java
index fd37599..c08c8b8 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java
@@ -16,13 +16,14 @@
package com.ning.billing.invoice.tests.inAdvance.annual;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
import org.joda.time.DateTime;
import org.testng.annotations.Test;
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
@Test(groups = {"fast", "invoicing", "proRation"})
public class DoubleProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java
index 8d0eb06..81d5347 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java
@@ -16,11 +16,12 @@
package com.ning.billing.invoice.tests.inAdvance.annual;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.tests.inAdvance.GenericProRationTestBase;
+import java.math.BigDecimal;
+
import org.testng.annotations.Test;
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.tests.inAdvance.GenericProRationTestBase;
@Test(groups = {"fast", "invoicing", "proRation"})
public class GenericProRationTests extends GenericProRationTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java
index a009fba..68611f7 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java
@@ -16,13 +16,14 @@
package com.ning.billing.invoice.tests.inAdvance.annual;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
import org.joda.time.DateTime;
import org.testng.annotations.Test;
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
@Test(groups = {"fast", "invoicing", "proRation"})
public class LeadingProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java
index f882005..88922e2 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java
@@ -16,13 +16,14 @@
package com.ning.billing.invoice.tests.inAdvance.annual;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
import org.joda.time.DateTime;
import org.testng.annotations.Test;
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
@Test(groups = {"fast", "invoicing", "proRation"})
public class ProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java
index e0216b5..d1c2013 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java
@@ -16,13 +16,14 @@
package com.ning.billing.invoice.tests.inAdvance.annual;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
import org.joda.time.DateTime;
import org.testng.annotations.Test;
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
@Test(groups = {"fast", "invoicing", "proRation"})
public class TrailingProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java
index 60892e6..70f1f8f 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java
@@ -16,11 +16,12 @@
package com.ning.billing.invoice.tests.inAdvance;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import java.math.BigDecimal;
+
import org.joda.time.DateTime;
import org.testng.annotations.Test;
-import java.math.BigDecimal;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
@Test(groups = {"fast", "invoicing", "proRation"})
public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java
index 240b8aa..8c9b61d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java
@@ -16,13 +16,14 @@
package com.ning.billing.invoice.tests.inAdvance.monthly;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
import org.joda.time.DateTime;
import org.testng.annotations.Test;
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
@Test(groups = {"fast", "invoicing", "proRation"})
public class DoubleProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java
index 8b40db8..b749ab8 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java
@@ -16,11 +16,12 @@
package com.ning.billing.invoice.tests.inAdvance.monthly;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.tests.inAdvance.GenericProRationTestBase;
+import java.math.BigDecimal;
+
import org.testng.annotations.Test;
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.tests.inAdvance.GenericProRationTestBase;
@Test(groups = {"fast", "invoicing", "proRation"})
public class GenericProRationTests extends GenericProRationTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java
index 998e566..f723738 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java
@@ -16,13 +16,14 @@
package com.ning.billing.invoice.tests.inAdvance.monthly;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
import org.joda.time.DateTime;
import org.testng.annotations.Test;
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
@Test(groups = {"fast", "invoicing", "proRation"})
public class LeadingProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
index c3748d1..78f06b4 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
@@ -16,13 +16,14 @@
package com.ning.billing.invoice.tests.inAdvance.monthly;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
import org.joda.time.DateTime;
import org.testng.annotations.Test;
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
@Test(groups = {"fast", "invoicing", "proRation"})
public class ProRationTests extends ProRationInAdvanceTestBase {
@@ -214,9 +215,10 @@ public class ProRationTests extends ProRationInAdvanceTestBase {
DateTime targetDate = buildDateTime(2011, 4, 21);
BigDecimal expectedValue;
- expectedValue = SEVEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
+ expectedValue = SEVEN.divide(THIRTY_ONE, 2 * NUMBER_OF_DECIMALS, ROUNDING_METHOD);
expectedValue = expectedValue.add(ONE);
- expectedValue = expectedValue.add(THREE.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD));
+ expectedValue = expectedValue.add(THREE.divide(THIRTY_ONE, 2 * NUMBER_OF_DECIMALS, ROUNDING_METHOD));
+ expectedValue = expectedValue.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
testCalculateNumberOfBillingCycles(startDate, planChangeDate, targetDate, 7, expectedValue);
expectedValue = FIVE.divide(TWENTY_EIGHT, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(TWO);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java
index 6a5e5ef..581e8af 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java
@@ -16,13 +16,14 @@
package com.ning.billing.invoice.tests.inAdvance.monthly;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
import org.joda.time.DateTime;
import org.testng.annotations.Test;
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
@Test(groups = {"fast", "invoicing", "proRation"})
public class TrailingProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java
index 18bd096..b8f3848 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java
@@ -16,10 +16,11 @@
package com.ning.billing.invoice.tests.inAdvance;
+import org.testng.annotations.Test;
+
import com.ning.billing.invoice.model.BillingMode;
import com.ning.billing.invoice.model.InAdvanceBillingMode;
import com.ning.billing.invoice.tests.ProRationTestBase;
-import org.testng.annotations.Test;
@Test(groups = {"fast", "invoicing", "proRation"})
public abstract class ProRationInAdvanceTestBase extends ProRationTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java
index 184f5d5..e6c3cf3 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java
@@ -16,13 +16,14 @@
package com.ning.billing.invoice.tests.inAdvance.quarterly;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
import org.joda.time.DateTime;
import org.testng.annotations.Test;
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
@Test(groups = {"fast", "invoicing", "proRation"})
public class DoubleProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java
index c4237a6..3351807 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java
@@ -16,11 +16,12 @@
package com.ning.billing.invoice.tests.inAdvance.quarterly;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.tests.inAdvance.GenericProRationTestBase;
+import java.math.BigDecimal;
+
import org.testng.annotations.Test;
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.tests.inAdvance.GenericProRationTestBase;
@Test(groups = {"fast", "invoicing", "proRation"})
public class GenericProRationTests extends GenericProRationTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java
index 04ec683..18bb8af 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java
@@ -16,13 +16,14 @@
package com.ning.billing.invoice.tests.inAdvance.quarterly;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
import org.joda.time.DateTime;
import org.testng.annotations.Test;
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
@Test(groups = {"fast", "invoicing", "proRation"})
public class LeadingProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java
index e13db0d..2988dfe 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java
@@ -16,13 +16,14 @@
package com.ning.billing.invoice.tests.inAdvance.quarterly;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
import org.joda.time.DateTime;
import org.testng.annotations.Test;
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
@Test(groups = {"fast", "invoicing", "proRation"})
public class ProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java
index 8f63010..270518d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java
@@ -16,13 +16,14 @@
package com.ning.billing.invoice.tests.inAdvance.quarterly;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
+import java.math.BigDecimal;
+
import org.joda.time.DateTime;
import org.testng.annotations.Test;
-import java.math.BigDecimal;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.tests.inAdvance.ProRationInAdvanceTestBase;
@Test(groups = {"fast", "invoicing", "proRation"})
public class TrailingProRationTests extends ProRationInAdvanceTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
index bd8a38d..21dd092 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
@@ -16,17 +16,18 @@
package com.ning.billing.invoice.tests.inAdvance;
+import static org.testng.Assert.assertEquals;
+
+import java.math.BigDecimal;
+
+import org.joda.time.DateTime;
+import org.testng.annotations.Test;
+
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.invoice.model.BillingMode;
import com.ning.billing.invoice.model.InAdvanceBillingMode;
import com.ning.billing.invoice.model.InvalidDateSequenceException;
import com.ning.billing.invoice.tests.ProRationTestBase;
-import org.joda.time.DateTime;
-import org.testng.annotations.Test;
-
-import java.math.BigDecimal;
-
-import static org.testng.Assert.assertEquals;
@Test(groups = {"fast", "invoicing", "proRation"})
public class ValidationProRationTests extends ProRationTestBase {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
index 4b47237..25be5e2 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
@@ -16,10 +16,22 @@
package com.ning.billing.invoice.tests;
-import com.ning.billing.invoice.model.InvoicingConfiguration;
+import java.math.BigDecimal;
+
+import com.ning.billing.account.api.Account;
+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.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.entitlement.api.user.Subscription;
import org.joda.time.DateTime;
-import java.math.BigDecimal;
+import com.ning.billing.invoice.model.InvoicingConfiguration;
+
+import javax.annotation.Nullable;
public abstract class InvoicingTestBase {
protected static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
@@ -31,7 +43,7 @@ public abstract class InvoicingTestBase {
protected static final BigDecimal ONE_AND_A_HALF = new BigDecimal("1.5").setScale(NUMBER_OF_DECIMALS);
protected static final BigDecimal TWO = new BigDecimal("2.0").setScale(NUMBER_OF_DECIMALS);
protected static final BigDecimal THREE = new BigDecimal("3.0").setScale(NUMBER_OF_DECIMALS);
- //protected static final BigDecimal FOUR = new BigDecimal("4.0").setScale(NUMBER_OF_DECIMALS);
+ protected static final BigDecimal FOUR = new BigDecimal("4.0").setScale(NUMBER_OF_DECIMALS);
protected static final BigDecimal FIVE = new BigDecimal("5.0").setScale(NUMBER_OF_DECIMALS);
protected static final BigDecimal SIX = new BigDecimal("6.0").setScale(NUMBER_OF_DECIMALS);
protected static final BigDecimal SEVEN = new BigDecimal("7.0").setScale(NUMBER_OF_DECIMALS);
@@ -48,6 +60,7 @@ public abstract class InvoicingTestBase {
protected static final BigDecimal TWENTY = new BigDecimal("20.0").setScale(NUMBER_OF_DECIMALS);
protected static final BigDecimal TWENTY_FOUR = new BigDecimal("24.0").setScale(NUMBER_OF_DECIMALS);
+ protected static final BigDecimal TWENTY_FIVE = new BigDecimal("25.0").setScale(NUMBER_OF_DECIMALS);
protected static final BigDecimal TWENTY_EIGHT = new BigDecimal("28.0").setScale(NUMBER_OF_DECIMALS);
protected static final BigDecimal TWENTY_NINE = new BigDecimal("29.0").setScale(NUMBER_OF_DECIMALS);
@@ -69,4 +82,84 @@ public abstract class InvoicingTestBase {
protected DateTime buildDateTime(int year, int month, int day) {
return new DateTime(year, month, day, 0, 0, 0, 0);
}
+
+ protected BillingEvent createMockBillingEvent(@Nullable final Account account, final Subscription subscription,
+ final DateTime effectiveDate,
+ final Plan plan, final PlanPhase planPhase,
+ @Nullable final BigDecimal fixedPrice, @Nullable final BigDecimal recurringPrice,
+ final Currency currency, final BillingPeriod billingPeriod, final int billCycleDay,
+ final BillingModeType billingModeType, final String description,
+ final long totalOrdering,
+ final SubscriptionTransitionType type) {
+ return new BillingEvent() {
+ @Override
+ public Account getAccount() {
+ return account;
+ }
+ @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 BigDecimal getFixedPrice() {
+ return fixedPrice;
+ }
+ @Override
+ public BigDecimal getRecurringPrice() {
+ return recurringPrice;
+ }
+ @Override
+ public Currency getCurrency() {
+ return currency;
+ }
+ @Override
+ public SubscriptionTransitionType getTransitionType() {
+ return type;
+ }
+ @Override
+ public Long getTotalOrdering() {
+ return totalOrdering;
+ }
+ @Override
+ public int compareTo(BillingEvent e1) {
+ if (!getSubscription().getId().equals(e1.getSubscription().getId())) { // First order by subscription
+ return getSubscription().getId().compareTo(e1.getSubscription().getId());
+ } else { // subscriptions are the same
+ if (! getEffectiveDate().equals(e1.getEffectiveDate())) { // Secondly order by date
+ return getEffectiveDate().compareTo(e1.getEffectiveDate());
+ } else { // dates and subscriptions are the same
+ return getTotalOrdering().compareTo(e1.getTotalOrdering());
+ }
+ }
+ }
+ };
+ }
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
index 122b13b..1cd4e2f 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
@@ -16,17 +16,18 @@
package com.ning.billing.invoice.tests;
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.model.BillingMode;
-import com.ning.billing.invoice.model.InvalidDateSequenceException;
-import com.ning.billing.invoice.model.RecurringInvoiceItemData;
-import org.joda.time.DateTime;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
import java.math.BigDecimal;
import java.util.List;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.fail;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.model.BillingMode;
+import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.model.RecurringInvoiceItemData;
public abstract class ProRationTestBase extends InvoicingTestBase {
protected abstract BillingMode getBillingMode();
jaxrs/pom.xml 97(+97 -0)
diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
new file mode 100644
index 0000000..f8472a9
--- /dev/null
+++ b/jaxrs/pom.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ~ Copyright 2010-2011 Ning, Inc. ~ ~ Ning licenses this file to you
+ under the Apache License, version 2.0 ~ (the "License"); you may not use
+ this file except in compliance with the ~ License. You may obtain a copy
+ of the License at: ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless
+ required by applicable law or agreed to in writing, software ~ distributed
+ under the License is distributed on an "AS IS" BASIS, WITHOUT ~ WARRANTIES
+ OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for
+ the specific language governing permissions and limitations ~ under the License. -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill</artifactId>
+ <version>0.1.11-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <artifactId>killbill-jaxrs</artifactId>
+ <name>killbill-jaxrs</name>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>jsr311-api</artifactId>
+ <!-- WHY DO WE NEED VESRION HERE ?? -->
+ <version>1.1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.inject</groupId>
+ <artifactId>guice</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>joda-time</groupId>
+ <artifactId>joda-time</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.skife.config</groupId>
+ <artifactId>config-magic</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-core-asl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-jaxrs</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-mapper-asl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <scope>test</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/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
new file mode 100644
index 0000000..82645a5
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
@@ -0,0 +1,403 @@
+/*
+ * 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.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+import org.joda.time.DateTimeZone;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountData;
+import com.ning.billing.catalog.api.Currency;
+
+public class AccountJson extends AccountJsonSimple {
+
+ // STEPH Missing city, locale, postalCode from https://home.ninginc.com:8443/display/REVINFRA/Killbill+1.0+APIs
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String name;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final Integer length;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String email;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final Integer billCycleDay;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String currency;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String paymentProvider;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String timeZone;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String address1;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String address2;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String company;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String state;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String country;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String phone;
+
+
+ public AccountJson(Account account) {
+ super(account.getId().toString(), account.getExternalKey());
+ this.name = account.getName();
+ this.length = account.getFirstNameLength();
+ this.email = account.getEmail();
+ this.billCycleDay = account.getBillCycleDay();
+ this.currency = account.getCurrency().toString();
+ this.paymentProvider = account.getPaymentProviderName();
+ this.timeZone = account.getTimeZone().toString();
+ this.address1 = account.getAddress1();
+ this.address2 = account.getAddress2();
+ this.company = account.getCompanyName();
+ this.state = account.getStateOrProvince();
+ this.country = account.getCountry();
+ this.phone = account.getPhone();
+ }
+
+ public AccountData toAccountData() {
+ return new AccountData() {
+ @Override
+ public DateTimeZone getTimeZone() {
+ return (timeZone != null) ? DateTimeZone.forID(timeZone) : null;
+ }
+ @Override
+ public String getStateOrProvince() {
+ return state;
+ }
+ @Override
+ public String getPostalCode() {
+ return null;
+ }
+ @Override
+ public String getPhone() {
+ return phone;
+ }
+
+ @Override
+ public boolean isMigrated() {
+ return false;
+ }
+
+ @Override
+ public boolean isNotifiedForInvoices() {
+ return false;
+ }
+
+ @Override
+ public String getPaymentProviderName() {
+ return paymentProvider;
+ }
+ @Override
+ public String getName() {
+ return name;
+ }
+ @Override
+ public String getLocale() {
+ return null;
+ }
+ @Override
+ public int getFirstNameLength() {
+ return length;
+ }
+ @Override
+ public String getExternalKey() {
+ return externalKey;
+ }
+ @Override
+ public String getEmail() {
+ return email;
+ }
+ @Override
+ public Currency getCurrency() {
+ Currency result = (currency != null) ? Currency.valueOf(currency) : Currency.USD;
+ return result;
+ }
+ @Override
+ public String getCountry() {
+ return country;
+ }
+ @Override
+ public String getCompanyName() {
+ return company;
+ }
+ @Override
+ public String getCity() {
+ return null;
+ }
+ @Override
+ public int getBillCycleDay() {
+ return billCycleDay;
+ }
+ @Override
+ public String getAddress2() {
+ return address2;
+ }
+ @Override
+ public String getAddress1() {
+ return address1;
+ }
+ };
+ }
+
+ // Seems like Jackson (JacksonJsonProvider.readFrom(Class<Object>, Type, Annotation[], MediaType, MultivaluedMap<String,String>, InputStream) line: 443)
+ // needs us to define a default CTOR to instanciate the class first.
+ public AccountJson() {
+ super();
+ this.name = null;
+ this.length = null;
+ this.email = null;
+ this.billCycleDay = null;
+ this.currency = null;
+ this.paymentProvider = null;
+ this.timeZone = null;
+ this.address1 = null;
+ this.address2 = null;
+ this.company = null;
+ this.state = null;
+ this.country = null;
+ this.phone = null;
+ }
+
+ @JsonCreator
+ public AccountJson(@JsonProperty("accountId") String accountId,
+ @JsonProperty("name") String name,
+ @JsonProperty("firstNameLength") Integer length,
+ @JsonProperty("external_key") String externalKey,
+ @JsonProperty("email") String email,
+ @JsonProperty("billingDay") Integer billCycleDay,
+ @JsonProperty("currency") String currency,
+ @JsonProperty("paymentProvider") String paymentProvider,
+ @JsonProperty("timezone") String timeZone,
+ @JsonProperty("address1") String address1,
+ @JsonProperty("address2") String address2,
+ @JsonProperty("company") String company,
+ @JsonProperty("state") String state,
+ @JsonProperty("country") String country,
+ @JsonProperty("phone") String phone) {
+ super(accountId, externalKey);
+ this.name = name;
+ this.length = length;
+ this.email = email;
+ this.billCycleDay = billCycleDay;
+ this.currency = currency;
+ this.paymentProvider = paymentProvider;
+ this.timeZone = timeZone;
+ this.address1 = address1;
+ this.address2 = address2;
+ this.company = company;
+ this.state = state;
+ this.country = country;
+ this.phone = phone;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Integer getLength() {
+ return length;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public Integer getBillCycleDay() {
+ return billCycleDay;
+ }
+
+ public String getCurrency() {
+ return currency;
+ }
+
+ public String getPaymentProvider() {
+ return paymentProvider;
+ }
+
+ public String getTimeZone() {
+ return timeZone;
+ }
+
+ public String getAddress1() {
+ return address1;
+ }
+
+ public String getAddress2() {
+ return address2;
+ }
+
+ public String getCompany() {
+ return company;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public String getPhone() {
+ return phone;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((accountId == null) ? 0 : accountId.hashCode());
+ result = prime * result
+ + ((address1 == null) ? 0 : address1.hashCode());
+ result = prime * result
+ + ((address2 == null) ? 0 : address2.hashCode());
+ result = prime * result
+ + ((billCycleDay == null) ? 0 : billCycleDay.hashCode());
+ result = prime * result + ((company == null) ? 0 : company.hashCode());
+ result = prime * result + ((country == null) ? 0 : country.hashCode());
+ result = prime * result
+ + ((currency == null) ? 0 : currency.hashCode());
+ result = prime * result + ((email == null) ? 0 : email.hashCode());
+ result = prime * result
+ + ((externalKey == null) ? 0 : externalKey.hashCode());
+ result = prime * result + ((length == null) ? 0 : length.hashCode());
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ result = prime * result
+ + ((paymentProvider == null) ? 0 : paymentProvider.hashCode());
+ result = prime * result + ((phone == null) ? 0 : phone.hashCode());
+ result = prime * result + ((state == null) ? 0 : state.hashCode());
+ result = prime * result
+ + ((timeZone == null) ? 0 : timeZone.hashCode());
+ return result;
+ }
+
+ // Used to check POST versus GET
+ public boolean equalsNoId(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ AccountJson other = (AccountJson) obj;
+ if (address1 == null) {
+ if (other.address1 != null)
+ return false;
+ } else if (!address1.equals(other.address1))
+ return false;
+ if (address2 == null) {
+ if (other.address2 != null)
+ return false;
+ } else if (!address2.equals(other.address2))
+ return false;
+ if (billCycleDay == null) {
+ if (other.billCycleDay != null)
+ return false;
+ } else if (!billCycleDay.equals(other.billCycleDay))
+ return false;
+ if (company == null) {
+ if (other.company != null)
+ return false;
+ } else if (!company.equals(other.company))
+ return false;
+ if (country == null) {
+ if (other.country != null)
+ return false;
+ } else if (!country.equals(other.country))
+ return false;
+ if (currency == null) {
+ if (other.currency != null)
+ return false;
+ } else if (!currency.equals(other.currency))
+ return false;
+ if (email == null) {
+ if (other.email != null)
+ return false;
+ } else if (!email.equals(other.email))
+ return false;
+ if (externalKey == null) {
+ if (other.externalKey != null)
+ return false;
+ } else if (!externalKey.equals(other.externalKey))
+ return false;
+ if (length == null) {
+ if (other.length != null)
+ return false;
+ } else if (!length.equals(other.length))
+ return false;
+ if (name == null) {
+ if (other.name != null)
+ return false;
+ } else if (!name.equals(other.name))
+ return false;
+ if (paymentProvider == null) {
+ if (other.paymentProvider != null)
+ return false;
+ } else if (!paymentProvider.equals(other.paymentProvider))
+ return false;
+ if (phone == null) {
+ if (other.phone != null)
+ return false;
+ } else if (!phone.equals(other.phone))
+ return false;
+ if (state == null) {
+ if (other.state != null)
+ return false;
+ } else if (!state.equals(other.state))
+ return false;
+ if (timeZone == null) {
+ if (other.timeZone != null)
+ return false;
+ } else if (!timeZone.equals(other.timeZone))
+ return false;
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (equalsNoId(obj) == false) {
+ return false;
+ } else {
+ AccountJson other = (AccountJson) obj;
+ if (accountId == null) {
+ if (other.accountId != null)
+ return false;
+ } else if (!accountId.equals(other.accountId))
+ return false;
+ }
+ return true;
+ }
+ }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonSimple.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonSimple.java
new file mode 100644
index 0000000..d7e1b2d
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonSimple.java
@@ -0,0 +1,49 @@
+/*
+ * 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.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+public class AccountJsonSimple {
+
+ @JsonView(BundleTimelineViews.Base.class)
+ protected final String accountId;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ protected final String externalKey;
+
+ public AccountJsonSimple() {
+ this.accountId = null;
+ this.externalKey = null;
+ }
+
+ @JsonCreator
+ public AccountJsonSimple(@JsonProperty("accountId") String accountId,
+ @JsonProperty("externalKey") String externalKey) {
+ this.accountId = accountId;
+ this.externalKey = externalKey;
+ }
+
+ public String getAccountId() {
+ return accountId;
+ }
+
+ public String getExternalKey() {
+ return externalKey;
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
new file mode 100644
index 0000000..881745a
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
@@ -0,0 +1,138 @@
+/*
+ * 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.jaxrs.json;
+
+import java.math.BigDecimal;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.payment.api.PaymentAttempt;
+
+public class AccountTimelineJson {
+
+ @JsonView(BundleTimelineViews.ReadTimeline.class)
+ private final List<PaymentJsonWithBundleKeys> payments;
+
+ @JsonView(BundleTimelineViews.ReadTimeline.class)
+ private final List<InvoiceJsonWithBundleKeys> invoices;
+
+ @JsonView(BundleTimelineViews.ReadTimeline.class)
+ private final AccountJsonSimple account;
+
+ @JsonView(BundleTimelineViews.Timeline.class)
+ private final List<BundleJsonWithSubscriptions> bundles;
+
+ @JsonCreator
+ public AccountTimelineJson(@JsonProperty("account") AccountJsonSimple account,
+ @JsonProperty("bundles") List<BundleJsonWithSubscriptions> bundles,
+ @JsonProperty("invoices") List<InvoiceJsonWithBundleKeys> invoices,
+ @JsonProperty("payments") List<PaymentJsonWithBundleKeys> payments) {
+ this.account = account;
+ this.bundles = bundles;
+ this.invoices = invoices;
+ this.payments = payments;
+ }
+
+ private String getBundleExternalKey(UUID invoiceId, List<Invoice> invoices, List<BundleTimeline> bundles) {
+ for (Invoice cur : invoices) {
+ if (cur.getId().equals(invoiceId)) {
+ return getBundleExternalKey(cur, bundles);
+ }
+ }
+ return null;
+ }
+
+ private String getBundleExternalKey(Invoice invoice, List<BundleTimeline> bundles) {
+ Set<UUID> b = new HashSet<UUID>();
+ for (final InvoiceItem cur : invoice.getInvoiceItems()) {
+ b.add(cur.getBundleId());
+ }
+ boolean first = true;
+ StringBuilder tmp = new StringBuilder();
+ for (final UUID cur : b) {
+ for (final BundleTimeline bt : bundles) {
+ if (bt.getBundleId().equals(cur)) {
+ if (!first) {
+ tmp.append(",");
+ }
+ tmp.append(bt.getExternalKey());
+ first = false;
+ break;
+ }
+ }
+ }
+ return tmp.toString();
+ }
+
+ public AccountTimelineJson(Account account, List<Invoice> invoices, List<PaymentAttempt> payments, List<BundleTimeline> bundles) {
+ this.account = new AccountJsonSimple(account.getId().toString(), account.getExternalKey());
+ this.bundles = new LinkedList<BundleJsonWithSubscriptions>();
+ for (BundleTimeline cur : bundles) {
+ this.bundles.add(new BundleJsonWithSubscriptions(account.getId(), cur));
+ }
+ this.invoices = new LinkedList<InvoiceJsonWithBundleKeys>();
+ for (Invoice cur : invoices) {
+ this.invoices.add(new InvoiceJsonWithBundleKeys(cur.getTotalAmount(), cur.getId().toString(), cur.getInvoiceDate(), cur.getTargetDate(),
+ Integer.toString(cur.getInvoiceNumber()), cur.getBalance(),
+ getBundleExternalKey(cur, bundles)));
+ }
+ this.payments = new LinkedList<PaymentJsonWithBundleKeys>();
+ for (PaymentAttempt cur : payments) {
+
+
+ String status = cur.getPaymentId() != null ? "Success" : "Failed";
+ BigDecimal paidAmount = cur.getPaymentId() != null ? cur.getAmount() : BigDecimal.ZERO;
+
+ this.payments.add(new PaymentJsonWithBundleKeys(cur.getAmount(), paidAmount, cur.getInvoiceId(), cur.getPaymentId(), cur.getCreatedDate(), cur.getUpdatedDate(),
+ cur.getRetryCount(), cur.getCurrency().toString(), status,
+ getBundleExternalKey(cur.getInvoiceId(), invoices, bundles)));
+ }
+ }
+
+ public AccountTimelineJson() {
+ this.account = null;
+ this.bundles = null;
+ this.invoices = null;
+ this.payments = null;
+ }
+
+ public List<PaymentJsonWithBundleKeys> getPayments() {
+ return payments;
+ }
+
+ public List<InvoiceJsonWithBundleKeys> getInvoices() {
+ return invoices;
+ }
+
+ public AccountJsonSimple getAccount() {
+ return account;
+ }
+
+ public List<BundleJsonWithSubscriptions> getBundles() {
+ return bundles;
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonNoSubsciptions.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonNoSubsciptions.java
new file mode 100644
index 0000000..c1221b8
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonNoSubsciptions.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.jaxrs.json;
+
+import java.util.List;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+
+public class BundleJsonNoSubsciptions extends BundleJsonSimple {
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String accountId;
+
+
+ @JsonCreator
+ public BundleJsonNoSubsciptions(@JsonProperty("bundleId") String bundleId,
+ @JsonProperty("accountId") String accountId,
+ @JsonProperty("externalKey") String externalKey,
+ @JsonProperty("subscriptions") List<SubscriptionJsonWithEvents> subscriptions) {
+ super(bundleId, externalKey);
+ this.accountId = accountId;
+ }
+
+ public String getAccountId() {
+ return accountId;
+ }
+
+
+ public BundleJsonNoSubsciptions(SubscriptionBundle bundle) {
+ super(bundle.getId().toString(), bundle.getKey());
+ this.accountId = bundle.getAccountId().toString();
+ }
+
+ public BundleJsonNoSubsciptions() {
+ super(null, null);
+ this.accountId = null;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((accountId == null) ? 0 : accountId.hashCode());
+ result = prime * result
+ + ((bundleId == null) ? 0 : bundleId.hashCode());
+ result = prime * result
+ + ((externalKey == null) ? 0 : externalKey.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (equalsNoId(obj) == false) {
+ return false;
+ }
+ BundleJsonNoSubsciptions other = (BundleJsonNoSubsciptions) obj;
+ if (bundleId == null) {
+ if (other.bundleId != null)
+ return false;
+ } else if (!bundleId.equals(other.bundleId))
+ return false;
+ return true;
+ }
+
+ public boolean equalsNoId(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ BundleJsonNoSubsciptions other = (BundleJsonNoSubsciptions) obj;
+ if (accountId == null) {
+ if (other.accountId != null)
+ return false;
+ } else if (!accountId.equals(other.accountId))
+ return false;
+ if (externalKey == null) {
+ if (other.externalKey != null)
+ return false;
+ } else if (!externalKey.equals(other.externalKey))
+ return false;
+ return true;
+ }
+
+
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonSimple.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonSimple.java
new file mode 100644
index 0000000..8c2d836
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonSimple.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.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+public class BundleJsonSimple {
+
+ @JsonView(BundleTimelineViews.Base.class)
+ protected final String bundleId;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ protected final String externalKey;
+
+ @JsonCreator
+ public BundleJsonSimple(@JsonProperty("bundleId") String bundleId,
+ @JsonProperty("externalKey") String externalKey) {
+ super();
+ this.bundleId = bundleId;
+ this.externalKey = externalKey;
+ }
+
+ public BundleJsonSimple() {
+ this.bundleId = null;
+ this.externalKey = null;
+ }
+
+ @JsonProperty("bundleId")
+ public String getBundleId() {
+ return bundleId;
+ }
+
+ @JsonProperty("externalKey")
+ public String getExternalKey() {
+ return externalKey;
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonWithSubscriptions.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonWithSubscriptions.java
new file mode 100644
index 0000000..fa45cdd
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJsonWithSubscriptions.java
@@ -0,0 +1,66 @@
+/*
+ * 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.jaxrs.json;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+
+public class BundleJsonWithSubscriptions extends BundleJsonSimple {
+
+ @JsonView(BundleTimelineViews.Timeline.class)
+ private final List<SubscriptionJsonWithEvents> subscriptions;
+
+ @JsonCreator
+ public BundleJsonWithSubscriptions(@JsonProperty("bundleId") String bundleId,
+ @JsonProperty("externalKey") String externalKey,
+ @JsonProperty("subscriptions") List<SubscriptionJsonWithEvents> subscriptions) {
+ super(bundleId, externalKey);
+ this.subscriptions = subscriptions;
+ }
+
+ @JsonProperty("subscriptions")
+ public List<SubscriptionJsonWithEvents> getSubscriptions() {
+ return subscriptions;
+ }
+
+ public BundleJsonWithSubscriptions(final UUID accountId, final BundleTimeline bundle) {
+ super(bundle.getBundleId().toString(), bundle.getExternalKey());
+ this.subscriptions = new LinkedList<SubscriptionJsonWithEvents>();
+ for (SubscriptionTimeline cur : bundle.getSubscriptions()) {
+ this.subscriptions.add(new SubscriptionJsonWithEvents(bundle.getBundleId(), cur));
+ }
+ }
+
+ public BundleJsonWithSubscriptions(SubscriptionBundle bundle) {
+ super(bundle.getId().toString(), bundle.getKey());
+ this.subscriptions = null;
+ }
+
+ public BundleJsonWithSubscriptions() {
+ super(null, null);
+ this.subscriptions = null;
+ }
+
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineJson.java
new file mode 100644
index 0000000..099d837
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineJson.java
@@ -0,0 +1,74 @@
+/*
+ * 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.jaxrs.json;
+
+import java.util.List;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+public class BundleTimelineJson {
+
+ @JsonView(BundleTimelineViews.Timeline.class)
+ private final String viewId;
+
+ @JsonView(BundleTimelineViews.Timeline.class)
+ private final BundleJsonWithSubscriptions bundle;
+
+ @JsonView(BundleTimelineViews.ReadTimeline.class)
+ private final List<PaymentJsonSimple> payments;
+
+ @JsonView(BundleTimelineViews.ReadTimeline.class)
+ private final List<InvoiceJsonSimple> invoices;
+
+ @JsonView(BundleTimelineViews.WriteTimeline.class)
+ private final String resonForChange;
+
+ @JsonCreator
+ public BundleTimelineJson(@JsonProperty("viewId") String viewId,
+ @JsonProperty("bundle") BundleJsonWithSubscriptions bundle,
+ @JsonProperty("payments") List<PaymentJsonSimple> payments,
+ @JsonProperty("invoices") List<InvoiceJsonSimple> invoices,
+ @JsonProperty("reasonForChange") String reason) {
+ this.viewId = viewId;
+ this.bundle = bundle;
+ this.payments = payments;
+ this.invoices = invoices;
+ this.resonForChange = reason;
+ }
+
+ public String getViewId() {
+ return viewId;
+ }
+
+ public BundleJsonWithSubscriptions getBundle() {
+ return bundle;
+ }
+
+ public List<PaymentJsonSimple> getPayments() {
+ return payments;
+ }
+
+ public List<InvoiceJsonSimple> getInvoices() {
+ return invoices;
+ }
+
+ public String getResonForChange() {
+ return resonForChange;
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineViews.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineViews.java
new file mode 100644
index 0000000..0397bec
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleTimelineViews.java
@@ -0,0 +1,25 @@
+/*
+ * 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.jaxrs.json;
+
+
+public class BundleTimelineViews {
+ public static class Base {};
+ public static class Timeline extends Base {};
+ public static class ReadTimeline extends Timeline {};
+ public static class WriteTimeline extends Timeline {};
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CustomFieldJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CustomFieldJson.java
new file mode 100644
index 0000000..cb9e36b
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CustomFieldJson.java
@@ -0,0 +1,51 @@
+/*
+ * 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.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+
+import com.ning.billing.util.customfield.CustomField;
+
+public class CustomFieldJson {
+
+ private final String name;
+ private final String value;
+
+ public CustomFieldJson() {
+ this.name = null;
+ this.value = null;
+ }
+
+ @JsonCreator
+ public CustomFieldJson(String name, String value) {
+ super();
+ this.name = name;
+ this.value = value;
+ }
+
+ public CustomFieldJson(CustomField input) {
+ this.name = input.getName();
+ this.value = input.getValue();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJsonSimple.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJsonSimple.java
new file mode 100644
index 0000000..be2516d
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJsonSimple.java
@@ -0,0 +1,158 @@
+/*
+ * 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.jaxrs.json;
+
+import java.math.BigDecimal;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+import org.joda.time.DateTime;
+
+import com.ning.billing.invoice.api.Invoice;
+
+public class InvoiceJsonSimple {
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final BigDecimal amount;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String invoiceId;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final DateTime invoiceDate;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final DateTime targetDate;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String invoiceNumber;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final BigDecimal balance;
+
+
+ public InvoiceJsonSimple() {
+ this.amount = null;
+ this.invoiceId = null;
+ this.invoiceDate = null;
+ this.targetDate = null;
+ this.invoiceNumber = null;
+ this.balance = null;
+ }
+
+ @JsonCreator
+ public InvoiceJsonSimple(@JsonProperty("amount") BigDecimal amount,
+ @JsonProperty("invoiceId") String invoiceId,
+ @JsonProperty("invoiceDate") DateTime invoiceDate,
+ @JsonProperty("targetDate") DateTime targetDate,
+ @JsonProperty("invoiceNumber") String invoiceNumber,
+ @JsonProperty("balance") BigDecimal balance) {
+ super();
+ this.amount = amount;
+ this.invoiceId = invoiceId;
+ this.invoiceDate = invoiceDate;
+ this.targetDate = targetDate;
+ this.invoiceNumber = invoiceNumber;
+ this.balance = balance;
+ }
+
+ public InvoiceJsonSimple(Invoice input) {
+ this.amount = input.getTotalAmount();
+ this.invoiceId = input.getId().toString();
+ this.invoiceDate = input.getInvoiceDate();
+ this.targetDate = input.getTargetDate();
+ this.invoiceNumber = String.valueOf(input.getInvoiceNumber());
+ this.balance = input.getBalance();
+ }
+
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
+ public String getInvoiceId() {
+ return invoiceId;
+ }
+
+ public DateTime getInvoiceDate() {
+ return invoiceDate;
+ }
+
+ public DateTime getTargetDate() {
+ return targetDate;
+ }
+
+ public String getInvoiceNumber() {
+ return invoiceNumber;
+ }
+
+ public BigDecimal getBalance() {
+ return balance;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((amount == null) ? 0 : amount.hashCode());
+ result = prime * result + ((balance == null) ? 0 : balance.hashCode());
+ result = prime * result
+ + ((invoiceDate == null) ? 0 : invoiceDate.hashCode());
+ result = prime * result
+ + ((invoiceId == null) ? 0 : invoiceId.hashCode());
+ result = prime * result
+ + ((invoiceNumber == null) ? 0 : invoiceNumber.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ InvoiceJsonSimple other = (InvoiceJsonSimple) obj;
+ if (amount == null) {
+ if (other.amount != null)
+ return false;
+ } else if (!amount.equals(other.amount))
+ return false;
+ if (balance == null) {
+ if (other.balance != null)
+ return false;
+ } else if (!balance.equals(other.balance))
+ return false;
+ if (invoiceDate == null) {
+ if (other.invoiceDate != null)
+ return false;
+ } else if (!invoiceDate.equals(other.invoiceDate))
+ return false;
+ if (invoiceId == null) {
+ if (other.invoiceId != null)
+ return false;
+ } else if (!invoiceId.equals(other.invoiceId))
+ return false;
+ if (invoiceNumber == null) {
+ if (other.invoiceNumber != null)
+ return false;
+ } else if (!invoiceNumber.equals(other.invoiceNumber))
+ return false;
+ return true;
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJsonWithBundleKeys.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJsonWithBundleKeys.java
new file mode 100644
index 0000000..cc15072
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJsonWithBundleKeys.java
@@ -0,0 +1,57 @@
+package com.ning.billing.jaxrs.json;
+import java.math.BigDecimal;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
+import com.ning.billing.invoice.api.Invoice;
+
+/*
+ * 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.
+ */
+
+public class InvoiceJsonWithBundleKeys extends InvoiceJsonSimple {
+
+
+ private final String bundleKeys;
+
+
+ public InvoiceJsonWithBundleKeys() {
+ super();
+ this.bundleKeys = null;
+ }
+
+ @JsonCreator
+ public InvoiceJsonWithBundleKeys(@JsonProperty("amount") BigDecimal amount,
+ @JsonProperty("invoiceId") String invoiceId,
+ @JsonProperty("invoiceDate") DateTime invoiceDate,
+ @JsonProperty("targetDate") DateTime targetDate,
+ @JsonProperty("invoiceNumber") String invoiceNumber,
+ @JsonProperty("balance") BigDecimal balance,
+ @JsonProperty("externalBundleKeys") String bundleKeys) {
+ super(amount, invoiceId, invoiceDate, targetDate, invoiceNumber, balance);
+ this.bundleKeys = bundleKeys;
+ }
+
+ public InvoiceJsonWithBundleKeys(Invoice input, String bundleKeys) {
+ super(input);
+ this.bundleKeys = bundleKeys;
+ }
+
+ public String getBundleKeys() {
+ return bundleKeys;
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJsonSimple.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJsonSimple.java
new file mode 100644
index 0000000..c0a6d7c
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJsonSimple.java
@@ -0,0 +1,118 @@
+/*
+ * 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.jaxrs.json;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+import org.joda.time.DateTime;
+
+import com.ning.billing.util.clock.DefaultClock;
+
+public class PaymentJsonSimple {
+
+ private final BigDecimal paidAmount;
+
+ private final BigDecimal amount;
+
+ private final UUID invoiceId;
+
+ private final UUID paymentId;
+
+ private final DateTime requestedDate;
+
+ private final DateTime effectiveDate;
+
+ private final Integer retryCount;
+
+ private final String currency;
+
+ private final String status;
+
+ public PaymentJsonSimple() {
+ this.amount = null;
+ this.paidAmount = null;
+ this.invoiceId = null;
+ this.paymentId = null;
+ this.requestedDate = null;
+ this.effectiveDate = null;
+ this.currency = null;
+ this.retryCount = null;
+ this.status = null;
+ }
+
+ @JsonCreator
+ public PaymentJsonSimple(@JsonProperty("amount") BigDecimal amount,
+ @JsonProperty("paidAmount") BigDecimal paidAmount,
+ @JsonProperty("invoiceId") UUID invoiceId,
+ @JsonProperty("paymentId") UUID paymentId,
+ @JsonProperty("requestedDate") DateTime requestedDate,
+ @JsonProperty("effectiveDate") DateTime effectiveDate,
+ @JsonProperty("retryCount") Integer retryCount,
+ @JsonProperty("currency") String currency,
+ @JsonProperty("status") String status) {
+ super();
+ this.amount = amount;
+ this.paidAmount = paidAmount;
+ this.invoiceId = invoiceId;
+ this.paymentId = paymentId;
+ this.requestedDate = DefaultClock.toUTCDateTime(requestedDate);
+ this.effectiveDate = DefaultClock.toUTCDateTime(effectiveDate);
+ this.currency = currency;
+ this.retryCount = retryCount;
+ this.status = status;
+ }
+
+ public BigDecimal getPaidAmount() {
+ return paidAmount;
+ }
+
+ public UUID getInvoiceId() {
+ return invoiceId;
+ }
+
+ public UUID getPaymentId() {
+ return paymentId;
+ }
+
+ public DateTime getRequestedDate() {
+ return DefaultClock.toUTCDateTime(requestedDate);
+ }
+
+ public DateTime getEffectiveDate() {
+ return DefaultClock.toUTCDateTime(effectiveDate);
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
+ public Integer getRetryCount() {
+ return retryCount;
+ }
+
+ public String getCurrency() {
+ return currency;
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJsonWithBundleKeys.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJsonWithBundleKeys.java
new file mode 100644
index 0000000..e322a59
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJsonWithBundleKeys.java
@@ -0,0 +1,53 @@
+/*
+ * 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.jaxrs.json;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
+public class PaymentJsonWithBundleKeys extends PaymentJsonSimple {
+
+ private final String bundleKeys;
+
+ public PaymentJsonWithBundleKeys() {
+ super();
+ this.bundleKeys = null;
+ }
+
+ @JsonCreator
+ public PaymentJsonWithBundleKeys(@JsonProperty("amount") BigDecimal amount,
+ @JsonProperty("paidAmount") BigDecimal paidAmount,
+ @JsonProperty("invoiceId") UUID invoiceId,
+ @JsonProperty("paymentId") UUID paymentId,
+ @JsonProperty("requestedDt") DateTime requestedDate,
+ @JsonProperty("effectiveDt") DateTime effectiveDate,
+ @JsonProperty("retryCount") Integer retryCount,
+ @JsonProperty("currency") String currency,
+ @JsonProperty("status") String status,
+ @JsonProperty("externalBundleKeys") String bundleKeys) {
+ super(amount, paidAmount, invoiceId, paymentId, requestedDate, effectiveDate, retryCount, currency, status);
+ this.bundleKeys = bundleKeys;
+ }
+
+ public String getBundleKeys() {
+ return bundleKeys;
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonNoEvents.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonNoEvents.java
new file mode 100644
index 0000000..95a5e09
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonNoEvents.java
@@ -0,0 +1,210 @@
+/*
+ * 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.jaxrs.json;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.util.clock.DefaultClock;
+
+public class SubscriptionJsonNoEvents extends SubscriptionJsonSimple {
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final DateTime startDate;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String bundleId;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String productName;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String productCategory;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String billingPeriod;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final String priceList;
+
+ @JsonView(BundleTimelineViews.Base.class)
+ private final DateTime chargedThroughDate;
+
+
+
+ @JsonCreator
+ public SubscriptionJsonNoEvents(@JsonProperty("subscriptionId") String subscriptionId,
+ @JsonProperty("bundleId") String bundleId,
+ @JsonProperty("startDate") DateTime startDate,
+ @JsonProperty("productName") String productName,
+ @JsonProperty("productCategory") String productCategory,
+ @JsonProperty("billingPeriod") String billingPeriod,
+ @JsonProperty("priceList") String priceList,
+ @JsonProperty("chargedThroughDate") DateTime chargedThroughDate) {
+ super(subscriptionId);
+ this.bundleId = bundleId;
+ this.startDate = startDate;
+ this.productName = productName;
+ this.productCategory = productCategory;
+ this.billingPeriod = billingPeriod;
+ this.priceList = priceList;
+ this.chargedThroughDate = chargedThroughDate;
+ }
+
+ public SubscriptionJsonNoEvents() {
+ super(null);
+ this.bundleId = null;
+ this.startDate = null;
+ this.productName = null;
+ this.productCategory = null;
+ this.billingPeriod = null;
+ this.priceList = null;
+ this.chargedThroughDate = null;
+ }
+
+ public SubscriptionJsonNoEvents(final Subscription data) {
+ super(data.getId().toString());
+ this.bundleId = data.getBundleId().toString();
+ this.startDate = data.getStartDate();
+ this.productName = data.getCurrentPlan().getProduct().getName();
+ this.productCategory = data.getCurrentPlan().getProduct().getCategory().toString();
+ this.billingPeriod = data.getCurrentPlan().getBillingPeriod().toString();
+ this.priceList = data.getCurrentPriceList().getName();
+ this.chargedThroughDate = data.getChargedThroughDate();
+ }
+
+
+ public String getSubscriptionId() {
+ return subscriptionId;
+ }
+
+ public String getBundleId() {
+ return bundleId;
+ }
+
+ public DateTime getStartDate() {
+ return startDate;
+ }
+
+ public String getProductName() {
+ return productName;
+ }
+
+ public String getProductCategory() {
+ return productCategory;
+ }
+
+ public String getBillingPeriod() {
+ return billingPeriod;
+ }
+
+ public String getPriceList() {
+ return priceList;
+ }
+
+ public DateTime getChargedThroughDate() {
+ return chargedThroughDate;
+ }
+
+
+ @Override
+ public String toString() {
+ return "SubscriptionJson [subscriptionId=" + subscriptionId
+ + ", bundleId=" + bundleId + ", productName=" + productName
+ + ", productCategory=" + productCategory + ", billingPeriod="
+ + billingPeriod + ", priceList=" + priceList + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((billingPeriod == null) ? 0 : billingPeriod.hashCode());
+ result = prime * result
+ + ((bundleId == null) ? 0 : bundleId.hashCode());
+ result = prime * result
+ + ((priceList == null) ? 0 : priceList.hashCode());
+ result = prime * result
+ + ((productCategory == null) ? 0 : productCategory.hashCode());
+ result = prime * result
+ + ((productName == null) ? 0 : productName.hashCode());
+ result = prime * result
+ + ((subscriptionId == null) ? 0 : subscriptionId.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (equalsNoId(obj) == false) {
+ return false;
+ }
+ SubscriptionJsonNoEvents other = (SubscriptionJsonNoEvents) obj;
+ if (subscriptionId == null) {
+ if (other.subscriptionId != null)
+ return false;
+ } else if (!subscriptionId.equals(other.subscriptionId))
+ return false;
+ return true;
+ }
+
+ public boolean equalsNoId(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SubscriptionJsonNoEvents other = (SubscriptionJsonNoEvents) obj;
+ if (billingPeriod == null) {
+ if (other.billingPeriod != null)
+ return false;
+ } else if (!billingPeriod.equals(other.billingPeriod))
+ return false;
+ if (bundleId == null) {
+ if (other.bundleId != null)
+ return false;
+ } else if (!bundleId.equals(other.bundleId))
+ return false;
+ if (priceList == null) {
+ if (other.priceList != null)
+ return false;
+ } else if (!priceList.equals(other.priceList))
+ return false;
+ if (productCategory == null) {
+ if (other.productCategory != null)
+ return false;
+ } else if (!productCategory.equals(other.productCategory))
+ return false;
+ if (productName == null) {
+ if (other.productName != null)
+ return false;
+ } else if (!productName.equals(other.productName))
+ return false;
+ return true;
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonSimple.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonSimple.java
new file mode 100644
index 0000000..5fb782e
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonSimple.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+
+public class SubscriptionJsonSimple {
+
+ @JsonView(BundleTimelineViews.Base.class)
+ protected final String subscriptionId;
+
+ public SubscriptionJsonSimple() {
+ this.subscriptionId = null;
+ }
+
+ @JsonCreator
+ public SubscriptionJsonSimple(@JsonProperty("subscriptionId") String subscriptionId) {
+ this.subscriptionId = subscriptionId;
+ }
+
+ public String getSubscriptionId() {
+ return subscriptionId;
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonWithEvents.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonWithEvents.java
new file mode 100644
index 0000000..be5ba2c
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonWithEvents.java
@@ -0,0 +1,261 @@
+/*
+ * 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.jaxrs.json;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonView;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
+import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.ExistingEvent;
+import com.ning.billing.entitlement.api.user.Subscription;
+
+import com.ning.billing.util.clock.DefaultClock;
+
+public class SubscriptionJsonWithEvents extends SubscriptionJsonSimple {
+
+ @JsonView(BundleTimelineViews.ReadTimeline.class)
+ private final List<SubscriptionReadEventJson> events;
+
+ @JsonView(BundleTimelineViews.WriteTimeline.class)
+ private final List<SubscriptionDeletedEventJson> deletedEvents;
+
+ @JsonView(BundleTimelineViews.WriteTimeline.class)
+ private final List<SubscriptionNewEventJson> newEvents;
+
+
+ public static class SubscriptionReadEventJson extends SubscriptionBaseEventJson {
+
+ @JsonView(BundleTimelineViews.Timeline.class)
+ private final String eventId;
+
+ @JsonView(BundleTimelineViews.Timeline.class)
+ private final DateTime effectiveDate;
+
+ public SubscriptionReadEventJson() {
+ super();
+ this.eventId = null;
+ this.effectiveDate = null;
+ }
+
+ @JsonCreator
+ public SubscriptionReadEventJson(@JsonProperty("eventId") String eventId,
+ @JsonProperty("billingPeriod") String billingPeriod,
+ @JsonProperty("requestedDt") DateTime requestedDate,
+ @JsonProperty("effectiveDt") DateTime effectiveDate,
+ @JsonProperty("product") String product,
+ @JsonProperty("priceList") String priceList,
+ @JsonProperty("eventType") String eventType,
+ @JsonProperty("phase") String phase) {
+ super(billingPeriod, requestedDate, product, priceList, eventType, phase);
+ this.eventId = eventId;
+ this.effectiveDate = effectiveDate;
+ }
+
+ public String getEventId() {
+ return eventId;
+ }
+
+ public DateTime getEffectiveDate() {
+ return DefaultClock.toUTCDateTime(effectiveDate);
+ }
+
+ @Override
+ public String toString() {
+ return "SubscriptionReadEventJson [eventId=" + eventId
+ + ", effectiveDate=" + effectiveDate
+ + ", getBillingPeriod()=" + getBillingPeriod()
+ + ", getRequestedDate()=" + getRequestedDate()
+ + ", getProduct()=" + getProduct() + ", getPriceList()="
+ + getPriceList() + ", getEventType()=" + getEventType()
+ + ", getPhase()=" + getPhase() + ", getClass()="
+ + getClass() + ", hashCode()=" + hashCode()
+ + ", toString()=" + super.toString() + "]";
+ }
+ }
+
+ public static class SubscriptionDeletedEventJson extends SubscriptionReadEventJson {
+ @JsonCreator
+ public SubscriptionDeletedEventJson(@JsonProperty("event_id") String eventId,
+ @JsonProperty("billing_period") String billingPeriod,
+ @JsonProperty("requested_date") DateTime requestedDate,
+ @JsonProperty("effective_date") DateTime effectiveDate,
+ @JsonProperty("product") String product,
+ @JsonProperty("price_list") String priceList,
+ @JsonProperty("event_type") String eventType,
+ @JsonProperty("phase") String phase) {
+ super(eventId, billingPeriod, requestedDate, effectiveDate, product, priceList, eventType, phase);
+
+ }
+ }
+
+
+ public static class SubscriptionNewEventJson extends SubscriptionBaseEventJson {
+ @JsonCreator
+ public SubscriptionNewEventJson(@JsonProperty("billing_period") String billingPeriod,
+ @JsonProperty("requested_date") DateTime requestedDate,
+ @JsonProperty("product") String product,
+ @JsonProperty("price_list") String priceList,
+ @JsonProperty("event_type") String eventType,
+ @JsonProperty("phase") String phase) {
+ super(billingPeriod, requestedDate, product, priceList, eventType, phase);
+ }
+
+ @Override
+ public String toString() {
+ return "SubscriptionNewEventJson [getBillingPeriod()="
+ + getBillingPeriod() + ", getRequestedDate()="
+ + getRequestedDate() + ", getProduct()=" + getProduct()
+ + ", getPriceList()=" + getPriceList()
+ + ", getEventType()=" + getEventType() + ", getPhase()="
+ + getPhase() + ", getClass()=" + getClass()
+ + ", hashCode()=" + hashCode() + ", toString()="
+ + super.toString() + "]";
+ }
+ }
+
+ public static class SubscriptionBaseEventJson {
+
+ @JsonView(BundleTimelineViews.Timeline.class)
+ private final String billingPeriod;
+
+ @JsonView(BundleTimelineViews.Timeline.class)
+ private final DateTime requestedDate;
+
+
+ @JsonView(BundleTimelineViews.Timeline.class)
+ private final String product;
+
+ @JsonView(BundleTimelineViews.Timeline.class)
+ private final String priceList;
+
+ @JsonView(BundleTimelineViews.Timeline.class)
+ private final String eventType;
+
+ @JsonView(BundleTimelineViews.Timeline.class)
+ private final String phase;
+
+ public SubscriptionBaseEventJson() {
+ this.billingPeriod = null;
+ this.requestedDate = null;
+ this.product = null;
+ this.priceList = null;
+ this.eventType = null;
+ this.phase = null;
+ }
+
+ @JsonCreator
+ public SubscriptionBaseEventJson(@JsonProperty("billing_period") String billingPeriod,
+ @JsonProperty("requested_date") DateTime requestedDate,
+ @JsonProperty("product") String product,
+ @JsonProperty("price_list") String priceList,
+ @JsonProperty("event_type") String eventType,
+ @JsonProperty("phase") String phase) {
+ super();
+ this.billingPeriod = billingPeriod;
+ this.requestedDate = DefaultClock.toUTCDateTime(requestedDate);
+ this.product = product;
+ this.priceList = priceList;
+ this.eventType = eventType;
+ this.phase = phase;
+ }
+
+ public String getBillingPeriod() {
+ return billingPeriod;
+ }
+
+ public DateTime getRequestedDate() {
+ return DefaultClock.toUTCDateTime(requestedDate);
+ }
+
+ public String getProduct() {
+ return product;
+ }
+
+ public String getPriceList() {
+ return priceList;
+ }
+
+ public String getEventType() {
+ return eventType;
+ }
+
+ public String getPhase() {
+ return phase;
+ }
+ }
+
+
+ @JsonCreator
+ public SubscriptionJsonWithEvents(@JsonProperty("subscription_id") String subscriptionId,
+ @JsonProperty("events") List<SubscriptionReadEventJson> events,
+ @JsonProperty("new_events") List<SubscriptionNewEventJson> newEvents,
+ @JsonProperty("deleted_events") List<SubscriptionDeletedEventJson> deletedEvents) {
+ super(subscriptionId);
+ this.events = events;
+ this.deletedEvents = deletedEvents;
+ this.newEvents = newEvents;
+ }
+
+ public SubscriptionJsonWithEvents() {
+ super(null);
+ this.events = null;
+ this.deletedEvents = null;
+ this.newEvents = null;
+ }
+
+ public SubscriptionJsonWithEvents(final Subscription data,
+ List<SubscriptionReadEventJson> events, List<SubscriptionDeletedEventJson> deletedEvents, List<SubscriptionNewEventJson> newEvents) {
+ super(data.getId().toString());
+ this.events = events;
+ this.deletedEvents = deletedEvents;
+ this.newEvents = newEvents;
+ }
+
+ public SubscriptionJsonWithEvents(final UUID bundleId, final SubscriptionTimeline input) {
+ super(input.getId().toString());
+ this.events = new LinkedList<SubscriptionReadEventJson>();
+ for (ExistingEvent cur : input.getExistingEvents()) {
+ PlanPhaseSpecifier spec = cur.getPlanPhaseSpecifier();
+ this.events.add(new SubscriptionReadEventJson(cur.getEventId().toString(), spec.getBillingPeriod().toString(), cur.getRequestedDate(), cur.getEffectiveDate(),
+ spec.getProductName(), spec.getPriceListName(), cur.getSubscriptionTransitionType().toString(), spec.getPhaseType().toString()));
+ }
+ this.deletedEvents = null;
+ this.newEvents = null;
+ }
+
+ public String getSubscriptionId() {
+ return subscriptionId;
+ }
+
+ public List<SubscriptionReadEventJson> getEvents() {
+ return events;
+ }
+
+ public List<SubscriptionNewEventJson> getNewEvents() {
+ return newEvents;
+ }
+
+ public List<SubscriptionDeletedEventJson> getDeletedEvents() {
+ return deletedEvents;
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/TagDefinitionJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/TagDefinitionJson.java
new file mode 100644
index 0000000..25ef04c
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/TagDefinitionJson.java
@@ -0,0 +1,77 @@
+/*
+ * 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.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+public class TagDefinitionJson {
+
+ private final String name;
+ private final String description;
+
+ public TagDefinitionJson() {
+ this.name = null;
+ this.description = null;
+ }
+
+ @JsonCreator
+ public TagDefinitionJson(@JsonProperty("name") String name,
+ @JsonProperty("description") String description) {
+ super();
+ this.name = name;
+ this.description = description;
+ }
+
+ public String getName() {
+ return name;
+ }
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((description == null) ? 0 : description.hashCode());
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ TagDefinitionJson other = (TagDefinitionJson) obj;
+ if (description == null) {
+ if (other.description != null)
+ return false;
+ } else if (!description.equals(other.description))
+ return false;
+ if (name == null) {
+ if (other.name != null)
+ return false;
+ } else if (!name.equals(other.name))
+ return false;
+ return true;
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
new file mode 100644
index 0000000..b8db3f8
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
@@ -0,0 +1,450 @@
+/*
+ * 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.jaxrs.resources;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Collections2;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+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.AccountUserApi;
+import com.ning.billing.entitlement.api.timeline.BundleTimeline;
+import com.ning.billing.entitlement.api.timeline.EntitlementRepairException;
+import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.AccountTimelineJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.CustomFieldJson;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+import com.ning.billing.jaxrs.util.TagHelper;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentApiException;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.customfield.StringCustomField;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+
+
+@Singleton
+@Path(BaseJaxrsResource.ACCOUNTS_PATH)
+public class AccountResource implements BaseJaxrsResource {
+
+ private static final Logger log = LoggerFactory.getLogger(AccountResource.class);
+
+ private final AccountUserApi accountApi;
+ private final EntitlementUserApi entitlementApi;
+ private final EntitlementTimelineApi timelineApi;
+ private final InvoiceUserApi invoiceApi;
+ private final PaymentApi paymentApi;
+ private final Context context;
+ private final TagUserApi tagUserApi;
+ private final JaxrsUriBuilder uriBuilder;
+ private final TagHelper tagHelper;
+
+ @Inject
+ public AccountResource(final JaxrsUriBuilder uriBuilder,
+ final AccountUserApi accountApi,
+ final EntitlementUserApi entitlementApi,
+ final InvoiceUserApi invoiceApi,
+ final PaymentApi paymentApi,
+ final EntitlementTimelineApi timelineApi,
+ final TagUserApi tagUserApi,
+ final TagHelper tagHelper,
+ final Context context) {
+ this.uriBuilder = uriBuilder;
+ this.accountApi = accountApi;
+ this.tagUserApi = tagUserApi;
+ this.entitlementApi = entitlementApi;
+ this.invoiceApi = invoiceApi;
+ this.paymentApi = paymentApi;
+ this.timelineApi = timelineApi;
+ this.context = context;
+ this.tagHelper = tagHelper;
+ }
+
+ @GET
+ @Path("/{accountId:" + UUID_PATTERN + "}")
+ @Produces(APPLICATION_JSON)
+ public Response getAccount(@PathParam("accountId") String accountId) {
+ try {
+ Account account = accountApi.getAccountById(UUID.fromString(accountId));
+
+ AccountJson json = new AccountJson(account);
+ return Response.status(Status.OK).entity(json).build();
+ } catch (AccountApiException e) {
+ return Response.status(Status.NO_CONTENT).build();
+ }
+
+ }
+
+ @GET
+ @Path("/{accountId:" + UUID_PATTERN + "}/" + BUNDLES)
+ @Produces(APPLICATION_JSON)
+ public Response getAccountBundles(@PathParam("accountId") String accountId) {
+ try {
+ UUID uuid = UUID.fromString(accountId);
+ accountApi.getAccountById(uuid);
+
+ List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(uuid);
+ Collection<BundleJsonNoSubsciptions> result = Collections2.transform(bundles, new Function<SubscriptionBundle, BundleJsonNoSubsciptions>() {
+ @Override
+ public BundleJsonNoSubsciptions apply(SubscriptionBundle input) {
+ return new BundleJsonNoSubsciptions(input);
+ }
+ });
+ return Response.status(Status.OK).entity(result).build();
+ } catch (AccountApiException e) {
+ return Response.status(Status.NO_CONTENT).build();
+ }
+ }
+
+
+ @GET
+ @Produces(APPLICATION_JSON)
+ public Response getAccountByKey(@QueryParam(QUERY_EXTERNAL_KEY) String externalKey) {
+ try {
+ Account account = null;
+ if (externalKey != null) {
+ account = accountApi.getAccountByKey(externalKey);
+ }
+ if (account == null) {
+ return Response.status(Status.NO_CONTENT).build();
+ }
+ AccountJson json = new AccountJson(account);
+ return Response.status(Status.OK).entity(json).build();
+ } catch (AccountApiException e) {
+ return Response.status(Status.NO_CONTENT).build();
+ }
+ }
+
+
+ @POST
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ public Response createAccount(final AccountJson json,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) {
+
+ try {
+ AccountData data = json.toAccountData();
+ final Account account = accountApi.createAccount(data, null, null, context.createContext(createdBy, reason, comment));
+ Response response = uriBuilder.buildResponse(AccountResource.class, "getAccount", account.getId());
+ return response;
+ } catch (AccountApiException e) {
+ final String error = String.format("Failed to create account %s", json);
+ log.info(error, e);
+ return Response.status(Status.BAD_REQUEST).entity(error).build();
+ } catch (IllegalArgumentException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ }
+ }
+
+ @PUT
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @Path("/{accountId:" + UUID_PATTERN + "}")
+ public Response updateAccount(final AccountJson json,
+ @PathParam("accountId") final String accountId,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) {
+ try {
+ AccountData data = json.toAccountData();
+ UUID uuid = UUID.fromString(accountId);
+ accountApi.updateAccount(uuid, data, context.createContext(createdBy, reason, comment));
+ return getAccount(accountId);
+ } catch (AccountApiException e) {
+ if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID.getCode()) {
+ return Response.status(Status.NO_CONTENT).build();
+ } else {
+ log.info(String.format("Failed to update account %s with %s", accountId, json), e);
+ return Response.status(Status.BAD_REQUEST).build();
+ }
+ } catch (IllegalArgumentException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ }
+ }
+
+ // Not supported
+ @DELETE
+ @Path("/{accountId:" + UUID_PATTERN + "}")
+ @Produces(APPLICATION_JSON)
+ public Response cancelAccount(@PathParam("accountId") String accountId) {
+ /*
+ try {
+ accountApi.cancelAccount(accountId);
+ return Response.status(Status.NO_CONTENT).build();
+ } catch (AccountApiException e) {
+ log.info(String.format("Failed to cancel account %s", accountId), e);
+ return Response.status(Status.BAD_REQUEST).build();
+ }
+ */
+ return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+ }
+
+ @GET
+ @Path("/{accountId:" + UUID_PATTERN + "}/" + TIMELINE)
+ @Produces(APPLICATION_JSON)
+ public Response getAccountTimeline(@PathParam("accountId") String accountId) {
+ try {
+ Account account = accountApi.getAccountById(UUID.fromString(accountId));
+
+ List<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId());
+
+ List<PaymentAttempt> payments = new LinkedList<PaymentAttempt>();
+
+ if (invoices.size() > 0) {
+ Collection<String> tmp = Collections2.transform(invoices, new Function<Invoice, String>() {
+ @Override
+ public String apply(Invoice input) {
+ return input.getId().toString();
+ }
+ });
+ List<String> invoicesId = new ArrayList<String>();
+ invoicesId.addAll(tmp);
+ for (String curId : invoicesId) {
+ payments.addAll(paymentApi.getPaymentAttemptsForInvoiceId(curId));
+ }
+ }
+
+ List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(account.getId());
+ List<BundleTimeline> bundlesTimeline = new LinkedList<BundleTimeline>();
+ for (SubscriptionBundle cur : bundles) {
+ bundlesTimeline.add(timelineApi.getBundleRepair(cur.getId()));
+ }
+ AccountTimelineJson json = new AccountTimelineJson(account, invoices, payments, bundlesTimeline);
+ return Response.status(Status.OK).entity(json).build();
+ } catch (AccountApiException e) {
+ return Response.status(Status.NO_CONTENT).build();
+ } catch (PaymentApiException e) {
+ log.error(e.getMessage());
+ return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+ } catch (EntitlementRepairException e) {
+ log.error(e.getMessage());
+ return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+ }
+ }
+
+
+ /**************************** TAGS ******************************/
+
+ @GET
+ @Path(BaseJaxrsResource.TAGS + "/{accountId:" + UUID_PATTERN + "}")
+ @Produces(APPLICATION_JSON)
+ public Response getAccountTags(@PathParam("accountId") String accountId) {
+ try {
+ Account account = accountApi.getAccountById(UUID.fromString(accountId));
+ List<Tag> tags = account.getTagList();
+ Collection<String> tagNameList = (tags.size() == 0) ?
+ Collections.<String>emptyList() :
+ Collections2.transform(tags, new Function<Tag, String>() {
+ @Override
+ public String apply(Tag input) {
+ return input.getTagDefinitionName();
+ }
+ });
+ return Response.status(Status.OK).entity(tagNameList).build();
+ } catch (AccountApiException e) {
+ return Response.status(Status.NO_CONTENT).build();
+ }
+ }
+
+
+ @POST
+ @Path(BaseJaxrsResource.TAGS + "/{accountId:" + UUID_PATTERN + "}")
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ public Response createAccountTag(@PathParam("accountId") final String accountId,
+ @QueryParam(QUERY_TAGS) final String tagList,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) {
+
+ try {
+ Preconditions.checkNotNull(tagList, "Query % list cannot be null", QUERY_TAGS);
+
+ Account account = accountApi.getAccountById(UUID.fromString(accountId));
+
+ List<TagDefinition> input = tagHelper.getTagDifinitionFromTagList(tagList);
+ account.addTagsFromDefinitions(input);
+ Response response = uriBuilder.buildResponse(AccountResource.class, "getAccountTags", account.getId());
+ return response;
+ } catch (AccountApiException e) {
+ return Response.status(Status.NO_CONTENT).build();
+ } catch (IllegalArgumentException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ } catch (NullPointerException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ } catch (TagDefinitionApiException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ }
+ }
+
+ @DELETE
+ @Path(BaseJaxrsResource.TAGS + "/{accountId:" + UUID_PATTERN + "}")
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ public Response deleteAccountTag(@PathParam("accountId") final String accountId,
+ @QueryParam(QUERY_TAGS) final String tagList,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) {
+
+ try {
+ Account account = accountApi.getAccountById(UUID.fromString(accountId));
+
+ // Tag APIs needs tome rework...
+ String inputTagList = tagList;
+ if (inputTagList == null) {
+ List<Tag> existingTags = account.getTagList();
+ StringBuilder tmp = new StringBuilder();
+ for (Tag cur : existingTags) {
+ tmp.append(cur.getTagDefinitionName());
+ tmp.append(",");
+ }
+ inputTagList = tmp.toString();
+ }
+
+ List<TagDefinition> input = tagHelper.getTagDifinitionFromTagList(tagList);
+ for (TagDefinition cur : input) {
+ account.removeTag(cur);
+ }
+
+ return Response.status(Status.OK).build();
+ } catch (AccountApiException e) {
+ return Response.status(Status.NO_CONTENT).build();
+ } catch (IllegalArgumentException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ } catch (NullPointerException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ } catch (TagDefinitionApiException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ }
+ }
+
+ /************************ CUSTOM FIELDS ******************************/
+
+ @GET
+ @Path(BaseJaxrsResource.CUSTOM_FIELDS + "/{accountId:" + UUID_PATTERN + "}")
+ @Produces(APPLICATION_JSON)
+ public Response getAccountCustomFields(@PathParam("accountId") String accountId) {
+ try {
+ Account account = accountApi.getAccountById(UUID.fromString(accountId));
+ List<CustomField> fields = account.getFieldList();
+ List<CustomFieldJson> result = new LinkedList<CustomFieldJson>();
+ for (CustomField cur : fields) {
+ result.add(new CustomFieldJson(cur));
+ }
+ return Response.status(Status.OK).entity(result).build();
+ } catch (AccountApiException e) {
+ return Response.status(Status.NO_CONTENT).build();
+ }
+ }
+
+
+ @POST
+ @Path(BaseJaxrsResource.CUSTOM_FIELDS + "/{accountId:" + UUID_PATTERN + "}")
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ public Response createCustomField(@PathParam("accountId") final String accountId,
+ List<CustomFieldJson> customFields,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) {
+
+ try {
+
+ Account account = accountApi.getAccountById(UUID.fromString(accountId));
+ LinkedList<CustomField> input = new LinkedList<CustomField>();
+ for (CustomFieldJson cur : customFields) {
+ input.add(new StringCustomField(cur.getName(), cur.getValue()));
+ }
+ account.saveFields(input, context.createContext(createdBy, reason, comment));
+ Response response = uriBuilder.buildResponse(AccountResource.class, "getAccountCustomFields", account.getId());
+ return response;
+ } catch (AccountApiException e) {
+ return Response.status(Status.NO_CONTENT).build();
+ } catch (IllegalArgumentException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ } catch (NullPointerException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ }
+ }
+
+ @DELETE
+ @Path(BaseJaxrsResource.CUSTOM_FIELDS + "/{accountId:" + UUID_PATTERN + "}")
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ public Response deleteCustomFields(@PathParam("accountId") final String accountId,
+ @QueryParam(QUERY_CUSTOM_FIELDS) final String cutomFieldList,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) {
+
+ try {
+ Account account = accountApi.getAccountById(UUID.fromString(accountId));
+ // STEPH missing API to delete custom fields
+ return Response.status(Status.OK).build();
+ } catch (AccountApiException e) {
+ return Response.status(Status.NO_CONTENT).build();
+ } catch (IllegalArgumentException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ } catch (NullPointerException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ }
+ }
+
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java
new file mode 100644
index 0000000..2a9e2ee
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java
@@ -0,0 +1,73 @@
+/*
+ * 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.jaxrs.resources;
+
+public interface BaseJaxrsResource {
+
+ public static final String API_PREFIX = "";
+ public static final String API_VERSION = "/1.0";
+ public static final String API_POSTFIX = "/kb";
+
+ public static final String PREFIX = API_PREFIX + API_VERSION + API_POSTFIX;
+
+ public static final String TIMELINE = "timeline";
+
+ /*
+ * Metadata Additional headers
+ */
+ public static String HDR_CREATED_BY = "X-Killbill-CreatedBy";
+ public static String HDR_REASON = "X-Killbill-Reason";
+ public static String HDR_COMMENT = "X-Killbill-Comment";
+
+ /*
+ * Patterns
+ */
+ public static String STRING_PATTERN = "\\w+";
+ public static String UUID_PATTERN = "\\w+-\\w+-\\w+-\\w+-\\w+";
+
+ /*
+ * Query parameters
+ */
+ public static final String QUERY_EXTERNAL_KEY = "external_key";
+ public static final String QUERY_REQUESTED_DT = "requested_date";
+ public static final String QUERY_CALL_COMPLETION = "call_completion";
+ public static final String QUERY_CALL_TIMEOUT = "call_timeout_sec";
+ public static final String QUERY_DRY_RUN = "dry_run";
+ public static final String QUERY_TARGET_DATE = "target_date";
+ public static final String QUERY_ACCOUNT_ID = "account_id";
+
+ public static final String QUERY_TAGS = "tag_list";
+ public static final String QUERY_CUSTOM_FIELDS = "custom_field_list";
+
+ public static final String ACCOUNTS = "accounts";
+ public static final String ACCOUNTS_PATH = PREFIX + "/" + ACCOUNTS;
+
+ public static final String BUNDLES = "bundles";
+ public static final String BUNDLES_PATH = PREFIX + "/" + BUNDLES;
+
+ public static final String SUBSCRIPTIONS = "subscriptions";
+ public static final String SUBSCRIPTIONS_PATH = PREFIX + "/" + SUBSCRIPTIONS;
+
+ public static final String TAG_DEFINITIONS = "tag_definitions";
+ public static final String TAG_DEFINITIONS_PATH = PREFIX + "/" + TAG_DEFINITIONS;
+
+ public static final String INVOICES = "invoices";
+ public static final String INVOICES_PATH = PREFIX + "/" + INVOICES;
+
+
+ public static final String TAGS = "tags";
+ public static final String CUSTOM_FIELDS = "custom_fields";
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
new file mode 100644
index 0000000..e62d48e
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
@@ -0,0 +1,150 @@
+/*
+ * 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.jaxrs.resources;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+
+@Path(BaseJaxrsResource.BUNDLES_PATH)
+public class BundleResource implements BaseJaxrsResource {
+
+ private static final Logger log = LoggerFactory.getLogger(BundleResource.class);
+
+ private final EntitlementUserApi entitlementApi;
+ private final Context context;
+ private final JaxrsUriBuilder uriBuilder;
+
+ @Inject
+ public BundleResource(final JaxrsUriBuilder uriBuilder, final EntitlementUserApi entitlementApi, final Context context) {
+ this.uriBuilder = uriBuilder;
+ this.entitlementApi = entitlementApi;
+ this.context = context;
+ }
+
+ @GET
+ @Path("/{bundleId:" + UUID_PATTERN + "}")
+ @Produces(APPLICATION_JSON)
+ public Response getBundle(@PathParam("bundleId") final String bundleId) throws EntitlementUserApiException {
+ try {
+ SubscriptionBundle bundle = entitlementApi.getBundleFromId(UUID.fromString(bundleId));
+ BundleJsonNoSubsciptions json = new BundleJsonNoSubsciptions(bundle);
+ return Response.status(Status.OK).entity(json).build();
+ } catch (EntitlementUserApiException e) {
+ if (e.getCode() == ErrorCode.ENT_GET_INVALID_BUNDLE_ID.getCode()) {
+ return Response.status(Status.NO_CONTENT).build();
+ } else {
+ throw e;
+ }
+
+ }
+ }
+
+ @GET
+ @Produces(APPLICATION_JSON)
+ public Response getBundleByKey(@QueryParam(QUERY_EXTERNAL_KEY) final String externalKey) throws EntitlementUserApiException {
+ try {
+ SubscriptionBundle bundle = entitlementApi.getBundleForKey(externalKey);
+ BundleJsonNoSubsciptions json = new BundleJsonNoSubsciptions(bundle);
+ return Response.status(Status.OK).entity(json).build();
+ } catch (EntitlementUserApiException e) {
+ if (e.getCode() == ErrorCode.ENT_GET_INVALID_BUNDLE_KEY.getCode()) {
+ return Response.status(Status.NO_CONTENT).build();
+ } else {
+ throw e;
+ }
+
+ }
+ }
+
+ @POST
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ public Response createBundle(final BundleJsonNoSubsciptions json,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) {
+ try {
+ UUID accountId = UUID.fromString(json.getAccountId());
+ final SubscriptionBundle bundle = entitlementApi.createBundleForAccount(accountId, json.getExternalKey(),
+ context.createContext(createdBy, reason, comment));
+ return uriBuilder.buildResponse(BundleResource.class, "getBundle", bundle.getId());
+ } catch (EntitlementUserApiException e) {
+ log.info(String.format("Failed to create bundle %s", json), e);
+ return Response.status(Status.BAD_REQUEST).build();
+ } catch (IllegalArgumentException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ }
+ }
+
+ @GET
+ @Path("/{bundleId:" + UUID_PATTERN + "}/" + SUBSCRIPTIONS)
+ @Produces(APPLICATION_JSON)
+ public Response getBundleSubscriptions(@PathParam("bundleId") final String bundleId) throws EntitlementUserApiException {
+ try {
+ UUID uuid = UUID.fromString(bundleId);
+ SubscriptionBundle bundle = entitlementApi.getBundleFromId(uuid);
+ if (bundle == null) {
+ return Response.status(Status.NO_CONTENT).build();
+ }
+ List<Subscription> bundles = entitlementApi.getSubscriptionsForBundle(uuid);
+ Collection<SubscriptionJsonNoEvents> result = Collections2.transform(bundles, new Function<Subscription, SubscriptionJsonNoEvents>() {
+ @Override
+ public SubscriptionJsonNoEvents apply(Subscription input) {
+ return new SubscriptionJsonNoEvents(input);
+ }
+ });
+ return Response.status(Status.OK).entity(result).build();
+ } catch (EntitlementUserApiException e) {
+ if (e.getCode() == ErrorCode.ENT_GET_INVALID_BUNDLE_ID.getCode()) {
+ return Response.status(Status.NO_CONTENT).build();
+ } else {
+ throw e;
+ }
+
+ }
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
new file mode 100644
index 0000000..fd2ec57
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.jaxrs.resources;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+import com.google.inject.Inject;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+
+import com.ning.billing.jaxrs.json.InvoiceJsonSimple;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+
+
+@Path(BaseJaxrsResource.INVOICES_PATH)
+public class InvoiceResource implements BaseJaxrsResource {
+
+
+ private static final Logger log = LoggerFactory.getLogger(InvoiceResource.class);
+
+ private final DateTimeFormatter DATE_TIME_FORMATTER = ISODateTimeFormat.dateTime();
+
+ private final AccountUserApi accountApi;
+ private final InvoiceUserApi invoiceApi;
+ private final Context context;
+ private final JaxrsUriBuilder uriBuilder;
+
+ @Inject
+ public InvoiceResource(final AccountUserApi accountApi,
+ final InvoiceUserApi invoiceApi,
+ final Context context,
+ final JaxrsUriBuilder uriBuilder) {
+ this.accountApi = accountApi;
+ this.invoiceApi = invoiceApi;
+ this.context = context;
+ this.uriBuilder = uriBuilder;
+ }
+
+ @GET
+ @Produces(APPLICATION_JSON)
+ public Response getInvoices(@QueryParam(QUERY_ACCOUNT_ID) final String accountId) {
+ try {
+
+ Preconditions.checkNotNull(accountId, "% query parameter must be specified", QUERY_ACCOUNT_ID);
+ accountApi.getAccountById(UUID.fromString(accountId));
+ List<Invoice> invoices = invoiceApi.getInvoicesByAccount(UUID.fromString(accountId));
+ List<InvoiceJsonSimple> result = new LinkedList<InvoiceJsonSimple>();
+ for (Invoice cur : invoices) {
+ result.add(new InvoiceJsonSimple(cur));
+ }
+ return Response.status(Status.OK).entity(result).build();
+ } catch (AccountApiException e) {
+ return Response.status(Status.NO_CONTENT).build();
+ } catch (NullPointerException e) {
+ return Response.status(Status.BAD_REQUEST).build();
+ }
+ }
+
+ @GET
+ @Path("/{invoiceId:\\w+-\\w+-\\w+-\\w+-\\w+}")
+ @Produces(APPLICATION_JSON)
+ public Response getInvoice(@PathParam("invoiceId") String invoiceId) {
+ Invoice invoice = invoiceApi.getInvoice(UUID.fromString(invoiceId));
+ InvoiceJsonSimple json = new InvoiceJsonSimple(invoice);
+ return Response.status(Status.OK).entity(json).build();
+ }
+
+ @POST
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ public Response createFutureInvoice(final InvoiceJsonSimple invoice,
+ @QueryParam(QUERY_ACCOUNT_ID) final String accountId,
+ @QueryParam(QUERY_TARGET_DATE) final String targetDate,
+ @QueryParam(QUERY_DRY_RUN) @DefaultValue("false") final Boolean dryRun,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) {
+
+ try {
+
+ Preconditions.checkNotNull(accountId, "% needs to be specified", QUERY_ACCOUNT_ID);
+ Preconditions.checkNotNull(targetDate, "% needs to be specified", QUERY_TARGET_DATE);
+
+ DateTime inputDate = (targetDate != null) ? DATE_TIME_FORMATTER.parseDateTime(targetDate) : null;
+
+ accountApi.getAccountById(UUID.fromString(accountId));
+ Invoice generatedInvoice = invoiceApi.triggerInvoiceGeneration(UUID.fromString(accountId), inputDate, dryRun.booleanValue(),
+ context.createContext(createdBy, reason, comment));
+ if (dryRun) {
+ return Response.status(Status.OK).entity(new InvoiceJsonSimple(generatedInvoice)).build();
+ } else {
+ return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", generatedInvoice.getId());
+ }
+ } catch (AccountApiException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ } catch (InvoiceApiException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ } catch (NullPointerException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ }
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
new file mode 100644
index 0000000..2988f5a
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
@@ -0,0 +1,62 @@
+/*
+ * 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.jaxrs.resources;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import com.ning.billing.jaxrs.json.PaymentJsonSimple;
+
+
+@Path("/1.0/payment")
+public class PaymentResource {
+
+
+ @GET
+ @Path("/{invoiceId:\\w+-\\w+-\\w+-\\w+-\\w+}")
+ @Produces(APPLICATION_JSON)
+ public Response getPayments(@PathParam("invoiceId") String invoiceId) {
+ return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+ }
+
+ @GET
+ @Path("/account/{accountId:\\w+-\\w+-\\w+-\\w+-\\w+}")
+ @Produces(APPLICATION_JSON)
+ public Response getAllPayments(@PathParam("accountId") String accountId) {
+ return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+ }
+
+ @POST
+ @Produces(APPLICATION_JSON)
+ @Consumes(APPLICATION_JSON)
+ @Path("/{invoiceId:\\w+-\\w+-\\w+-\\w+-\\w+}")
+ public Response createInstantPayment(PaymentJsonSimple payment,
+ @PathParam("invoiceId") String invoiceId,
+ @QueryParam("last4CC") String last4CC,
+ @QueryParam("nameOnCC") String nameOnCC) {
+ return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
new file mode 100644
index 0000000..67c5b5c
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
@@ -0,0 +1,345 @@
+/*
+ * 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.jaxrs.resources;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+import java.util.concurrent.TimeoutException;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.invoice.api.EmptyInvoiceEvent;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+import com.ning.billing.jaxrs.util.KillbillEventHandler;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.userrequest.CompletionUserRequestBase;
+
+@Path(BaseJaxrsResource.SUBSCRIPTIONS_PATH)
+public class SubscriptionResource implements BaseJaxrsResource {
+
+ private static final Logger log = LoggerFactory.getLogger(SubscriptionResource.class);
+
+ private final DateTimeFormatter DATE_TIME_FORMATTER = ISODateTimeFormat.dateTime();
+
+ private final EntitlementUserApi entitlementApi;
+ private final Context context;
+ private final JaxrsUriBuilder uriBuilder;
+ private final KillbillEventHandler killbillHandler;
+
+ @Inject
+ public SubscriptionResource(final JaxrsUriBuilder uriBuilder, final EntitlementUserApi entitlementApi,
+ final Clock clock, final Context context, final KillbillEventHandler killbillHandler) {
+ this.uriBuilder = uriBuilder;
+ this.entitlementApi = entitlementApi;
+ this.context = context;
+ this.killbillHandler = killbillHandler;
+ }
+
+ @GET
+ @Path("/{subscriptionId:" + UUID_PATTERN + "}")
+ @Produces(APPLICATION_JSON)
+ public Response getSubscription(@PathParam("subscriptionId") final String subscriptionId) throws EntitlementUserApiException {
+
+ try {
+ UUID uuid = UUID.fromString(subscriptionId);
+ Subscription subscription = entitlementApi.getSubscriptionFromId(uuid);
+ SubscriptionJsonNoEvents json = new SubscriptionJsonNoEvents(subscription);
+ return Response.status(Status.OK).entity(json).build();
+ } catch (EntitlementUserApiException e) {
+ if (e.getCode() == ErrorCode.ENT_INVALID_SUBSCRIPTION_ID.getCode()) {
+ return Response.status(Status.NO_CONTENT).build();
+ } else {
+ throw e;
+ }
+ }
+ }
+
+
+ @POST
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ public Response createSubscription(final SubscriptionJsonNoEvents subscription,
+ @QueryParam(QUERY_REQUESTED_DT) final String requestedDate,
+ @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") final Boolean callCompletion,
+ @QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") final long timeoutSec,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) {
+
+
+ SubscriptionCallCompletionCallback<Subscription> callback = new SubscriptionCallCompletionCallback<Subscription>() {
+ @Override
+ public Subscription doOperation(final CallContext ctx) throws EntitlementUserApiException, InterruptedException, TimeoutException {
+
+ DateTime inputDate = (requestedDate != null) ? DATE_TIME_FORMATTER.parseDateTime(requestedDate) : null;
+ UUID uuid = UUID.fromString(subscription.getBundleId());
+
+ PlanPhaseSpecifier spec = new PlanPhaseSpecifier(subscription.getProductName(),
+ ProductCategory.valueOf(subscription.getProductCategory()),
+ BillingPeriod.valueOf(subscription.getBillingPeriod()), subscription.getPriceList(), null);
+ return entitlementApi.createSubscription(uuid, spec, inputDate, ctx);
+ }
+ @Override
+ public boolean isImmOperation() {
+ return true;
+ }
+ @Override
+ public Response doResponseOk(final Subscription createdSubscription) {
+ return uriBuilder.buildResponse(SubscriptionResource.class, "getSubscription", createdSubscription.getId());
+ }
+ };
+ SubscriptionCallCompletion<Subscription> callCompletionCreation = new SubscriptionCallCompletion<Subscription>();
+ return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, createdBy, reason, comment);
+ }
+
+ @PUT
+ @Produces(APPLICATION_JSON)
+ @Consumes(APPLICATION_JSON)
+ @Path("/{subscriptionId:" + UUID_PATTERN + "}")
+ public Response changeSubscriptionPlan(final SubscriptionJsonNoEvents subscription,
+ @PathParam("subscriptionId") final String subscriptionId,
+ @QueryParam(QUERY_REQUESTED_DT) final String requestedDate,
+ @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") final Boolean callCompletion,
+ @QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") final long timeoutSec,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) {
+
+ SubscriptionCallCompletionCallback<Response> callback = new SubscriptionCallCompletionCallback<Response>() {
+
+ private boolean isImmediateOp = true;
+
+ @Override
+ public Response doOperation(CallContext ctx)
+ throws EntitlementUserApiException, InterruptedException,
+ TimeoutException {
+ try {
+ UUID uuid = UUID.fromString(subscriptionId);
+ Subscription current = entitlementApi.getSubscriptionFromId(uuid);
+ DateTime inputDate = (requestedDate != null) ? DATE_TIME_FORMATTER.parseDateTime(requestedDate) : null;
+ isImmediateOp = current.changePlan(subscription.getProductName(), BillingPeriod.valueOf(subscription.getBillingPeriod()), subscription.getPriceList(), inputDate, ctx);
+ return Response.status(Status.OK).build();
+ } catch (EntitlementUserApiException e) {
+ log.warn("Subscription not found: " + subscriptionId , e);
+ return Response.status(Status.NO_CONTENT).build();
+ }
+ }
+ @Override
+ public boolean isImmOperation() {
+ return isImmediateOp;
+ }
+ @Override
+ public Response doResponseOk(Response operationResponse) {
+ if (operationResponse.getStatus() != Status.OK.getStatusCode()) {
+ return operationResponse;
+ }
+ try {
+ return getSubscription(subscriptionId);
+ } catch (EntitlementUserApiException e) {
+ if (e.getCode() == ErrorCode.ENT_GET_INVALID_BUNDLE_ID.getCode()) {
+ return Response.status(Status.NO_CONTENT).build();
+ } else {
+ return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+ }
+ }
+ }
+ };
+ SubscriptionCallCompletion<Response> callCompletionCreation = new SubscriptionCallCompletion<Response>();
+ return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, createdBy, reason, comment);
+ }
+
+ @PUT
+ @Path("/{subscriptionId:" + UUID_PATTERN + "}/uncancel")
+ @Produces(APPLICATION_JSON)
+ public Response uncancelSubscriptionPlan(@PathParam("subscriptionId") final String subscriptionId,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) {
+ try {
+ UUID uuid = UUID.fromString(subscriptionId);
+ Subscription current = entitlementApi.getSubscriptionFromId(uuid);
+
+ current.uncancel(context.createContext(createdBy, reason, comment));
+ return Response.status(Status.OK).build();
+ } catch (EntitlementUserApiException e) {
+ if(e.getCode() == ErrorCode.ENT_INVALID_SUBSCRIPTION_ID.getCode()) {
+ return Response.status(Status.NO_CONTENT).build();
+ } else {
+ log.info(String.format("Failed to uncancel plan for subscription %s", subscriptionId), e);
+ return Response.status(Status.BAD_REQUEST).build();
+ }
+ }
+ }
+
+ @DELETE
+ @Path("/{subscriptionId:" + UUID_PATTERN + "}")
+ @Produces(APPLICATION_JSON)
+ public Response cancelSubscriptionPlan(final @PathParam("subscriptionId") String subscriptionId,
+ @QueryParam(QUERY_REQUESTED_DT) final String requestedDate,
+ @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") final Boolean callCompletion,
+ @QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") final long timeoutSec,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) {
+
+ SubscriptionCallCompletionCallback<Response> callback = new SubscriptionCallCompletionCallback<Response>() {
+
+ private boolean isImmediateOp = true;
+
+ @Override
+ public Response doOperation(CallContext ctx)
+ throws EntitlementUserApiException, InterruptedException,
+ TimeoutException {
+ try {
+ UUID uuid = UUID.fromString(subscriptionId);
+
+ Subscription current = entitlementApi.getSubscriptionFromId(uuid);
+
+ DateTime inputDate = (requestedDate != null) ? DATE_TIME_FORMATTER.parseDateTime(requestedDate) : null;
+ isImmediateOp = current.cancel(inputDate, false, ctx);
+ return Response.status(Status.OK).build();
+ } catch (EntitlementUserApiException e) {
+ if(e.getCode() == ErrorCode.ENT_INVALID_SUBSCRIPTION_ID.getCode()) {
+ return Response.status(Status.NO_CONTENT).build();
+ } else {
+ throw e;
+ }
+ }
+ }
+ @Override
+ public boolean isImmOperation() {
+ return isImmediateOp;
+ }
+ @Override
+ public Response doResponseOk(Response operationResponse) {
+ return operationResponse;
+ }
+ };
+ SubscriptionCallCompletion<Response> callCompletionCreation = new SubscriptionCallCompletion<Response>();
+ return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, createdBy, reason, comment);
+ }
+
+ private final static class CompletionUserRequestSubscription extends CompletionUserRequestBase {
+
+ public CompletionUserRequestSubscription(final UUID userToken) {
+ super(userToken);
+ }
+ @Override
+ public void onSubscriptionTransition(SubscriptionEvent curEvent) {
+ log.debug(String.format("Got event SubscriptionTransition token = %s, type = %s, remaining = %d ",
+ curEvent.getUserToken(), curEvent.getTransitionType(), curEvent.getRemainingEventsForUserOperation()));
+ }
+ @Override
+ public void onEmptyInvoice(final EmptyInvoiceEvent curEvent) {
+ log.debug(String.format("Got event EmptyInvoiceNotification token = %s ", curEvent.getUserToken()));
+ notifyForCompletion();
+ }
+ @Override
+ public void onInvoiceCreation(InvoiceCreationEvent curEvent) {
+ log.debug(String.format("Got event InvoiceCreationNotification token = %s ", curEvent.getUserToken()));
+ if (curEvent.getAmountOwed().compareTo(BigDecimal.ZERO) <= 0) {
+ notifyForCompletion();
+ }
+ }
+ @Override
+ public void onPaymentInfo(PaymentInfoEvent curEvent) {
+ log.debug(String.format("Got event PaymentInfo token = %s ", curEvent.getUserToken()));
+ notifyForCompletion();
+ }
+ @Override
+ public void onPaymentError(PaymentErrorEvent curEvent) {
+ log.debug(String.format("Got event PaymentError token = %s ", curEvent.getUserToken()));
+ notifyForCompletion();
+ }
+ }
+
+ private interface SubscriptionCallCompletionCallback<T> {
+ public T doOperation(final CallContext ctx) throws EntitlementUserApiException, InterruptedException, TimeoutException;
+ public boolean isImmOperation();
+ public Response doResponseOk(final T operationResponse);
+ }
+
+ private class SubscriptionCallCompletion<T> {
+
+ public Response withSynchronization(final SubscriptionCallCompletionCallback<T> callback,
+ final long timeoutSec,
+ final boolean callCompletion,
+ final String createdBy,
+ final String reason,
+ final String comment) {
+
+ CallContext ctx = context.createContext(createdBy, reason, comment);
+ CompletionUserRequestSubscription waiter = callCompletion ? new CompletionUserRequestSubscription(ctx.getUserToken()) : null;
+ try {
+ if (waiter != null) {
+ killbillHandler.registerCompletionUserRequestWaiter(waiter);
+ }
+ T operationValue = callback.doOperation(ctx);
+ if (waiter != null && callback.isImmOperation()) {
+ waiter.waitForCompletion(timeoutSec * 1000);
+ }
+ return callback.doResponseOk(operationValue);
+ } catch (EntitlementUserApiException e) {
+ log.info(String.format("Failed to complete operation"), e);
+ return Response.status(Status.BAD_REQUEST).build();
+ } catch (InterruptedException e) {
+ return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+ } catch (TimeoutException e) {
+ return Response.status(Status.fromStatusCode(408)).build();
+ } finally {
+ if (waiter != null) {
+ killbillHandler.unregisterCompletionUserRequestWaiter(waiter);
+ }
+ }
+ }
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagResource.java
new file mode 100644
index 0000000..3111240
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagResource.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.jaxrs.resources;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.ning.billing.jaxrs.json.TagDefinitionJson;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.tag.TagDefinition;
+
+@Singleton
+@Path(BaseJaxrsResource.TAG_DEFINITIONS_PATH)
+public class TagResource implements BaseJaxrsResource {
+
+ private final TagUserApi tagUserApi;
+ private final Context context;
+ private final JaxrsUriBuilder uriBuilder;
+
+ @Inject
+ public TagResource(TagUserApi tagUserApi, final JaxrsUriBuilder uriBuilder, final Context context) {
+ this.tagUserApi = tagUserApi;
+ this.context = context;
+ this.uriBuilder = uriBuilder;
+ }
+
+ @GET
+ @Produces(APPLICATION_JSON)
+ public Response getTagDefinitions() {
+
+ List<TagDefinitionJson> result = new LinkedList<TagDefinitionJson>();
+ List<TagDefinition> tagDefinitions = tagUserApi.getTagDefinitions();
+ for (TagDefinition cur : tagDefinitions) {
+ result.add(new TagDefinitionJson(cur.getName(), cur.getDescription()));
+ }
+ return Response.status(Status.OK).entity(result).build();
+ }
+
+ @GET
+ @Path("/{tagDefinitionName:" + STRING_PATTERN + "}")
+ @Produces(APPLICATION_JSON)
+ public Response getTagDefinition(@PathParam("tagDefinitionName") final String tagDefName) {
+ try {
+ TagDefinition tagDef = tagUserApi.getTagDefinition(tagDefName);
+ TagDefinitionJson json = new TagDefinitionJson(tagDef.getName(), tagDef.getDescription());
+ return Response.status(Status.OK).entity(json).build();
+ } catch (TagDefinitionApiException e) {
+ return Response.status(Status.NO_CONTENT).build();
+ }
+ }
+
+
+
+ @POST
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ public Response createTagDefinition(final TagDefinitionJson json,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) {
+ try {
+ TagDefinition createdTagDef = tagUserApi.create(json.getName(), json.getDescription(), context.createContext(createdBy, reason, comment));
+ return uriBuilder.buildResponse(TagResource.class, "getTagDefinition", createdTagDef.getName());
+ } catch (TagDefinitionApiException e) {
+ return Response.status(Status.NO_CONTENT).build();
+ } catch (IllegalArgumentException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ }
+ }
+
+ @DELETE
+ @Path("/{tagDefinitionName:" + STRING_PATTERN + "}")
+ @Produces(APPLICATION_JSON)
+ public Response deleteTagDefinition(@PathParam("tagDefinitionName") String tagDefName,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) {
+ try {
+ tagUserApi.deleteTagDefinition(tagDefName, context.createContext(createdBy, reason, comment));
+ return Response.status(Status.NO_CONTENT).build();
+ } catch (TagDefinitionApiException e) {
+ return Response.status(Status.NO_CONTENT).build();
+ } catch (IllegalArgumentException e) {
+ return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+ }
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/Context.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/Context.java
new file mode 100644
index 0000000..729f166
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/Context.java
@@ -0,0 +1,51 @@
+/*
+ * 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.jaxrs.util;
+
+import java.util.UUID;
+
+import com.google.common.base.Preconditions;
+import com.google.inject.Inject;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.UserType;
+
+public class Context {
+
+ private final CallOrigin origin;
+ private final UserType userType;
+ final CallContextFactory contextFactory;
+
+ @Inject
+ public Context(final CallContextFactory factory) {
+ super();
+ this.origin = CallOrigin.EXTERNAL;
+ this.userType = UserType.CUSTOMER;
+ this.contextFactory = factory;
+ }
+
+ public CallContext createContext(final String createdBy, final String reason, final String comment)
+ throws IllegalArgumentException {
+ try {
+ Preconditions.checkNotNull(createdBy, String.format("Header %s needs to be set", BaseJaxrsResource.HDR_CREATED_BY));
+ return contextFactory.createCallContext(createdBy, origin, userType, UUID.randomUUID());
+ } catch (NullPointerException e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java
new file mode 100644
index 0000000..b351b4c
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.jaxrs.util;
+
+import java.net.URI;
+import java.util.UUID;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+
+public class JaxrsUriBuilder {
+
+
+ public Response buildResponse(final Class<? extends BaseJaxrsResource> theClass, final String getMethodName, final Object objectId) {
+ URI uri = UriBuilder.fromPath(objectId.toString()).build();
+ Response.ResponseBuilder ri = Response.created(uri);
+ return ri.entity(new Object() {
+ @SuppressWarnings(value = "all")
+ public URI getUri() {
+ return UriBuilder.fromResource(theClass).path(theClass, getMethodName).build(objectId);
+ }
+ }).build();
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/KillbillEventHandler.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/KillbillEventHandler.java
new file mode 100644
index 0000000..dc18416
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/KillbillEventHandler.java
@@ -0,0 +1,70 @@
+/*
+ * 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.jaxrs.util;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.google.common.eventbus.Subscribe;
+import com.ning.billing.util.bus.BusEvent;
+import com.ning.billing.util.userrequest.CompletionUserRequest;
+import com.ning.billing.util.userrequest.CompletionUserRequestNotifier;
+
+public class KillbillEventHandler {
+
+
+ private final List<CompletionUserRequest> activeWaiters;
+
+ public KillbillEventHandler() {
+ activeWaiters = new LinkedList<CompletionUserRequest>();
+ }
+
+ public void registerCompletionUserRequestWaiter(final CompletionUserRequest waiter) {
+ if (waiter == null) {
+ return;
+ }
+ synchronized(activeWaiters) {
+ activeWaiters.add(waiter);
+ }
+ }
+
+ public void unregisterCompletionUserRequestWaiter(final CompletionUserRequest waiter) {
+ if (waiter == null) {
+ return;
+ }
+ synchronized(activeWaiters) {
+ activeWaiters.remove(waiter);
+ }
+ }
+
+ /*
+ * IRS event handler for killbill entitlement events
+ */
+ @Subscribe
+ public void handleEntitlementevents(final BusEvent event) {
+ List<CompletionUserRequestNotifier> runningWaiters = new ArrayList<CompletionUserRequestNotifier>();
+ synchronized(activeWaiters) {
+ runningWaiters.addAll(activeWaiters);
+ }
+ if (runningWaiters.size() == 0) {
+ return;
+ }
+ for (CompletionUserRequestNotifier cur : runningWaiters) {
+ cur.onBusEvent(event);
+ }
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/TagHelper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/TagHelper.java
new file mode 100644
index 0000000..e965deb
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/TagHelper.java
@@ -0,0 +1,49 @@
+/*
+ * 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.jaxrs.util;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.tag.TagDefinition;
+
+public class TagHelper {
+
+ private final TagUserApi tagUserApi;
+
+ @Inject
+ public TagHelper(final TagUserApi tagUserApi) {
+ this.tagUserApi = tagUserApi;
+ }
+
+ public List<TagDefinition> getTagDifinitionFromTagList(final String tagList) throws TagDefinitionApiException {
+ List<TagDefinition> result = new LinkedList<TagDefinition>();
+ String [] tagParts = tagList.split(",\\s*");
+ for (String cur : tagParts) {
+ TagDefinition curDef = tagUserApi.getTagDefinition(cur);
+ // Yack should throw excption
+ if (curDef == null) {
+ throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_DOES_NOT_EXIST, cur);
+ }
+ result.add(curDef);
+ }
+ return result;
+ }
+}
diff --git a/jaxrs/src/main/resources/.dont-let-git-remove-this-directory b/jaxrs/src/main/resources/.dont-let-git-remove-this-directory
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/jaxrs/src/main/resources/.dont-let-git-remove-this-directory
jaxrs/src/test/resources/log4j.xml 36(+36 -0)
diff --git a/jaxrs/src/test/resources/log4j.xml b/jaxrs/src/test/resources/log4j.xml
new file mode 100644
index 0000000..512d2dc
--- /dev/null
+++ b/jaxrs/src/test/resources/log4j.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2010-2011 Ning, Inc.
+ ~
+ ~ Ning licenses this file to you under the Apache License, version 2.0
+ ~ (the "License"); you may not use this file except in compliance with the
+ ~ License. You may obtain a copy of the License at:
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ ~ License for the specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+ <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
+ <param name="Target" value="System.out"/>
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%p %d{ISO8601} %X{trace} %t %c %m%n"/>
+ </layout>
+ </appender>
+
+
+ <logger name="com.ning.billing.jaxrs">
+ <level value="info"/>
+ </logger>
+
+ <root>
+ <priority value="info"/>
+ <appender-ref ref="stdout"/>
+ </root>
+</log4j:configuration>
junction/pom.xml 114(+114 -0)
diff --git a/junction/pom.xml b/junction/pom.xml
new file mode 100644
index 0000000..0375b37
--- /dev/null
+++ b/junction/pom.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ~ Copyright 2010-2011 Ning, Inc. ~ ~ Ning licenses this file to you
+ under the Apache License, version 2.0 ~ (the "License"); you may not use
+ this file except in compliance with the ~ License. You may obtain a copy
+ of the License at: ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless
+ required by applicable law or agreed to in writing, software ~ distributed
+ under the License is distributed on an "AS IS" BASIS, WITHOUT ~ WARRANTIES
+ OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for
+ the specific language governing permissions and limitations ~ under the License. -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill</artifactId>
+ <version>0.1.11-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <artifactId>killbill-junction</artifactId>
+ <name>killbill-junction</name>
+ <packaging>jar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.inject</groupId>
+ <artifactId>guice</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.skife.config</groupId>
+ <artifactId>config-magic</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>joda-time</groupId>
+ <artifactId>joda-time</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jdbi</groupId>
+ <artifactId>jdbi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <!-- TEST SCOPE -->
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-util</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-catalog</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-catalog</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-mxj</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-mxj-db-files</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <!-- Strangely this is needed in order to run the tests in local db mode -->
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>test</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/junction/src/main/java/com/ning/billing/junction/api/blocking/DefaultBlockingApi.java b/junction/src/main/java/com/ning/billing/junction/api/blocking/DefaultBlockingApi.java
new file mode 100644
index 0000000..904d303
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/api/blocking/DefaultBlockingApi.java
@@ -0,0 +1,71 @@
+/*
+ * 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.junction.api.blocking;
+
+import java.util.SortedSet;
+import java.util.UUID;
+
+import com.google.inject.Inject;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
+import com.ning.billing.junction.dao.BlockingStateDao;
+import com.ning.billing.util.clock.Clock;
+
+public class DefaultBlockingApi implements BlockingApi {
+ private BlockingStateDao dao;
+ private Clock clock;
+
+ @Inject
+ public DefaultBlockingApi(BlockingStateDao dao, Clock clock) {
+ this.dao = dao;
+ this.clock = clock;
+ }
+
+ @Override
+ public BlockingState getBlockingStateFor(Blockable overdueable) {
+ BlockingState state = dao.getBlockingStateFor(overdueable);
+ if(state == null) {
+ state = DefaultBlockingState.getClearState();
+ }
+ return state;
+
+ }
+
+ @Override
+ public BlockingState getBlockingStateFor(UUID overdueableId) {
+ return dao.getBlockingStateFor(overdueableId);
+ }
+
+ @Override
+ public SortedSet<BlockingState> getBlockingHistory(Blockable overdueable) {
+ return dao.getBlockingHistoryFor(overdueable);
+ }
+
+ @Override
+ public SortedSet<BlockingState> getBlockingHistory(UUID overdueableId) {
+ return dao.getBlockingHistoryFor(overdueableId);
+ }
+
+ @Override
+ public <T extends Blockable> void setBlockingState(BlockingState state) {
+ dao.setBlockingState(state, clock);
+
+ }
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/block/BlockingChecker.java b/junction/src/main/java/com/ning/billing/junction/block/BlockingChecker.java
new file mode 100644
index 0000000..04d68f0
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/block/BlockingChecker.java
@@ -0,0 +1,37 @@
+/*
+ * 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.junction.block;
+
+import java.util.UUID;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApiException;
+
+public interface BlockingChecker {
+
+ public void checkBlockedChange(Blockable blockable) throws BlockingApiException;
+
+ public void checkBlockedEntitlement(Blockable blockable) throws BlockingApiException;
+
+ public void checkBlockedBilling(Blockable blockable) throws BlockingApiException;
+
+ public void checkBlockedChange(UUID bundleId, Blockable.Type type) throws BlockingApiException;
+
+ public void checkBlockedEntitlement(UUID bundleId, Blockable.Type type) throws BlockingApiException;
+
+ public void checkBlockedBilling(UUID bundleId, Blockable.Type type) throws BlockingApiException;
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/block/DefaultBlockingChecker.java b/junction/src/main/java/com/ning/billing/junction/block/DefaultBlockingChecker.java
new file mode 100644
index 0000000..8becce5
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/block/DefaultBlockingChecker.java
@@ -0,0 +1,226 @@
+/*
+ * 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.junction.block;
+
+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.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApiException;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.dao.BlockingStateDao;
+
+public class DefaultBlockingChecker implements BlockingChecker {
+
+ private static class BlockingAggregator {
+ private boolean blockChange = false;
+ private boolean blockEntitlement= false;
+ private boolean blockBilling = false;
+
+ public void or(BlockingState state) {
+ if (state == null) { return; }
+ blockChange = blockChange || state.isBlockChange();
+ blockEntitlement = blockEntitlement || state.isBlockEntitlement();
+ blockBilling = blockBilling || state.isBlockBilling();
+ }
+
+ public void or(BlockingAggregator state) {
+ if (state == null) { return; }
+ blockChange = blockChange || state.isBlockChange();
+ blockEntitlement = blockEntitlement || state.isBlockEntitlement();
+ blockBilling = blockBilling || state.isBlockBilling();
+ }
+
+ public boolean isBlockChange() {
+ return blockChange;
+ }
+ public boolean isBlockEntitlement() {
+ return blockEntitlement;
+ }
+ public boolean isBlockBilling() {
+ return blockBilling;
+ }
+
+ }
+
+ private static final Object TYPE_SUBSCRIPTION = "Subscription";
+ private static final Object TYPE_BUNDLE = "Bundle";
+ private static final Object TYPE_ACCOUNT = "ACCOUNT";
+
+ private static final Object ACTION_CHANGE = "Change";
+ private static final Object ACTION_ENTITLEMENT = "Entitlement";
+ private static final Object ACTION_BILLING = "Billing";
+
+ private final EntitlementUserApi entitlementApi;
+ private final BlockingStateDao dao;
+
+ @Inject
+ public DefaultBlockingChecker(EntitlementUserApi entitlementApi, BlockingStateDao dao) {
+ this.entitlementApi = entitlementApi;
+ this.dao = dao;
+ }
+
+ public BlockingAggregator getBlockedStateSubscriptionId(UUID subscriptionId) throws EntitlementUserApiException {
+ Subscription subscription = entitlementApi.getSubscriptionFromId(subscriptionId);
+ return getBlockedStateSubscription(subscription);
+ }
+
+ public BlockingAggregator getBlockedStateSubscription(Subscription subscription) throws EntitlementUserApiException {
+ BlockingAggregator result = new BlockingAggregator();
+ if(subscription != null) {
+ BlockingState subscriptionState = subscription.getBlockingState();
+ if(subscriptionState != null) {
+ result.or(subscriptionState);
+ }
+ if(subscription.getBundleId() != null) {
+ result.or(getBlockedStateBundleId(subscription.getBundleId()));
+ }
+ }
+ return result;
+ }
+
+ public BlockingAggregator getBlockedStateBundleId(UUID bundleId) throws EntitlementUserApiException {
+ SubscriptionBundle bundle = entitlementApi.getBundleFromId(bundleId);
+ return getBlockedStateBundle(bundle);
+ }
+
+ public BlockingAggregator getBlockedStateBundle(SubscriptionBundle bundle) {
+ BlockingAggregator result = getBlockedStateAccountId(bundle.getAccountId());
+ BlockingState bundleState = bundle.getBlockingState();
+ if(bundleState != null) {
+ result.or(bundleState);
+ }
+ return result;
+ }
+
+ public BlockingAggregator getBlockedStateAccountId(UUID accountId) {
+ BlockingAggregator result = new BlockingAggregator();
+ if(accountId != null) {
+ BlockingState accountState = dao.getBlockingStateFor(accountId);
+ result.or(accountState);
+ }
+ return result;
+ }
+
+ public BlockingAggregator getBlockedStateAccount(Account account) {
+ if(account != null) {
+ return getBlockedStateAccountId(account.getId());
+ }
+ return new BlockingAggregator();
+ }
+ @Override
+ public void checkBlockedChange(Blockable blockable) throws BlockingApiException {
+ try {
+ if(blockable instanceof Subscription && getBlockedStateSubscription((Subscription) blockable).isBlockChange()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_CHANGE, TYPE_SUBSCRIPTION, blockable.getId().toString());
+ } else if(blockable instanceof SubscriptionBundle && getBlockedStateBundle((SubscriptionBundle) blockable).isBlockChange()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_CHANGE, TYPE_BUNDLE, blockable.getId().toString());
+ } else if(blockable instanceof Account && getBlockedStateAccount((Account) blockable).isBlockChange()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_CHANGE, TYPE_ACCOUNT, blockable.getId().toString());
+ }
+ } catch (EntitlementUserApiException e) {
+ throw new BlockingApiException(e, ErrorCode.values()[e.getCode()]);
+ }
+ }
+
+ @Override
+ public void checkBlockedEntitlement(Blockable blockable) throws BlockingApiException {
+ try {
+ if(blockable instanceof Subscription && getBlockedStateSubscription((Subscription) blockable).isBlockEntitlement()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_ENTITLEMENT, TYPE_SUBSCRIPTION, blockable.getId().toString());
+ } else if(blockable instanceof SubscriptionBundle && getBlockedStateBundle((SubscriptionBundle) blockable).isBlockEntitlement()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_ENTITLEMENT, TYPE_BUNDLE, blockable.getId().toString());
+ } else if(blockable instanceof Account && getBlockedStateAccount((Account) blockable).isBlockEntitlement()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_ENTITLEMENT, TYPE_ACCOUNT, blockable.getId().toString());
+ }
+ } catch (EntitlementUserApiException e) {
+ throw new BlockingApiException(e, ErrorCode.values()[e.getCode()]);
+ }
+ }
+
+ @Override
+ public void checkBlockedBilling(Blockable blockable) throws BlockingApiException {
+ try {
+ if(blockable instanceof Subscription && getBlockedStateSubscription((Subscription) blockable).isBlockBilling()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_BILLING, TYPE_SUBSCRIPTION, blockable.getId().toString());
+ } else if(blockable instanceof SubscriptionBundle && getBlockedStateBundle((SubscriptionBundle) blockable).isBlockBilling()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_BILLING, TYPE_BUNDLE, blockable.getId().toString());
+ } else if(blockable instanceof Account && getBlockedStateAccount((Account) blockable).isBlockBilling()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_BILLING, TYPE_ACCOUNT, blockable.getId().toString());
+ }
+ } catch (EntitlementUserApiException e) {
+ throw new BlockingApiException(e, ErrorCode.values()[e.getCode()]);
+ }
+ }
+
+
+ @Override
+ public void checkBlockedChange(UUID blockableId, Blockable.Type type) throws BlockingApiException {
+ try {
+ if(type == Blockable.Type.SUBSCRIPTION && getBlockedStateSubscriptionId(blockableId).isBlockChange()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_CHANGE, TYPE_SUBSCRIPTION, blockableId.toString());
+ } else if(type == Blockable.Type.SUBSCRIPTION_BUNDLE && getBlockedStateBundleId(blockableId).isBlockChange()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_CHANGE, TYPE_BUNDLE, blockableId.toString());
+ } else if(type == Blockable.Type.ACCOUNT && getBlockedStateAccountId(blockableId).isBlockChange()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_CHANGE, TYPE_ACCOUNT, blockableId.toString());
+
+ }
+ } catch (EntitlementUserApiException e) {
+ throw new BlockingApiException(e, ErrorCode.values()[e.getCode()]);
+ }
+ }
+
+ @Override
+ public void checkBlockedEntitlement(UUID blockableId, Blockable.Type type) throws BlockingApiException {
+ try {
+ if(type == Blockable.Type.SUBSCRIPTION && getBlockedStateSubscriptionId(blockableId).isBlockEntitlement()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_ENTITLEMENT, TYPE_SUBSCRIPTION, blockableId.toString());
+ } else if(type == Blockable.Type.SUBSCRIPTION_BUNDLE && getBlockedStateBundleId(blockableId).isBlockEntitlement()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_ENTITLEMENT, TYPE_BUNDLE, blockableId.toString());
+ } else if(type == Blockable.Type.ACCOUNT && getBlockedStateAccountId(blockableId).isBlockEntitlement()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_ENTITLEMENT, TYPE_ACCOUNT, blockableId.toString());
+ }
+ } catch (EntitlementUserApiException e) {
+ throw new BlockingApiException(e, ErrorCode.values()[e.getCode()]);
+ }
+ }
+
+ @Override
+ public void checkBlockedBilling(UUID blockableId, Blockable.Type type) throws BlockingApiException {
+ try {
+ if(type == Blockable.Type.SUBSCRIPTION && getBlockedStateSubscriptionId(blockableId).isBlockBilling()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_BILLING, TYPE_SUBSCRIPTION, blockableId.toString());
+ } else if(type == Blockable.Type.SUBSCRIPTION_BUNDLE && getBlockedStateBundleId(blockableId).isBlockBilling()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_BILLING, TYPE_BUNDLE, blockableId.toString());
+ } else if(type == Blockable.Type.ACCOUNT && getBlockedStateAccountId(blockableId).isBlockBilling()) {
+ throw new BlockingApiException(ErrorCode.BLOCK_BLOCKED_ACTION,ACTION_BILLING, TYPE_ACCOUNT, blockableId.toString());
+ }
+ } catch (EntitlementUserApiException e) {
+ throw new BlockingApiException(e, ErrorCode.values()[e.getCode()]);
+ }
+ }
+
+
+
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/dao/BlockingStateDao.java b/junction/src/main/java/com/ning/billing/junction/dao/BlockingStateDao.java
new file mode 100644
index 0000000..6f5396d
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/dao/BlockingStateDao.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.junction.dao;
+
+import java.util.SortedSet;
+import java.util.UUID;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.Blockable.Type;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.util.clock.Clock;
+
+public interface BlockingStateDao {
+
+ //Read
+ public BlockingState getBlockingStateFor(Blockable blockable);
+
+ public BlockingState getBlockingStateFor(UUID blockableId);
+
+ public SortedSet<BlockingState> getBlockingHistoryFor(Blockable blockable);
+
+ public SortedSet<BlockingState> getBlockingHistoryFor(UUID blockableId);
+
+ //Write
+ <T extends Blockable> void setBlockingState(BlockingState state, Clock clock);
+
+}
\ No newline at end of file
diff --git a/junction/src/main/java/com/ning/billing/junction/dao/BlockingStateSqlDao.java b/junction/src/main/java/com/ning/billing/junction/dao/BlockingStateSqlDao.java
new file mode 100644
index 0000000..ba241a4
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/dao/BlockingStateSqlDao.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.junction.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.SortedSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.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.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.Blockable.Type;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingApiException;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
+import com.ning.billing.overdue.OverdueState;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.dao.BinderBase;
+import com.ning.billing.util.dao.MapperBase;
+
+@ExternalizedSqlViaStringTemplate3()
+public interface BlockingStateSqlDao extends BlockingStateDao, CloseMe, Transmogrifier {
+
+ @Override
+ @SqlUpdate
+ public abstract <T extends Blockable> void setBlockingState(
+ @Bind(binder = BlockingStateBinder.class) BlockingState state,
+ @Bind(binder = CurrentTimeBinder.class) Clock clock) ;
+
+
+ @Override
+ @SqlQuery
+ @Mapper(BlockingHistorySqlMapper.class)
+ public abstract BlockingState getBlockingStateFor(@Bind(binder = BlockableBinder.class)Blockable overdueable) ;
+
+ @Override
+ @SqlQuery
+ @Mapper(BlockingHistorySqlMapper.class)
+ public abstract BlockingState getBlockingStateFor(@Bind(binder = UUIDBinder.class) UUID overdueableId);
+
+ @Override
+ @SqlQuery
+ @Mapper(BlockingHistorySqlMapper.class)
+ public abstract SortedSet<BlockingState> getBlockingHistoryFor(@Bind(binder = BlockableBinder.class)Blockable blockable) ;
+
+ @Override
+ @SqlQuery
+ @Mapper(BlockingHistorySqlMapper.class)
+ public abstract SortedSet<BlockingState> getBlockingHistoryFor(@Bind(binder = UUIDBinder.class) UUID blockableId);
+
+
+ public class BlockingHistorySqlMapper extends MapperBase implements ResultSetMapper<BlockingState> {
+
+ @Override
+ public BlockingState map(int index, ResultSet r, StatementContext ctx)
+ throws SQLException {
+
+ DateTime timestamp;
+ UUID blockableId;
+ String stateName;
+ String service;
+ boolean blockChange;
+ boolean blockEntitlement;
+ boolean blockBilling;
+ Type type;
+ try {
+ timestamp = new DateTime(r.getDate("created_date"));
+ blockableId = UUID.fromString(r.getString("id"));
+ stateName = r.getString("state") == null ? BlockingApi.CLEAR_STATE_NAME : r.getString("state");
+ type = Type.get(r.getString("type"));
+ service = r.getString("service");
+ blockChange = r.getBoolean("block_change");
+ blockEntitlement = r.getBoolean("block_entitlement");
+ blockBilling = r.getBoolean("block_billing");
+ } catch (BlockingApiException e) {
+ throw new SQLException(e);
+ }
+ return new DefaultBlockingState(blockableId, stateName, type, service, blockChange, blockEntitlement, blockBilling, timestamp);
+ }
+ }
+
+ public static class BlockingStateSqlMapper extends MapperBase implements ResultSetMapper<String> {
+
+ @Override
+ public String map(int index, ResultSet r, StatementContext ctx)
+ throws SQLException {
+ return r.getString("state") == null ? BlockingApi.CLEAR_STATE_NAME : r.getString("state");
+ }
+ }
+
+ public static class BlockingStateBinder extends BinderBase implements Binder<Bind, DefaultBlockingState> {
+ @Override
+ public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, DefaultBlockingState state) {
+ stmt.bind("id", state.getBlockedId().toString());
+ stmt.bind("state", state.getStateName().toString());
+ stmt.bind("type", state.getType().toString());
+ stmt.bind("service", state.getService().toString());
+ stmt.bind("block_change", state.isBlockChange());
+ stmt.bind("block_entitlement", state.isBlockEntitlement());
+ stmt.bind("block_billing", state.isBlockBilling());
+ }
+ }
+
+ public static class UUIDBinder extends BinderBase implements Binder<Bind, UUID> {
+ @Override
+ public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, UUID id) {
+ stmt.bind("id", id.toString());
+ }
+ }
+
+ public static class BlockableBinder extends BinderBase implements Binder<Bind, Blockable> {
+ @Override
+ public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, Blockable overdueable) {
+ stmt.bind("id", overdueable.getId().toString());
+ }
+ }
+
+ public static class OverdueStateBinder<T extends Blockable> extends BinderBase implements Binder<Bind, OverdueState<T>> {
+ @Override
+ public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, OverdueState<T> overdueState) {
+ stmt.bind("state", overdueState.getName());
+ }
+ }
+
+ public class BlockableTypeBinder extends BinderBase implements Binder<Bind, Blockable.Type>{
+
+ @Override
+ public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, Type type) {
+ stmt.bind("type", type.name());
+ }
+
+ }
+
+ public static class CurrentTimeBinder extends BinderBase implements Binder<Bind, Clock> {
+ @Override
+ public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, Clock clock) {
+ stmt.bind("created_date", clock.getUTCNow().toDate());
+ }
+
+ }
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/glue/DefaultJunctionModule.java b/junction/src/main/java/com/ning/billing/junction/glue/DefaultJunctionModule.java
new file mode 100644
index 0000000..0065ab3
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/glue/DefaultJunctionModule.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.junction.glue;
+
+import org.skife.jdbi.v2.IDBI;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.glue.JunctionModule;
+import com.ning.billing.junction.api.BillingApi;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.blocking.DefaultBlockingApi;
+import com.ning.billing.junction.block.BlockingChecker;
+import com.ning.billing.junction.block.DefaultBlockingChecker;
+import com.ning.billing.junction.dao.BlockingStateDao;
+import com.ning.billing.junction.dao.BlockingStateSqlDao;
+import com.ning.billing.junction.plumbing.api.BlockingAccountUserApi;
+import com.ning.billing.junction.plumbing.api.BlockingEntitlementUserApi;
+import com.ning.billing.junction.plumbing.billing.BlockingCalculator;
+import com.ning.billing.junction.plumbing.billing.DefaultBillingApi;
+
+public class DefaultJunctionModule extends AbstractModule implements JunctionModule {
+
+ @Override
+ protected void configure() {
+ // External
+ installBlockingApi();
+ installAccountUserApi();
+ installBillingApi();
+ installEntitlementUserApi();
+ installBlockingChecker();
+
+ // Internal
+ installBlockingCalculator();
+ installBlockingStateDao();
+ }
+
+ public void installBlockingChecker() {
+ bind(BlockingChecker.class).to(DefaultBlockingChecker.class).asEagerSingleton();
+
+ }
+
+ public void installBillingApi() {
+ bind(BillingApi.class).to(DefaultBillingApi.class).asEagerSingleton();
+ }
+
+ public void installBlockingStateDao() {
+ bind(BlockingStateDao.class).toProvider(BlockingDaoProvider.class);
+ }
+
+ public void installAccountUserApi() {
+ bind(AccountUserApi.class).to(BlockingAccountUserApi.class).asEagerSingleton();
+ }
+
+ public void installEntitlementUserApi() {
+ bind(EntitlementUserApi.class).to(BlockingEntitlementUserApi.class).asEagerSingleton();
+ }
+
+ public void installBlockingApi() {
+ bind(BlockingApi.class).to(DefaultBlockingApi.class).asEagerSingleton();
+ }
+
+ public void installBlockingCalculator() {
+ bind(BlockingCalculator.class).asEagerSingleton();
+ }
+
+ public static class BlockingDaoProvider implements Provider<BlockingStateDao>{
+ private IDBI dbi;
+
+
+ @Inject
+ public BlockingDaoProvider(IDBI dbi){
+ this.dbi = dbi;
+ }
+ @Override
+ public BlockingStateDao get() {
+ return dbi.onDemand(BlockingStateSqlDao.class);
+ }
+ }
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccount.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccount.java
new file mode 100644
index 0000000..83b9aaf
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccount.java
@@ -0,0 +1,218 @@
+/*
+ * 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.junction.plumbing.api;
+
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.tag.ControlTagType;
+import org.joda.time.DateTimeZone;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.MutableAccountData;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+
+public class BlockingAccount implements Account {
+ private final Account account;
+ private BlockingState blockingState = null;
+ private BlockingApi blockingApi;
+
+ public BlockingAccount( Account account, BlockingApi blockingApi) {
+ this.account = account;
+ this.blockingApi = blockingApi;
+ }
+
+ public List<Tag> getTagList() {
+ return account.getTagList();
+ }
+
+ @Override
+ public boolean hasTag(TagDefinition tagDefinition) {
+ return account.hasTag(tagDefinition);
+ }
+
+ public UUID getId() {
+ return account.getId();
+ }
+
+ @Override
+ public boolean hasTag(ControlTagType controlTagType) {
+ return account.hasTag(controlTagType);
+ }
+
+ public void addTag(TagDefinition definition) {
+ account.addTag(definition);
+ }
+
+ public String getFieldValue(String fieldName) {
+ return account.getFieldValue(fieldName);
+ }
+
+ public String getExternalKey() {
+ return account.getExternalKey();
+ }
+
+ public String getName() {
+ return account.getName();
+ }
+
+ public void addTags(List<Tag> tags) {
+ account.addTags(tags);
+ }
+
+ @Override
+ public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+ account.addTagsFromDefinitions(tagDefinitions);
+ }
+
+ public void setFieldValue(String fieldName, String fieldValue) {
+ account.setFieldValue(fieldName, fieldValue);
+ }
+
+ public int getFirstNameLength() {
+ return account.getFirstNameLength();
+ }
+
+ public void clearTags() {
+ account.clearTags();
+ }
+
+ public String getEmail() {
+ return account.getEmail();
+ }
+
+ public void removeTag(TagDefinition definition) {
+ account.removeTag(definition);
+ }
+
+ public void saveFieldValue(String fieldName, String fieldValue, CallContext context) {
+ account.saveFieldValue(fieldName, fieldValue, context);
+ }
+
+ public int getBillCycleDay() {
+ return account.getBillCycleDay();
+ }
+
+ public boolean generateInvoice() {
+ return account.generateInvoice();
+ }
+
+ public Currency getCurrency() {
+ return account.getCurrency();
+ }
+
+ public boolean processPayment() {
+ return account.processPayment();
+ }
+
+ public List<CustomField> getFieldList() {
+ return account.getFieldList();
+ }
+
+ public String getPaymentProviderName() {
+ return account.getPaymentProviderName();
+ }
+
+ public MutableAccountData toMutableAccountData() {
+ return account.toMutableAccountData();
+ }
+
+ public void setFields(List<CustomField> fields) {
+ account.setFields(fields);
+ }
+
+ public DateTimeZone getTimeZone() {
+ return account.getTimeZone();
+ }
+
+ public String getLocale() {
+ return account.getLocale();
+ }
+
+ public BlockingState getBlockingState() {
+ if(blockingState == null) {
+ blockingState = blockingApi.getBlockingStateFor(account);
+ }
+ return blockingState;
+ }
+
+ public void saveFields(List<CustomField> fields, CallContext context) {
+ account.saveFields(fields, context);
+ }
+
+ public String getAddress1() {
+ return account.getAddress1();
+ }
+
+ public String getAddress2() {
+ return account.getAddress2();
+ }
+
+ public void clearFields() {
+ account.clearFields();
+ }
+
+ public String getCompanyName() {
+ return account.getCompanyName();
+ }
+
+ public void clearPersistedFields(CallContext context) {
+ account.clearPersistedFields(context);
+ }
+
+ public String getCity() {
+ return account.getCity();
+ }
+
+ public String getStateOrProvince() {
+ return account.getStateOrProvince();
+ }
+
+ public ObjectType getObjectType() {
+ return account.getObjectType();
+ }
+
+ public String getPostalCode() {
+ return account.getPostalCode();
+ }
+
+ public String getCountry() {
+ return account.getCountry();
+ }
+
+ public String getPhone() {
+ return account.getPhone();
+ }
+
+ @Override
+ public boolean isMigrated() {
+ return account.isMigrated();
+ }
+
+ @Override
+ public boolean isNotifiedForInvoices() {
+ return account.isNotifiedForInvoices();
+ }
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java
new file mode 100644
index 0000000..45e3d5c
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java
@@ -0,0 +1,102 @@
+/*
+ * 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.junction.plumbing.api;
+
+import java.util.List;
+import java.util.UUID;
+
+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.AccountData;
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.account.api.MigrationAccountData;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.glue.RealImplementation;
+import com.ning.billing.util.tag.TagDefinition;
+
+public class BlockingAccountUserApi implements AccountUserApi {
+ private AccountUserApi userApi;
+ private BlockingApi blockingApi;
+
+ @Inject
+ public BlockingAccountUserApi(@RealImplementation AccountUserApi userApi, BlockingApi blockingApi) {
+ this.userApi = userApi;
+ this.blockingApi = blockingApi;
+ }
+
+ @Override
+ public Account createAccount(AccountData data, List<CustomField> fields, List<TagDefinition> tagDefinitions, CallContext context)
+ throws AccountApiException {
+ return userApi.createAccount(data, fields, tagDefinitions, context);
+ }
+
+ @Override
+ public Account migrateAccount(MigrationAccountData data, List<CustomField> fields, List<TagDefinition> tagDefinitions,
+ CallContext context) throws AccountApiException {
+ return userApi.migrateAccount(data, fields, tagDefinitions, context);
+ }
+
+ @Override
+ public void updateAccount(Account account, CallContext context) throws AccountApiException {
+ userApi.updateAccount(account, context);
+ }
+
+ @Override
+ public void updateAccount(String key, AccountData accountData, CallContext context) throws AccountApiException {
+ userApi.updateAccount(key, accountData, context);
+ }
+
+ @Override
+ public void updateAccount(UUID accountId, AccountData accountData, CallContext context) throws AccountApiException {
+ userApi.updateAccount(accountId, accountData, context);
+ }
+
+ @Override
+ public Account getAccountByKey(String key) throws AccountApiException {
+ return new BlockingAccount(userApi.getAccountByKey(key), blockingApi);
+ }
+
+ @Override
+ public Account getAccountById(UUID accountId) throws AccountApiException {
+ return userApi.getAccountById(accountId);
+ }
+
+ @Override
+ public List<Account> getAccounts() {
+ return userApi.getAccounts();
+ }
+
+ @Override
+ public UUID getIdFromKey(String externalKey) throws AccountApiException {
+ return userApi.getIdFromKey(externalKey);
+ }
+
+ @Override
+ public List<AccountEmail> getEmails(UUID accountId) {
+ return userApi.getEmails(accountId);
+ }
+
+ @Override
+ public void saveEmails(UUID accountId, List<AccountEmail> emails, CallContext context) {
+ userApi.saveEmails(accountId, emails, context);
+ }
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingEntitlementUserApi.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingEntitlementUserApi.java
new file mode 100644
index 0000000..9452146
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingEntitlementUserApi.java
@@ -0,0 +1,138 @@
+/*
+ * 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.junction.plumbing.api;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionStatusDryRun;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingApiException;
+import com.ning.billing.junction.block.BlockingChecker;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.glue.RealImplementation;
+
+public class BlockingEntitlementUserApi implements EntitlementUserApi {
+ private final EntitlementUserApi entitlementUserApi;
+ private final BlockingApi blockingApi;
+ private final BlockingChecker checker;
+
+ @Inject
+ public BlockingEntitlementUserApi(@RealImplementation EntitlementUserApi userApi, BlockingApi blockingApi, BlockingChecker checker) {
+ this.entitlementUserApi = userApi;
+ this.blockingApi = blockingApi;
+ this.checker = checker;
+ }
+
+ @Override
+ public SubscriptionBundle getBundleFromId(UUID id) throws EntitlementUserApiException {
+ SubscriptionBundle bundle = entitlementUserApi.getBundleFromId(id);
+ return new BlockingSubscriptionBundle(bundle, blockingApi);
+ }
+
+ @Override
+ public Subscription getSubscriptionFromId(UUID id) throws EntitlementUserApiException {
+ Subscription subscription = entitlementUserApi.getSubscriptionFromId(id);
+ return new BlockingSubscription(subscription, blockingApi, checker);
+ }
+
+ @Override
+ public SubscriptionBundle getBundleForKey(String bundleKey) throws EntitlementUserApiException {
+ SubscriptionBundle bundle = entitlementUserApi.getBundleForKey(bundleKey);
+ return new BlockingSubscriptionBundle(bundle, blockingApi);
+ }
+
+ @Override
+ public List<SubscriptionBundle> getBundlesForAccount(UUID accountId) {
+ List<SubscriptionBundle> result = new ArrayList<SubscriptionBundle>();
+ List<SubscriptionBundle> bundles = entitlementUserApi.getBundlesForAccount(accountId);
+ for(SubscriptionBundle bundle : bundles) {
+ result.add(new BlockingSubscriptionBundle(bundle, blockingApi));
+ }
+ return result;
+ }
+
+ @Override
+ public List<Subscription> getSubscriptionsForBundle(UUID bundleId) {
+ List<Subscription> result = new ArrayList<Subscription>();
+ List<Subscription> subscriptions = entitlementUserApi.getSubscriptionsForBundle(bundleId);
+ for(Subscription subscription : subscriptions) {
+ result.add(new BlockingSubscription(subscription, blockingApi, checker));
+ }
+ return result;
+ }
+
+ @Override
+ public List<Subscription> getSubscriptionsForKey(String bundleKey) {
+ List<Subscription> result = new ArrayList<Subscription>();
+ List<Subscription> subscriptions = entitlementUserApi.getSubscriptionsForKey(bundleKey);
+ for(Subscription subscription : subscriptions) {
+ result.add(new BlockingSubscription(subscription, blockingApi, checker));
+ }
+ return result;
+ }
+
+ @Override
+ public List<SubscriptionStatusDryRun> getDryRunChangePlanStatus(
+ UUID subscriptionId, String productName, DateTime requestedDate)
+ throws EntitlementUserApiException {
+ return entitlementUserApi.getDryRunChangePlanStatus(subscriptionId, productName, requestedDate);
+ }
+
+ @Override
+ public Subscription getBaseSubscription(UUID bundleId) throws EntitlementUserApiException {
+ return new BlockingSubscription(entitlementUserApi.getBaseSubscription(bundleId), blockingApi, checker);
+ }
+
+ @Override
+ public SubscriptionBundle createBundleForAccount(UUID accountId, String bundleKey, CallContext context)
+ throws EntitlementUserApiException {
+ try {
+ checker.checkBlockedChange(accountId, Blockable.Type.ACCOUNT);
+ return new BlockingSubscriptionBundle(entitlementUserApi.createBundleForAccount(accountId, bundleKey, context), blockingApi);
+ }catch (BlockingApiException e) {
+ throw new EntitlementUserApiException(e, e.getCode(), e.getMessage());
+ }
+ }
+
+ @Override
+ public Subscription createSubscription(UUID bundleId, PlanPhaseSpecifier spec, DateTime requestedDate,
+ CallContext context) throws EntitlementUserApiException {
+ try {
+ checker.checkBlockedChange(bundleId, Blockable.Type.SUBSCRIPTION_BUNDLE);
+ return new BlockingSubscription(entitlementUserApi.createSubscription(bundleId, spec, requestedDate, context), blockingApi, checker);
+ }catch (BlockingApiException e) {
+ throw new EntitlementUserApiException(e, e.getCode(), e.getMessage());
+ }
+ }
+
+ @Override
+ public DateTime getNextBillingDate(UUID account) {
+ return entitlementUserApi.getNextBillingDate(account);
+ }
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java
new file mode 100644
index 0000000..3de3305
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscription.java
@@ -0,0 +1,227 @@
+/*
+ * 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.junction.plumbing.api;
+
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.tag.ControlTagType;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceList;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingApiException;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.block.BlockingChecker;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+
+
+public class BlockingSubscription implements Subscription {
+ private final Subscription subscription;
+ private final BlockingApi blockingApi;
+ private final BlockingChecker checker;
+
+ private BlockingState blockingState = null;
+
+ public BlockingSubscription(Subscription subscription, BlockingApi blockingApi, BlockingChecker checker) {
+ this.subscription = subscription;
+ this.blockingApi = blockingApi;
+ this.checker = checker;
+ }
+
+ public List<Tag> getTagList() {
+ return subscription.getTagList();
+ }
+
+ @Override
+ public boolean hasTag(TagDefinition tagDefinition) {
+ return subscription.hasTag(tagDefinition);
+ }
+
+ public UUID getId() {
+ return subscription.getId();
+ }
+
+ public boolean hasTag(ControlTagType controlTagType) {
+ return subscription.hasTag(controlTagType);
+ }
+
+ public void addTag(TagDefinition definition) {
+ subscription.addTag(definition);
+ }
+
+ public String getFieldValue(String fieldName) {
+ return subscription.getFieldValue(fieldName);
+ }
+
+ public void addTags(List<Tag> tags) {
+ subscription.addTags(tags);
+ }
+
+ @Override
+ public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+ subscription.addTagsFromDefinitions(tagDefinitions);
+ }
+
+ public void setFieldValue(String fieldName, String fieldValue) {
+ subscription.setFieldValue(fieldName, fieldValue);
+ }
+
+ public void clearTags() {
+ subscription.clearTags();
+ }
+
+ public void removeTag(TagDefinition definition) {
+ subscription.removeTag(definition);
+ }
+
+ public void saveFieldValue(String fieldName, String fieldValue, CallContext context) {
+ subscription.saveFieldValue(fieldName, fieldValue, context);
+ }
+
+ public boolean generateInvoice() {
+ return subscription.generateInvoice();
+ }
+
+ public boolean processPayment() {
+ return subscription.processPayment();
+ }
+
+ public List<CustomField> getFieldList() {
+ return subscription.getFieldList();
+ }
+
+ public void setFields(List<CustomField> fields) {
+ subscription.setFields(fields);
+ }
+
+ public void saveFields(List<CustomField> fields, CallContext context) {
+ subscription.saveFields(fields, context);
+ }
+
+ public void clearFields() {
+ subscription.clearFields();
+ }
+
+ public void clearPersistedFields(CallContext context) {
+ subscription.clearPersistedFields(context);
+ }
+
+ public ObjectType getObjectType() {
+ return subscription.getObjectType();
+ }
+
+ public boolean cancel(DateTime requestedDate, boolean eot, CallContext context) throws EntitlementUserApiException {
+ return subscription.cancel(requestedDate, eot, context);
+ }
+
+ public boolean uncancel(CallContext context) throws EntitlementUserApiException {
+ return subscription.uncancel(context);
+ }
+
+ public boolean changePlan(String productName, BillingPeriod term, String planSet, DateTime requestedDate,
+ CallContext context) throws EntitlementUserApiException {
+ try {
+ checker.checkBlockedChange(this);
+ } catch (BlockingApiException e) {
+ throw new EntitlementUserApiException(e, e.getCode(), e.getMessage());
+ }
+ return subscription.changePlan(productName, term, planSet, requestedDate, context);
+ }
+
+ public boolean recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
+ throws EntitlementUserApiException {
+ return subscription.recreate(spec, requestedDate, context);
+ }
+
+ public UUID getBundleId() {
+ return subscription.getBundleId();
+ }
+
+ public SubscriptionState getState() {
+ return subscription.getState();
+ }
+
+ public DateTime getStartDate() {
+ return subscription.getStartDate();
+ }
+
+ public DateTime getEndDate() {
+ return subscription.getEndDate();
+ }
+
+ public Plan getCurrentPlan() {
+ return subscription.getCurrentPlan();
+ }
+
+ public PriceList getCurrentPriceList() {
+ return subscription.getCurrentPriceList();
+ }
+
+ public PlanPhase getCurrentPhase() {
+ return subscription.getCurrentPhase();
+ }
+
+ public DateTime getChargedThroughDate() {
+ return subscription.getChargedThroughDate();
+ }
+
+ public DateTime getPaidThroughDate() {
+ return subscription.getPaidThroughDate();
+ }
+
+ public ProductCategory getCategory() {
+ return subscription.getCategory();
+ }
+
+ public SubscriptionEvent getPendingTransition() {
+ return subscription.getPendingTransition();
+ }
+
+ public SubscriptionEvent getPreviousTransition() {
+ return subscription.getPreviousTransition();
+ }
+
+ public List<SubscriptionEvent> getBillingTransitions() {
+ return subscription.getBillingTransitions();
+ }
+
+ public BlockingState getBlockingState() {
+ if(blockingState == null) {
+ blockingState = blockingApi.getBlockingStateFor(this);
+ }
+ return blockingState;
+ }
+
+ public Subscription getDelegateSubscription() {
+ return subscription;
+ }
+
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscriptionBundle.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscriptionBundle.java
new file mode 100644
index 0000000..fd23104
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingSubscriptionBundle.java
@@ -0,0 +1,67 @@
+/*
+ * 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.junction.plumbing.api;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.overdue.OverdueState;
+
+public class BlockingSubscriptionBundle implements SubscriptionBundle {
+ private final SubscriptionBundle subscriptionBundle;
+ private final BlockingApi blockingApi;
+
+ private BlockingState blockingState = null;
+
+ public BlockingSubscriptionBundle(SubscriptionBundle subscriptionBundle, BlockingApi blockingApi) {
+ this.subscriptionBundle = subscriptionBundle;
+ this.blockingApi = blockingApi;
+ }
+
+ public UUID getAccountId() {
+ return subscriptionBundle.getAccountId();
+ }
+
+ public UUID getId() {
+ return subscriptionBundle.getId();
+ }
+
+ public DateTime getStartDate() {
+ return subscriptionBundle.getStartDate();
+ }
+
+ public String getKey() {
+ return subscriptionBundle.getKey();
+ }
+
+ public OverdueState<SubscriptionBundle> getOverdueState() {
+ return subscriptionBundle.getOverdueState();
+ }
+
+ @Override
+ public BlockingState getBlockingState() {
+ if(blockingState == null) {
+ blockingState = blockingApi.getBlockingStateFor(this);
+ }
+ return blockingState;
+ }
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
new file mode 100644
index 0000000..b1c5558
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.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.junction.plumbing.billing;
+
+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.AccountApiException;
+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.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+
+public class BillCycleDayCalculator {
+ private static final Logger log = LoggerFactory.getLogger(BillCycleDayCalculator.class);
+
+ private final CatalogService catalogService;
+ private final EntitlementUserApi entitlementApi;
+
+ @Inject
+ public BillCycleDayCalculator(final CatalogService catalogService, final EntitlementUserApi entitlementApi) {
+ super();
+ this.catalogService = catalogService;
+ this.entitlementApi = entitlementApi;
+ }
+
+ protected int calculateBcd(SubscriptionBundle bundle, Subscription subscription, final SubscriptionEvent transition, final Account account)
+ throws CatalogApiException, AccountApiException, EntitlementUserApiException {
+
+ Catalog catalog = catalogService.getFullCatalog();
+
+ Plan prevPlan = (transition.getPreviousPlan() != null) ? catalog.findPlan(transition.getPreviousPlan(), transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null;
+ Plan nextPlan = (transition.getNextPlan() != null) ? catalog.findPlan(transition.getNextPlan(), transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null;
+
+ Plan plan = (transition.getTransitionType() != SubscriptionTransitionType.CANCEL) ? nextPlan : prevPlan;
+ Product product = plan.getProduct();
+
+ PlanPhase prevPhase = (transition.getPreviousPhase() != null) ? catalog.findPhase(transition.getPreviousPhase(), transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null;
+ PlanPhase nextPhase = (transition.getNextPhase() != null) ? catalog.findPhase(transition.getNextPhase(), transition.getEffectiveTransitionTime(), transition.getSubscriptionStartDate()) : null;
+
+ PlanPhase phase = (transition.getTransitionType() != SubscriptionTransitionType.CANCEL) ? nextPhase : prevPhase;
+
+ BillingAlignment alignment = catalog.billingAlignment(
+ new PlanPhaseSpecifier(product.getName(),
+ product.getCategory(),
+ phase.getBillingPeriod(),
+ transition.getNextPriceList(),
+ phase.getPhaseType()),
+ transition.getRequestedTransitionTime());
+
+ int result = -1;
+ switch (alignment) {
+ case ACCOUNT :
+ result = account.getBillCycleDay();
+ if(result == 0) {
+ result = calculateBcdFromSubscription(subscription, plan, account);
+ }
+ break;
+ case BUNDLE :
+ Subscription baseSub = entitlementApi.getBaseSubscription(bundle.getId());
+ Plan basePlan = baseSub.getCurrentPlan();
+ result = calculateBcdFromSubscription(baseSub, basePlan, account);
+ break;
+ case SUBSCRIPTION :
+ result = calculateBcdFromSubscription(subscription, plan, account);
+ break;
+ }
+ if(result == -1) {
+ throw new CatalogApiException(ErrorCode.CAT_INVALID_BILLING_ALIGNMENT, alignment.toString());
+ }
+ return result;
+
+ }
+
+ private int calculateBcdFromSubscription(Subscription subscription, Plan plan, Account account) throws AccountApiException {
+ DateTime date = plan.dateOfFirstRecurringNonZeroCharge(subscription.getStartDate());
+ return date.toDateTime(account.getTimeZone()).getDayOfMonth();
+ }
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java
new file mode 100644
index 0000000..999c5d6
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BlockingCalculator.java
@@ -0,0 +1,267 @@
+/*
+ * 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.junction.plumbing.billing;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+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.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
+
+public class BlockingCalculator {
+ private final BlockingApi blockingApi;
+
+ protected static class DisabledDuration {
+ private final DateTime start;
+ private final DateTime end;
+
+ public DisabledDuration(DateTime start,DateTime end) {
+ this.start = start;
+ this.end = end;
+ }
+ public DateTime getStart() {
+ return start;
+ }
+ public DateTime getEnd() {
+ return end;
+ }
+
+ }
+
+ protected static class MergeEvent extends DefaultBlockingState {
+
+ public MergeEvent(DateTime timestamp) {
+ super(null,null,null,null,false,false,false,timestamp);
+ }
+
+ }
+
+ @Inject
+ public BlockingCalculator(BlockingApi blockingApi) {
+ this.blockingApi = blockingApi;
+ }
+
+ public void insertBlockingEvents(SortedSet<BillingEvent> billingEvents) {
+ if(billingEvents.size() <= 0) { return; }
+
+ Account account = billingEvents.first().getAccount();
+
+ Hashtable<UUID,List<Subscription>> bundleMap = createBundleSubscriptionMap(billingEvents);
+
+ SortedSet<BillingEvent> billingEventsToAdd = new TreeSet<BillingEvent>();
+ SortedSet<BillingEvent> billingEventsToRemove = new TreeSet<BillingEvent>();
+
+ for(UUID bundleId : bundleMap.keySet()) {
+ SortedSet<BlockingState> blockingEvents = blockingApi.getBlockingHistory(bundleId);
+ blockingEvents.addAll(blockingApi.getBlockingHistory(account.getId()));
+ List<DisabledDuration> blockingDurations = createBlockingDurations(blockingEvents);
+
+ for (Subscription subscription: bundleMap.get(bundleId)) {
+ billingEventsToAdd.addAll(createNewEvents( blockingDurations, billingEvents, account, subscription));
+ billingEventsToRemove.addAll(eventsToRemove(blockingDurations, billingEvents, subscription));
+ }
+ }
+
+ for(BillingEvent eventToAdd: billingEventsToAdd ) {
+ billingEvents.add(eventToAdd);
+ }
+
+ for(BillingEvent eventToRemove : billingEventsToRemove) {
+ billingEvents.remove(eventToRemove);
+ }
+
+ }
+
+ protected SortedSet<BillingEvent> eventsToRemove(List<DisabledDuration> disabledDuration,
+ SortedSet<BillingEvent> billingEvents, Subscription subscription) {
+ SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
+
+ SortedSet<BillingEvent> filteredBillingEvents = filter(billingEvents, subscription);
+ for(DisabledDuration duration : disabledDuration) {
+ for(BillingEvent event : filteredBillingEvents) {
+ if(duration.getEnd() == null || event.getEffectiveDate().isBefore(duration.getEnd())) {
+ if( event.getEffectiveDate().isAfter(duration.getStart()) ) { //between the pair
+ result.add(event);
+ }
+ } else { //after the last event of the pair no need to keep checking
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ protected SortedSet<BillingEvent> createNewEvents( List<DisabledDuration> disabledDuration, SortedSet<BillingEvent> billingEvents, Account account, Subscription subscription) {
+ SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
+ for(DisabledDuration duration : disabledDuration) {
+ BillingEvent precedingInitialEvent = precedingBillingEventForSubscription(duration.getStart(), billingEvents, subscription);
+ BillingEvent precedingFinalEvent = precedingBillingEventForSubscription(duration.getEnd(), billingEvents, subscription);
+
+ if(precedingInitialEvent != null) { // there is a preceding billing event
+ result.add(createNewDisableEvent(duration.getStart(), precedingInitialEvent));
+ if(duration.getEnd() != null) { // no second event in the pair means they are still disabled (no re-enable)
+ result.add(createNewReenableEvent(duration.getEnd(), precedingFinalEvent));
+ }
+
+ } else if(precedingFinalEvent != null) { // can happen - e.g. phase event
+ //
+ // TODO: check with Jeff that this is going to do something sensible
+ //
+ result.add(createNewReenableEvent(duration.getEnd(), precedingFinalEvent));
+
+ }
+
+ // N.B. if there's no precedingInitial and no precedingFinal then there's nothing to do
+ }
+ return result;
+ }
+
+ protected BillingEvent precedingBillingEventForSubscription(DateTime datetime, SortedSet<BillingEvent> billingEvents, Subscription subscription) {
+ if(datetime == null) { //second of a pair can be null if there's no re-enabling
+ return null;
+ }
+
+ SortedSet<BillingEvent> filteredBillingEvents = filter(billingEvents, subscription);
+ BillingEvent result = filteredBillingEvents.first();
+
+ if(datetime.isBefore(result.getEffectiveDate())) {
+ //This case can happen, for example, if we have an add on and the bundle goes into disabled before the add on is created
+ return null;
+ }
+
+ for(BillingEvent event : filteredBillingEvents) {
+ if(event.getEffectiveDate().isAfter(datetime)) { // found it its the previous event
+ return result;
+ } else { // still looking
+ result = event;
+ }
+ }
+ return result;
+ }
+
+ protected SortedSet<BillingEvent> filter(SortedSet<BillingEvent> billingEvents, Subscription subscription) {
+ SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
+ for(BillingEvent event : billingEvents) {
+ if(event.getSubscription() == subscription) {
+ result.add(event);
+ }
+ }
+ return result;
+ }
+
+ protected BillingEvent createNewDisableEvent(DateTime odEventTime, BillingEvent previousEvent) {
+ final Account account = previousEvent.getAccount();
+ final int billCycleDay = previousEvent.getBillCycleDay();
+ final Subscription subscription = previousEvent.getSubscription();
+ final DateTime effectiveDate = odEventTime;
+ final PlanPhase planPhase = previousEvent.getPlanPhase();
+ final Plan plan = previousEvent.getPlan();
+ final BigDecimal fixedPrice = BigDecimal.ZERO;
+ final BigDecimal recurringPrice = BigDecimal.ZERO;
+ final Currency currency = previousEvent.getCurrency();
+ final String description = "";
+ final BillingModeType billingModeType = previousEvent.getBillingMode();
+ final BillingPeriod billingPeriod = previousEvent.getBillingPeriod();
+ final SubscriptionTransitionType type = SubscriptionTransitionType.CANCEL;
+ final Long totalOrdering = 0L; //TODO
+
+ return new DefaultBillingEvent(account, subscription, effectiveDate, plan, planPhase,
+ fixedPrice, recurringPrice, currency,
+ billingPeriod, billCycleDay, billingModeType,
+ description, totalOrdering, type);
+ }
+
+ protected BillingEvent createNewReenableEvent(DateTime odEventTime, BillingEvent previousEvent) {
+ final Account account = previousEvent.getAccount();
+ final int billCycleDay = previousEvent.getBillCycleDay();
+ final Subscription subscription = previousEvent.getSubscription();
+ final DateTime effectiveDate = odEventTime;
+ final PlanPhase planPhase = previousEvent.getPlanPhase();
+ final Plan plan = previousEvent.getPlan();
+ final BigDecimal fixedPrice = previousEvent.getFixedPrice();
+ final BigDecimal recurringPrice = previousEvent.getRecurringPrice();
+ final Currency currency = previousEvent.getCurrency();
+ final String description = "";
+ final BillingModeType billingModeType = previousEvent.getBillingMode();
+ final BillingPeriod billingPeriod = previousEvent.getBillingPeriod();
+ final SubscriptionTransitionType type = SubscriptionTransitionType.RE_CREATE;
+ final Long totalOrdering = 0L; //TODO
+
+ return new DefaultBillingEvent(account, subscription, effectiveDate, plan, planPhase,
+ fixedPrice, recurringPrice, currency,
+ billingPeriod, billCycleDay, billingModeType,
+ description, totalOrdering, type);
+ }
+
+ protected Hashtable<UUID,List<Subscription>> createBundleSubscriptionMap(SortedSet<BillingEvent> billingEvents) {
+ Hashtable<UUID,List<Subscription>> result = new Hashtable<UUID,List<Subscription>>();
+ for(BillingEvent event : billingEvents) {
+ UUID bundleId = event.getSubscription().getBundleId();
+ List<Subscription> subs = result.get(bundleId);
+ if(subs == null) {
+ subs = new ArrayList<Subscription>();
+ result.put(bundleId,subs);
+ }
+ if(!result.contains(event.getSubscription())) {
+ subs.add(event.getSubscription());
+ }
+ }
+ return result;
+ }
+
+
+
+ protected List<DisabledDuration> createBlockingDurations(SortedSet<BlockingState> overdueBundleEvents) {
+ List<DisabledDuration> result = new ArrayList<BlockingCalculator.DisabledDuration>();
+ BlockingState first = null;
+
+ for(BlockingState e : overdueBundleEvents) {
+ if(e.isBlockBilling() && first == null) { // found a transition to disabled
+ first = e;
+ } else if(first != null && !e.isBlockBilling()) { // found a transition from disabled
+ result.add(new DisabledDuration(first.getTimestamp(), e.getTimestamp()));
+ first = null;
+ }
+ }
+
+ if(first != null) { // found a transition to disabled with no terminating event
+ result.add(new DisabledDuration(first.getTimestamp(), null));
+ }
+
+ return result;
+ }
+
+}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingApi.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingApi.java
new file mode 100644
index 0000000..8f88049
--- /dev/null
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingApi.java
@@ -0,0 +1,130 @@
+/*
+ * 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.junction.plumbing.billing;
+
+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.AccountApiException;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.account.api.MutableAccountData;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.ChargeThruApi;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.junction.api.BillingApi;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.UserType;
+
+public class DefaultBillingApi implements BillingApi {
+ private static final String API_USER_NAME = "Billing Api";
+ private static final Logger log = LoggerFactory.getLogger(DefaultBillingApi.class);
+ private final ChargeThruApi chargeThruApi;
+ private final CallContextFactory factory;
+ private final AccountUserApi accountApi;
+ private final BillCycleDayCalculator bcdCalculator;
+ private final EntitlementUserApi entitlementUserApi;
+ private final CatalogService catalogService;
+ private final BlockingCalculator blockCalculator;
+
+ @Inject
+ public DefaultBillingApi(final ChargeThruApi chargeThruApi, final CallContextFactory factory, final AccountUserApi accountApi,
+ final BillCycleDayCalculator bcdCalculator, final EntitlementUserApi entitlementUserApi, final BlockingCalculator blockCalculator,
+ final CatalogService catalogService) {
+
+ this.chargeThruApi = chargeThruApi;
+ this.accountApi = accountApi;
+ this.bcdCalculator = bcdCalculator;
+ this.factory = factory;
+ this.entitlementUserApi = entitlementUserApi;
+ this.catalogService = catalogService;
+ this.blockCalculator = blockCalculator;
+ }
+
+ @Override
+ public SortedSet<BillingEvent> getBillingEventsForAccountAndUpdateAccountBCD(final UUID accountId) {
+ CallContext context = factory.createCallContext(API_USER_NAME, CallOrigin.INTERNAL, UserType.SYSTEM);
+
+ List<SubscriptionBundle> bundles = entitlementUserApi.getBundlesForAccount(accountId);
+ SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
+
+ try {
+ Account account = accountApi.getAccountById(accountId);
+ for (final SubscriptionBundle bundle: bundles) {
+ List<Subscription> subscriptions = entitlementUserApi.getSubscriptionsForBundle(bundle.getId());
+
+ for (final Subscription subscription: subscriptions) {
+ for (final SubscriptionEvent transition : subscription.getBillingTransitions()) {
+ try {
+ int bcd = bcdCalculator.calculateBcd(bundle, subscription, transition, account);
+
+ if(account.getBillCycleDay() == 0) {
+ MutableAccountData modifiedData = account.toMutableAccountData();
+ modifiedData.setBillCycleDay(bcd);
+ accountApi.updateAccount(account.getExternalKey(), modifiedData, context);
+ }
+
+ BillingEvent event = new DefaultBillingEvent(account, transition, subscription, bcd, account.getCurrency(), catalogService.getFullCatalog());
+ result.add(event);
+ } catch (CatalogApiException e) {
+ log.error("Failing to identify catalog components while creating BillingEvent from transition: " +
+ transition.getId().toString(), e);
+ } catch (Exception e) {
+ log.warn("Failed while getting BillingEvent", e);
+ }
+
+ }
+ }
+ }
+ } catch (AccountApiException e) {
+ log.warn("Failed while getting BillingEvent", e);
+ }
+ blockCalculator.insertBlockingEvents(result);
+ return result;
+ }
+
+
+ @Override
+ public UUID getAccountIdFromSubscriptionId(UUID subscriptionId) throws EntitlementBillingApiException {
+ UUID result = chargeThruApi.getAccountIdFromSubscriptionId(subscriptionId);
+ if (result == null) {
+ throw new EntitlementBillingApiException(ErrorCode.ENT_INVALID_SUBSCRIPTION_ID, subscriptionId.toString());
+ }
+ return result;
+ }
+
+ @Override
+ public void setChargedThroughDate(UUID subscriptionId, DateTime ctd, CallContext context) {
+ chargeThruApi.setChargedThroughDate(subscriptionId, ctd, context);
+ }
+}
diff --git a/junction/src/main/resources/com/ning/billing/junction/dao/BlockingStateSqlDao.sql.stg b/junction/src/main/resources/com/ning/billing/junction/dao/BlockingStateSqlDao.sql.stg
new file mode 100644
index 0000000..0bd6686
--- /dev/null
+++ b/junction/src/main/resources/com/ning/billing/junction/dao/BlockingStateSqlDao.sql.stg
@@ -0,0 +1,56 @@
+group BlockingStateSqlDao;
+
+getBlockingStateFor() ::= <<
+ select
+ id
+ , state
+ , type
+ , service
+ , block_change
+ , block_entitlement
+ , block_billing
+ , created_date
+ from blocking_states
+ where id = :id
+ order by created_date desc
+ limit 1
+ ;
+>>
+
+getBlockingHistoryFor() ::= <<
+ select
+ id
+ , state
+ , type
+ , service
+ , block_change
+ , block_entitlement
+ , block_billing
+ , created_date
+ from blocking_states
+ where id = :id
+ order by created_date asc
+ ;
+>>
+
+setBlockingState() ::= <<
+ insert into blocking_states (
+ id
+ , state
+ , type
+ , service
+ , block_change
+ , block_entitlement
+ , block_billing
+ , created_date
+ ) values (
+ :id
+ , :state
+ , :type
+ , :service
+ , :block_change
+ , :block_entitlement
+ , :block_billing
+ , :created_date
+ );
+>>
\ No newline at end of file
diff --git a/junction/src/main/resources/com/ning/billing/junction/ddl.sql b/junction/src/main/resources/com/ning/billing/junction/ddl.sql
new file mode 100644
index 0000000..92b3437
--- /dev/null
+++ b/junction/src/main/resources/com/ning/billing/junction/ddl.sql
@@ -0,0 +1,15 @@
+
+DROP TABLE IF EXISTS blocking_states;
+CREATE TABLE blocking_states (
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ id char(36) NOT NULL,
+ type varchar(20) NOT NULL,
+ state varchar(50) NOT NULL,
+ service varchar(20) NOT NULL,
+ block_change bool NOT NULL,
+ block_entitlement bool NOT NULL,
+ block_billing bool NOT NULL,
+ created_date datetime NOT NULL,
+ PRIMARY KEY(record_id)
+) ENGINE=innodb;
+CREATE INDEX blocking_states_id ON blocking_states(id);
\ No newline at end of file
diff --git a/junction/src/test/java/com/ning/billing/junction/api/blocking/TestBlockingApi.java b/junction/src/test/java/com/ning/billing/junction/api/blocking/TestBlockingApi.java
new file mode 100644
index 0000000..c6241d8
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/api/blocking/TestBlockingApi.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.junction.api.blocking;
+
+import java.io.IOException;
+import java.util.SortedSet;
+import java.util.UUID;
+
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.google.inject.Inject;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.MockModule;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
+import com.ning.billing.junction.dao.TestBlockingDao;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.mock.glue.MockEntitlementModule;
+import com.ning.billing.util.clock.ClockMock;
+
+@Guice(modules = { MockModule.class, MockEntitlementModule.class })
+public class TestBlockingApi {
+ private Logger log = LoggerFactory.getLogger(TestBlockingDao.class);
+
+ @Inject
+ private MysqlTestingHelper helper;
+
+ @Inject
+ private BlockingApi api;
+
+ @Inject
+ private ClockMock clock;
+
+ @BeforeClass(groups={"slow"})
+ public void setup() throws IOException {
+ log.info("Starting set up TestBlockingApi");
+
+ final String utilDdl = IOUtils.toString(TestBlockingDao.class.getResourceAsStream("/com/ning/billing/junction/ddl.sql"));
+
+ helper.startMysql();
+ helper.initDb(utilDdl);
+
+ }
+
+ @BeforeMethod(groups={"slow"})
+ public void clean() {
+ helper.cleanupTable("blocking_states");
+ clock.resetDeltaFromReality();
+ }
+
+ @AfterClass(groups = "slow")
+ public void stopMysql()
+ {
+ helper.stopMysql();
+ }
+
+ @Test(groups={"slow"}, enabled=true)
+ public void testApi() {
+
+ UUID uuid = UUID.randomUUID();
+ String overdueStateName = "WayPassedItMan";
+ String service = "TEST";
+
+ boolean blockChange = true;
+ boolean blockEntitlement = false;
+ boolean blockBilling = false;
+
+ BlockingState state1 = new DefaultBlockingState(uuid, overdueStateName, Blockable.Type.SUBSCRIPTION_BUNDLE, service, blockChange, blockEntitlement,blockBilling);
+ api.setBlockingState(state1);
+ clock.setDeltaFromReality(1000 * 3600 * 24);
+
+ String overdueStateName2 = "NoReallyThisCantGoOn";
+ BlockingState state2 = new DefaultBlockingState(uuid, overdueStateName2, Blockable.Type.SUBSCRIPTION_BUNDLE, service, blockChange, blockEntitlement,blockBilling);
+ api.setBlockingState(state2);
+
+ SubscriptionBundle bundle = BrainDeadProxyFactory.createBrainDeadProxyFor(SubscriptionBundle.class);
+ ((ZombieControl)bundle).addResult("getId", uuid);
+
+ Assert.assertEquals(api.getBlockingStateFor(bundle).getStateName(), overdueStateName2);
+ Assert.assertEquals(api.getBlockingStateFor(bundle.getId()).getStateName(), overdueStateName2);
+
+ }
+
+ @Test(groups={"slow"}, enabled=true)
+ public void testApiHistory() throws Exception {
+ UUID uuid = UUID.randomUUID();
+ String overdueStateName = "WayPassedItMan";
+ String service = "TEST";
+
+ boolean blockChange = true;
+ boolean blockEntitlement = false;
+ boolean blockBilling = false;
+
+ BlockingState state1 = new DefaultBlockingState(uuid, overdueStateName, Blockable.Type.SUBSCRIPTION_BUNDLE, service, blockChange, blockEntitlement,blockBilling);
+ api.setBlockingState(state1);
+
+ clock.setDeltaFromReality(1000 * 3600 * 24);
+
+ String overdueStateName2 = "NoReallyThisCantGoOn";
+ BlockingState state2 = new DefaultBlockingState(uuid, overdueStateName2, Blockable.Type.SUBSCRIPTION_BUNDLE, service, blockChange, blockEntitlement,blockBilling);
+ api.setBlockingState(state2);
+
+ SubscriptionBundle bundle = BrainDeadProxyFactory.createBrainDeadProxyFor(SubscriptionBundle.class);
+ ((ZombieControl)bundle).addResult("getId", uuid);
+
+
+ SortedSet<BlockingState> history1 = api.getBlockingHistory(bundle);
+ SortedSet<BlockingState> history2 = api.getBlockingHistory(bundle.getId());
+
+ Assert.assertEquals(history1.size(), 2);
+ Assert.assertEquals(history1.first().getStateName(), overdueStateName);
+ Assert.assertEquals(history1.last().getStateName(), overdueStateName2);
+
+ Assert.assertEquals(history2.size(), 2);
+ Assert.assertEquals(history2.first().getStateName(), overdueStateName);
+ Assert.assertEquals(history2.last().getStateName(), overdueStateName2);
+
+ }
+
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/blocking/MockBlockingChecker.java b/junction/src/test/java/com/ning/billing/junction/blocking/MockBlockingChecker.java
new file mode 100644
index 0000000..bf131ae
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/blocking/MockBlockingChecker.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.junction.blocking;
+
+import java.util.UUID;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.Blockable.Type;
+import com.ning.billing.junction.api.BlockingApiException;
+import com.ning.billing.junction.block.BlockingChecker;
+
+public class MockBlockingChecker implements BlockingChecker {
+
+ @Override
+ public void checkBlockedChange(Blockable blockable) throws BlockingApiException {
+ // Intentionally blank
+
+ }
+
+ @Override
+ public void checkBlockedEntitlement(Blockable blockable) throws BlockingApiException {
+ // Intentionally blank
+ }
+
+ @Override
+ public void checkBlockedBilling(Blockable blockable) throws BlockingApiException {
+ // Intentionally blank
+ }
+
+ @Override
+ public void checkBlockedChange(UUID bundleId, Type type) throws BlockingApiException {
+ // Intentionally blank
+ }
+
+ @Override
+ public void checkBlockedEntitlement(UUID bundleId, Type type) throws BlockingApiException {
+ // Intentionally blank
+ }
+
+ @Override
+ public void checkBlockedBilling(UUID bundleId, Type type) throws BlockingApiException {
+ // Intentionally blank
+ }
+
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/blocking/TestBlockingChecker.java b/junction/src/test/java/com/ning/billing/junction/blocking/TestBlockingChecker.java
new file mode 100644
index 0000000..0bff4da
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/blocking/TestBlockingChecker.java
@@ -0,0 +1,380 @@
+/*
+ * 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.junction.blocking;
+
+import java.util.SortedSet;
+import java.util.UUID;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApiException;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
+import com.ning.billing.junction.block.BlockingChecker;
+import com.ning.billing.junction.block.DefaultBlockingChecker;
+import com.ning.billing.junction.dao.BlockingStateDao;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.util.clock.Clock;
+
+
+public class TestBlockingChecker {
+
+ private BlockingState bundleState;
+ private BlockingState subscriptionState;
+ private BlockingState accountState;
+
+ private BlockingStateDao dao = new BlockingStateDao() {
+
+ @Override
+ public BlockingState getBlockingStateFor(Blockable blockable) {
+ if(blockable.getId() == account.getId()) {
+ return accountState;
+ } else if(blockable.getId() == subscription.getId()) {
+ return subscriptionState;
+ } else {
+ return bundleState;
+ }
+ }
+
+ @Override
+ public BlockingState getBlockingStateFor(UUID blockableId) {
+ if(blockableId == account.getId()) {
+ return accountState;
+ } else if(blockableId == subscription.getId()) {
+ return subscriptionState;
+ } else {
+ return bundleState;
+ }
+ }
+
+ @Override
+ public SortedSet<BlockingState> getBlockingHistoryFor(Blockable overdueable) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public SortedSet<BlockingState> getBlockingHistoryFor(UUID overdueableId) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public <T extends Blockable> void setBlockingState(BlockingState state, Clock clock) {
+ throw new NotImplementedException();
+ }
+
+ };
+ private BlockingChecker checker;
+ private Subscription subscription;
+ private Account account;
+ private SubscriptionBundle bundle;
+
+ @BeforeClass(groups={"fast"})
+ public void setup() {
+ account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+ ((ZombieControl) account).addResult("getId", UUID.randomUUID());
+
+ bundle = BrainDeadProxyFactory.createBrainDeadProxyFor(SubscriptionBundle.class);
+ ((ZombieControl) bundle).addResult("getAccountId", account.getId());
+ ((ZombieControl) bundle).addResult("getId", UUID.randomUUID());
+ ((ZombieControl) bundle).addResult("getKey", "key");
+
+ subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+ ((ZombieControl) subscription).addResult("getId", UUID.randomUUID());
+ ((ZombieControl) subscription).addResult("getBundleId", bundle.getId());
+
+
+ Injector i = Guice.createInjector(new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ bind(BlockingChecker.class).to(DefaultBlockingChecker.class).asEagerSingleton();
+
+ bind(BlockingStateDao.class).toInstance(dao);
+
+ EntitlementUserApi entitlementUserApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class);
+ bind(EntitlementUserApi.class).toInstance(entitlementUserApi);
+ ((ZombieControl) entitlementUserApi).addResult("getBundleFromId",bundle);
+
+ }
+
+ });
+ checker = i.getInstance(BlockingChecker.class);
+ }
+
+
+ private void setStateBundle(boolean bC, boolean bE, boolean bB) {
+ bundleState = new DefaultBlockingState(UUID.randomUUID(), "state", Blockable.Type.SUBSCRIPTION_BUNDLE, "test-service", bC, bE,bB);
+ ((ZombieControl) bundle).addResult("getBlockingState", bundleState);
+ }
+
+ private void setStateAccount(boolean bC, boolean bE, boolean bB) {
+ accountState = new DefaultBlockingState(UUID.randomUUID(), "state", Blockable.Type.SUBSCRIPTION_BUNDLE, "test-service", bC, bE,bB);
+ }
+
+ private void setStateSubscription(boolean bC, boolean bE, boolean bB) {
+ subscriptionState = new DefaultBlockingState(UUID.randomUUID(), "state", Blockable.Type.SUBSCRIPTION_BUNDLE, "test-service", bC, bE,bB);
+ ((ZombieControl) subscription).addResult("getBlockingState", subscriptionState);
+ }
+
+ @Test(groups={"fast"}, enabled = true)
+ public void testSubscriptionChecker() throws Exception {
+ setStateAccount(false, false, false);
+ setStateBundle(false, false, false);
+ setStateSubscription(false, false, false);
+ checker.checkBlockedChange(subscription);
+ checker.checkBlockedEntitlement(subscription);
+ checker.checkBlockedBilling(subscription);
+
+ //BLOCKED SUBSCRIPTION
+ setStateSubscription(true, false, false);
+ checker.checkBlockedEntitlement(subscription);
+ checker.checkBlockedBilling(subscription);
+ try {
+ checker.checkBlockedChange(subscription);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+ setStateSubscription(false, true, false);
+ checker.checkBlockedChange(subscription);
+ checker.checkBlockedBilling(subscription);
+ try {
+ checker.checkBlockedEntitlement(subscription);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+ setStateSubscription(false, false, true);
+ checker.checkBlockedChange(subscription);
+ checker.checkBlockedEntitlement(subscription);
+ try {
+ checker.checkBlockedBilling(subscription);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+ //BLOCKED BUNDLE
+ setStateSubscription(false, false, false);
+ setStateBundle(true, false, false);
+ checker.checkBlockedEntitlement(subscription);
+ checker.checkBlockedBilling(subscription);
+ try {
+ checker.checkBlockedChange(subscription);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+ setStateBundle(false, true, false);
+ checker.checkBlockedChange(subscription);
+ checker.checkBlockedBilling(subscription);
+ try {
+ checker.checkBlockedEntitlement(subscription);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+ setStateBundle(false, false, true);
+ checker.checkBlockedChange(subscription);
+ checker.checkBlockedEntitlement(subscription);
+ try {
+ checker.checkBlockedBilling(subscription);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+
+ //BLOCKED ACCOUNT
+ setStateSubscription(false, false, false);
+ setStateBundle(false, false, false);
+ setStateAccount(true, false, false);
+ checker.checkBlockedEntitlement(subscription);
+ checker.checkBlockedBilling(subscription);
+ try {
+ checker.checkBlockedChange(subscription);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+ setStateAccount(false, true, false);
+ checker.checkBlockedChange(subscription);
+ checker.checkBlockedBilling(subscription);
+ try {
+ checker.checkBlockedEntitlement(subscription);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+ setStateAccount(false, false, true);
+ checker.checkBlockedChange(subscription);
+ checker.checkBlockedEntitlement(subscription);
+ try {
+ checker.checkBlockedBilling(subscription);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+
+ }
+
+ @Test(groups={"fast"}, enabled = true)
+ public void testBundleChecker() throws Exception {
+ setStateAccount(false, false, false);
+ setStateBundle(false, false, false);
+ setStateSubscription(false, false, false);
+ checker.checkBlockedChange(bundle);
+ checker.checkBlockedEntitlement(bundle);
+ checker.checkBlockedBilling(bundle);
+
+ //BLOCKED BUNDLE
+ setStateSubscription(false, false, false);
+ setStateBundle(true, false, false);
+ checker.checkBlockedEntitlement(bundle);
+ checker.checkBlockedBilling(bundle);
+ try {
+ checker.checkBlockedChange(bundle);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+ setStateBundle(false, true, false);
+ checker.checkBlockedChange(bundle);
+ checker.checkBlockedBilling(bundle);
+ try {
+ checker.checkBlockedEntitlement(bundle);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+ setStateBundle(false, false, true);
+ checker.checkBlockedChange(bundle);
+ checker.checkBlockedEntitlement(bundle);
+ try {
+ checker.checkBlockedBilling(bundle);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+
+ //BLOCKED ACCOUNT
+ setStateSubscription(false, false, false);
+ setStateBundle(false, false, false);
+ setStateAccount(true, false, false);
+ checker.checkBlockedEntitlement(bundle);
+ checker.checkBlockedBilling(bundle);
+ try {
+ checker.checkBlockedChange(bundle);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+ setStateAccount(false, true, false);
+ checker.checkBlockedChange(bundle);
+ checker.checkBlockedBilling(bundle);
+ try {
+ checker.checkBlockedEntitlement(bundle);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+ setStateAccount(false, false, true);
+ checker.checkBlockedChange(bundle);
+ checker.checkBlockedEntitlement(bundle);
+ try {
+ checker.checkBlockedBilling(bundle);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+
+ }
+
+ @Test(groups={"fast"}, enabled = true)
+ public void testAccountChecker() throws Exception {
+ setStateAccount(false, false, false);
+ setStateBundle(false, false, false);
+ setStateSubscription(false, false, false);
+ checker.checkBlockedChange(account);
+ checker.checkBlockedEntitlement(account);
+ checker.checkBlockedBilling(account);
+
+ //BLOCKED ACCOUNT
+ setStateSubscription(false, false, false);
+ setStateBundle(false, false, false);
+ setStateAccount(true, false, false);
+ checker.checkBlockedEntitlement(account);
+ checker.checkBlockedBilling(account);
+ try {
+ checker.checkBlockedChange(account);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+ setStateAccount(false, true, false);
+ checker.checkBlockedChange(account);
+ checker.checkBlockedBilling(account);
+ try {
+ checker.checkBlockedEntitlement(account);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+ setStateAccount(false, false, true);
+ checker.checkBlockedChange(account);
+ checker.checkBlockedEntitlement(account);
+ try {
+ checker.checkBlockedBilling(account);
+ Assert.fail("The call should have been blocked!");
+ } catch (BlockingApiException e) {
+ //Expected behavior
+ }
+
+
+
+ }
+
+
+
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/dao/TestBlockingDao.java b/junction/src/test/java/com/ning/billing/junction/dao/TestBlockingDao.java
new file mode 100644
index 0000000..8e02e55
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/dao/TestBlockingDao.java
@@ -0,0 +1,136 @@
+/*
+ * 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.junction.dao;
+
+import java.io.IOException;
+import java.util.SortedSet;
+import java.util.UUID;
+
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.google.inject.Inject;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.MockModule;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.mock.glue.MockEntitlementModule;
+import com.ning.billing.util.clock.ClockMock;
+
+@Guice(modules = {MockModule.class, MockEntitlementModule.class})
+public class TestBlockingDao {
+ private Logger log = LoggerFactory.getLogger(TestBlockingDao.class);
+
+ @Inject
+ private MysqlTestingHelper helper;
+
+ @Inject
+ private BlockingStateDao dao;
+
+ @BeforeClass(groups={"slow"})
+ public void setup() throws IOException {
+ log.info("Starting set up TestBlockingDao");
+
+ final String utilDdl = IOUtils.toString(TestBlockingDao.class.getResourceAsStream("/com/ning/billing/junction/ddl.sql"));
+
+ helper.startMysql();
+ helper.initDb(utilDdl);
+
+ }
+
+ @AfterClass(groups = "slow")
+ public void stopMysql()
+ {
+ if (helper != null) {
+ helper.stopMysql();
+ }
+ }
+
+ @Test(groups={"slow"}, enabled=true)
+ public void testDao() {
+ ClockMock clock = new ClockMock();
+ UUID uuid = UUID.randomUUID();
+ String overdueStateName = "WayPassedItMan";
+ String service = "TEST";
+
+ boolean blockChange = true;
+ boolean blockEntitlement = false;
+ boolean blockBilling = false;
+
+ BlockingState state1 = new DefaultBlockingState(uuid, overdueStateName, Blockable.Type.SUBSCRIPTION_BUNDLE, service, blockChange, blockEntitlement,blockBilling);
+ dao.setBlockingState(state1, clock);
+ clock.setDeltaFromReality(1000 * 3600 * 24);
+
+ String overdueStateName2 = "NoReallyThisCantGoOn";
+ BlockingState state2 = new DefaultBlockingState(uuid, overdueStateName2, Blockable.Type.SUBSCRIPTION_BUNDLE, service, blockChange, blockEntitlement,blockBilling);
+ dao.setBlockingState(state2, clock);
+
+ SubscriptionBundle bundle = BrainDeadProxyFactory.createBrainDeadProxyFor(SubscriptionBundle.class);
+ ((ZombieControl)bundle).addResult("getId", uuid);
+
+ Assert.assertEquals(dao.getBlockingStateFor(bundle).getStateName(), state2.getStateName());
+ Assert.assertEquals(dao.getBlockingStateFor(bundle.getId()).getStateName(), overdueStateName2);
+
+ }
+
+ @Test(groups={"slow"}, enabled=true)
+ public void testDaoHistory() throws Exception {
+ ClockMock clock = new ClockMock();
+ UUID uuid = UUID.randomUUID();
+ String overdueStateName = "WayPassedItMan";
+ String service = "TEST";
+
+ boolean blockChange = true;
+ boolean blockEntitlement = false;
+ boolean blockBilling = false;
+
+ BlockingState state1 = new DefaultBlockingState(uuid, overdueStateName, Blockable.Type.SUBSCRIPTION_BUNDLE, service, blockChange, blockEntitlement,blockBilling);
+ dao.setBlockingState(state1, clock);
+ clock.setDeltaFromReality(1000 * 3600 * 24);
+
+ String overdueStateName2 = "NoReallyThisCantGoOn";
+ BlockingState state2 = new DefaultBlockingState(uuid, overdueStateName2, Blockable.Type.SUBSCRIPTION_BUNDLE, service, blockChange, blockEntitlement,blockBilling);
+ dao.setBlockingState(state2, clock);
+
+ SubscriptionBundle bundle = BrainDeadProxyFactory.createBrainDeadProxyFor(SubscriptionBundle.class);
+ ((ZombieControl)bundle).addResult("getId", uuid);
+
+
+ SortedSet<BlockingState> history1 = dao.getBlockingHistoryFor(bundle);
+ SortedSet<BlockingState> history2 = dao.getBlockingHistoryFor(bundle.getId());
+
+ Assert.assertEquals(history1.size(), 2);
+ Assert.assertEquals(history1.first().getStateName(), overdueStateName);
+ Assert.assertEquals(history1.last().getStateName(), overdueStateName2);
+
+ Assert.assertEquals(history2.size(), 2);
+ Assert.assertEquals(history2.first().getStateName(), overdueStateName);
+ Assert.assertEquals(history2.last().getStateName(), overdueStateName2);
+
+ }
+
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/MockBlockingModule.java b/junction/src/test/java/com/ning/billing/junction/MockBlockingModule.java
new file mode 100644
index 0000000..75814aa
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/MockBlockingModule.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.junction;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.dao.BlockingStateDao;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+
+public class MockBlockingModule extends AbstractModule {
+ public static final String CLEAR_STATE="Clear";
+
+ @Override
+ protected void configure() {
+ BlockingApi BlockingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BlockingApi.class);
+ ((ZombieControl) BlockingApi).addResult("getOverdueStateNameFor", MockBlockingModule.CLEAR_STATE);
+ bind(BlockingStateDao.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(BlockingStateDao.class));
+ bind(BlockingApi.class).toInstance(BlockingApi);
+ }
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/MockSubscription.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/MockSubscription.java
new file mode 100644
index 0000000..8c85596
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/MockSubscription.java
@@ -0,0 +1,202 @@
+/*
+ * 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.junction.plumbing.billing;
+
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.util.dao.ObjectType;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceList;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+
+
+public class MockSubscription implements Subscription {
+ Subscription sub = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+
+ public List<Tag> getTagList() {
+ return sub.getTagList();
+ }
+
+ public UUID getId() {
+ return sub.getId();
+ }
+
+ public boolean hasTag(TagDefinition tagDefinition) {
+ return sub.hasTag(tagDefinition);
+ }
+
+ public String getFieldValue(String fieldName) {
+ return sub.getFieldValue(fieldName);
+ }
+
+ public boolean hasTag(ControlTagType controlTagType) {
+ return sub.hasTag(controlTagType);
+ }
+
+ public void setFieldValue(String fieldName, String fieldValue) {
+ sub.setFieldValue(fieldName, fieldValue);
+ }
+
+ public void addTag(TagDefinition definition) {
+ sub.addTag(definition);
+ }
+
+ public void addTags(List<Tag> tags) {
+ sub.addTags(tags);
+ }
+
+ public void saveFieldValue(String fieldName, String fieldValue, CallContext context) {
+ sub.saveFieldValue(fieldName, fieldValue, context);
+ }
+
+ public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+ sub.addTagsFromDefinitions(tagDefinitions);
+ }
+
+ public List<CustomField> getFieldList() {
+ return sub.getFieldList();
+ }
+
+ public void clearTags() {
+ sub.clearTags();
+ }
+
+ public void setFields(List<CustomField> fields) {
+ sub.setFields(fields);
+ }
+
+ public void removeTag(TagDefinition definition) {
+ sub.removeTag(definition);
+ }
+
+ public void saveFields(List<CustomField> fields, CallContext context) {
+ sub.saveFields(fields, context);
+ }
+
+ public boolean generateInvoice() {
+ return sub.generateInvoice();
+ }
+
+ public boolean processPayment() {
+ return sub.processPayment();
+ }
+
+ public void clearFields() {
+ sub.clearFields();
+ }
+
+ public void clearPersistedFields(CallContext context) {
+ sub.clearPersistedFields(context);
+ }
+
+ @Override
+ public ObjectType getObjectType() {
+ return sub.getObjectType();
+ }
+
+ public boolean cancel(DateTime requestedDate, boolean eot, CallContext context) throws EntitlementUserApiException {
+ return sub.cancel(requestedDate, eot, context);
+ }
+
+ public boolean uncancel(CallContext context) throws EntitlementUserApiException {
+ return sub.uncancel(context);
+ }
+
+ public boolean changePlan(String productName, BillingPeriod term, String planSet, DateTime requestedDate,
+ CallContext context) throws EntitlementUserApiException {
+ return sub.changePlan(productName, term, planSet, requestedDate, context);
+ }
+
+ public boolean recreate(PlanPhaseSpecifier spec, DateTime requestedDate, CallContext context)
+ throws EntitlementUserApiException {
+ return sub.recreate(spec, requestedDate, context);
+ }
+
+ public UUID getBundleId() {
+ return sub.getBundleId();
+ }
+
+ public SubscriptionState getState() {
+ return sub.getState();
+ }
+
+ public DateTime getStartDate() {
+ return sub.getStartDate();
+ }
+
+ public DateTime getEndDate() {
+ return sub.getEndDate();
+ }
+
+ public Plan getCurrentPlan() {
+ return sub.getCurrentPlan();
+ }
+
+ public BlockingState getBlockingState() {
+ return sub.getBlockingState();
+ }
+
+ public PriceList getCurrentPriceList() {
+ return sub.getCurrentPriceList();
+ }
+
+ public PlanPhase getCurrentPhase() {
+ return sub.getCurrentPhase();
+ }
+
+ public DateTime getChargedThroughDate() {
+ return sub.getChargedThroughDate();
+ }
+
+ public DateTime getPaidThroughDate() {
+ return sub.getPaidThroughDate();
+ }
+
+ public ProductCategory getCategory() {
+ return sub.getCategory();
+ }
+
+ public SubscriptionEvent getPendingTransition() {
+ return sub.getPendingTransition();
+ }
+
+ public SubscriptionEvent getPreviousTransition() {
+ return sub.getPreviousTransition();
+ }
+
+ public List<SubscriptionEvent> getBillingTransitions() {
+ return sub.getBillingTransitions();
+ }
+
+
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/MockSubscriptionEvent.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/MockSubscriptionEvent.java
new file mode 100644
index 0000000..cbba255
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/MockSubscriptionEvent.java
@@ -0,0 +1,333 @@
+/*
+ * 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.junction.plumbing.billing;
+
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.util.bus.BusEvent.BusEventType;
+
+public class MockSubscriptionEvent implements SubscriptionEvent {
+
+ private final Long totalOrdering;
+ private final UUID subscriptionId;
+ private final UUID bundleId;
+ private final UUID eventId;
+ private final DateTime requestedTransitionTime;
+ private final DateTime effectiveTransitionTime;
+ private final SubscriptionState previousState;
+ private final String previousPriceList;
+ private final String previousPlan;
+ private final String previousPhase;
+ private final SubscriptionState nextState;
+ private final String nextPriceList;
+ private final String nextPlan;
+ private final String nextPhase;
+ private final Integer remainingEventsForUserOperation;
+ private final UUID userToken;
+ private final SubscriptionTransitionType transitionType;
+
+ private final DateTime startDate;
+
+ @JsonCreator
+ public MockSubscriptionEvent(@JsonProperty("eventId") UUID eventId,
+ @JsonProperty("subscriptionId") UUID subscriptionId,
+ @JsonProperty("bundleId") UUID bundleId,
+ @JsonProperty("requestedTransitionTime") DateTime requestedTransitionTime,
+ @JsonProperty("effectiveTransitionTime") DateTime effectiveTransitionTime,
+ @JsonProperty("previousState") SubscriptionState previousState,
+ @JsonProperty("previousPlan") String previousPlan,
+ @JsonProperty("previousPhase") String previousPhase,
+ @JsonProperty("previousPriceList") String previousPriceList,
+ @JsonProperty("nextState") SubscriptionState nextState,
+ @JsonProperty("nextPlan") String nextPlan,
+ @JsonProperty("nextPhase") String nextPhase,
+ @JsonProperty("nextPriceList") String nextPriceList,
+ @JsonProperty("totalOrdering") Long totalOrdering,
+ @JsonProperty("userToken") UUID userToken,
+ @JsonProperty("transitionType") SubscriptionTransitionType transitionType,
+ @JsonProperty("remainingEventsForUserOperation") Integer remainingEventsForUserOperation,
+ @JsonProperty("startDate") DateTime startDate) {
+ super();
+ this.eventId = eventId;
+ this.subscriptionId = subscriptionId;
+ this.bundleId = bundleId;
+ this.requestedTransitionTime = requestedTransitionTime;
+ this.effectiveTransitionTime = effectiveTransitionTime;
+ this.previousState = previousState;
+ this.previousPriceList = previousPriceList;
+ this.previousPlan = previousPlan;
+ this.previousPhase = previousPhase;
+ this.nextState = nextState;
+ this.nextPlan = nextPlan;
+ this.nextPriceList = nextPriceList;
+ this.nextPhase = nextPhase;
+ this.totalOrdering = totalOrdering;
+ this.userToken = userToken;
+ this.transitionType = transitionType;
+ this.remainingEventsForUserOperation = remainingEventsForUserOperation;
+ this.startDate = startDate;
+ }
+
+ @JsonIgnore
+ @Override
+ public BusEventType getBusEventType() {
+ return BusEventType.SUBSCRIPTION_TRANSITION;
+ }
+
+ @JsonProperty("eventId")
+ @Override
+ public UUID getId() {
+ return eventId;
+ }
+
+ @Override
+ public UUID getSubscriptionId() {
+ return subscriptionId;
+ }
+
+ @Override
+ public UUID getBundleId() {
+ return bundleId;
+ }
+
+
+ @Override
+ public SubscriptionState getPreviousState() {
+ return previousState;
+ }
+
+ @Override
+ public String getPreviousPlan() {
+ return previousPlan;
+ }
+
+ @Override
+ public String getPreviousPhase() {
+ return previousPhase;
+ }
+
+ @Override
+ public String getNextPlan() {
+ return nextPlan;
+ }
+
+ @Override
+ public String getNextPhase() {
+ return nextPhase;
+ }
+
+ @Override
+ public SubscriptionState getNextState() {
+ return nextState;
+ }
+
+
+ @Override
+ public String getPreviousPriceList() {
+ return previousPriceList;
+ }
+
+ @Override
+ public String getNextPriceList() {
+ return nextPriceList;
+ }
+
+ @Override
+ public UUID getUserToken() {
+ return userToken;
+ }
+
+ @Override
+ public Integer getRemainingEventsForUserOperation() {
+ return remainingEventsForUserOperation;
+ }
+
+
+ @Override
+ public DateTime getRequestedTransitionTime() {
+ return requestedTransitionTime;
+ }
+
+ @Override
+ public DateTime getEffectiveTransitionTime() {
+ return effectiveTransitionTime;
+ }
+
+ @Override
+ public Long getTotalOrdering() {
+ return totalOrdering;
+ }
+
+ @Override
+ public SubscriptionTransitionType getTransitionType() {
+ return transitionType;
+ }
+
+ @JsonProperty("startDate")
+ @Override
+ public DateTime getSubscriptionStartDate() {
+ return startDate;
+ }
+
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((bundleId == null) ? 0 : bundleId.hashCode());
+ result = prime
+ * result
+ + ((effectiveTransitionTime == null) ? 0
+ : effectiveTransitionTime.hashCode());
+ result = prime * result + ((eventId == null) ? 0 : eventId.hashCode());
+ result = prime * result
+ + ((nextPhase == null) ? 0 : nextPhase.hashCode());
+ result = prime * result
+ + ((nextPlan == null) ? 0 : nextPlan.hashCode());
+ result = prime * result
+ + ((nextPriceList == null) ? 0 : nextPriceList.hashCode());
+ result = prime * result
+ + ((nextState == null) ? 0 : nextState.hashCode());
+ result = prime * result
+ + ((previousPhase == null) ? 0 : previousPhase.hashCode());
+ result = prime * result
+ + ((previousPlan == null) ? 0 : previousPlan.hashCode());
+ result = prime
+ * result
+ + ((previousPriceList == null) ? 0 : previousPriceList
+ .hashCode());
+ result = prime * result
+ + ((previousState == null) ? 0 : previousState.hashCode());
+ result = prime
+ * result
+ + ((remainingEventsForUserOperation == null) ? 0
+ : remainingEventsForUserOperation.hashCode());
+ result = prime
+ * result
+ + ((requestedTransitionTime == null) ? 0
+ : requestedTransitionTime.hashCode());
+ result = prime * result
+ + ((subscriptionId == null) ? 0 : subscriptionId.hashCode());
+ result = prime * result
+ + ((totalOrdering == null) ? 0 : totalOrdering.hashCode());
+ result = prime * result
+ + ((transitionType == null) ? 0 : transitionType.hashCode());
+ result = prime * result
+ + ((userToken == null) ? 0 : userToken.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ MockSubscriptionEvent other = (MockSubscriptionEvent) obj;
+ if (bundleId == null) {
+ if (other.bundleId != null)
+ return false;
+ } else if (!bundleId.equals(other.bundleId))
+ return false;
+ if (effectiveTransitionTime == null) {
+ if (other.effectiveTransitionTime != null)
+ return false;
+ } else if (effectiveTransitionTime
+ .compareTo(other.effectiveTransitionTime) != 0)
+ return false;
+ if (eventId == null) {
+ if (other.eventId != null)
+ return false;
+ } else if (!eventId.equals(other.eventId))
+ return false;
+ if (nextPhase == null) {
+ if (other.nextPhase != null)
+ return false;
+ } else if (!nextPhase.equals(other.nextPhase))
+ return false;
+ if (nextPlan == null) {
+ if (other.nextPlan != null)
+ return false;
+ } else if (!nextPlan.equals(other.nextPlan))
+ return false;
+ if (nextPriceList == null) {
+ if (other.nextPriceList != null)
+ return false;
+ } else if (!nextPriceList.equals(other.nextPriceList))
+ return false;
+ if (nextState != other.nextState)
+ return false;
+ if (previousPhase == null) {
+ if (other.previousPhase != null)
+ return false;
+ } else if (!previousPhase.equals(other.previousPhase))
+ return false;
+ if (previousPlan == null) {
+ if (other.previousPlan != null)
+ return false;
+ } else if (!previousPlan.equals(other.previousPlan))
+ return false;
+ if (previousPriceList == null) {
+ if (other.previousPriceList != null)
+ return false;
+ } else if (!previousPriceList.equals(other.previousPriceList))
+ return false;
+ if (previousState != other.previousState)
+ return false;
+ if (remainingEventsForUserOperation == null) {
+ if (other.remainingEventsForUserOperation != null)
+ return false;
+ } else if (!remainingEventsForUserOperation
+ .equals(other.remainingEventsForUserOperation))
+ return false;
+ if (requestedTransitionTime == null) {
+ if (other.requestedTransitionTime != null)
+ return false;
+ } else if (requestedTransitionTime
+ .compareTo(other.requestedTransitionTime) != 0)
+ return false;
+ if (subscriptionId == null) {
+ if (other.subscriptionId != null)
+ return false;
+ } else if (!subscriptionId.equals(other.subscriptionId))
+ return false;
+ if (totalOrdering == null) {
+ if (other.totalOrdering != null)
+ return false;
+ } else if (!totalOrdering.equals(other.totalOrdering))
+ return false;
+ if (transitionType != other.transitionType)
+ return false;
+ if (userToken == null) {
+ if (other.userToken != null)
+ return false;
+ } else if (!userToken.equals(other.userToken))
+ return false;
+ return true;
+ }
+
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java
new file mode 100644
index 0000000..435ee50
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBlockingCalculator.java
@@ -0,0 +1,734 @@
+/*
+ * 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.junction.plumbing.billing;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.MockPlan;
+import com.ning.billing.catalog.MockPlanPhase;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.Blockable.Type;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
+import com.ning.billing.junction.dao.BlockingStateDao;
+import com.ning.billing.junction.plumbing.billing.BlockingCalculator.DisabledDuration;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+
+
+public class TestBlockingCalculator {
+
+ private static final String DISABLED_BUNDLE = "disabled-bundle";
+ private static final String CLEAR_BUNDLE = "clear-bundle";
+
+ private BlockingApi blockingApi;
+ private Account account;
+ private Subscription subscription1;
+ private Subscription subscription2;
+ private Subscription subscription3;
+ private Subscription subscription4;
+ private UUID bundleId1 = UUID.randomUUID();
+ private UUID bundleId2 = UUID.randomUUID();
+ private Clock clock;
+ private BlockingCalculator odc;
+
+ @BeforeClass
+ public void setUpBeforeClass() throws Exception {
+
+ clock = new ClockMock();
+
+ Injector i = Guice.createInjector(new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ blockingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BlockingApi.class);
+ account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+ subscription1 = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class, Comparable.class);
+ subscription2 = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class, Comparable.class);
+ subscription3 = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class, Comparable.class);
+ subscription4 = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class, Comparable.class);
+ ((ZombieControl) account).addResult("getId", UUID.randomUUID());
+ ((ZombieControl) subscription1).addResult("getBundleId", bundleId1);
+ ((ZombieControl) subscription2).addResult("getBundleId", bundleId1);
+ ((ZombieControl) subscription3).addResult("getBundleId", bundleId1);
+ ((ZombieControl) subscription4).addResult("getBundleId", bundleId2);
+ ((ZombieControl) subscription1).addResult("compareTo", 1);
+ ((ZombieControl) subscription2).addResult("compareTo", 1);
+ ((ZombieControl) subscription3).addResult("compareTo", 1);
+ ((ZombieControl) subscription4).addResult("compareTo", 1);
+ ((ZombieControl) subscription1).addResult("getId", UUID.randomUUID());
+ ((ZombieControl) subscription2).addResult("getId", UUID.randomUUID());
+ ((ZombieControl) subscription3).addResult("getId", UUID.randomUUID());
+ ((ZombieControl) subscription4).addResult("getId", UUID.randomUUID());
+
+
+ bind(BlockingStateDao.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(BlockingStateDao.class));
+ bind(BlockingApi.class).toInstance(blockingApi);
+
+ }
+
+ });
+ odc = i.getInstance(BlockingCalculator.class);
+
+ }
+
+ @Test
+ // S1-S2-S3 subscriptions in B1
+ // B1 -----[--------]
+ // S1 --A-------------------------------------
+ // S2 --B------C------------------------------
+ // S3 ------------------D---------------------
+
+
+ //Result
+ // S1 --A--[-------]--------------------------
+ // S2 --B--[-------]--------------------------
+ // S3 ------------------D---------------------
+
+ public void testInsertBlockingEvents() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, null));
+ BillingEvent A = createRealEvent(now.minusDays(1).minusHours(1), subscription1);
+ BillingEvent B = createRealEvent(now.minusDays(1), subscription2);
+ BillingEvent C = createRealEvent(now.plusDays(1), subscription2);
+ BillingEvent D = createRealEvent(now.plusDays(3), subscription3);
+ billingEvents.add(A);
+ billingEvents.add(B);
+ billingEvents.add(C);
+ billingEvents.add(D);
+
+ SortedSet<BlockingState> blockingStates = new TreeSet<BlockingState>();
+ blockingStates.add(new DefaultBlockingState(bundleId1,DISABLED_BUNDLE, Blockable.Type.SUBSCRIPTION_BUNDLE, "test", true, true, true, now));
+ blockingStates.add(new DefaultBlockingState(bundleId1,CLEAR_BUNDLE, Blockable.Type.SUBSCRIPTION_BUNDLE, "test", false, false, false, now.plusDays(2)));
+
+ ((ZombieControl)blockingApi).addResult("getBlockingHistory", blockingStates);
+
+
+ odc.insertBlockingEvents(billingEvents);
+
+ assertEquals(billingEvents.size(), 7);
+
+ SortedSet<BillingEvent> s1Events = odc.filter(billingEvents, subscription1);
+ Iterator<BillingEvent> it1 = s1Events.iterator();
+ assertEquals(it1.next(), A);
+ assertEquals(it1.next().getTransitionType(), SubscriptionTransitionType.CANCEL);
+ assertEquals(it1.next().getTransitionType(), SubscriptionTransitionType.RE_CREATE);
+
+ SortedSet<BillingEvent> s2Events = odc.filter(billingEvents, subscription2);
+ Iterator<BillingEvent> it2 = s2Events.iterator();
+ assertEquals(it2.next(), B);
+ assertEquals(it2.next().getTransitionType(), SubscriptionTransitionType.CANCEL);
+ assertEquals(it2.next().getTransitionType(), SubscriptionTransitionType.RE_CREATE);
+
+ SortedSet<BillingEvent> s3Events = odc.filter(billingEvents, subscription3);
+ Iterator<BillingEvent> it3 = s3Events.iterator();
+ assertEquals(it3.next(),D);
+ }
+
+ // Open ended duration with a previous event
+ // --X--[----------------------------------
+ @Test
+ public void testEventsToRemoveOpenPrev() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, null));
+ billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
+
+ SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents, subscription1);
+
+ assertEquals(results.size(), 0);
+ }
+
+ // Open with previous and following events
+ // --X--[----Y-----------------------------
+ @Test
+ public void testEventsToRemoveOpenPrevFollow() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, null));
+ BillingEvent e1 = createRealEvent(now.minusDays(1), subscription1);
+ BillingEvent e2 = createRealEvent(now.plusDays(1), subscription1);
+ billingEvents.add(e1);
+ billingEvents.add(e2);
+
+ SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents, subscription1);
+
+ assertEquals(results.size(), 1);
+ assertEquals(results.first(), e2);
+ }
+
+ // Open with no previous event (only following)
+ // -----[----X-----------------------------
+ @Test
+ public void testEventsToRemoveOpenFollow() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, null));
+ BillingEvent e1 = createRealEvent(now.plusDays(1), subscription1);
+ billingEvents.add(e1);
+
+ SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents, subscription1);
+
+ assertEquals(results.size(), 1);
+ assertEquals(results.first(), e1);
+ }
+
+ // Closed duration with a single previous event
+ // --X--[------------]---------------------
+ @Test
+ public void testEventsToRemoveClosedPrev() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+ BillingEvent e1 = createRealEvent(now.minusDays(1), subscription1);
+ billingEvents.add(e1);
+
+ SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents, subscription1);
+
+ assertEquals(results.size(), 0);
+ }
+
+ // Closed duration with a previous event and in-between event
+ // --X--[------Y-----]---------------------
+ @Test
+ public void testEventsToRemoveClosedPrevBetw() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+ BillingEvent e1 = createRealEvent(now.minusDays(1), subscription1);
+ BillingEvent e2 = createRealEvent(now.plusDays(1), subscription1);
+ billingEvents.add(e1);
+ billingEvents.add(e2);
+
+
+ SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents, subscription1);
+
+ assertEquals(results.size(), 1);
+ assertEquals(results.first(), e2);
+ }
+
+ // Closed duration with a previous event and in-between event and following
+ // --X--[------Y-----]-------Z-------------
+ @Test
+ public void testEventsToRemoveClosedPrevBetwNext() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+ BillingEvent e1 = createRealEvent(now.minusDays(1), subscription1);
+ BillingEvent e2 = createRealEvent(now.plusDays(1), subscription1);
+ BillingEvent e3 = createRealEvent(now.plusDays(3), subscription1);
+ billingEvents.add(e1);
+ billingEvents.add(e2);
+ billingEvents.add(e3);
+
+ SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents, subscription1);
+
+ assertEquals(results.size(), 1);
+ assertEquals(results.first(), e2);
+ }
+
+ // Closed with no previous event but in-between events
+ // -----[------Y-----]---------------------
+ @Test
+ public void testEventsToRemoveClosedBetwn() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+ BillingEvent e2 = createRealEvent(now.plusDays(1), subscription1);
+ billingEvents.add(e2);
+
+
+ SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents, subscription1);
+
+ assertEquals(results.size(), 1);
+ assertEquals(results.first(), e2);
+ }
+
+ // Closed with no previous event but in-between events and following
+ // -----[------Y-----]-------Z-------------
+ @Test
+ public void testEventsToRemoveClosedBetweenFollow() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+
+ BillingEvent e2 = createRealEvent(now.plusDays(1), subscription1);
+ BillingEvent e3 = createRealEvent(now.plusDays(3), subscription1);
+ billingEvents.add(e2);
+ billingEvents.add(e3);
+
+ SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents, subscription1);
+
+ assertEquals(results.size(), 1);
+ assertEquals(results.first(), e2);
+ }
+
+ // Closed duration with only following
+ // -----[------------]-------Z-------------
+ @Test
+ public void testEventsToRemoveClosedFollow() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+
+ BillingEvent e3 = createRealEvent(now.plusDays(3), subscription1);
+
+ billingEvents.add(e3);
+
+ SortedSet<BillingEvent> results = odc.eventsToRemove( disabledDuration, billingEvents, subscription1);
+
+ assertEquals(results.size(), 0);
+ }
+
+ // Open ended duration with a previous event
+ // --X--[----------------------------------
+ @Test
+ public void testCreateNewEventsOpenPrev() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, null));
+ billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
+
+ SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents, account, subscription1);
+
+ assertEquals(results.size(), 1);
+ assertEquals(results.first().getEffectiveDate(), now);
+ assertEquals(results.first().getRecurringPrice(), BigDecimal.ZERO);
+ assertEquals(results.first().getTransitionType(), SubscriptionTransitionType.CANCEL);
+ }
+
+ // Open with previous and following events
+ // --X--[----Y-----------------------------
+ @Test
+ public void testCreateNewEventsOpenPrevFollow() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, null));
+ billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
+ billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
+
+ SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents, account, subscription1);
+
+ assertEquals(results.size(), 1);
+ assertEquals(results.first().getEffectiveDate(), now);
+ assertEquals(results.first().getRecurringPrice(), BigDecimal.ZERO);
+ assertEquals(results.first().getTransitionType(), SubscriptionTransitionType.CANCEL);
+ }
+
+ // Open with no previous event (only following)
+ // -----[----X-----------------------------
+ @Test
+ public void testCreateNewEventsOpenFollow() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, null));
+ billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
+
+ SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents, account, subscription1);
+
+ assertEquals(results.size(), 0);
+ }
+
+ // Closed duration with a single previous event
+ // --X--[------------]---------------------
+ @Test
+ public void testCreateNewEventsClosedPrev() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+ billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
+
+ SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents, account, subscription1);
+
+ assertEquals(results.size(), 2);
+ assertEquals(results.first().getEffectiveDate(), now);
+ assertEquals(results.first().getRecurringPrice(), BigDecimal.ZERO);
+ assertEquals(results.first().getTransitionType(), SubscriptionTransitionType.CANCEL);
+ assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
+ assertEquals(results.last().getRecurringPrice(), billingEvents.first().getRecurringPrice());
+ assertEquals(results.last().getTransitionType(), SubscriptionTransitionType.RE_CREATE);
+ }
+
+ // Closed duration with a previous event and in-between event
+ // --X--[------Y-----]---------------------
+ @Test
+ public void testCreateNewEventsClosedPrevBetw() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+ billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
+ billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
+
+ SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents, account, subscription1);
+
+ assertEquals(results.size(), 2);
+ assertEquals(results.first().getEffectiveDate(), now);
+ assertEquals(results.first().getRecurringPrice(), BigDecimal.ZERO);
+ assertEquals(results.first().getTransitionType(), SubscriptionTransitionType.CANCEL);
+ assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
+ assertEquals(results.last().getRecurringPrice(), billingEvents.first().getRecurringPrice());
+ assertEquals(results.last().getTransitionType(), SubscriptionTransitionType.RE_CREATE);
+ }
+
+ // Closed duration with a previous event and in-between event and following
+ // --X--[------Y-----]-------Z-------------
+ @Test
+ public void testCreateNewEventsClosedPrevBetwNext() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+ billingEvents.add(createRealEvent(now.minusDays(1), subscription1));
+ billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
+ billingEvents.add(createRealEvent(now.plusDays(3), subscription1));
+
+ SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents, account, subscription1);
+
+ assertEquals(results.size(), 2);
+ assertEquals(results.first().getEffectiveDate(), now);
+ assertEquals(results.first().getRecurringPrice(), BigDecimal.ZERO);
+ assertEquals(results.first().getTransitionType(), SubscriptionTransitionType.CANCEL);
+ assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
+ assertEquals(results.last().getRecurringPrice(), billingEvents.first().getRecurringPrice());
+ assertEquals(results.last().getTransitionType(), SubscriptionTransitionType.RE_CREATE);
+ }
+
+ // Closed with no previous event but in-between events
+ // -----[------Y-----]---------------------
+ @Test
+ public void testCreateNewEventsClosedBetwn() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+ billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
+
+ SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents, account, subscription1);
+
+ assertEquals(results.size(), 1);
+ assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
+ assertEquals(results.last().getRecurringPrice(), billingEvents.first().getRecurringPrice());
+ assertEquals(results.last().getTransitionType(), SubscriptionTransitionType.RE_CREATE);
+ }
+
+ // Closed with no previous event but in-between events and following
+ // -----[------Y-----]-------Z-------------
+ @Test
+ public void testCreateNewEventsClosedBetweenFollow() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+ billingEvents.add(createRealEvent(now.plusDays(1), subscription1));
+
+ SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents, account, subscription1);
+
+ assertEquals(results.size(), 1);
+ assertEquals(results.last().getEffectiveDate(), now.plusDays(2));
+ assertEquals(results.last().getRecurringPrice(), billingEvents.first().getRecurringPrice());
+ assertEquals(results.last().getTransitionType(), SubscriptionTransitionType.RE_CREATE);
+ }
+
+ // Closed duration with only following
+ // -----[------------]-------Z-------------
+ @Test
+ public void testCreateNewEventsClosedFollow() {
+ DateTime now = clock.getUTCNow();
+ List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>();
+ SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>();
+
+ disabledDuration.add(new DisabledDuration(now, now.plusDays(2)));
+ billingEvents.add(createRealEvent(now.plusDays(3), subscription1));
+
+ SortedSet<BillingEvent> results = odc.createNewEvents( disabledDuration, billingEvents, account, subscription1);
+
+ assertEquals(results.size(), 0);
+ }
+
+ @Test
+ public void testPrecedingBillingEventForSubscription() {
+ DateTime now = new DateTime();
+
+ SortedSet<BillingEvent> events = new TreeSet<BillingEvent>();
+
+ events.add(createRealEvent(now.minusDays(10), subscription1));
+ events.add(createRealEvent(now.minusDays(6), subscription1));
+ events.add(createRealEvent(now.minusDays(5), subscription1));
+ events.add(createRealEvent(now.minusDays(1), subscription1));
+
+ BillingEvent minus11 = odc.precedingBillingEventForSubscription(now.minusDays(11), events, subscription1);
+ assertNull(minus11);
+
+ BillingEvent minus5andAHalf= odc.precedingBillingEventForSubscription(now.minusDays(5).minusHours(12), events, subscription1);
+ assertNotNull(minus5andAHalf);
+ assertEquals(minus5andAHalf.getEffectiveDate(), now.minusDays(6));
+
+
+ }
+
+ protected BillingEvent createRealEvent(DateTime effectiveDate, Subscription subscription) {
+ final Account account = this.account;
+ final int billCycleDay = 1;
+ final PlanPhase planPhase = new MockPlanPhase();
+ final Plan plan = new MockPlan();
+ final BigDecimal fixedPrice = BigDecimal.TEN;
+ final BigDecimal recurringPrice = BigDecimal.TEN;
+ final Currency currency = Currency.USD;
+ final String description = "";
+ final BillingModeType billingModeType = BillingModeType.IN_ADVANCE;
+ final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
+ final SubscriptionTransitionType type = SubscriptionTransitionType.CHANGE;
+ final Long totalOrdering = 0L; //TODO
+
+ return new DefaultBillingEvent(account, subscription, effectiveDate, plan, planPhase,
+ fixedPrice, recurringPrice, currency,
+ billingPeriod, billCycleDay, billingModeType,
+ description, totalOrdering, type);
+ }
+
+
+ @Test
+ public void testFilter() {
+ SortedSet<BillingEvent> events = new TreeSet<BillingEvent>();
+
+ events.add(createBillingEvent(subscription1));
+ events.add(createBillingEvent(subscription1));
+ events.add(createBillingEvent(subscription1));
+ events.add(createBillingEvent(subscription2));
+
+ SortedSet<BillingEvent> result1 = odc.filter(events, subscription1);
+ SortedSet<BillingEvent> result2 = odc.filter(events, subscription2);
+ SortedSet<BillingEvent> result3 = odc.filter(events, subscription3);
+
+ assertEquals(result1.size(), 3);
+ assertEquals(result1.first().getSubscription(), subscription1);
+ assertEquals(result1.last().getSubscription(), subscription1);
+ assertEquals(result2.size(), 1);
+ assertEquals(result2.first().getSubscription(), subscription2);
+ assertEquals(result3.size(), 0);
+ }
+
+ @Test
+ public void testCreateNewDisableEvent() {
+ DateTime now = clock.getUTCNow();
+ BillingEvent event = new MockBillingEvent();
+
+ BillingEvent result = odc.createNewDisableEvent(now, event);
+ assertEquals(result.getBillCycleDay(),event.getBillCycleDay());
+ assertEquals(result.getEffectiveDate(), now);
+ assertEquals(result.getPlanPhase(), event.getPlanPhase());
+ assertEquals(result.getPlan(), event.getPlan());
+ assertEquals(result.getFixedPrice(),BigDecimal.ZERO);
+ assertEquals(result.getRecurringPrice(), BigDecimal.ZERO);
+ assertEquals(result.getCurrency(), event.getCurrency());
+ assertEquals(result.getDescription(), "");
+ assertEquals(result.getBillingMode(), event.getBillingMode());
+ assertEquals(result.getBillingPeriod(), event.getBillingPeriod());
+ assertEquals(result.getTransitionType(), SubscriptionTransitionType.CANCEL);
+ assertEquals(result.getTotalOrdering(), new Long(0));
+ }
+
+ @Test
+ public void testCreateNewReenableEvent() {
+ DateTime now = clock.getUTCNow();
+ BillingEvent event = new MockBillingEvent();
+
+ BillingEvent result = odc.createNewReenableEvent(now, event);
+ assertEquals(result.getBillCycleDay(),event.getBillCycleDay());
+ assertEquals(result.getEffectiveDate(), now);
+ assertEquals(result.getPlanPhase(), event.getPlanPhase());
+ assertEquals(result.getPlan(), event.getPlan());
+ assertEquals(result.getFixedPrice(),event.getFixedPrice());
+ assertEquals(result.getRecurringPrice(), event.getRecurringPrice());
+ assertEquals(result.getCurrency(), event.getCurrency());
+ assertEquals(result.getDescription(), "");
+ assertEquals(result.getBillingMode(), event.getBillingMode());
+ assertEquals(result.getBillingPeriod(), event.getBillingPeriod());
+ assertEquals(result.getTransitionType(), SubscriptionTransitionType.RE_CREATE);
+ assertEquals(result.getTotalOrdering(), new Long(0));
+ }
+
+ private class MockBillingEvent extends DefaultBillingEvent {
+ public MockBillingEvent() {
+ super(account, subscription1, clock.getUTCNow(), null, null, BigDecimal.ZERO, BigDecimal.TEN, Currency.USD, BillingPeriod.ANNUAL,
+ 4, BillingModeType.IN_ADVANCE, "", 3L, SubscriptionTransitionType.CREATE);
+ }
+ }
+
+ @Test
+ public void testCreateBundleSubscriptionMap() {
+ SortedSet<BillingEvent> events = new TreeSet<BillingEvent>();
+ events.add(createBillingEvent(subscription1));
+ events.add(createBillingEvent(subscription2));
+ events.add(createBillingEvent(subscription3));
+ events.add(createBillingEvent(subscription4));
+
+ Hashtable<UUID,List<Subscription>> map = odc.createBundleSubscriptionMap(events);
+
+ assertNotNull(map);
+ assertEquals(map.keySet().size(),2);
+ assertEquals(map.get(bundleId1).size(), 3);
+ assertEquals(map.get(bundleId2).size(), 1);
+
+ }
+
+ private BillingEvent createBillingEvent(Subscription subscription) {
+ BillingEvent result = BrainDeadProxyFactory.createBrainDeadProxyFor(BillingEvent.class, Comparable.class);
+ ((ZombieControl)result).addResult("getSubscription", subscription);
+ ((ZombieControl)result).addResult("compareTo", 1);
+ return result;
+ }
+
+ @Test
+ public void testCreateDisablePairs() {
+ SortedSet<BlockingState> blockingEvents;
+ UUID ovdId = UUID.randomUUID();
+ DateTime now = clock.getUTCNow();
+
+ //simple events open clear -> disabled
+ blockingEvents = new TreeSet<BlockingState>();
+ blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false, now));
+ blockingEvents.add(new DefaultBlockingState(ovdId,DISABLED_BUNDLE, Type.SUBSCRIPTION_BUNDLE, "test", true, true, true,now.plusDays(1)));
+
+ List<DisabledDuration> pairs = odc.createBlockingDurations(blockingEvents);
+ assertEquals(pairs.size(), 1);
+ assertNotNull(pairs.get(0).getStart());
+ assertEquals(pairs.get(0).getStart(),now.plusDays(1));
+ assertNull(pairs.get(0).getEnd());
+
+ //simple events closed clear -> disabled
+ blockingEvents = new TreeSet<BlockingState>();
+ blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false,now));
+ blockingEvents.add(new DefaultBlockingState(ovdId,DISABLED_BUNDLE, Type.SUBSCRIPTION_BUNDLE, "test", true, true, true,now.plusDays(1)));
+ blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false,now.plusDays(2)));
+
+ pairs = odc.createBlockingDurations(blockingEvents);
+ assertEquals(pairs.size(), 1);
+ assertNotNull(pairs.get(0).getStart());
+ assertEquals(pairs.get(0).getStart(),now.plusDays(1));
+ assertNotNull(pairs.get(0).getEnd());
+ assertEquals(pairs.get(0).getEnd(),now.plusDays(2));
+
+ //simple BUNDLE events closed clear -> disabled
+ blockingEvents = new TreeSet<BlockingState>();
+ blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false,now));
+ blockingEvents.add(new DefaultBlockingState(ovdId,DISABLED_BUNDLE, Type.SUBSCRIPTION_BUNDLE, "test", true, true, true,now.plusDays(1)));
+ blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false,now.plusDays(2)));
+
+ pairs = odc.createBlockingDurations(blockingEvents);
+ assertEquals(pairs.size(), 1);
+ assertNotNull(pairs.get(0).getStart());
+ assertEquals(pairs.get(0).getStart(),now.plusDays(1));
+ assertNotNull(pairs.get(0).getEnd());
+ assertEquals(pairs.get(0).getEnd(),now.plusDays(2));
+
+
+ //two or more disableds in a row
+ blockingEvents = new TreeSet<BlockingState>();
+ blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false,now));
+ blockingEvents.add(new DefaultBlockingState(ovdId,DISABLED_BUNDLE, Type.SUBSCRIPTION_BUNDLE, "test", true, true, true,now.plusDays(1)));
+ blockingEvents.add(new DefaultBlockingState(ovdId,DISABLED_BUNDLE, Type.SUBSCRIPTION_BUNDLE, "test", true, true, true,now.plusDays(2)));
+ blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false,now.plusDays(3)));
+
+ pairs = odc.createBlockingDurations(blockingEvents);
+ assertEquals(pairs.size(), 1);
+ assertNotNull(pairs.get(0).getStart());
+ assertEquals(pairs.get(0).getStart(),now.plusDays(1));
+ assertNotNull(pairs.get(0).getEnd());
+ assertEquals(pairs.get(0).getEnd(),now.plusDays(3));
+
+
+ blockingEvents = new TreeSet<BlockingState>();
+ blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false,now));
+ blockingEvents.add(new DefaultBlockingState(ovdId,DISABLED_BUNDLE, Type.SUBSCRIPTION_BUNDLE, "test", true, true, true,now.plusDays(1)));
+ blockingEvents.add(new DefaultBlockingState(ovdId,DISABLED_BUNDLE, Type.SUBSCRIPTION_BUNDLE, "test", true, true, true,now.plusDays(2)));
+ blockingEvents.add(new DefaultBlockingState(ovdId,DISABLED_BUNDLE, Type.SUBSCRIPTION_BUNDLE, "test", true, true, true,now.plusDays(3)));
+ blockingEvents.add(new DefaultBlockingState(ovdId,CLEAR_BUNDLE, Type.SUBSCRIPTION_BUNDLE,"test", false, false, false,now.plusDays(4)));
+
+ pairs = odc.createBlockingDurations(blockingEvents);
+ assertEquals(pairs.size(), 1);
+ assertNotNull(pairs.get(0).getStart());
+ assertEquals(pairs.get(0).getStart(),now.plusDays(1));
+ assertNotNull(pairs.get(0).getEnd());
+ assertEquals(pairs.get(0).getEnd(),now.plusDays(4));
+
+ }
+}
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultEntitlementBillingApi.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultEntitlementBillingApi.java
new file mode 100644
index 0000000..85e239f
--- /dev/null
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestDefaultEntitlementBillingApi.java
@@ -0,0 +1,459 @@
+/*
+ * 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.junction.plumbing.billing;
+
+
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.catalog.MockCatalog;
+import com.ning.billing.catalog.MockCatalogService;
+import com.ning.billing.catalog.api.BillingAlignment;
+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.CurrencyValueNull;
+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.catalog.api.Price;
+import com.ning.billing.catalog.api.PriceList;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+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.SubscriptionEvent;
+import com.ning.billing.junction.api.BillingApi;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.Blockable.Type;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.junction.api.DefaultBlockingState;
+import com.ning.billing.lifecycle.KillbillService.ServiceException;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+
+public class TestDefaultEntitlementBillingApi {
+
+ class MockPrice implements InternationalPrice {
+ private final BigDecimal price;
+
+ public MockPrice(String val) {
+ price = new BigDecimal(val);
+ }
+
+ @Override
+ public boolean isZero() {
+ return price.compareTo(BigDecimal.ZERO) == 0;
+ }
+
+ @Override
+ public Price[] getPrices() {
+ return new Price[]{
+ new Price() {
+
+ @Override
+ public Currency getCurrency() {
+ return Currency.USD;
+ }
+
+ @Override
+ public BigDecimal getValue() throws CurrencyValueNull {
+ return price;
+ }
+
+ }
+ };
+ }
+
+ @Override
+ public BigDecimal getPrice(Currency currency) throws CatalogApiException {
+ return price;
+ }
+ };
+
+ private static final String DISABLED_BUNDLE = "disabled-bundle";
+ private static final String CLEAR_BUNDLE = "clear-bundle";
+
+ private static final UUID eventId = new UUID(0L,0L);
+ private static final UUID subId = new UUID(1L,0L);
+ private static final UUID bunId = new UUID(2L,0L);
+
+ private CatalogService catalogService;
+ private List<SubscriptionBundle> bundles;
+ private List<Subscription> subscriptions;
+
+ private List<SubscriptionEvent> subscriptionTransitions;
+ private EntitlementUserApi entitlementApi;
+
+ private BlockingCalculator blockCalculator = new BlockingCalculator(null) {
+ @Override
+ public void insertBlockingEvents(SortedSet<BillingEvent> billingEvents) {
+
+ }
+
+ };
+
+
+
+ private Clock clock;
+ private Subscription subscription;
+ private DateTime subscriptionStartDate;
+ private Plan subscriptionPlan;
+
+ @BeforeSuite(groups={"fast", "slow"})
+ public void setup() throws ServiceException {
+ catalogService = new MockCatalogService(new MockCatalog());
+ clock = new ClockMock();
+ }
+
+ @BeforeMethod(groups={"fast", "slow"})
+ public void setupEveryTime() {
+ bundles = new ArrayList<SubscriptionBundle>();
+ final SubscriptionBundle bundle = BrainDeadProxyFactory.createBrainDeadProxyFor(SubscriptionBundle.class);
+ ((ZombieControl)bundle).addResult("getId", eventId);
+
+ //new SubscriptionBundleData( eventId,"TestKey", subId, clock.getUTCNow().minusDays(4), null);
+ bundles.add(bundle);
+
+
+ subscriptionTransitions = new LinkedList<SubscriptionEvent>();
+ subscriptions = new LinkedList<Subscription>();
+
+ subscriptionStartDate = clock.getUTCNow().minusDays(3);
+ subscription = new MockSubscription() {
+ @Override
+ public List<SubscriptionEvent> getBillingTransitions() {
+ return subscriptionTransitions;
+ }
+
+ @Override
+ public Plan getCurrentPlan() {
+ return subscriptionPlan;
+ }
+
+ @Override
+ public UUID getId() {
+ return subId;
+ }
+
+ @Override
+ public UUID getBundleId() {
+ return bunId;
+ }
+
+ @Override
+ public DateTime getStartDate() {
+ return subscriptionStartDate;
+ }
+
+
+ };
+
+ subscriptions.add(subscription);
+
+ entitlementApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class);
+ ((ZombieControl) entitlementApi).addResult("getBundlesForAccount", bundles);
+ ((ZombieControl) entitlementApi).addResult("getSubscriptionsForBundle", subscriptions);
+ ((ZombieControl) entitlementApi).addResult("getSubscriptionFromId", subscription);
+ ((ZombieControl) entitlementApi).addResult("getBundleFromId", bundle);
+ ((ZombieControl) entitlementApi).addResult("getBaseSubscription", subscription);
+
+ assertTrue(true);
+ }
+
+ @Test(enabled=true, groups="fast")
+ public void testBillingEventsEmpty() {
+ UUID accountId = UUID.randomUUID();
+ Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+ ((ZombieControl) account).addResult("getId", accountId).addResult("getCurrency", Currency.USD);
+
+ AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
+ ((ZombieControl) accountApi).addResult("getAccountById", account);
+
+ BillCycleDayCalculator bcdCalculator = new BillCycleDayCalculator(catalogService, entitlementApi);
+ CallContextFactory factory = new DefaultCallContextFactory(clock);
+
+ BillingApi api = new DefaultBillingApi(null, factory, accountApi, bcdCalculator, entitlementApi, blockCalculator, catalogService);
+
+ SortedSet<BillingEvent> events = api.getBillingEventsForAccountAndUpdateAccountBCD(new UUID(0L,0L));
+ Assert.assertEquals(events.size(), 0);
+ }
+
+ @Test(enabled=true, groups="fast")
+ public void testBillingEventsNoBillingPeriod() throws CatalogApiException {
+ DateTime now = clock.getUTCNow();
+ DateTime then = now.minusDays(1);
+ Plan nextPlan = catalogService.getFullCatalog().findPlan("PickupTrialEvergreen10USD", now);
+ PlanPhase nextPhase = nextPlan.getAllPhases()[0]; // The trial has no billing period
+ PriceList nextPriceList = catalogService.getFullCatalog().findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
+
+ SubscriptionEvent t = new MockSubscriptionEvent(
+ eventId, subId, bunId, then, now, null, null, null, null, SubscriptionState.ACTIVE,
+ nextPlan.getName(), nextPhase.getName(),
+ nextPriceList.getName(), 1L,null,
+ SubscriptionTransitionType.CREATE, 0, null);
+
+ subscriptionTransitions.add(t);
+
+ AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
+ Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+ ((ZombieControl)account).addResult("getBillCycleDay", 32);
+ ((ZombieControl)account).addResult("getCurrency", Currency.USD);
+ ((ZombieControl)accountApi).addResult("getAccountById", account);
+
+ BillCycleDayCalculator bcdCalculator = new BillCycleDayCalculator(catalogService, entitlementApi);
+ CallContextFactory factory = new DefaultCallContextFactory(clock);
+ BillingApi api = new DefaultBillingApi(null, factory, accountApi, bcdCalculator, entitlementApi, blockCalculator, catalogService);
+ SortedSet<BillingEvent> events = api.getBillingEventsForAccountAndUpdateAccountBCD(new UUID(0L,0L));
+
+ checkFirstEvent(events, nextPlan, 32, subId, now, nextPhase, SubscriptionTransitionType.CREATE.toString());
+ }
+
+ @Test(enabled=false, groups="fast")
+ public void testBillingEventsAnnual() throws CatalogApiException {
+ DateTime now = clock.getUTCNow();
+ DateTime then = now.minusDays(1);
+ Plan nextPlan = catalogService.getFullCatalog().findPlan("PickupTrialEvergreen10USD", now);
+ PlanPhase nextPhase = nextPlan.getAllPhases()[1];
+ PriceList nextPriceList = catalogService.getFullCatalog().findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
+ SubscriptionEvent t = new MockSubscriptionEvent(
+ eventId, subId, bunId, then, now, null, null, null, null, SubscriptionState.ACTIVE,
+ nextPlan.getName(), nextPhase.getName(),
+ nextPriceList.getName(), 1L,null,
+ SubscriptionTransitionType.CREATE, 0, null);
+
+ subscriptionTransitions.add(t);
+
+ Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+ ((ZombieControl)account).addResult("getBillCycleDay", 1).addResult("getTimeZone", DateTimeZone.UTC)
+ .addResult("getCurrency", Currency.USD);
+
+ ((MockCatalog)catalogService.getFullCatalog()).setBillingAlignment(BillingAlignment.SUBSCRIPTION);
+
+ AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
+ ((ZombieControl)accountApi).addResult("getAccountById", account);
+
+ BillCycleDayCalculator bcdCalculator = new BillCycleDayCalculator(catalogService, entitlementApi);
+ CallContextFactory factory = new DefaultCallContextFactory(clock);
+
+ BillingApi api = new DefaultBillingApi(null, factory, accountApi, bcdCalculator, entitlementApi, blockCalculator, catalogService);
+ SortedSet<BillingEvent> events = api.getBillingEventsForAccountAndUpdateAccountBCD(new UUID(0L,0L));
+
+ checkFirstEvent(events, nextPlan, subscription.getStartDate().plusDays(30).getDayOfMonth(), subId, now, nextPhase, SubscriptionTransitionType.CREATE.toString());
+ }
+
+ @Test(enabled=true, groups="fast")
+ public void testBillingEventsMonthly() throws CatalogApiException {
+ DateTime now = clock.getUTCNow();
+ DateTime then = now.minusDays(1);
+ Plan nextPlan = catalogService.getFullCatalog().findPlan("PickupTrialEvergreen10USD", now);
+ PlanPhase nextPhase = nextPlan.getAllPhases()[1];
+ PriceList nextPriceList = catalogService.getFullCatalog().findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
+
+ SubscriptionEvent t = new MockSubscriptionEvent(
+ eventId, subId, bunId, then, now, null, null, null, null, SubscriptionState.ACTIVE,
+ nextPlan.getName(), nextPhase.getName(),
+ nextPriceList.getName(), 1L,null,
+ SubscriptionTransitionType.CREATE, 0, null);
+
+
+ subscriptionTransitions.add(t);
+
+ AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
+ Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+ ((ZombieControl)account).addResult("getBillCycleDay", 32);
+ ((ZombieControl)account).addResult("getCurrency", Currency.USD);
+ ((ZombieControl)accountApi).addResult("getAccountById", account);
+
+ ((MockCatalog)catalogService.getFullCatalog()).setBillingAlignment(BillingAlignment.ACCOUNT);
+
+ BillCycleDayCalculator bcdCalculator = new BillCycleDayCalculator(catalogService, entitlementApi);
+ CallContextFactory factory = new DefaultCallContextFactory(clock);
+ BillingApi api = new DefaultBillingApi(null, factory, accountApi, bcdCalculator, entitlementApi, blockCalculator, catalogService);
+
+ SortedSet<BillingEvent> events = api.getBillingEventsForAccountAndUpdateAccountBCD(new UUID(0L,0L));
+
+ checkFirstEvent(events, nextPlan, 32, subId, now, nextPhase, SubscriptionTransitionType.CREATE.toString());
+ }
+
+ @Test(enabled=false, groups="fast")
+ public void testBillingEventsAddOn() throws CatalogApiException {
+ DateTime now = clock.getUTCNow();
+ DateTime then = now.minusDays(1);
+ Plan nextPlan = catalogService.getFullCatalog().findPlan("Horn1USD", now);
+ PlanPhase nextPhase = nextPlan.getAllPhases()[0];
+ PriceList nextPriceList = catalogService.getFullCatalog().findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
+
+ SubscriptionEvent t = new MockSubscriptionEvent(
+ eventId, subId, bunId, then, now, null, null, null, null, SubscriptionState.ACTIVE,
+ nextPlan.getName(), nextPhase.getName(),
+ nextPriceList.getName(), 1L,null,
+ SubscriptionTransitionType.CREATE, 0, null);
+
+ subscriptionTransitions.add(t);
+
+ Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+ ((ZombieControl)account).addResult("getBillCycleDay", 1).addResult("getTimeZone", DateTimeZone.UTC);
+ ((ZombieControl)account).addResult("getCurrency", Currency.USD);
+
+ AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
+ ((ZombieControl)accountApi).addResult("getAccountById", account);
+
+ ((MockCatalog)catalogService.getFullCatalog()).setBillingAlignment(BillingAlignment.BUNDLE);
+
+ BillCycleDayCalculator bcdCalculator = new BillCycleDayCalculator(catalogService, entitlementApi);
+ CallContextFactory factory = new DefaultCallContextFactory(clock);
+
+ BillingApi api = new DefaultBillingApi(null, factory, accountApi, bcdCalculator, entitlementApi, blockCalculator, catalogService);
+ subscriptionPlan = catalogService.getFullCatalog().findPlan("PickupTrialEvergreen10USD", now);
+
+ SortedSet<BillingEvent> events = api.getBillingEventsForAccountAndUpdateAccountBCD(new UUID(0L,0L));
+
+ checkFirstEvent(events, nextPlan, subscription.getStartDate().plusDays(30).getDayOfMonth(), subId, now, nextPhase, SubscriptionTransitionType.CREATE.toString());
+ }
+
+ @Test(enabled=true, groups="fast")
+ public void testBillingEventsWithBlock() throws CatalogApiException {
+ DateTime now = clock.getUTCNow();
+ DateTime then = now.minusDays(1);
+ Plan nextPlan = catalogService.getFullCatalog().findPlan("PickupTrialEvergreen10USD", now);
+ PlanPhase nextPhase = nextPlan.getAllPhases()[1];
+ PriceList nextPriceList = catalogService.getFullCatalog().findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
+
+
+ SubscriptionEvent t = new MockSubscriptionEvent(
+ eventId, subId, bunId, then, now, null, null, null, null, SubscriptionState.ACTIVE,
+ nextPlan.getName(), nextPhase.getName(),
+ nextPriceList.getName(), 1L,null,
+ SubscriptionTransitionType.CREATE, 0, null);
+
+ subscriptionTransitions.add(t);
+
+ AccountUserApi accountApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
+ Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+ ((ZombieControl)account).addResult("getBillCycleDay", 32);
+ ((ZombieControl)account).addResult("getCurrency", Currency.USD);
+ ((ZombieControl)accountApi).addResult("getAccountById", account);
+ ((ZombieControl)account).addResult("getId", UUID.randomUUID());
+
+ ((MockCatalog)catalogService.getFullCatalog()).setBillingAlignment(BillingAlignment.ACCOUNT);
+
+ final SortedSet<BlockingState> blockingStates = new TreeSet<BlockingState>();
+ blockingStates.add(new DefaultBlockingState(bunId,DISABLED_BUNDLE, Blockable.Type.SUBSCRIPTION_BUNDLE, "test", true, true, true, now.plusDays(1)));
+ blockingStates.add(new DefaultBlockingState(bunId,CLEAR_BUNDLE, Blockable.Type.SUBSCRIPTION_BUNDLE, "test", false, false, false, now.plusDays(2)));
+
+ BlockingCalculator blockingCal = new BlockingCalculator(new BlockingApi() {
+
+ @Override
+ public <T extends Blockable> void setBlockingState(BlockingState state) {}
+
+ @Override
+ public BlockingState getBlockingStateFor(UUID overdueableId) {
+ return null;
+ }
+
+ @Override
+ public BlockingState getBlockingStateFor(Blockable overdueable) {
+ return null;
+ }
+
+ @Override
+ public SortedSet<BlockingState> getBlockingHistory(UUID overdueableId) {
+ if(overdueableId == bunId) {
+ return blockingStates;
+ }
+ return new TreeSet<BlockingState>();
+ }
+
+ @Override
+ public SortedSet<BlockingState> getBlockingHistory(Blockable overdueable) {
+ return new TreeSet<BlockingState>();
+ }
+ });
+
+ BillCycleDayCalculator bcdCalculator = new BillCycleDayCalculator(catalogService, entitlementApi);
+ CallContextFactory factory = new DefaultCallContextFactory(clock);
+ BillingApi api = new DefaultBillingApi(null, factory, accountApi, bcdCalculator, entitlementApi, blockingCal, catalogService);
+ SortedSet<BillingEvent> events = api.getBillingEventsForAccountAndUpdateAccountBCD(new UUID(0L,0L));
+
+ Assert.assertEquals(events.size(), 3);
+ Iterator<BillingEvent> it = events.iterator();
+
+ checkEvent(it.next(), nextPlan, 32, subId, now, nextPhase, SubscriptionTransitionType.CREATE.toString(), nextPhase.getFixedPrice(), nextPhase.getRecurringPrice());
+ checkEvent(it.next(), nextPlan, 32, subId, now.plusDays(1), nextPhase, SubscriptionTransitionType.CANCEL.toString(), new MockPrice("0"), new MockPrice("0"));
+ checkEvent(it.next(), nextPlan, 32, subId, now.plusDays(2), nextPhase, SubscriptionTransitionType.RE_CREATE.toString(), nextPhase.getFixedPrice(), nextPhase.getRecurringPrice());
+
+ }
+
+ 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);
+ checkEvent(events.first(), nextPlan,
+ BCD, id, time, nextPhase, desc, nextPhase.getFixedPrice(), nextPhase.getRecurringPrice());
+ }
+
+ private void checkEvent(BillingEvent event, Plan nextPlan,
+ int BCD, UUID id, DateTime time, PlanPhase nextPhase, String desc, InternationalPrice fixedPrice, InternationalPrice recurringPrice) throws CatalogApiException {
+ if(fixedPrice != null) {
+ Assert.assertEquals(fixedPrice.getPrice(Currency.USD), event.getFixedPrice());
+ } else {
+ assertNull(event.getFixedPrice());
+ }
+
+ if(recurringPrice != null) {
+ Assert.assertEquals(recurringPrice.getPrice(Currency.USD), event.getRecurringPrice());
+ } else {
+ assertNull(event.getRecurringPrice());
+ }
+
+ 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.getTransitionType().toString());
+ }
+}
junction/src/test/resources/log4j.xml 40(+40 -0)
diff --git a/junction/src/test/resources/log4j.xml b/junction/src/test/resources/log4j.xml
new file mode 100644
index 0000000..ac530a1
--- /dev/null
+++ b/junction/src/test/resources/log4j.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2010-2011 Ning, Inc.
+ ~
+ ~ Ning licenses this file to you under the Apache License, version 2.0
+ ~ (the "License"); you may not use this file except in compliance with the
+ ~ License. You may obtain a copy of the License at:
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ ~ License for the specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+ <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
+ <param name="Target" value="System.out"/>
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%p %d{ISO8601} %X{trace} %t %c %m%n"/>
+ </layout>
+ </appender>
+
+
+ <logger name="com.ning.billing.entitlement">
+ <level value="info"/>
+ </logger>
+
+ <logger name="com.ning.billing.util.notificationq">
+ <level value="info"/>
+ </logger>
+
+ <root>
+ <priority value="info"/>
+ <appender-ref ref="stdout"/>
+ </root>
+</log4j:configuration>
diff --git a/junction/src/test/resources/resource.properties b/junction/src/test/resources/resource.properties
new file mode 100644
index 0000000..d63334b
--- /dev/null
+++ b/junction/src/test/resources/resource.properties
@@ -0,0 +1,7 @@
+killbill.catalog.uri=file:src/test/resources/catalogSample.xml
+killbill.entitlement.dao.claim.time=60000
+killbill.entitlement.dao.ready.max=1
+killbill.entitlement.engine.notifications.sleep=500
+user.timezone=UTC
+
+
overdue/pom.xml 118(+118 -0)
diff --git a/overdue/pom.xml b/overdue/pom.xml
new file mode 100644
index 0000000..3662f2b
--- /dev/null
+++ b/overdue/pom.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ~ Copyright 2010-2011 Ning, Inc. ~ ~ Ning licenses this file to you
+ under the Apache License, version 2.0 ~ (the "License"); you may not use
+ this file except in compliance with the ~ License. You may obtain a copy
+ of the License at: ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless
+ required by applicable law or agreed to in writing, software ~ distributed
+ under the License is distributed on an "AS IS" BASIS, WITHOUT ~ WARRANTIES
+ OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for
+ the specific language governing permissions and limitations ~ under the License. -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill</artifactId>
+ <version>0.1.11-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <artifactId>killbill-overdue</artifactId>
+ <name>killbill-overdue</name>
+ <packaging>jar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.inject</groupId>
+ <artifactId>guice</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.skife.config</groupId>
+ <artifactId>config-magic</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>joda-time</groupId>
+ <artifactId>joda-time</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jdbi</groupId>
+ <artifactId>jdbi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <!-- TEST SCOPE -->
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-util</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-catalog</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-catalog</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-mxj</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-mxj-db-files</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.jayway.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</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/overdue/src/main/java/com/ning/billing/ovedue/notification/DefaultOverdueCheckNotifier.java b/overdue/src/main/java/com/ning/billing/ovedue/notification/DefaultOverdueCheckNotifier.java
new file mode 100644
index 0000000..9f45075
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/ovedue/notification/DefaultOverdueCheckNotifier.java
@@ -0,0 +1,105 @@
+/*
+ * 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.ovedue.notification;
+
+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.config.NotificationConfig;
+import com.ning.billing.overdue.OverdueProperties;
+import com.ning.billing.overdue.listener.OverdueListener;
+import com.ning.billing.overdue.service.DefaultOverdueService;
+import com.ning.billing.util.notificationq.NotificationQueue;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+
+public class DefaultOverdueCheckNotifier implements OverdueCheckNotifier {
+
+ private final static Logger log = LoggerFactory.getLogger(DefaultOverdueCheckNotifier.class);
+
+ public static final String OVERDUE_CHECK_NOTIFIER_QUEUE = "overdue-check-queue";
+
+ private final NotificationQueueService notificationQueueService;
+ private final OverdueProperties config;
+
+ private NotificationQueue overdueQueue;
+ private final OverdueListener listener;
+
+ @Inject
+ public DefaultOverdueCheckNotifier(NotificationQueueService notificationQueueService,
+ OverdueProperties config, OverdueListener listener){
+ this.notificationQueueService = notificationQueueService;
+ this.config = config;
+ this.listener = listener;
+ }
+
+ @Override
+ public void initialize() {
+ try {
+ overdueQueue = notificationQueueService.createNotificationQueue(DefaultOverdueService.OVERDUE_SERVICE_NAME,
+ OVERDUE_CHECK_NOTIFIER_QUEUE,
+ new NotificationQueueHandler() {
+ @Override
+ public void handleReadyNotification(String notificationKey, DateTime eventDate) {
+ try {
+ UUID key = UUID.fromString(notificationKey);
+ processEvent(key , eventDate);
+ } catch (IllegalArgumentException e) {
+ log.error("The key returned from the NextBillingNotificationQueue is not a valid UUID", e);
+ return;
+ }
+
+ }
+ },
+ new NotificationConfig() {
+ @Override
+ public boolean isNotificationProcessingOff() {
+ return config.isNotificationProcessingOff();
+ }
+ @Override
+ public long getSleepTimeMs() {
+ return config.getSleepTimeMs();
+ }
+ });
+ } catch (NotificationQueueAlreadyExists e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void start() {
+ overdueQueue.startQueue();
+ }
+
+ @Override
+ public void stop() {
+ if (overdueQueue != null) {
+ overdueQueue.stopQueue();
+ }
+ }
+
+ private void processEvent(UUID overdueableId, DateTime eventDateTime) {
+ listener.handleNextOverdueCheck(overdueableId);
+ }
+
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/ovedue/notification/DefaultOverdueCheckPoster.java b/overdue/src/main/java/com/ning/billing/ovedue/notification/DefaultOverdueCheckPoster.java
new file mode 100644
index 0000000..81ba766
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/ovedue/notification/DefaultOverdueCheckPoster.java
@@ -0,0 +1,77 @@
+/*
+ * 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.ovedue.notification;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.service.DefaultOverdueService;
+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;
+
+public class DefaultOverdueCheckPoster implements OverdueCheckPoster {
+ private final static Logger log = LoggerFactory.getLogger(DefaultOverdueCheckNotifier.class);
+
+ private final NotificationQueueService notificationQueueService;
+
+ @Inject
+ public DefaultOverdueCheckPoster(
+ NotificationQueueService notificationQueueService) {
+ super();
+ this.notificationQueueService = notificationQueueService;
+ }
+
+ @Override
+ public void insertOverdueCheckNotification(final Blockable overdueable, final DateTime futureNotificationTime) {
+ NotificationQueue checkOverdueQueue;
+ try {
+ checkOverdueQueue = notificationQueueService.getNotificationQueue(DefaultOverdueService.OVERDUE_SERVICE_NAME,
+ DefaultOverdueCheckNotifier.OVERDUE_CHECK_NOTIFIER_QUEUE);
+ log.info("Queuing overdue check notification. id: {}, timestamp: {}", overdueable.getId().toString(), futureNotificationTime.toString());
+
+ checkOverdueQueue.recordFutureNotification(futureNotificationTime, new NotificationKey(){
+ @Override
+ public String toString() {
+ return overdueable.getId().toString();
+ }
+ });
+ } catch (NoSuchNotificationQueue e) {
+ log.error("Attempting to put items on a non-existent queue (DefaultOverdueCheck).", e);
+ }
+
+ }
+
+
+ @Override
+ public void clearNotificationsFor(final Blockable overdueable) {
+ NotificationQueue checkOverdueQueue;
+ try {
+ checkOverdueQueue = notificationQueueService.getNotificationQueue(DefaultOverdueService.OVERDUE_SERVICE_NAME,
+ DefaultOverdueCheckNotifier.OVERDUE_CHECK_NOTIFIER_QUEUE);
+ checkOverdueQueue.removeNotificationsByKey(overdueable.getId());
+ } catch (NoSuchNotificationQueue e) {
+ log.error("Attempting to clear items from a non-existent queue (DefaultOverdueCheck).", e);
+ }
+ }
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/ovedue/notification/OverdueCheckNotifier.java b/overdue/src/main/java/com/ning/billing/ovedue/notification/OverdueCheckNotifier.java
new file mode 100644
index 0000000..7ef6ab8
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/ovedue/notification/OverdueCheckNotifier.java
@@ -0,0 +1,28 @@
+/*
+ * 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.ovedue.notification;
+
+
+public interface OverdueCheckNotifier {
+
+ public void initialize();
+
+ public void start();
+
+ public void stop();
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/ovedue/notification/OverdueCheckPoster.java b/overdue/src/main/java/com/ning/billing/ovedue/notification/OverdueCheckPoster.java
new file mode 100644
index 0000000..3a2aa48
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/ovedue/notification/OverdueCheckPoster.java
@@ -0,0 +1,31 @@
+/*
+ * 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.ovedue.notification;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+import com.ning.billing.junction.api.Blockable;
+
+
+public interface OverdueCheckPoster {
+
+ void insertOverdueCheckNotification(Blockable blockable, DateTime futureNotificationTime);
+
+ void clearNotificationsFor(Blockable blockable);
+
+}
\ No newline at end of file
diff --git a/overdue/src/main/java/com/ning/billing/overdue/api/DefaultOverdueUserApi.java b/overdue/src/main/java/com/ning/billing/overdue/api/DefaultOverdueUserApi.java
new file mode 100644
index 0000000..83487ea
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/api/DefaultOverdueUserApi.java
@@ -0,0 +1,78 @@
+/*
+ * 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.overdue.api;
+
+import org.apache.commons.lang.NotImplementedException;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.OverdueState;
+import com.ning.billing.overdue.OverdueUserApi;
+import com.ning.billing.overdue.config.OverdueConfig;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.overdue.config.api.OverdueError;
+import com.ning.billing.overdue.config.api.OverdueStateSet;
+import com.ning.billing.overdue.service.ExtendedOverdueService;
+import com.ning.billing.overdue.wrapper.OverdueWrapper;
+import com.ning.billing.overdue.wrapper.OverdueWrapperFactory;
+
+public class DefaultOverdueUserApi implements OverdueUserApi {
+
+
+ private final OverdueWrapperFactory factory;
+ private final BlockingApi accessApi;
+ private final OverdueConfig overdueConfig;
+
+ @Inject
+ public DefaultOverdueUserApi(OverdueWrapperFactory factory,BlockingApi accessApi, ExtendedOverdueService service, CatalogService catalogService) {
+ this.factory = factory;
+ this.accessApi = accessApi;
+ this.overdueConfig = service.getOverdueConfig();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T extends Blockable> OverdueState<T> getOverdueStateFor(T overdueable) throws OverdueError {
+ try {
+ String stateName = accessApi.getBlockingStateFor(overdueable).getStateName();
+ OverdueStateSet<SubscriptionBundle> states = overdueConfig.getBundleStateSet();
+ return (OverdueState<T>) states.findState(stateName);
+ } catch (OverdueApiException e) {
+ throw new OverdueError(e, ErrorCode.OVERDUE_CAT_ERROR_ENCOUNTERED,overdueable.getId(), overdueable.getClass().getSimpleName());
+ }
+ }
+
+ @Override
+ public <T extends Blockable> OverdueState<T> refreshOverdueStateFor(T overdueable) throws OverdueError, OverdueApiException {
+ OverdueWrapper<T> wrapper = factory.createOverdueWrapperFor(overdueable);
+ return wrapper.refresh();
+ }
+
+
+ @Override
+ public <T extends Blockable> void setOverrideBillingStateForAccount(
+ T overdueable, BillingState<T> state) {
+ throw new NotImplementedException();
+ }
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
new file mode 100644
index 0000000..abd4e0a
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
@@ -0,0 +1,107 @@
+/*
+ * 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.overdue.applicator;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.joda.time.DateTime;
+import org.joda.time.Period;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.DefaultBlockingState;
+import com.ning.billing.ovedue.notification.OverdueCheckPoster;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.OverdueService;
+import com.ning.billing.overdue.OverdueState;
+import com.ning.billing.overdue.config.api.OverdueError;
+import com.ning.billing.util.clock.Clock;
+
+public class OverdueStateApplicator<T extends Blockable>{
+
+ private final BlockingApi blockingApi;
+ private final Clock clock;
+ private final OverdueCheckPoster poster;
+
+
+ @Inject
+ public OverdueStateApplicator(BlockingApi accessApi, Clock clock, OverdueCheckPoster poster) {
+ this.blockingApi = accessApi;
+ this.clock = clock;
+ this.poster = poster;
+ }
+
+ public void apply(T overdueable, OverdueState<T> previousOverdueState, OverdueState<T> nextOverdueState) throws OverdueError {
+ if(previousOverdueState.getName().equals(nextOverdueState.getName())) {
+ return; // nothing to do
+ }
+
+ storeNewState(overdueable, nextOverdueState);
+ try {
+ Period reevaluationInterval = nextOverdueState.getReevaluationInterval();
+ if(!nextOverdueState.isClearState()) {
+ createFutureNotification(overdueable, clock.getUTCNow().plus(reevaluationInterval));
+ }
+ } catch(OverdueApiException e) {
+ if(e.getCode() != ErrorCode.OVERDUE_NO_REEVALUATION_INTERVAL.getCode()) {
+ new OverdueError(e);
+ }
+ }
+
+ if(nextOverdueState.isClearState()) {
+ clear(overdueable);
+ }
+
+
+
+ }
+
+
+ protected void storeNewState(T blockable, OverdueState<T> nextOverdueState) throws OverdueError {
+ try {
+ blockingApi.setBlockingState(new DefaultBlockingState(blockable.getId(), nextOverdueState.getName(), Blockable.Type.get(blockable),
+ OverdueService.OVERDUE_SERVICE_NAME, blockChanges(nextOverdueState), blockEntitlement(nextOverdueState), blockBilling(nextOverdueState)));
+ } catch (Exception e) {
+ throw new OverdueError(e, ErrorCode.OVERDUE_CAT_ERROR_ENCOUNTERED, blockable.getId(), blockable.getClass().getName());
+ }
+ }
+
+ private boolean blockChanges(OverdueState<T> nextOverdueState) {
+ return nextOverdueState.blockChanges();
+ }
+
+ private boolean blockBilling(OverdueState<T> nextOverdueState) {
+ return nextOverdueState.disableEntitlementAndChangesBlocked();
+ }
+
+ private boolean blockEntitlement(OverdueState<T> nextOverdueState) {
+ return nextOverdueState.disableEntitlementAndChangesBlocked();
+ }
+
+ protected void createFutureNotification(T overdueable,
+ DateTime timeOfNextCheck) {
+ poster.insertOverdueCheckNotification(overdueable, timeOfNextCheck);
+
+ }
+
+ protected void clear(T blockable) {
+ //Need to clear the overrride table here too (when we add it)
+ poster.clearNotificationsFor(blockable);
+ }
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/calculator/BillingStateCalculator.java b/overdue/src/main/java/com/ning/billing/overdue/calculator/BillingStateCalculator.java
new file mode 100644
index 0000000..d47f3f3
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/calculator/BillingStateCalculator.java
@@ -0,0 +1,81 @@
+/*
+ * 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.overdue.calculator;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.util.clock.Clock;
+
+public abstract class BillingStateCalculator<T extends Blockable> {
+
+ private final InvoiceUserApi invoiceApi;
+ private final Clock clock;
+
+ protected class InvoiceDateComparator implements Comparator<Invoice> {
+ @Override
+ public int compare(Invoice i1, Invoice i2) {
+ DateTime d1 = i1.getInvoiceDate();
+ DateTime d2 = i2.getInvoiceDate();
+ if(d1.compareTo(d2) == 0) {
+ return i1.hashCode() - i2.hashCode(); // consistent (arbitrary) resolution for tied dates
+ }
+ return d1.compareTo(d2);
+ }
+ }
+
+ @Inject
+ public BillingStateCalculator(InvoiceUserApi invoiceApi, Clock clock) {
+ this.invoiceApi = invoiceApi;
+ this.clock = clock;
+ }
+
+ public abstract BillingState<T> calculateBillingState(T overdueable) throws EntitlementUserApiException;
+
+ protected DateTime earliest(SortedSet<Invoice> unpaidInvoices) {
+ return unpaidInvoices.first().getInvoiceDate();
+ }
+
+ protected BigDecimal sumBalance(SortedSet<Invoice> unpaidInvoices) {
+ BigDecimal sum = BigDecimal.ZERO;
+ Iterator<Invoice> it = unpaidInvoices.iterator();
+ while(it.hasNext()) {
+ sum = sum.add(it.next().getBalance());
+ }
+ return sum;
+ }
+
+ protected SortedSet<Invoice> unpaidInvoicesForAccount(UUID accountId) {
+ Collection<Invoice> invoices = invoiceApi.getUnpaidInvoicesByAccountId(accountId, clock.getUTCNow());
+ SortedSet<Invoice> sortedInvoices = new TreeSet<Invoice>(new InvoiceDateComparator());
+ sortedInvoices.addAll(invoices);
+ return sortedInvoices;
+ }
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/calculator/BillingStateCalculatorBundle.java b/overdue/src/main/java/com/ning/billing/overdue/calculator/BillingStateCalculatorBundle.java
new file mode 100644
index 0000000..89a7cc6
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/calculator/BillingStateCalculatorBundle.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.overdue.calculator;
+
+import java.math.BigDecimal;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PriceList;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.overdue.config.api.BillingStateBundle;
+import com.ning.billing.overdue.config.api.PaymentResponse;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.tag.Tag;
+
+public class BillingStateCalculatorBundle extends BillingStateCalculator<SubscriptionBundle>{
+
+ private EntitlementUserApi entitlementApi;
+
+ @Inject
+ public BillingStateCalculatorBundle(EntitlementUserApi entitlementApi, InvoiceUserApi invoiceApi, Clock clock) {
+ super(invoiceApi, clock);
+ this.entitlementApi = entitlementApi;
+ }
+
+ @Override
+ public BillingStateBundle calculateBillingState(SubscriptionBundle bundle) throws EntitlementUserApiException {
+
+ SortedSet<Invoice> unpaidInvoices = unpaidInvoicesForBundle(bundle.getId(), bundle.getAccountId());
+
+ Subscription basePlan = entitlementApi.getBaseSubscription(bundle.getId());
+
+ UUID id = bundle.getId();
+ int numberOfUnpaidInvoices = unpaidInvoices.size();
+ BigDecimal unpaidInvoiceBalance = sumBalance(unpaidInvoices);
+ DateTime dateOfEarliestUnpaidInvoice = earliest(unpaidInvoices);
+ PaymentResponse responseForLastFailedPayment = PaymentResponse.INSUFFICIENT_FUNDS; //TODO MDW
+ Tag[] tags = new Tag[]{}; //TODO MDW
+ Product basePlanProduct = basePlan.getCurrentPlan().getProduct();
+ BillingPeriod basePlanBillingPeriod = basePlan.getCurrentPlan().getBillingPeriod();
+ PriceList basePlanPriceList = basePlan.getCurrentPriceList();
+ PhaseType basePlanPhaseType = basePlan.getCurrentPhase().getPhaseType();
+
+
+ return new BillingStateBundle(
+ id,
+ numberOfUnpaidInvoices,
+ unpaidInvoiceBalance,
+ dateOfEarliestUnpaidInvoice,
+ responseForLastFailedPayment,
+ tags,
+ basePlanProduct,
+ basePlanBillingPeriod,
+ basePlanPriceList,
+ basePlanPhaseType);
+
+ }
+
+ public SortedSet<Invoice> unpaidInvoicesForBundle(UUID bundleId, UUID accountId) {
+ SortedSet<Invoice> unpaidInvoices = unpaidInvoicesForAccount(accountId);
+ SortedSet<Invoice> result = new TreeSet<Invoice>(new InvoiceDateComparator());
+ result.addAll(unpaidInvoices);
+ for(Invoice invoice : unpaidInvoices) {
+ if(!invoiceHasAnItemFromBundle(invoice, bundleId)) {
+ result.remove(invoice);
+ }
+ }
+ return result;
+ }
+
+ private boolean invoiceHasAnItemFromBundle(Invoice invoice, UUID bundleId) {
+ for(InvoiceItem item : invoice.getInvoiceItems()) {
+ if(item.getBundleId().equals(bundleId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/Condition.java b/overdue/src/main/java/com/ning/billing/overdue/config/Condition.java
new file mode 100644
index 0000000..6fc8564
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/Condition.java
@@ -0,0 +1,30 @@
+/*
+ * 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.overdue.config;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.config.api.BillingState;
+
+
+
+public interface Condition<T extends Blockable> {
+
+ public boolean evaluate(BillingState state, DateTime now);
+
+}
\ No newline at end of file
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/DefaultCondition.java b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultCondition.java
new file mode 100644
index 0000000..44f6c99
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultCondition.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.overdue.config;
+
+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 javax.xml.bind.annotation.XmlElementWrapper;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.TimeUnit;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.overdue.config.api.PaymentResponse;
+import com.ning.billing.util.config.ValidatingConfig;
+import com.ning.billing.util.config.ValidationErrors;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.Tag;
+
+@XmlAccessorType(XmlAccessType.NONE)
+
+public class DefaultCondition<T extends Blockable> extends ValidatingConfig<OverdueConfig> implements Condition<T> {
+ @XmlElement(required=false, name="numberOfUnpaidInvoicesEqualsOrExceeds")
+ private Integer numberOfUnpaidInvoicesEqualsOrExceeds;
+
+ @XmlElement(required=false, name="totalUnpaidInvoiceBalanceEqualsOrExceeds")
+ private BigDecimal totalUnpaidInvoiceBalanceEqualsOrExceeds;
+
+ @XmlElement(required=false, name="timeSinceEarliestUnpaidInvoiceEqualsOrExceeds")
+ private DefaultDuration timeSinceEarliestUnpaidInvoiceEqualsOrExceeds;
+
+ @XmlElementWrapper(required=false, name="responseForLastFailedPaymentIn")
+ @XmlElement(required=false, name="response")
+ private PaymentResponse[] responseForLastFailedPayment;
+
+ @XmlElement(required=false, name="controlTag")
+ private ControlTagType controlTag;
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.catalog.overdue.Condition#evaluate(com.ning.billing.catalog.api.overdue.BillingState, org.joda.time.DateTime)
+ */
+ @Override
+ public boolean evaluate(BillingState state, DateTime now) {
+ return
+ (numberOfUnpaidInvoicesEqualsOrExceeds == null || state.getNumberOfUnpaidInvoices() >= numberOfUnpaidInvoicesEqualsOrExceeds.intValue() ) &&
+ (totalUnpaidInvoiceBalanceEqualsOrExceeds == null || totalUnpaidInvoiceBalanceEqualsOrExceeds.compareTo(state.getBalanceOfUnpaidInvoices()) <= 0) &&
+ (timeSinceEarliestUnpaidInvoiceEqualsOrExceeds == null || !timeSinceEarliestUnpaidInvoiceEqualsOrExceeds.addToDateTime(state.getDateOfEarliestUnpaidInvoice()).isAfter(now)) &&
+ (responseForLastFailedPayment == null || responseIsIn(state.getResponseForLastFailedPayment(), responseForLastFailedPayment)) &&
+ (controlTag == null || isTagIn(controlTag, state.getTags()));
+ }
+
+ private boolean responseIsIn(PaymentResponse actualResponse,
+ PaymentResponse[] responseForLastFailedPayment) {
+ for(PaymentResponse response: responseForLastFailedPayment) {
+ if(response.equals(actualResponse)) return true;
+ }
+ return false;
+ }
+
+ private boolean isTagIn(ControlTagType tag, Tag[] tags) {
+ for(Tag t : tags) {
+ if (t.getTagDefinitionName().equals(tag.toString())) return true;
+ }
+ return false;
+ }
+
+ @Override
+ public ValidationErrors validate(OverdueConfig root,
+ ValidationErrors errors) {
+ return errors;
+ }
+
+ @Override
+ public void initialize(OverdueConfig root, URI uri) {
+ }
+
+ public Duration getTimeOffset() {
+ if (timeSinceEarliestUnpaidInvoiceEqualsOrExceeds != null) {
+ return timeSinceEarliestUnpaidInvoiceEqualsOrExceeds;
+ } else {
+ return new DefaultDuration().setUnit(TimeUnit.DAYS).setNumber(0); // zero time
+ }
+
+ }
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/DefaultDuration.java b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultDuration.java
new file mode 100644
index 0000000..7301e46
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultDuration.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.overdue.config;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+
+import org.joda.time.DateTime;
+import org.joda.time.Period;
+
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.TimeUnit;
+import com.ning.billing.util.config.ValidatingConfig;
+import com.ning.billing.util.config.ValidationErrors;
+
+@XmlAccessorType(XmlAccessType.NONE)
+public class DefaultDuration extends ValidatingConfig<OverdueConfig> implements Duration {
+ @XmlElement(required=true)
+ private TimeUnit unit;
+
+ @XmlElement(required=false)
+ private Integer number = -1;
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.catalog.IDuration#getUnit()
+ */
+ @Override
+ public TimeUnit getUnit() {
+ return unit;
+ }
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.catalog.IDuration#getLength()
+ */
+ @Override
+ public int getNumber() {
+ return number;
+ }
+
+ @Override
+ public DateTime addToDateTime(DateTime dateTime) {
+ if ((number == null) && (unit != TimeUnit.UNLIMITED)) {return dateTime;}
+
+ switch (unit) {
+ case DAYS:
+ return dateTime.plusDays(number);
+ case MONTHS:
+ return dateTime.plusMonths(number);
+ case YEARS:
+ return dateTime.plusYears(number);
+ case UNLIMITED:
+ return dateTime.plusYears(100);
+ default:
+ return dateTime;
+ }
+ }
+
+ @Override
+ public Period toJodaPeriod() {
+ if ((number == null) && (unit != TimeUnit.UNLIMITED)) {return new Period();}
+
+ switch (unit) {
+ case DAYS:
+ return new Period().withDays(number);
+ case MONTHS:
+ return new Period().withMonths(number);
+ case YEARS:
+ return new Period().withYears(number);
+ case UNLIMITED:
+ return new Period().withYears(100);
+ default:
+ return new Period();
+ }
+ }
+
+ @Override
+ public ValidationErrors validate(OverdueConfig catalog, ValidationErrors errors) {
+ //TODO MDW - Validation TimeUnit UNLIMITED iff number == -1
+ return errors;
+ }
+
+ protected DefaultDuration setUnit(TimeUnit unit) {
+ this.unit = unit;
+ return this;
+ }
+
+ protected DefaultDuration setNumber(Integer number) {
+ this.number = number;
+ return this;
+ }
+
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueState.java b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueState.java
new file mode 100644
index 0000000..690d717
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueState.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.overdue.config;
+
+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.XmlID;
+
+import org.joda.time.DateTime;
+import org.joda.time.Period;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.TimeUnit;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.OverdueState;
+import com.ning.billing.util.config.ValidatingConfig;
+import com.ning.billing.util.config.ValidationError;
+import com.ning.billing.util.config.ValidationErrors;
+
+@XmlAccessorType(XmlAccessType.NONE)
+public class DefaultOverdueState<T extends Blockable> extends ValidatingConfig<OverdueConfig> implements OverdueState<T> {
+
+ private static final int MAX_NAME_LENGTH = 50;
+
+ @XmlElement(required=false, name="condition")
+ private DefaultCondition<T> condition;
+
+ @XmlAttribute(required=true, name="name")
+ @XmlID
+ private String name;
+
+ @XmlElement(required=false, name="externalMessage")
+ private String externalMessage = "";
+
+ @XmlElement(required=false, name="blockChanges")
+ private Boolean blockChanges = false;
+
+ @XmlElement(required=false, name="disableEntitlementAndChangesBlocked")
+ private Boolean disableEntitlement = false;
+
+ @XmlElement(required=false, name="daysBetweenPaymentRetries")
+ private Integer daysBetweenPaymentRetries = 8;
+
+ @XmlElement(required=false, name="isClearState")
+ private Boolean isClearState = false;
+
+ @XmlElement(required=false, name="autoReevaluationInterval")
+ private DefaultDuration autoReevaluationInterval;
+
+
+
+ //Other actions could include
+ // - send email
+ // - trigger payment retry?
+ // - add tagStore to bundle/account
+ // - set payment failure email template
+ // - set payment retry interval
+ // - backup payment mechanism?
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.catalog.overdue.OverdueState#getStageName()
+ */
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.catalog.overdue.OverdueState#getExternalMessage()
+ */
+ @Override
+ public String getExternalMessage() {
+ return externalMessage;
+ }
+
+ @Override
+ public boolean blockChanges() {
+ return blockChanges || disableEntitlement;
+ }
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.catalog.overdue.OverdueState#applyCancel()
+ */
+ @Override
+ public boolean disableEntitlementAndChangesBlocked() {
+ return disableEntitlement;
+ }
+
+ @Override
+ public Period getReevaluationInterval() throws OverdueApiException {
+ if(autoReevaluationInterval == null || autoReevaluationInterval.getUnit() == TimeUnit.UNLIMITED || autoReevaluationInterval.getNumber() == 0) {
+ throw new OverdueApiException(ErrorCode.OVERDUE_NO_REEVALUATION_INTERVAL, name);
+ }
+ return autoReevaluationInterval.toJodaPeriod();
+ }
+
+ protected DefaultCondition<T> getCondition() {
+ return condition;
+ }
+
+ protected DefaultOverdueState<T> setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ protected DefaultOverdueState<T> setExternalMessage(String externalMessage) {
+ this.externalMessage = externalMessage;
+ return this;
+ }
+
+ protected DefaultOverdueState<T> setDisableEntitlement(boolean cancel) {
+ this.disableEntitlement = cancel;
+ return this;
+ }
+
+ protected DefaultOverdueState<T> setBlockChanges(boolean cancel) {
+ this.blockChanges = cancel;
+ return this;
+ }
+
+ protected DefaultOverdueState<T> setCondition(DefaultCondition<T> condition) {
+ this.condition = condition;
+ return this;
+ }
+
+ @Override
+ public boolean isClearState() {
+ return isClearState;
+ }
+
+ @Override
+ public ValidationErrors validate(OverdueConfig root,
+ ValidationErrors errors) {
+ if(name.length() > MAX_NAME_LENGTH) {
+ errors.add(new ValidationError(String.format("Name of state '%s' exceeds the maximum length of %d",name,MAX_NAME_LENGTH),root.getURI(), DefaultOverdueState.class, name));
+ }
+ return errors;
+ }
+
+ @Override
+ public int getDaysBetweenPaymentRetries() {
+ return daysBetweenPaymentRetries;
+ }
+
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueStateSet.java b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueStateSet.java
new file mode 100644
index 0000000..277fb1b
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueStateSet.java
@@ -0,0 +1,102 @@
+/*
+ * 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.overdue.config;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+
+import org.joda.time.DateTime;
+import org.joda.time.MutablePeriod;
+import org.joda.time.Period;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.OverdueState;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.overdue.config.api.OverdueStateSet;
+import com.ning.billing.util.config.ValidatingConfig;
+import com.ning.billing.util.config.ValidationErrors;
+
+@XmlAccessorType(XmlAccessType.NONE)
+public abstract class DefaultOverdueStateSet<T extends Blockable> extends ValidatingConfig<OverdueConfig> implements OverdueStateSet<T> {
+ private static final Period ZERO_PERIOD = new Period();
+ private DefaultOverdueState<T> clearState;
+
+ protected abstract DefaultOverdueState<T>[] getStates();
+
+ private DefaultOverdueState<T> getClearState() throws OverdueApiException {
+ for(DefaultOverdueState<T> overdueState : getStates()) {
+ if(overdueState.isClearState()) {
+ return overdueState;
+ }
+ }
+ throw new OverdueApiException(ErrorCode.CAT_MISSING_CLEAR_STATE);
+ }
+
+ @Override
+ public OverdueState<T> findState(String stateName) throws OverdueApiException {
+ for(DefaultOverdueState<T> state: getStates()) {
+ if(state.getName().equals(stateName) ) { return state; }
+ }
+ throw new OverdueApiException(ErrorCode.CAT_NO_SUCH_OVEDUE_STATE, stateName);
+ }
+
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.catalog.overdue.OverdueBillingState#findClearState()
+ */
+ @Override
+ public DefaultOverdueState<T> findClearState() throws OverdueApiException {
+ if (clearState != null) {
+ clearState = getClearState();
+ }
+ return clearState;
+ }
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.catalog.overdue.OverdueBillingState#calculateOverdueState(com.ning.billing.catalog.api.overdue.BillingState, org.joda.time.DateTime)
+ */
+ @Override
+ public DefaultOverdueState<T> calculateOverdueState(BillingState<T> billingState, DateTime now) throws OverdueApiException {
+ for(DefaultOverdueState<T> overdueState : getStates()) {
+ if(overdueState.getCondition().evaluate(billingState, now)) {
+ return overdueState;
+ }
+ }
+ return findClearState();
+ }
+
+ @Override
+ public ValidationErrors validate(OverdueConfig root,
+ ValidationErrors errors) {
+ for(DefaultOverdueState<T> state: getStates()) {
+ state.validate(root, errors);
+ }
+ try {
+ getClearState();
+ } catch (OverdueApiException e) {
+ if(e.getCode() == ErrorCode.CAT_MISSING_CLEAR_STATE.getCode()) {
+ errors.add("Overdue state set is missing a clear state.",
+ root.getURI(), this.getClass(), "");
+ }
+ }
+
+ return errors;
+ }
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/OverdueConfig.java b/overdue/src/main/java/com/ning/billing/overdue/config/OverdueConfig.java
new file mode 100644
index 0000000..16d5a51
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/OverdueConfig.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.overdue.config;
+
+import java.net.URI;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.util.config.ValidatingConfig;
+import com.ning.billing.util.config.ValidationErrors;
+
+@XmlRootElement(name="overdueConfig")
+@XmlAccessorType(XmlAccessType.NONE)
+public class OverdueConfig extends ValidatingConfig<OverdueConfig> {
+
+ @XmlElement(required=true, name="bundleOverdueStates")
+ private OverdueStatesBundle bundleOverdueStates;
+
+ public DefaultOverdueStateSet<SubscriptionBundle> getBundleStateSet() {
+ return bundleOverdueStates;
+ }
+
+ @Override
+ public ValidationErrors validate(OverdueConfig root,
+ ValidationErrors errors) {
+ return bundleOverdueStates.validate(root, errors);
+ }
+
+ public OverdueConfig setOverdueStatesBundle(OverdueStatesBundle bundleODS) {
+ this.bundleOverdueStates = bundleODS;
+ return this;
+ }
+
+
+ public URI getURI() {
+ return null;
+ }
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/OverdueStatesBundle.java b/overdue/src/main/java/com/ning/billing/overdue/config/OverdueStatesBundle.java
new file mode 100644
index 0000000..3f07db5
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/OverdueStatesBundle.java
@@ -0,0 +1,44 @@
+/*
+ * 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.overdue.config;
+
+import javax.xml.bind.annotation.XmlElement;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.TimeUnit;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.OverdueState;
+
+public class OverdueStatesBundle extends DefaultOverdueStateSet<SubscriptionBundle>{
+
+ @XmlElement(required=true, name="state")
+ private DefaultOverdueState<SubscriptionBundle>[] bundleOverdueStates;
+
+ @Override
+ protected DefaultOverdueState<SubscriptionBundle>[] getStates() {
+ return bundleOverdueStates;
+ }
+
+ protected OverdueStatesBundle setBundleOverdueStates(DefaultOverdueState<SubscriptionBundle>[] bundleOverdueStates) {
+ this.bundleOverdueStates = bundleOverdueStates;
+ return this;
+ }
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/exceptions/OverdueError.java b/overdue/src/main/java/com/ning/billing/overdue/exceptions/OverdueError.java
new file mode 100644
index 0000000..3959408
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/exceptions/OverdueError.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.overdue.exceptions;
+
+public class OverdueError extends Error {
+
+ private static final long serialVersionUID = 131398536;
+
+ public OverdueError() {
+ super();
+ }
+
+ public OverdueError(String msg, Throwable arg1) {
+ super(msg, arg1);
+ }
+
+ public OverdueError(String msg) {
+ super(msg);
+ }
+
+ public OverdueError(Throwable msg) {
+ super(msg);
+ }
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/glue/OverdueModule.java b/overdue/src/main/java/com/ning/billing/overdue/glue/OverdueModule.java
new file mode 100644
index 0000000..da54c7b
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/glue/OverdueModule.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.overdue.glue;
+
+import org.skife.config.ConfigurationObjectFactory;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.overdue.OverdueProperties;
+
+
+public class OverdueModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ final OverdueProperties config = new ConfigurationObjectFactory(System.getProperties()).build(OverdueProperties.class);
+ bind(OverdueProperties.class).toInstance(config);
+ }
+
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/listener/OverdueDispatcher.java b/overdue/src/main/java/com/ning/billing/overdue/listener/OverdueDispatcher.java
new file mode 100644
index 0000000..8f2d03f
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/listener/OverdueDispatcher.java
@@ -0,0 +1,87 @@
+/*
+ * 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.overdue.listener;
+
+import java.util.UUID;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.config.api.OverdueError;
+import com.ning.billing.overdue.wrapper.OverdueWrapper;
+import com.ning.billing.overdue.wrapper.OverdueWrapperFactory;
+
+public class OverdueDispatcher {
+ Logger log = LoggerFactory.getLogger(OverdueDispatcher.class);
+
+ private final EntitlementUserApi entitlementUserApi;
+ private final AccountUserApi accountUserApi;
+ private final OverdueWrapperFactory factory;
+
+ @Inject
+ public OverdueDispatcher(AccountUserApi accountUserApi,
+ EntitlementUserApi entitlementUserApi,
+ OverdueWrapperFactory factory) {
+ this.accountUserApi = accountUserApi;
+ this.entitlementUserApi = entitlementUserApi;
+ this.factory = factory;
+ }
+
+ public void processOverdueForAccount(UUID accountId) {
+ try {
+ Account account = accountUserApi.getAccountById(accountId);
+ processOverdue(account);
+ } catch (AccountApiException e) {
+ log.error("Error processing Overdue for Account with id: " + accountId.toString(), e);
+ }
+ }
+
+ public void processOverdueForBundle(UUID bundleId) {
+ try {
+ SubscriptionBundle bundle = entitlementUserApi.getBundleFromId(bundleId);
+ processOverdue(bundle);
+ } catch (EntitlementUserApiException e) {
+ log.error("Error processing Overdue for Bundle with id: " + bundleId.toString(), e);
+ }
+ }
+
+ public void processOverdue(Blockable bloackable) {
+ try {
+ OverdueWrapper<?> wrapper = factory.createOverdueWrapperFor(bloackable);
+ wrapper.refresh();
+ } catch (OverdueError e) {
+ log.error("Error processing Overdue for Blockable with id: " + bloackable.getId().toString(), e);
+ } catch (OverdueApiException e) {
+ log.error("Error processing Overdue for Blockable with id: " + bloackable.getId().toString(), e);
+ }
+ }
+
+ public void processOverdue(UUID blockableId) {
+
+
+ }
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/listener/OverdueListener.java b/overdue/src/main/java/com/ning/billing/overdue/listener/OverdueListener.java
new file mode 100644
index 0000000..2c193b4
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/listener/OverdueListener.java
@@ -0,0 +1,66 @@
+/*
+ * 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.overdue.listener;
+
+import java.util.UUID;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Inject;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
+
+public class OverdueListener {
+ OverdueDispatcher dispatcher;
+
+ //
+ //TODO disabled overdue for prod deployment - comments should be removed
+ //
+
+ private final static Logger log = LoggerFactory.getLogger(OverdueListener.class);
+ private final PaymentApi paymentApi;
+
+ @Inject
+ public OverdueListener(OverdueDispatcher dispatcher, PaymentApi paymentApi) {
+ this.dispatcher = dispatcher;
+ this.paymentApi = paymentApi;
+ }
+
+ @Subscribe
+ public void handlePaymentInfoEvent(final PaymentInfoEvent event) {
+// String paymentId = event.getPaymentId();
+// PaymentAttempt attempt = paymentApi.getPaymentAttemptForPaymentId(paymentId);
+// UUID accountId = attempt.getAccountId();
+// dispatcher.processOverdueForAccount(accountId);
+ }
+
+ @Subscribe
+ public void handlePaymentErrorEvent(final PaymentErrorEvent event) {
+// UUID accountId = event.getAccountId();
+// dispatcher.processOverdueForAccount(accountId);
+ }
+
+ public void handleNextOverdueCheck(UUID overdueableId) {
+// dispatcher.processOverdue(overdueableId);
+ }
+
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/OverdueProperties.java b/overdue/src/main/java/com/ning/billing/overdue/OverdueProperties.java
new file mode 100644
index 0000000..b4f4d0e
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/OverdueProperties.java
@@ -0,0 +1,45 @@
+/*
+ * 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.overdue;
+
+import org.skife.config.Config;
+import org.skife.config.Default;
+
+import com.ning.billing.config.KillbillConfig;
+import com.ning.billing.config.NotificationConfig;
+
+
+public interface OverdueProperties extends NotificationConfig, KillbillConfig {
+
+ @Override
+ @Config("killbill.overdue.engine.notifications.sleep")
+ @Default("500")
+ public long getSleepTimeMs();
+
+ @Override
+ @Config("killbill.notifications.off")
+ @Default("false")
+ public boolean isNotificationProcessingOff();
+
+ @Config("killbill.overdue.maxNumberOfMonthsInFuture")
+ @Default("36")
+ public int getNumberOfMonthsInFuture();
+
+ @Config("killbill.overdue.configUri")
+ @Default("jar:///com/ning/billing/irs/catalog/Catalog.xml")
+ public String getConfigURI();
+}
\ No newline at end of file
diff --git a/overdue/src/main/java/com/ning/billing/overdue/service/DefaultOverdueService.java b/overdue/src/main/java/com/ning/billing/overdue/service/DefaultOverdueService.java
new file mode 100644
index 0000000..d810a84
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/service/DefaultOverdueService.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.overdue.service;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import com.google.inject.Inject;
+import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import com.ning.billing.overdue.OverdueProperties;
+import com.ning.billing.overdue.OverdueUserApi;
+import com.ning.billing.overdue.config.OverdueConfig;
+import com.ning.billing.util.config.XMLLoader;
+
+public class DefaultOverdueService implements ExtendedOverdueService {
+ public static final String OVERDUE_SERVICE_NAME = "overdue-service";
+ private OverdueUserApi userApi;
+ private OverdueConfig overdueConfig;
+ private OverdueProperties properties;
+
+ private boolean isInitialized;
+
+ @Inject
+ public DefaultOverdueService(OverdueUserApi userApi, OverdueProperties properties){
+ this.userApi = userApi;
+ this.properties = properties;
+ }
+
+ @Override
+ public String getName() {
+ return OVERDUE_SERVICE_NAME;
+ }
+
+ @Override
+ public OverdueUserApi getUserApi() {
+ return userApi;
+ }
+
+ @Override
+ public OverdueConfig getOverdueConfig() {
+ return overdueConfig;
+ }
+
+ @LifecycleHandlerType(LifecycleLevel.INIT_SERVICE)
+ public synchronized void loadConfig() throws ServiceException {
+ if (!isInitialized) {
+ try {
+ System.out.println("Overdue config URI" + properties.getConfigURI());
+ URI u = new URI(properties.getConfigURI());
+ overdueConfig = XMLLoader.getObjectFromUri(u, OverdueConfig.class);
+
+ isInitialized = true;
+ } catch (URISyntaxException e) {
+ // overdueConfig = new OverdueConfig();
+ } catch (Exception e) {
+ throw new ServiceException(e);
+ }
+ }
+ }
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/service/ExtendedOverdueService.java b/overdue/src/main/java/com/ning/billing/overdue/service/ExtendedOverdueService.java
new file mode 100644
index 0000000..abcb38a
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/service/ExtendedOverdueService.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.overdue.service;
+
+import com.ning.billing.overdue.OverdueService;
+import com.ning.billing.overdue.config.OverdueConfig;
+
+public interface ExtendedOverdueService extends OverdueService {
+
+ public OverdueConfig getOverdueConfig();
+
+}
diff --git a/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapper.java b/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapper.java
new file mode 100644
index 0000000..5b811ee
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapper.java
@@ -0,0 +1,72 @@
+/*
+ * 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.overdue.wrapper;
+
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.overdue.OverdueApiException;
+import com.ning.billing.overdue.OverdueState;
+import com.ning.billing.overdue.applicator.OverdueStateApplicator;
+import com.ning.billing.overdue.calculator.BillingStateCalculator;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.overdue.config.api.OverdueError;
+import com.ning.billing.overdue.config.api.OverdueStateSet;
+import com.ning.billing.util.clock.Clock;
+
+public class OverdueWrapper<T extends Blockable> {
+ private final T overdueable;
+ private final BlockingApi api;
+ private final Clock clock;
+ private final OverdueStateSet<T> overdueStateSet;
+ private final BillingStateCalculator<T> billingStateCalcuator;
+ private final OverdueStateApplicator<T> overdueStateApplicator;
+
+ public OverdueWrapper(T overdueable, BlockingApi api,
+ OverdueStateSet<T> overdueStateSet,
+ Clock clock,
+ BillingStateCalculator<T> billingStateCalcuator,
+ OverdueStateApplicator<T> overdueStateApplicator) {
+ this.overdueable = overdueable;
+ this.overdueStateSet = overdueStateSet;
+ this.api = api;
+ this.clock = clock;
+ this.billingStateCalcuator = billingStateCalcuator;
+ this.overdueStateApplicator = overdueStateApplicator;
+ }
+
+ public OverdueState<T> refresh() throws OverdueError, OverdueApiException {
+ try {
+ if(overdueStateSet == null) { // No configuration available
+ return null;
+ }
+
+ OverdueState<T> nextOverdueState;
+ BillingState<T> billingState = billingStateCalcuator.calculateBillingState(overdueable);
+ String previousOverdueStateName = api.getBlockingStateFor(overdueable).getStateName();
+ nextOverdueState = overdueStateSet.calculateOverdueState(billingState, clock.getUTCNow());
+
+ if(!previousOverdueStateName.equals(nextOverdueState.getName())) {
+ overdueStateApplicator.apply(overdueable, nextOverdueState, nextOverdueState);
+ }
+
+ return nextOverdueState;
+ } catch (EntitlementUserApiException e) {
+ throw new OverdueError(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapperFactory.java b/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapperFactory.java
new file mode 100644
index 0000000..0fd6854
--- /dev/null
+++ b/overdue/src/main/java/com/ning/billing/overdue/wrapper/OverdueWrapperFactory.java
@@ -0,0 +1,94 @@
+/*
+ * 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.overdue.wrapper;
+
+import java.util.UUID;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.overdue.applicator.OverdueStateApplicator;
+import com.ning.billing.overdue.calculator.BillingStateCalculatorBundle;
+import com.ning.billing.overdue.config.OverdueConfig;
+import com.ning.billing.overdue.config.api.OverdueError;
+import com.ning.billing.overdue.service.ExtendedOverdueService;
+import com.ning.billing.util.clock.Clock;
+
+public class OverdueWrapperFactory {
+ private static final Logger log = LoggerFactory.getLogger(OverdueWrapperFactory.class);
+
+ private final OverdueConfig overdueConfig;
+ private final EntitlementUserApi entitlementApi;
+ private final BillingStateCalculatorBundle billingStateCalcuatorBundle;
+ private final OverdueStateApplicator<SubscriptionBundle> overdueStateApplicatorBundle;
+ private final BlockingApi api;
+ private final Clock clock;
+
+ @Inject
+ public OverdueWrapperFactory(BlockingApi api, ExtendedOverdueService service, Clock clock,
+ BillingStateCalculatorBundle billingStateCalcuatorBundle,
+ OverdueStateApplicator<SubscriptionBundle> overdueStateApplicatorBundle,
+ EntitlementUserApi entitlementApi) {
+ this.billingStateCalcuatorBundle = billingStateCalcuatorBundle;
+ this.overdueStateApplicatorBundle = overdueStateApplicatorBundle;
+ this.entitlementApi = entitlementApi;
+ this.overdueConfig = service.getOverdueConfig();
+ this.api = api;
+ this.clock = clock;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T extends Blockable> OverdueWrapper<T> createOverdueWrapperFor(T bloackable) throws OverdueError {
+ if(bloackable instanceof SubscriptionBundle) {
+ return (OverdueWrapper<T>)new OverdueWrapper<SubscriptionBundle>((SubscriptionBundle)bloackable, api, overdueConfig.getBundleStateSet(),
+ clock, billingStateCalcuatorBundle, overdueStateApplicatorBundle );
+ } else {
+ throw new OverdueError(ErrorCode.OVERDUE_TYPE_NOT_SUPPORTED, bloackable.getId(), bloackable.getClass());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T extends Blockable> OverdueWrapper<T> createOverdueWrapperFor(UUID id) throws OverdueError {
+ BlockingState state = api.getBlockingStateFor(id);
+
+ try {
+ switch (state.getType()) {
+ case SUBSCRIPTION_BUNDLE : {
+ SubscriptionBundle bundle = entitlementApi.getBundleFromId(id);
+ return (OverdueWrapper<T>)new OverdueWrapper<SubscriptionBundle>(bundle, api, overdueConfig.getBundleStateSet(),
+ clock, billingStateCalcuatorBundle, overdueStateApplicatorBundle );
+ }
+ default : {
+ throw new OverdueError(ErrorCode.OVERDUE_TYPE_NOT_SUPPORTED, id, state.getType());
+ }
+
+ }
+ } catch (EntitlementUserApiException e) {
+ throw new OverdueError(e);
+ }
+ }
+
+
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/calculator/TestBillingStateCalculator.java b/overdue/src/test/java/com/ning/billing/overdue/calculator/TestBillingStateCalculator.java
new file mode 100644
index 0000000..db9ab74
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/calculator/TestBillingStateCalculator.java
@@ -0,0 +1,102 @@
+/*
+ * 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.overdue.calculator;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+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.Test;
+
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+
+public class TestBillingStateCalculator {
+ Clock clock = new ClockMock();
+ InvoiceUserApi invoiceApi = BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceUserApi.class);
+ private int hash = 0;
+ DateTime now;
+
+ public BillingStateCalculator<SubscriptionBundle> createBSCalc() {
+ now = new DateTime();
+ Collection<Invoice> invoices = new ArrayList<Invoice>();
+ invoices.add(createInvoice(now, BigDecimal.ZERO, null));
+ invoices.add(createInvoice(now.plusDays(1), BigDecimal.TEN, null));
+ invoices.add(createInvoice(now.plusDays(2), new BigDecimal("100.0"), null));
+
+ ((ZombieControl)invoiceApi).addResult("getUnpaidInvoicesByAccountId", invoices);
+
+ return new BillingStateCalculator<SubscriptionBundle>(invoiceApi, clock) {
+ @Override
+ public BillingState<SubscriptionBundle> calculateBillingState(
+ SubscriptionBundle overdueable) {
+ return null;
+ }};
+ }
+
+ public Invoice createInvoice(DateTime date, BigDecimal balance, List<InvoiceItem> invoiceItems) {
+ Invoice invoice = BrainDeadProxyFactory.createBrainDeadProxyFor(Invoice.class);
+ ((ZombieControl)invoice).addResult("getBalance", balance);
+ ((ZombieControl)invoice).addResult("getInvoiceDate", date);
+ ((ZombieControl)invoice).addResult("hashCode", hash++);
+ ((ZombieControl)invoice).addResult("getInvoiceItems", invoiceItems);
+
+ return invoice;
+ }
+
+ @Test(groups={"fast"}, enabled=true)
+ public void testUnpaidInvoices() {
+ BillingStateCalculator<SubscriptionBundle> calc = createBSCalc();
+ SortedSet<Invoice> invoices = calc.unpaidInvoicesForAccount(new UUID(0L,0L));
+
+ Assert.assertEquals(invoices.size(), 3);
+ Assert.assertEquals(BigDecimal.ZERO.compareTo(invoices.first().getBalance()), 0);
+ Assert.assertEquals(new BigDecimal("100.0").compareTo(invoices.last().getBalance()), 0);
+ }
+
+ @Test(groups={"fast"}, enabled=true)
+ public void testSum() {
+
+ BillingStateCalculator<SubscriptionBundle> calc = createBSCalc();
+ SortedSet<Invoice> invoices = calc.unpaidInvoicesForAccount(new UUID(0L,0L));
+ Assert.assertEquals(new BigDecimal("110.0").compareTo(calc.sumBalance(invoices)), 0);
+ }
+
+ @Test(groups={"fast"}, enabled=true)
+ public void testEarliest() {
+
+ BillingStateCalculator<SubscriptionBundle> calc = createBSCalc();
+ SortedSet<Invoice> invoices = calc.unpaidInvoicesForAccount(new UUID(0L,0L));
+ Assert.assertEquals(calc.earliest(invoices), now);
+ }
+
+
+
+
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/calculator/TestBillingStateCalculatorBundle.java b/overdue/src/test/java/com/ning/billing/overdue/calculator/TestBillingStateCalculatorBundle.java
new file mode 100644
index 0000000..bb7ece3
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/calculator/TestBillingStateCalculatorBundle.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.overdue.calculator;
+
+import java.math.BigDecimal;
+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.Test;
+
+import com.ning.billing.catalog.MockPlan;
+import com.ning.billing.catalog.MockPriceList;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PriceList;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.overdue.config.api.BillingStateBundle;
+import com.ning.billing.overdue.config.api.PaymentResponse;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+
+public class TestBillingStateCalculatorBundle extends TestBillingStateCalculator {
+
+
+ private List<InvoiceItem> createInvoiceItems(UUID[] bundleIds) {
+ List<InvoiceItem> result = new ArrayList<InvoiceItem> ();
+ for (UUID id : bundleIds) {
+ InvoiceItem ii = BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceItem.class);
+ ((ZombieControl)ii).addResult("getBundleId", id);
+ result.add(ii);
+ }
+ return result;
+ }
+
+ @Test(groups = {"fast"}, enabled=true)
+ public void testUnpaidInvoiceForBundle() {
+ UUID thisBundleId = new UUID(0L,0L);
+ UUID thatBundleId = new UUID(0L,1L);
+
+ now = new DateTime();
+ List<Invoice> invoices = new ArrayList<Invoice>(5);
+ invoices.add(createInvoice(now, BigDecimal.ZERO, createInvoiceItems(new UUID[]{thisBundleId,thatBundleId})));
+ invoices.add(createInvoice(now, BigDecimal.TEN, createInvoiceItems(new UUID[]{thatBundleId})));
+ invoices.add(createInvoice(now, new BigDecimal("100.00"), createInvoiceItems(new UUID[]{thatBundleId,thisBundleId,thatBundleId})));
+ invoices.add(createInvoice(now, new BigDecimal("1000.00"), createInvoiceItems(new UUID[]{thisBundleId})));
+ invoices.add(createInvoice(now, new BigDecimal("10000.00"), createInvoiceItems(new UUID[]{thatBundleId, thisBundleId})));
+
+
+ Clock clock = new ClockMock();
+ InvoiceUserApi invoiceApi = BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceUserApi.class);
+ EntitlementUserApi entitlementApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class);
+ ((ZombieControl)invoiceApi).addResult("getUnpaidInvoicesByAccountId", invoices);
+
+
+ BillingStateCalculatorBundle calc = new BillingStateCalculatorBundle(entitlementApi, invoiceApi, clock);
+ SortedSet<Invoice> resultinvoices = calc.unpaidInvoicesForBundle(thisBundleId, new UUID(0L,0L));
+
+ Assert.assertEquals(resultinvoices.size(), 4);
+ Assert.assertEquals(BigDecimal.ZERO.compareTo(resultinvoices.first().getBalance()), 0);
+ Assert.assertEquals(new BigDecimal("10000.0").compareTo(resultinvoices.last().getBalance()), 0);
+
+ }
+
+ @Test(groups = {"fast"}, enabled=true)
+ public void testcalculateBillingStateForBundle() throws Exception {
+
+ UUID thisBundleId = new UUID(0L,0L);
+ UUID thatBundleId = new UUID(0L,1L);
+
+ now = new DateTime();
+ List<Invoice> invoices = new ArrayList<Invoice>(5);
+ invoices.add(createInvoice(now.minusDays(5), BigDecimal.ZERO, createInvoiceItems(new UUID[]{thisBundleId,thatBundleId})));
+ invoices.add(createInvoice(now.minusDays(4), BigDecimal.TEN, createInvoiceItems(new UUID[]{thatBundleId})));
+ invoices.add(createInvoice(now.minusDays(3), new BigDecimal("100.00"), createInvoiceItems(new UUID[]{thatBundleId,thisBundleId,thatBundleId})));
+ invoices.add(createInvoice(now.minusDays(2), new BigDecimal("1000.00"), createInvoiceItems(new UUID[]{thisBundleId})));
+ invoices.add(createInvoice(now.minusDays(1), new BigDecimal("10000.00"), createInvoiceItems(new UUID[]{thatBundleId, thisBundleId})));
+
+
+ Clock clock = new ClockMock();
+ InvoiceUserApi invoiceApi = BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceUserApi.class);
+ ((ZombieControl)invoiceApi).addResult("getUnpaidInvoicesByAccountId", invoices);
+
+ SubscriptionBundle bundle = BrainDeadProxyFactory.createBrainDeadProxyFor(SubscriptionBundle.class);
+ ((ZombieControl)bundle).addResult("getId", thisBundleId);
+ ((ZombieControl)bundle).addResult("getAccountId", UUID.randomUUID());
+
+ EntitlementUserApi entitlementApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class);
+ Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+ ((ZombieControl)entitlementApi).addResult("getBaseSubscription",subscription);
+
+ Plan plan = MockPlan.createBicycleNoTrialEvergreen1USD();
+ PriceList pricelist = new MockPriceList();
+ ((ZombieControl)subscription).addResult("getCurrentPlan", plan);
+ ((ZombieControl)subscription).addResult("getCurrentPriceList", pricelist);
+ ((ZombieControl)subscription).addResult("getCurrentPhase", plan.getFinalPhase());
+
+ BillingStateCalculatorBundle calc = new BillingStateCalculatorBundle(entitlementApi, invoiceApi, clock);
+
+ BillingStateBundle state = calc.calculateBillingState(bundle);
+
+ Assert.assertEquals(state.getNumberOfUnpaidInvoices(),4);
+ Assert.assertEquals(state.getBalanceOfUnpaidInvoices().intValue(), 11100);
+ Assert.assertEquals(state.getDateOfEarliestUnpaidInvoice().compareTo(now.minusDays(5)), 0);
+ Assert.assertEquals(state.getResponseForLastFailedPayment(),PaymentResponse.INSUFFICIENT_FUNDS); //TODO needs more when implemented
+ Assert.assertEquals(state.getTags().length,0);//TODO needs more when implemented
+ Assert.assertEquals(state.getBasePlanBillingPeriod(), plan.getBillingPeriod());
+ Assert.assertEquals(state.getBasePlanPhaseType(), plan.getFinalPhase().getPhaseType());
+ Assert.assertEquals(state.getBasePlanPriceList(), pricelist);
+ Assert.assertEquals(state.getBasePlanProduct(), plan.getProduct());
+
+ }
+
+
+
+
+
+
+
+
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/config/CreateOverdueConfigSchema.java b/overdue/src/test/java/com/ning/billing/overdue/config/CreateOverdueConfigSchema.java
new file mode 100644
index 0000000..bbd4b18
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/config/CreateOverdueConfigSchema.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.overdue.config;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.Writer;
+
+import com.ning.billing.util.config.XMLSchemaGenerator;
+
+public class CreateOverdueConfigSchema {
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) throws Exception {
+ if(args.length != 1) {
+ System.err.println("Usage: <filepath>");
+ System.exit(0);
+ }
+
+ File f = new File(args[0]);
+ Writer w = new FileWriter(f);
+ w.write(XMLSchemaGenerator.xmlSchemaAsString(OverdueConfig.class));
+ w.close();
+
+ }
+
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/config/io/TestReadConfig.java b/overdue/src/test/java/com/ning/billing/overdue/config/io/TestReadConfig.java
new file mode 100644
index 0000000..8208a73
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/config/io/TestReadConfig.java
@@ -0,0 +1,31 @@
+/*
+ * 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.overdue.config.io;
+
+import org.testng.annotations.Test;
+
+import com.google.common.io.Resources;
+import com.ning.billing.overdue.config.OverdueConfig;
+import com.ning.billing.util.config.XMLLoader;
+
+public class TestReadConfig {
+ @Test(enabled=true)
+ public void testConfigLoad() throws Exception {
+ XMLLoader.getObjectFromString(Resources.getResource("OverdueConfig.xml").toExternalForm(), OverdueConfig.class);
+ }
+
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueRules.java b/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueRules.java
new file mode 100644
index 0000000..514c85f
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueRules.java
@@ -0,0 +1,33 @@
+/*
+ * 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.overdue.config;
+
+
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+
+
+public class MockOverdueRules extends OverdueConfig {
+ public static final String CLEAR_STATE="Clear";
+
+ @SuppressWarnings("unchecked")
+ public MockOverdueRules() {
+ OverdueStatesBundle bundleODS = new OverdueStatesBundle();
+ bundleODS.setBundleOverdueStates(new DefaultOverdueState[] { new DefaultOverdueState<SubscriptionBundle>().setName(CLEAR_STATE) });
+ setOverdueStatesBundle(bundleODS);
+
+ }
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueState.java b/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueState.java
new file mode 100644
index 0000000..d1b9ab1
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueState.java
@@ -0,0 +1,32 @@
+/*
+ * 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.overdue.config;
+
+import com.ning.billing.junction.api.Blockable;
+
+public class MockOverdueState<T extends Blockable> extends DefaultOverdueState<T> {
+
+ public MockOverdueState() {
+ setName(MockOverdueRules.CLEAR_STATE);
+ }
+
+ public MockOverdueState(String name, boolean blockChanges, boolean disableEntitlementAndBlockChanges) {
+ setName(name);
+ setBlockChanges(blockChanges);
+ setDisableEntitlement(disableEntitlementAndBlockChanges);
+ }
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueStatesBundle.java b/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueStatesBundle.java
new file mode 100644
index 0000000..f1020b2
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/config/MockOverdueStatesBundle.java
@@ -0,0 +1,31 @@
+/*
+ * 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.overdue.config;
+
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+
+public class MockOverdueStatesBundle extends OverdueStatesBundle {
+
+ public MockOverdueStatesBundle() {
+
+ }
+
+ public MockOverdueStatesBundle(DefaultOverdueState<SubscriptionBundle>[] states) {
+ setBundleOverdueStates(states);
+ }
+
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/config/TestCondition.java b/overdue/src/test/java/com/ning/billing/overdue/config/TestCondition.java
new file mode 100644
index 0000000..1e5f1e0
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/config/TestCondition.java
@@ -0,0 +1,147 @@
+/*
+00 * 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.overdue.config;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.overdue.config.api.BillingState;
+import com.ning.billing.overdue.config.api.PaymentResponse;
+import com.ning.billing.util.config.XMLLoader;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.DefaultControlTag;
+import com.ning.billing.util.tag.DescriptiveTag;
+import com.ning.billing.util.tag.Tag;
+
+public class TestCondition {
+
+ @XmlRootElement(name="condition")
+ private static class MockCondition extends DefaultCondition<Blockable> {}
+
+ @Test(groups={"fast"}, enabled=true)
+ public void testNumberOfUnpaidInvoicesEqualsOrExceeds() throws Exception {
+ String xml =
+ "<condition>" +
+ " <numberOfUnpaidInvoicesEqualsOrExceeds>1</numberOfUnpaidInvoicesEqualsOrExceeds>" +
+ "</condition>";
+ InputStream is = new ByteArrayInputStream(xml.getBytes());
+ MockCondition c = XMLLoader.getObjectFromStreamNoValidation(is, MockCondition.class);
+
+ BillingState<Blockable> state0 = new BillingState<Blockable>(new UUID(0L,1L), 0, BigDecimal.ZERO, new DateTime(), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+ BillingState<Blockable> state1 = new BillingState<Blockable>(new UUID(0L,1L), 1, BigDecimal.ZERO, new DateTime(), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+ BillingState<Blockable> state2 = new BillingState<Blockable>(new UUID(0L,1L), 2, BigDecimal.ZERO, new DateTime(), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+
+ Assert.assertTrue(!c.evaluate(state0, new DateTime()));
+ Assert.assertTrue(c.evaluate(state1, new DateTime()));
+ Assert.assertTrue(c.evaluate(state2, new DateTime()));
+ }
+
+ @Test(groups={"fast"}, enabled=true)
+ public void testTotalUnpaidInvoiceBalanceEqualsOrExceeds() throws Exception {
+ String xml =
+ "<condition>" +
+ " <totalUnpaidInvoiceBalanceEqualsOrExceeds>100.00</totalUnpaidInvoiceBalanceEqualsOrExceeds>" +
+ "</condition>";
+ InputStream is = new ByteArrayInputStream(xml.getBytes());
+ MockCondition c = XMLLoader.getObjectFromStreamNoValidation(is, MockCondition.class);
+
+ BillingState<Blockable> state0 = new BillingState<Blockable>(new UUID(0L,1L), 0, BigDecimal.ZERO, new DateTime(), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+ BillingState<Blockable> state1 = new BillingState<Blockable>(new UUID(0L,1L), 1, new BigDecimal("100.00"), new DateTime(), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+ BillingState<Blockable> state2 = new BillingState<Blockable>(new UUID(0L,1L), 1, new BigDecimal("200.00"), new DateTime(), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+
+ Assert.assertTrue(!c.evaluate(state0, new DateTime()));
+ Assert.assertTrue(c.evaluate(state1, new DateTime()));
+ Assert.assertTrue(c.evaluate(state2, new DateTime()));
+ }
+
+
+ @Test(groups={"fast"}, enabled=true)
+ public void testTimeSinceEarliestUnpaidInvoiceEqualsOrExceeds() throws Exception {
+ String xml =
+ "<condition>" +
+ " <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds><unit>DAYS</unit><number>10</number></timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+ "</condition>";
+ InputStream is = new ByteArrayInputStream(xml.getBytes());
+ MockCondition c = XMLLoader.getObjectFromStreamNoValidation(is, MockCondition.class);
+
+ DateTime now = new DateTime();
+
+ BillingState<Blockable> state0 = new BillingState<Blockable>(new UUID(0L,1L), 0, BigDecimal.ZERO, now, PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+ BillingState<Blockable> state1 = new BillingState<Blockable>(new UUID(0L,1L), 1, new BigDecimal("100.00"), now.minusDays(10), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+ BillingState<Blockable> state2 = new BillingState<Blockable>(new UUID(0L,1L), 1, new BigDecimal("200.00"), now.minusDays(20), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+
+ Assert.assertTrue(!c.evaluate(state0, now));
+ Assert.assertTrue(c.evaluate(state1, now));
+ Assert.assertTrue(c.evaluate(state2, now));
+ }
+
+ @Test(groups={"fast"}, enabled=true)
+ public void testResponseForLastFailedPaymentIn() throws Exception {
+ String xml =
+ "<condition>" +
+ " <responseForLastFailedPaymentIn><response>INSUFFICIENT_FUNDS</response><response>DO_NOT_HONOR</response></responseForLastFailedPaymentIn>" +
+ "</condition>";
+ InputStream is = new ByteArrayInputStream(xml.getBytes());
+ MockCondition c = XMLLoader.getObjectFromStreamNoValidation(is, MockCondition.class);
+
+ DateTime now = new DateTime();
+
+ BillingState<Blockable> state0 = new BillingState<Blockable>(new UUID(0L,1L), 0, BigDecimal.ZERO, now, PaymentResponse.LOST_OR_STOLEN_CARD, new Tag[]{});
+ BillingState<Blockable> state1 = new BillingState<Blockable>(new UUID(0L,1L), 1, new BigDecimal("100.00"), now.minusDays(10), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{});
+ BillingState<Blockable> state2 = new BillingState<Blockable>(new UUID(0L,1L), 1, new BigDecimal("200.00"), now.minusDays(20), PaymentResponse.DO_NOT_HONOR , new Tag[]{});
+
+ Assert.assertTrue(!c.evaluate(state0, now));
+ Assert.assertTrue(c.evaluate(state1, now));
+ Assert.assertTrue(c.evaluate(state2, now));
+ }
+
+ @Test(groups={"fast"}, enabled=true)
+ public void testHasControlTag() throws Exception {
+ String xml =
+ "<condition>" +
+ " <controlTag>OVERDUE_ENFORCEMENT_OFF</controlTag>" +
+ "</condition>";
+ InputStream is = new ByteArrayInputStream(xml.getBytes());
+ MockCondition c = XMLLoader.getObjectFromStreamNoValidation(is, MockCondition.class);
+
+ DateTime now = new DateTime();
+
+ BillingState<Blockable> state0 = new BillingState<Blockable>(new UUID(0L,1L), 0, BigDecimal.ZERO, now, PaymentResponse.LOST_OR_STOLEN_CARD, new Tag[]{new DefaultControlTag(ControlTagType.AUTO_INVOICING_OFF),new DescriptiveTag("Tag")});
+ BillingState<Blockable> state1 = new BillingState<Blockable>(new UUID(0L,1L), 1, new BigDecimal("100.00"), now.minusDays(10), PaymentResponse.INSUFFICIENT_FUNDS, new Tag[]{new DefaultControlTag(ControlTagType.OVERDUE_ENFORCEMENT_OFF)});
+ BillingState<Blockable> state2 = new BillingState<Blockable>(new UUID(0L,1L), 1, new BigDecimal("200.00"), now.minusDays(20),
+ PaymentResponse.DO_NOT_HONOR,
+ new Tag[]{new DefaultControlTag(ControlTagType.OVERDUE_ENFORCEMENT_OFF),
+ new DefaultControlTag(ControlTagType.AUTO_INVOICING_OFF),
+ new DescriptiveTag("Tag")});
+
+ Assert.assertTrue(!c.evaluate(state0, now));
+ Assert.assertTrue(c.evaluate(state1, now));
+ Assert.assertTrue(c.evaluate(state2, now));
+ }
+
+
+
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/config/TestOverdueConfig.java b/overdue/src/test/java/com/ning/billing/overdue/config/TestOverdueConfig.java
new file mode 100644
index 0000000..df4f4c3
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/config/TestOverdueConfig.java
@@ -0,0 +1,60 @@
+/*
+ * 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.overdue.config;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+import org.testng.annotations.Test;
+
+import com.ning.billing.util.config.XMLLoader;
+
+public class TestOverdueConfig {
+ private String xml =
+ "<overdueConfig>" +
+ " <bundleOverdueStates>" +
+ " <state name=\"OD1\">" +
+ " <condition>" +
+ " <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+ " <unit>MONTHS</unit><number>1</number>" +
+ " </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+ " </condition>" +
+ " <externalMessage>Reached OD1</externalMessage>" +
+ " <blockChanges>true</blockChanges>" +
+ " <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" +
+ " </state>" +
+ " <state name=\"OD2\">" +
+ " <condition>" +
+ " <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+ " <unit>MONTHS</unit><number>2</number>" +
+ " </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+ " </condition>" +
+ " <externalMessage>Reached OD1</externalMessage>" +
+ " <blockChanges>true</blockChanges>" +
+ " <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>" +
+ " </state>" +
+ " </bundleOverdueStates>" +
+ "</overdueConfig>";
+
+ @Test
+ public void testParseConfig() throws Exception {
+ InputStream is = new ByteArrayInputStream(xml.getBytes());
+ OverdueConfig c = XMLLoader.getObjectFromStreamNoValidation(is, OverdueConfig.class);
+
+ }
+
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/notification/MockOverdueCheckNotifier.java b/overdue/src/test/java/com/ning/billing/overdue/notification/MockOverdueCheckNotifier.java
new file mode 100644
index 0000000..a427ddb
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/notification/MockOverdueCheckNotifier.java
@@ -0,0 +1,36 @@
+/*
+ * 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.overdue.notification;
+
+import com.ning.billing.ovedue.notification.OverdueCheckNotifier;
+
+public class MockOverdueCheckNotifier implements OverdueCheckNotifier {
+ @Override
+ public void initialize() {
+ // do nothing
+ }
+
+ @Override
+ public void start() {
+ // do nothing
+ }
+
+ @Override
+ public void stop() {
+ // do nothing
+ }
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/notification/MockOverdueCheckPoster.java b/overdue/src/test/java/com/ning/billing/overdue/notification/MockOverdueCheckPoster.java
new file mode 100644
index 0000000..c1cfb65
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/notification/MockOverdueCheckPoster.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.overdue.notification;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.ovedue.notification.OverdueCheckPoster;
+
+public class MockOverdueCheckPoster implements OverdueCheckPoster {
+
+ @Override
+ public void insertOverdueCheckNotification(Blockable overdueable,
+ DateTime futureNotificationTime) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void clearNotificationsFor(Blockable blockable) {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/overdue/src/test/java/com/ning/billing/overdue/notification/TestOverdueCheckNotifier.java b/overdue/src/test/java/com/ning/billing/overdue/notification/TestOverdueCheckNotifier.java
new file mode 100644
index 0000000..7f48dda
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/notification/TestOverdueCheckNotifier.java
@@ -0,0 +1,205 @@
+/*
+ * 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.overdue.notification;
+
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+import org.apache.commons.io.IOUtils;
+import org.joda.time.DateTime;
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.catalog.DefaultCatalogService;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.config.CatalogConfig;
+import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.billing.ChargeThruApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.lifecycle.KillbillService.ServiceException;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.mock.glue.MockJunctionModule;
+import com.ning.billing.ovedue.notification.DefaultOverdueCheckNotifier;
+import com.ning.billing.ovedue.notification.DefaultOverdueCheckPoster;
+import com.ning.billing.ovedue.notification.OverdueCheckPoster;
+import com.ning.billing.overdue.OverdueProperties;
+import com.ning.billing.overdue.glue.OverdueModule;
+import com.ning.billing.overdue.listener.OverdueListener;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.InMemoryBus;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.customfield.dao.AuditedCustomFieldDao;
+import com.ning.billing.util.customfield.dao.CustomFieldDao;
+import com.ning.billing.util.globallocker.GlobalLocker;
+import com.ning.billing.util.globallocker.MySqlGlobalLocker;
+import com.ning.billing.util.notificationq.DefaultNotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
+import com.ning.billing.util.tag.dao.AuditedTagDao;
+import com.ning.billing.util.tag.dao.TagDao;
+
+public class TestOverdueCheckNotifier {
+ private Clock clock;
+ private DefaultOverdueCheckNotifier notifier;
+
+ private Bus eventBus;
+ private MysqlTestingHelper helper;
+ private OverdueListenerMock listener;
+ private NotificationQueueService notificationQueueService;
+
+ private static final class OverdueListenerMock extends OverdueListener {
+ int eventCount = 0;
+ UUID latestSubscriptionId = null;
+
+ public OverdueListenerMock() {
+ super(null,null);
+ }
+
+ @Override
+ public void handleNextOverdueCheck(UUID subscriptionId) {
+ eventCount++;
+ latestSubscriptionId=subscriptionId;
+ }
+
+ public int getEventCount() {
+ return eventCount;
+ }
+
+ public UUID getLatestSubscriptionId(){
+ return latestSubscriptionId;
+ }
+
+ }
+
+
+ @BeforeClass(groups={"slow"})
+ public void setup() throws ServiceException, IOException, ClassNotFoundException, SQLException {
+ //TestApiBase.loadSystemPropertiesFromClasspath("/entitlement.properties");
+ final Injector g = Guice.createInjector(Stage.PRODUCTION, new OverdueModule() {
+
+ protected void configure() {
+ super.configure();
+ bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+ bind(CallContextFactory.class).to(DefaultCallContextFactory.class).asEagerSingleton();
+ bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+ bind(NotificationQueueService.class).to(DefaultNotificationQueueService.class).asEagerSingleton();
+ final InvoiceConfig invoiceConfig = new ConfigurationObjectFactory(System.getProperties()).build(InvoiceConfig.class);
+ bind(InvoiceConfig.class).toInstance(invoiceConfig);
+ final CatalogConfig catalogConfig = new ConfigurationObjectFactory(System.getProperties()).build(CatalogConfig.class);
+ bind(CatalogConfig.class).toInstance(catalogConfig);
+ bind(CatalogService.class).to(DefaultCatalogService.class).asEagerSingleton();
+ final MysqlTestingHelper helper = new MysqlTestingHelper();
+ bind(MysqlTestingHelper.class).toInstance(helper);
+ IDBI dbi = helper.getDBI();
+ bind(IDBI.class).toInstance(dbi);
+ bind(TagDao.class).to(AuditedTagDao.class).asEagerSingleton();
+ bind(CustomFieldDao.class).to(AuditedCustomFieldDao.class).asEagerSingleton();
+ bind(GlobalLocker.class).to(MySqlGlobalLocker.class).asEagerSingleton();
+ bind(ChargeThruApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(ChargeThruApi.class));
+ install(new MockJunctionModule());
+ }
+ });
+
+ clock = g.getInstance(Clock.class);
+ IDBI dbi = g.getInstance(IDBI.class);
+
+ eventBus = g.getInstance(Bus.class);
+ helper = g.getInstance(MysqlTestingHelper.class);
+ notificationQueueService = g.getInstance(NotificationQueueService.class);
+
+ OverdueProperties properties = g.getInstance(OverdueProperties.class);
+
+ Subscription subscription = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+ EntitlementUserApi entitlementUserApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class);
+ ((ZombieControl) entitlementUserApi).addResult("getSubscriptionFromId", subscription);
+
+// CallContextFactory factory = new DefaultCallContextFactory(clock);
+ listener = new OverdueListenerMock();
+ notifier = new DefaultOverdueCheckNotifier(notificationQueueService,
+ properties, listener);
+
+ startMysql();
+ eventBus.start();
+ notifier.initialize();
+ notifier.start();
+ }
+
+ 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);
+ }
+
+ @Test(enabled=true, groups="slow")
+ public void test() throws Exception {
+ final UUID subscriptionId = new UUID(0L,1L);
+ final Blockable blockable = BrainDeadProxyFactory.createBrainDeadProxyFor(Subscription.class);
+ ((ZombieControl)blockable).addResult("getId", subscriptionId);
+ final DateTime now = new DateTime();
+ final DateTime readyTime = now.plusMillis(2000);
+ final OverdueCheckPoster poster = new DefaultOverdueCheckPoster(notificationQueueService);
+
+
+ poster.insertOverdueCheckNotification(blockable, readyTime);
+
+
+ // Move time in the future after the notification effectiveDate
+ ((ClockMock) clock).setDeltaFromReality(3000);
+
+
+ await().atMost(1, MINUTES).until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return listener.getEventCount() == 1;
+ }
+ });
+
+ Assert.assertEquals(listener.getEventCount(), 1);
+ Assert.assertEquals(listener.getLatestSubscriptionId(), subscriptionId);
+
+ }
+
+ @AfterClass(groups="slow")
+ public void tearDown() {
+ eventBus.stop();
+ notifier.stop();
+ helper.stopMysql();
+ }
+
+}
overdue/src/test/resources/log4j.xml 40(+40 -0)
diff --git a/overdue/src/test/resources/log4j.xml b/overdue/src/test/resources/log4j.xml
new file mode 100644
index 0000000..ac530a1
--- /dev/null
+++ b/overdue/src/test/resources/log4j.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2010-2011 Ning, Inc.
+ ~
+ ~ Ning licenses this file to you under the Apache License, version 2.0
+ ~ (the "License"); you may not use this file except in compliance with the
+ ~ License. You may obtain a copy of the License at:
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ ~ License for the specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+ <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
+ <param name="Target" value="System.out"/>
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%p %d{ISO8601} %X{trace} %t %c %m%n"/>
+ </layout>
+ </appender>
+
+
+ <logger name="com.ning.billing.entitlement">
+ <level value="info"/>
+ </logger>
+
+ <logger name="com.ning.billing.util.notificationq">
+ <level value="info"/>
+ </logger>
+
+ <root>
+ <priority value="info"/>
+ <appender-ref ref="stdout"/>
+ </root>
+</log4j:configuration>
overdue/src/test/resources/OverdueConfig.xml 20(+20 -0)
diff --git a/overdue/src/test/resources/OverdueConfig.xml b/overdue/src/test/resources/OverdueConfig.xml
new file mode 100644
index 0000000..e58ccc1
--- /dev/null
+++ b/overdue/src/test/resources/OverdueConfig.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ~ Copyright 2010-2011 Ning, Inc. ~ ~ Ning licenses this file to you
+ under the Apache License, version 2.0 ~ (the "License"); you may not use
+ this file except in compliance with the ~ License. You may obtain a copy
+ of the License at: ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless
+ required by applicable law or agreed to in writing, software ~ distributed
+ under the License is distributed on an "AS IS" BASIS, WITHOUT ~ WARRANTIES
+ OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for
+ the specific language governing permissions and limitations ~ under the License. -->
+
+<overdueConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+ <bundleOverdueStates>
+ <state name="Clear">
+ <isClearState>true</isClearState>
+ </state>
+ </bundleOverdueStates>
+</overdueConfig>
+
+
\ No newline at end of file
overdue/src/test/resources/OverdueConfigSchema.xsd 106(+106 -0)
diff --git a/overdue/src/test/resources/OverdueConfigSchema.xsd b/overdue/src/test/resources/OverdueConfigSchema.xsd
new file mode 100644
index 0000000..256da08
--- /dev/null
+++ b/overdue/src/test/resources/OverdueConfigSchema.xsd
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0">
+<xs:element name="overdueConfig" type="overdueConfig"/>
+<xs:complexType name="overdueConfig">
+<xs:complexContent>
+<xs:extension base="validatingConfig">
+<xs:sequence>
+<xs:element name="bundleOverdueStates" type="overdueStatesBundle"/>
+</xs:sequence>
+</xs:extension>
+</xs:complexContent>
+</xs:complexType>
+<xs:complexType abstract="true" name="validatingConfig">
+<xs:sequence/>
+</xs:complexType>
+<xs:complexType name="overdueStatesBundle">
+<xs:complexContent>
+<xs:extension base="defaultOverdueStateSet">
+<xs:sequence>
+<xs:element maxOccurs="unbounded" name="state" type="defaultOverdueState"/>
+</xs:sequence>
+</xs:extension>
+</xs:complexContent>
+</xs:complexType>
+<xs:complexType abstract="true" name="defaultOverdueStateSet">
+<xs:complexContent>
+<xs:extension base="validatingConfig">
+<xs:sequence/>
+</xs:extension>
+</xs:complexContent>
+</xs:complexType>
+<xs:complexType name="defaultOverdueState">
+<xs:complexContent>
+<xs:extension base="validatingConfig">
+<xs:sequence>
+<xs:element minOccurs="0" name="condition" type="defaultCondition"/>
+<xs:element minOccurs="0" name="externalMessage" type="xs:string"/>
+<xs:element minOccurs="0" name="disableEntitlementAndChangesBlocked" type="xs:boolean"/>
+<xs:element minOccurs="0" name="blockChanges" type="xs:boolean"/>
+<xs:element minOccurs="0" name="daysBetweenPaymentRetries" type="xs:int"/>
+<xs:element minOccurs="0" name="isClearState" type="xs:boolean"/>
+</xs:sequence>
+<xs:attribute name="name" type="xs:ID" use="required"/>
+</xs:extension>
+</xs:complexContent>
+</xs:complexType>
+<xs:complexType name="defaultCondition">
+<xs:complexContent>
+<xs:extension base="validatingConfig">
+<xs:sequence>
+<xs:element minOccurs="0" name="numberOfUnpaidInvoicesEqualsOrExceeds" type="xs:int"/>
+<xs:element minOccurs="0" name="totalUnpaidInvoiceBalanceEqualsOrExceeds" type="xs:decimal"/>
+<xs:element minOccurs="0" name="timeSinceEarliestUnpaidInvoiceEqualsOrExceeds" type="defaultDuration"/>
+<xs:element minOccurs="0" name="responseForLastFailedPaymentIn">
+<xs:complexType>
+<xs:sequence>
+<xs:element maxOccurs="unbounded" minOccurs="0" name="response" type="paymentResponse"/>
+</xs:sequence>
+</xs:complexType>
+</xs:element>
+<xs:element minOccurs="0" name="controlTag" type="controlTagType"/>
+</xs:sequence>
+</xs:extension>
+</xs:complexContent>
+</xs:complexType>
+<xs:complexType name="defaultDuration">
+<xs:complexContent>
+<xs:extension base="validatingConfig">
+<xs:sequence>
+<xs:element name="unit" type="timeUnit"/>
+<xs:element minOccurs="0" name="number" type="xs:int"/>
+</xs:sequence>
+</xs:extension>
+</xs:complexContent>
+</xs:complexType>
+<xs:simpleType name="timeUnit">
+<xs:restriction base="xs:string">
+<xs:enumeration value="DAYS"/>
+<xs:enumeration value="MONTHS"/>
+<xs:enumeration value="YEARS"/>
+<xs:enumeration value="UNLIMITED"/>
+</xs:restriction>
+</xs:simpleType>
+<xs:simpleType name="paymentResponse">
+<xs:restriction base="xs:string">
+<xs:enumeration value="INVALID_CARD"/>
+<xs:enumeration value="EXPIRED_CARD"/>
+<xs:enumeration value="LOST_OR_STOLEN_CARD"/>
+<xs:enumeration value="DO_NOT_HONOR"/>
+<xs:enumeration value="INSUFFICIENT_FUNDS"/>
+<xs:enumeration value="DECLINE"/>
+<xs:enumeration value="PROCESSING_ERROR"/>
+<xs:enumeration value="INVALID_AMOUNT"/>
+<xs:enumeration value="DUPLICATE_TRANSACTION"/>
+<xs:enumeration value="OTHER"/>
+</xs:restriction>
+</xs:simpleType>
+<xs:simpleType name="controlTagType">
+<xs:restriction base="xs:string">
+<xs:enumeration value="AUTO_PAY_OFF"/>
+<xs:enumeration value="AUTO_INVOICING_OFF"/>
+<xs:enumeration value="OVERDUE_ENFORCEMENT_OFF"/>
+<xs:enumeration value="WRITTEN_OFF"/>
+</xs:restriction>
+</xs:simpleType>
+</xs:schema>
diff --git a/overdue/src/test/resources/resource.properties b/overdue/src/test/resources/resource.properties
new file mode 100644
index 0000000..d63334b
--- /dev/null
+++ b/overdue/src/test/resources/resource.properties
@@ -0,0 +1,7 @@
+killbill.catalog.uri=file:src/test/resources/catalogSample.xml
+killbill.entitlement.dao.claim.time=60000
+killbill.entitlement.dao.ready.max=1
+killbill.entitlement.engine.notifications.sleep=500
+user.timezone=UTC
+
+
payment/pom.xml 29(+14 -15)
diff --git a/payment/pom.xml b/payment/pom.xml
index 8615efc..b9e1793 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -26,15 +26,24 @@
</dependency>
<dependency>
<groupId>com.ning.billing</groupId>
- <artifactId>killbill-invoice</artifactId>
+ <artifactId>killbill-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-junction</artifactId>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>com.ning.billing</groupId>
<artifactId>killbill-account</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>com.ning.billing</groupId>
- <artifactId>killbill-util</artifactId>
+ <artifactId>killbill-invoice</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
@@ -73,12 +82,13 @@
<artifactId>slf4j-api</artifactId>
</dependency>
+ <!-- TEST SCOPE -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
- <dependency>
+ <dependency>
<groupId>com.jayway.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
@@ -89,18 +99,7 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>com.ning.billing</groupId>
- <artifactId>killbill-account</artifactId>
- <type>test-jar</type>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>com.ning.billing</groupId>
- <artifactId>killbill-invoice</artifactId>
- <type>test-jar</type>
- <scope>test</scope>
- </dependency>
+
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-mxj</artifactId>
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
index c014b45..46b43e0 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
@@ -18,28 +18,28 @@ package com.ning.billing.payment.api;
import java.math.BigDecimal;
import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
-import javax.annotation.Nullable;
-
-import com.ning.billing.util.callcontext.CallContext;
import org.apache.commons.lang.StringUtils;
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.AccountApiException;
import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.config.PaymentConfig;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoicePaymentApi;
-import com.ning.billing.invoice.model.DefaultInvoicePayment;
import com.ning.billing.payment.RetryService;
import com.ning.billing.payment.dao.PaymentDao;
import com.ning.billing.payment.provider.PaymentProviderPlugin;
import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
-import com.ning.billing.payment.setup.PaymentConfig;
+import com.ning.billing.util.callcontext.CallContext;
public class DefaultPaymentApi implements PaymentApi {
private final PaymentProviderPluginRegistry pluginRegistry;
@@ -54,11 +54,11 @@ public class DefaultPaymentApi implements PaymentApi {
@Inject
public DefaultPaymentApi(PaymentProviderPluginRegistry pluginRegistry,
- AccountUserApi accountUserApi,
- InvoicePaymentApi invoicePaymentApi,
- RetryService retryService,
- PaymentDao paymentDao,
- PaymentConfig config) {
+ AccountUserApi accountUserApi,
+ InvoicePaymentApi invoicePaymentApi,
+ RetryService retryService,
+ PaymentDao paymentDao,
+ PaymentConfig config) {
this.pluginRegistry = pluginRegistry;
this.accountUserApi = accountUserApi;
this.invoicePaymentApi = invoicePaymentApi;
@@ -68,18 +68,26 @@ public class DefaultPaymentApi implements PaymentApi {
}
@Override
- public Either<PaymentError, PaymentMethodInfo> getPaymentMethod(@Nullable String accountKey, String paymentMethodId) {
+ public PaymentMethodInfo getPaymentMethod(final String accountKey, final String paymentMethodId)
+ throws PaymentApiException {
final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
- return plugin.getPaymentMethodInfo(paymentMethodId);
+ Either<PaymentErrorEvent, PaymentMethodInfo> result = plugin.getPaymentMethodInfo(paymentMethodId);
+ if (result.isLeft()) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, accountKey, paymentMethodId);
+ }
+ return result.getRight();
}
private PaymentProviderPlugin getPaymentProviderPlugin(String accountKey) {
String paymentProviderName = null;
if (accountKey != null) {
- final Account account = accountUserApi.getAccountByKey(accountKey);
- if (account != null) {
+ Account account;
+ try {
+ account = accountUserApi.getAccountByKey(accountKey);
return getPaymentProviderPlugin(account);
+ } catch (AccountApiException e) {
+ log.error("Error getting payment provider plugin.", e);
}
}
@@ -97,106 +105,144 @@ public class DefaultPaymentApi implements PaymentApi {
}
@Override
- public Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountKey) {
+ public List<PaymentMethodInfo> getPaymentMethods(String accountKey)
+ throws PaymentApiException {
final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
- return plugin.getPaymentMethods(accountKey);
+ Either<PaymentErrorEvent, List<PaymentMethodInfo>> result = plugin.getPaymentMethods(accountKey);
+ if (result.isLeft()) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_NO_PAYMENT_METHODS, accountKey);
+ }
+ return result.getRight();
}
@Override
- public Either<PaymentError, Void> updatePaymentGateway(String accountKey, CallContext context) {
+ public void updatePaymentGateway(String accountKey, CallContext context)
+ throws PaymentApiException {
final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
- return plugin.updatePaymentGateway(accountKey);
+ Either<PaymentErrorEvent, Void> result = plugin.updatePaymentGateway(accountKey);
+ if (result.isLeft()) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_UPD_GATEWAY_FAILED, accountKey, result.getLeft().getMessage());
+ }
+ return;
}
@Override
- public Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
+ public PaymentProviderAccount getPaymentProviderAccount(String accountKey)
+ throws PaymentApiException {
final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
- return plugin.getPaymentProviderAccount(accountKey);
+ Either<PaymentErrorEvent, PaymentProviderAccount> result = plugin.getPaymentProviderAccount(accountKey);
+ if (result.isLeft()) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_GET_PAYMENT_PROVIDER, accountKey, result.getLeft().getMessage());
+ }
+ return result.getRight();
}
@Override
- public Either<PaymentError, String> addPaymentMethod(@Nullable String accountKey, PaymentMethodInfo paymentMethod, CallContext context) {
+ public String addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod, CallContext context)
+ throws PaymentApiException {
final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
- return plugin.addPaymentMethod(accountKey, paymentMethod);
+ Either<PaymentErrorEvent, String> result = plugin.addPaymentMethod(accountKey, paymentMethod);
+ if (result.isLeft()) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_ADD_PAYMENT_METHOD, accountKey, result.getLeft().getMessage());
+ }
+ return result.getRight();
}
+
@Override
- public Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId, CallContext context) {
+ public void deletePaymentMethod(String accountKey, String paymentMethodId, CallContext context)
+ throws PaymentApiException {
final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
- return plugin.deletePaymentMethod(accountKey, paymentMethodId);
+ Either<PaymentErrorEvent, Void> result = plugin.deletePaymentMethod(accountKey, paymentMethodId);
+ if (result.isLeft()) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_DEL_PAYMENT_METHOD, accountKey, result.getLeft().getMessage());
+ }
+ return;
}
@Override
- public Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo, CallContext context) {
+ public PaymentMethodInfo updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo, CallContext context)
+ throws PaymentApiException {
final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
- return plugin.updatePaymentMethod(accountKey, paymentMethodInfo);
+ Either<PaymentErrorEvent, PaymentMethodInfo> result = plugin.updatePaymentMethod(accountKey, paymentMethodInfo);
+ if (result.isLeft()) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_UPD_PAYMENT_METHOD, accountKey, result.getLeft().getMessage());
+ }
+ return result.getRight();
}
@Override
- public List<Either<PaymentError, PaymentInfo>> createPayment(String accountKey, List<String> invoiceIds, CallContext context) {
- final Account account = accountUserApi.getAccountByKey(accountKey);
- return createPayment(account, invoiceIds, context);
+ public List<PaymentInfoEvent> createPayment(String accountKey, List<String> invoiceIds, CallContext context)
+ throws PaymentApiException {
+ try {
+ final Account account = accountUserApi.getAccountByKey(accountKey);
+ return createPayment(account, invoiceIds, context);
+ } catch (AccountApiException e) {
+ throw new PaymentApiException(e);
+ }
}
@Override
- public Either<PaymentError, PaymentInfo> createPaymentForPaymentAttempt(UUID paymentAttemptId, CallContext context) {
- PaymentAttempt paymentAttempt = paymentDao.getPaymentAttemptById(paymentAttemptId);
+ public PaymentInfoEvent createPaymentForPaymentAttempt(UUID paymentAttemptId, CallContext context)
+ throws PaymentApiException {
+ PaymentAttempt paymentAttempt = paymentDao.getPaymentAttemptById(paymentAttemptId);
if (paymentAttempt != null) {
- Invoice invoice = invoicePaymentApi.getInvoice(paymentAttempt.getInvoiceId());
- Account account = accountUserApi.getAccountById(paymentAttempt.getAccountId());
-
- if (invoice != null && account != null) {
- if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0 ) {
- // TODO: send a notification that invoice was ignored?
- log.info("Received invoice for payment with outstanding amount of 0 {} ", invoice);
- return Either.left(new PaymentError("invoice_balance_0",
- "Invoice balance was 0 or less",
- paymentAttempt.getAccountId(),
- paymentAttempt.getInvoiceId()));
- }
- else {
- PaymentAttempt newPaymentAttempt = new PaymentAttempt.Builder(paymentAttempt)
- .setRetryCount(paymentAttempt.getRetryCount() + 1)
- .setPaymentAttemptId(UUID.randomUUID())
- .build();
-
- paymentDao.createPaymentAttempt(newPaymentAttempt, context);
- return processPayment(getPaymentProviderPlugin(account), account, invoice, newPaymentAttempt, context);
+ try {
+ Invoice invoice = invoicePaymentApi.getInvoice(paymentAttempt.getInvoiceId());
+ Account account = accountUserApi.getAccountById(paymentAttempt.getAccountId());
+
+ if (invoice != null && account != null) {
+ if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0 ) {
+ // TODO: send a notification that invoice was ignored?
+ log.info("Received invoice for payment with outstanding amount of 0 {} ", invoice);
+ throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_PAYMENT_FOR_ATTEMPT_WITH_NON_POSITIVE_INV, account.getId());
+
+ } else {
+
+ PaymentAttempt newPaymentAttempt = new DefaultPaymentAttempt.Builder(paymentAttempt)
+ .setRetryCount(paymentAttempt.getRetryCount() + 1)
+ .setPaymentAttemptId(UUID.randomUUID())
+ .build();
+
+ paymentDao.createPaymentAttempt(newPaymentAttempt, context);
+ Either<PaymentErrorEvent, PaymentInfoEvent> result = processPayment(getPaymentProviderPlugin(account), account, invoice, newPaymentAttempt, context);
+ if (result.isLeft()) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_PAYMENT_FOR_ATTEMPT, account.getId(), paymentAttemptId, result.getLeft().getMessage());
+ }
+ return result.getRight();
+
+ }
}
+ } catch (AccountApiException e) {
+ throw new PaymentApiException(e);
}
}
- return Either.left(new PaymentError("retry_payment_error",
- "Could not load payment attempt, invoice or account for id " + paymentAttemptId,
- paymentAttempt.getAccountId(),
- paymentAttempt.getInvoiceId()));
+ throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_PAYMENT_FOR_ATTEMPT_BAD, paymentAttemptId);
}
@Override
- public List<Either<PaymentError, PaymentInfo>> createPayment(Account account, List<String> invoiceIds, CallContext context) {
+ public List<PaymentInfoEvent> createPayment(Account account, List<String> invoiceIds, CallContext context)
+ throws PaymentApiException {
+
final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
- List<Either<PaymentError, PaymentInfo>> processedPaymentsOrErrors = new ArrayList<Either<PaymentError, PaymentInfo>>(invoiceIds.size());
+ List<Either<PaymentErrorEvent, PaymentInfoEvent>> processedPaymentsOrErrors = new ArrayList<Either<PaymentErrorEvent, PaymentInfoEvent>>(invoiceIds.size());
for (String invoiceId : invoiceIds) {
Invoice invoice = invoicePaymentApi.getInvoice(UUID.fromString(invoiceId));
if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0 ) {
- // TODO: send a notification that invoice was ignored?
- log.info("Received invoice for payment with balance of 0 {} ", invoice);
- Either<PaymentError, PaymentInfo> result = Either.left(new PaymentError("invoice_balance_0",
- "Invoice balance was 0 or less",
- account.getId(),
- UUID.fromString(invoiceId)));
- processedPaymentsOrErrors.add(result);
+ log.debug("Received invoice for payment with balance of 0 {} ", invoice);
}
else if (invoice.isMigrationInvoice()) {
- log.info("Received invoice for payment that is a migration invoice - don't know how to handle those yet: {}", invoice);
- Either<PaymentError, PaymentInfo> result = Either.left(new PaymentError("migration invoice",
+ log.info("Received invoice for payment that is a migration invoice - don't know how to handle those yet: {}", invoice);
+ Either<PaymentErrorEvent, PaymentInfoEvent> result = Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("migration invoice",
"Invoice balance was a migration invoice",
account.getId(),
- UUID.fromString(invoiceId)));
- processedPaymentsOrErrors.add(result);
+ UUID.fromString(invoiceId),
+ context.getUserToken()));
+ processedPaymentsOrErrors.add(result);
}
else {
PaymentAttempt paymentAttempt = paymentDao.createPaymentAttempt(invoice, context);
@@ -205,16 +251,24 @@ public class DefaultPaymentApi implements PaymentApi {
}
}
- return processedPaymentsOrErrors;
+ List<Either<PaymentErrorEvent, PaymentInfoEvent>> result = processedPaymentsOrErrors;
+ List<PaymentInfoEvent> info = new LinkedList<PaymentInfoEvent>();
+ for (Either<PaymentErrorEvent, PaymentInfoEvent> cur : result) {
+ if (cur.isLeft()) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_PAYMENT, account.getId(), cur.getLeft().getMessage());
+ }
+ info.add(cur.getRight());
+ }
+ return info;
}
- private Either<PaymentError, PaymentInfo> processPayment(PaymentProviderPlugin plugin, Account account, Invoice invoice,
- PaymentAttempt paymentAttempt, CallContext context) {
- Either<PaymentError, PaymentInfo> paymentOrError = plugin.processInvoice(account, invoice);
- PaymentInfo paymentInfo = null;
+ private Either<PaymentErrorEvent, PaymentInfoEvent> processPayment(PaymentProviderPlugin plugin, Account account, Invoice invoice,
+ PaymentAttempt paymentAttempt, CallContext context) {
+ Either<PaymentErrorEvent, PaymentInfoEvent> paymentOrError = plugin.processInvoice(account, invoice);
+ PaymentInfoEvent paymentInfo = null;
if (paymentOrError.isLeft()) {
- String error = StringUtils.substring(paymentOrError.getLeft().getMessage() + paymentOrError.getLeft().getType(), 0, 100);
+ String error = StringUtils.substring(paymentOrError.getLeft().getMessage() + paymentOrError.getLeft().getBusEventType(), 0, 100);
log.info("Could not process a payment for " + paymentAttempt + " error was " + error);
scheduleRetry(paymentAttempt, error);
@@ -225,35 +279,36 @@ public class DefaultPaymentApi implements PaymentApi {
final String paymentMethodId = paymentInfo.getPaymentMethodId();
log.debug("Fetching payment method info for payment method id " + ((paymentMethodId == null) ? "null" : paymentMethodId));
- Either<PaymentError, PaymentMethodInfo> paymentMethodInfoOrError = plugin.getPaymentMethodInfo(paymentMethodId);
+ Either<PaymentErrorEvent, PaymentMethodInfo> paymentMethodInfoOrError = plugin.getPaymentMethodInfo(paymentMethodId);
if (paymentMethodInfoOrError.isRight()) {
PaymentMethodInfo paymentMethodInfo = paymentMethodInfoOrError.getRight();
if (paymentMethodInfo instanceof CreditCardPaymentMethodInfo) {
CreditCardPaymentMethodInfo ccPaymentMethod = (CreditCardPaymentMethodInfo)paymentMethodInfo;
- paymentDao.updatePaymentInfo(ccPaymentMethod.getType(), paymentInfo.getPaymentId(), ccPaymentMethod.getCardType(), ccPaymentMethod.getCardCountry(), context);
+ paymentDao.updatePaymentInfo(ccPaymentMethod.getType(), paymentInfo.getId(), ccPaymentMethod.getCardType(), ccPaymentMethod.getCardCountry(), context);
}
else if (paymentMethodInfo instanceof PaypalPaymentMethodInfo) {
PaypalPaymentMethodInfo paypalPaymentMethodInfo = (PaypalPaymentMethodInfo)paymentMethodInfo;
- paymentDao.updatePaymentInfo(paypalPaymentMethodInfo.getType(), paymentInfo.getPaymentId(), null, null, context);
+ paymentDao.updatePaymentInfo(paypalPaymentMethodInfo.getType(), paymentInfo.getId(), null, null, context);
}
} else {
log.info(paymentMethodInfoOrError.getLeft().getMessage());
}
- if (paymentInfo.getPaymentId() != null) {
- paymentDao.updatePaymentAttemptWithPaymentId(paymentAttempt.getPaymentAttemptId(), paymentInfo.getPaymentId(), context);
+ if (paymentInfo.getId() != null) {
+ paymentDao.updatePaymentAttemptWithPaymentId(paymentAttempt.getId(), paymentInfo.getId(), context);
}
}
- invoicePaymentApi.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttempt.getPaymentAttemptId(),
- invoice.getId(),
- paymentAttempt.getPaymentAttemptDate(),
- paymentInfo == null || paymentInfo.getStatus().equalsIgnoreCase("Error") ? null : paymentInfo.getAmount(),
-// paymentInfo.getRefundAmount(), TODO
- paymentInfo == null || paymentInfo.getStatus().equalsIgnoreCase("Error") ? null : invoice.getCurrency()),
- context);
+ invoicePaymentApi.notifyOfPaymentAttempt(
+ invoice.getId(),
+ paymentInfo == null || paymentInfo.getStatus().equalsIgnoreCase("Error") ? null : paymentInfo.getAmount(),
+ // paymentInfo.getRefundAmount(), TODO
+ paymentInfo == null || paymentInfo.getStatus().equalsIgnoreCase("Error") ? null : invoice.getCurrency(),
+ paymentAttempt.getId(),
+ paymentAttempt.getPaymentAttemptDate(),
+ context);
return paymentOrError;
}
@@ -290,32 +345,61 @@ public class DefaultPaymentApi implements PaymentApi {
}
@Override
- public Either<PaymentError, String> createPaymentProviderAccount(Account account, CallContext context) {
+ public String createPaymentProviderAccount(Account account, CallContext context)
+ throws PaymentApiException {
final PaymentProviderPlugin plugin = getPaymentProviderPlugin((Account)null);
- return plugin.createPaymentProviderAccount(account);
+ Either<PaymentErrorEvent, String> result = plugin.createPaymentProviderAccount(account);
+ if (result.isLeft()) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_PAYMENT_PROVIDER_ACCOUNT, account.getId(), result.getLeft().getMessage());
+ }
+ return result.getRight();
}
@Override
- public Either<PaymentError, Void> updatePaymentProviderAccountContact(String externalKey, CallContext context) {
- Account account = accountUserApi.getAccountByKey(externalKey);
- final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
- return plugin.updatePaymentProviderAccountExistingContact(account);
+ public void updatePaymentProviderAccountContact(String externalKey, CallContext context)
+ throws PaymentApiException {
+ try {
+ Account account = accountUserApi.getAccountByKey(externalKey);
+ final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
+ Either<PaymentErrorEvent, Void> result = plugin.updatePaymentProviderAccountExistingContact(account);
+ if (result.isLeft()) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_UPD_PAYMENT_PROVIDER_ACCOUNT, account.getId(), result.getLeft().getMessage());
+ }
+ return;
+ } catch (AccountApiException e) {
+ throw new PaymentApiException(e);
+ }
}
@Override
- public PaymentAttempt getPaymentAttemptForPaymentId(String id) {
+ public PaymentAttempt getPaymentAttemptForPaymentId(UUID id) {
return paymentDao.getPaymentAttemptForPaymentId(id);
}
@Override
- public List<Either<PaymentError, PaymentInfo>> createRefund(Account account, List<String> invoiceIds, CallContext context) {
- //TODO
- throw new UnsupportedOperationException();
+ public List<PaymentInfoEvent> createRefund(Account account, List<String> invoiceIds, CallContext context)
+ throws PaymentApiException {
+
+ final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
+ List<Either<PaymentErrorEvent, PaymentInfoEvent>> result = plugin.processRefund(account);
+ List<PaymentInfoEvent> info = new LinkedList<PaymentInfoEvent>();
+ for (Either<PaymentErrorEvent, PaymentInfoEvent> cur : result) {
+ if (cur.isLeft()) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_REFUND, account.getId(), cur.getLeft().getMessage());
+ }
+ info.add(cur.getRight());
+ }
+ return info;
+ }
+
+ @Override
+ public List<PaymentInfoEvent> getPaymentInfoList(List<String> invoiceIds) {
+ return paymentDao.getPaymentInfoList(invoiceIds);
}
@Override
- public List<PaymentInfo> getPaymentInfo(List<String> invoiceIds) {
- return paymentDao.getPaymentInfo(invoiceIds);
+ public PaymentInfoEvent getLastPaymentInfo(List<String> invoiceIds) {
+ return paymentDao.getLastPaymentInfo(invoiceIds);
}
@Override
@@ -324,7 +408,7 @@ public class DefaultPaymentApi implements PaymentApi {
}
@Override
- public PaymentInfo getPaymentInfoForPaymentAttemptId(String paymentAttemptId) {
+ public PaymentInfoEvent getPaymentInfoForPaymentAttemptId(String paymentAttemptId) {
return paymentDao.getPaymentInfoForPaymentAttemptId(paymentAttemptId);
}
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentAttempt.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentAttempt.java
new file mode 100644
index 0000000..681f0ca
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentAttempt.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import com.ning.billing.util.entity.EntityBase;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.google.common.base.Objects;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+
+public class DefaultPaymentAttempt extends EntityBase implements PaymentAttempt {
+ private final UUID invoiceId;
+ private final UUID accountId;
+ private final BigDecimal amount;
+ private final Currency currency;
+ private final UUID paymentId;
+ private final DateTime invoiceDate;
+ private final DateTime paymentAttemptDate;
+ private final Integer retryCount;
+ private final DateTime createdDate;
+ private final DateTime updatedDate;
+
+ public DefaultPaymentAttempt(UUID id,
+ UUID invoiceId,
+ UUID accountId,
+ BigDecimal amount,
+ Currency currency,
+ DateTime invoiceDate,
+ DateTime paymentAttemptDate,
+ UUID paymentId,
+ Integer retryCount,
+ DateTime createdDate, DateTime updatedDate) {
+ super(id);
+ this.invoiceId = invoiceId;
+ this.accountId = accountId;
+ this.amount = amount;
+ this.currency = currency;
+ this.invoiceDate = invoiceDate;
+ this.paymentAttemptDate = paymentAttemptDate == null ? new DateTime(DateTimeZone.UTC) : paymentAttemptDate;
+ this.paymentId = paymentId;
+ this.retryCount = retryCount == null ? 0 : retryCount;
+ this.createdDate = createdDate;
+ this.updatedDate = updatedDate;
+ }
+
+ public DefaultPaymentAttempt(UUID paymentAttemptId, UUID invoiceId, UUID accountId, BigDecimal amount, Currency currency, DateTime invoiceDate, DateTime paymentAttemptDate) {
+ this(paymentAttemptId, invoiceId, accountId, amount, currency, invoiceDate, paymentAttemptDate, null, null, null, null);
+ }
+
+ public DefaultPaymentAttempt(UUID paymentAttemptId, UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime paymentAttemptDate) {
+ this(paymentAttemptId, invoiceId, accountId, null, null, invoiceDate, paymentAttemptDate, null, null, null, null);
+ }
+
+ public DefaultPaymentAttempt(UUID paymentAttemptId, Invoice invoice) {
+ this(paymentAttemptId, invoice.getId(), invoice.getAccountId(), invoice.getBalance(), invoice.getCurrency(), invoice.getInvoiceDate(), null, null, null, null, null);
+ }
+
+ @Override public DateTime getInvoiceDate() {
+ return invoiceDate;
+ }
+
+ @Override public UUID getId() {
+ return id;
+ }
+
+ @Override public UUID getPaymentId() {
+ return paymentId;
+ }
+
+ @Override public DateTime getPaymentAttemptDate() {
+ return paymentAttemptDate;
+ }
+
+ @Override public UUID getInvoiceId() {
+ return invoiceId;
+ }
+
+ @Override public UUID getAccountId() {
+ return accountId;
+ }
+
+ @Override public BigDecimal getAmount() {
+ return amount;
+ }
+
+ @Override public Currency getCurrency() {
+ return currency;
+ }
+
+ @Override public Integer getRetryCount() {
+ return retryCount;
+ }
+
+ @Override public DateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ @Override public DateTime getUpdatedDate() {
+ return updatedDate;
+ }
+
+ @Override
+ public String toString() {
+ return "PaymentAttempt [paymentAttemptId=" + id + ", invoiceId=" + invoiceId + ", accountId=" + accountId + ", amount=" + amount + ", currency=" + currency + ", paymentId=" + paymentId + ", invoiceDate=" + invoiceDate + ", paymentAttemptDate=" + paymentAttemptDate + ", retryCount=" + retryCount + "]";
+ }
+
+ public Builder cloner() {
+ return new Builder(this);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(id,
+ invoiceId,
+ accountId,
+ amount,
+ currency,
+ invoiceDate,
+ paymentAttemptDate,
+ paymentId,
+ retryCount,
+ createdDate, updatedDate);
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final PaymentAttempt that = (PaymentAttempt) o;
+
+ if (accountId != null ? !accountId.equals(that.getAccountId()) : that.getAccountId() != null) return false;
+ if (amount != null ? !(amount.compareTo(that.getAmount()) == 0) : that.getAmount() != null) return false;
+ if (currency != that.getCurrency()) return false;
+ if (invoiceDate == null ? that.getInvoiceDate() != null : invoiceDate.compareTo(that.getInvoiceDate()) != 0) return false;
+ if (invoiceId != null ? !invoiceId.equals(that.getInvoiceId()) : that.getInvoiceId() != null) return false;
+ if (paymentAttemptDate == null ? that.getPaymentAttemptDate() != null : paymentAttemptDate.compareTo(that.getPaymentAttemptDate()) != 0) return false;
+ if (id != null ? !id.equals(that.getId()) : that.getId() != null)
+ return false;
+ if (paymentId != null ? !paymentId.equals(that.getPaymentId()) : that.getPaymentId() != null) return false;
+ if (retryCount != null ? !retryCount.equals(that.getRetryCount()) : that.getRetryCount() != null) return false;
+ if (createdDate.compareTo(that.getCreatedDate()) != 0) return false;
+ if (updatedDate.compareTo(that.getUpdatedDate()) != 0) return false;
+
+ return true;
+ }
+
+ public static class Builder {
+ private UUID id;
+ private UUID invoiceId;
+ private UUID accountId;
+ private BigDecimal amount;
+ private Currency currency;
+ private DateTime invoiceDate;
+ private DateTime paymentAttemptDate;
+ private UUID paymentId;
+ private Integer retryCount;
+ private DateTime createdDate;
+ private DateTime updatedDate;
+
+ public Builder() {
+ }
+
+ public Builder(PaymentAttempt src) {
+ this.id = src.getId();
+ this.invoiceId = src.getInvoiceId();
+ this.accountId = src.getAccountId();
+ this.amount = src.getAmount();
+ this.currency = src.getCurrency();
+ this.invoiceDate = src.getInvoiceDate();
+ this.paymentAttemptDate = src.getPaymentAttemptDate();
+ this.paymentId = src.getPaymentId();
+ this.retryCount = src.getRetryCount();
+ this.createdDate = src.getCreatedDate();
+ this.updatedDate = src.getUpdatedDate();
+ }
+
+ public Builder setPaymentAttemptId(UUID paymentAttemptId) {
+ this.id = paymentAttemptId;
+ return this;
+ }
+
+ public Builder setInvoiceId(UUID invoiceId) {
+ this.invoiceId = invoiceId;
+ return this;
+ }
+
+ public Builder setAccountId(UUID accountId) {
+ this.accountId = accountId;
+ return this;
+ }
+
+ public Builder setAmount(BigDecimal amount) {
+ this.amount = amount;
+ return this;
+ }
+
+ public Builder setCurrency(Currency currency) {
+ this.currency = currency;
+ return this;
+ }
+
+ public Builder setInvoiceDate(DateTime invoiceDate) {
+ this.invoiceDate = invoiceDate;
+ return this;
+ }
+
+ public Builder setPaymentAttemptDate(DateTime paymentAttemptDate) {
+ this.paymentAttemptDate = paymentAttemptDate;
+ return this;
+ }
+
+ public Builder setPaymentId(UUID paymentId) {
+ this.paymentId = paymentId;
+ return this;
+ }
+
+ public Builder setRetryCount(Integer retryCount) {
+ this.retryCount = retryCount;
+ return this;
+ }
+
+ public Builder setCreatedDate(DateTime createdDate) {
+ this.createdDate = createdDate;
+ return this;
+ }
+
+ public Builder setUpdateDate(DateTime updateDate) {
+ this.updatedDate = updateDate;
+ return this;
+ }
+
+ public PaymentAttempt build() {
+ return new DefaultPaymentAttempt(id,
+ invoiceId,
+ accountId,
+ amount,
+ currency,
+ invoiceDate,
+ paymentAttemptDate,
+ paymentId,
+ retryCount,
+ createdDate, updatedDate);
+ }
+ }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/AuditedPaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/AuditedPaymentDao.java
index bcdda9a..39201ac 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/AuditedPaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/AuditedPaymentDao.java
@@ -19,50 +19,57 @@ package com.ning.billing.payment.dao;
import java.util.List;
import java.util.UUID;
+import com.ning.billing.payment.api.DefaultPaymentAttempt;
import com.ning.billing.util.ChangeType;
-import com.ning.billing.util.audit.dao.AuditSqlDao;
import com.ning.billing.util.callcontext.CallContext;
-import org.apache.commons.lang.Validate;
+import com.ning.billing.util.dao.EntityAudit;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.TableName;
import org.skife.jdbi.v2.IDBI;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentInfoEvent;
import org.skife.jdbi.v2.Transaction;
import org.skife.jdbi.v2.TransactionStatus;
public class AuditedPaymentDao implements PaymentDao {
- private final PaymentSqlDao sqlDao;
+ private final PaymentSqlDao paymentSqlDao;
+ private final PaymentAttemptSqlDao paymentAttemptSqlDao;
@Inject
public AuditedPaymentDao(IDBI dbi) {
- this.sqlDao = dbi.onDemand(PaymentSqlDao.class);
+ this.paymentSqlDao = dbi.onDemand(PaymentSqlDao.class);
+ this.paymentAttemptSqlDao = dbi.onDemand(PaymentAttemptSqlDao.class);
}
@Override
- public PaymentAttempt getPaymentAttemptForPaymentId(String paymentId) {
- return sqlDao.getPaymentAttemptForPaymentId(paymentId);
+ public PaymentAttempt getPaymentAttemptForPaymentId(UUID paymentId) {
+ return paymentAttemptSqlDao.getPaymentAttemptForPaymentId(paymentId.toString());
}
@Override
public List<PaymentAttempt> getPaymentAttemptsForInvoiceId(String invoiceId) {
- return sqlDao.getPaymentAttemptsForInvoiceId(invoiceId);
+ return paymentAttemptSqlDao.getPaymentAttemptsForInvoiceId(invoiceId);
}
@Override
public PaymentAttempt createPaymentAttempt(final PaymentAttempt paymentAttempt, final CallContext context) {
- return sqlDao.inTransaction(new Transaction<PaymentAttempt, PaymentSqlDao>() {
+ return paymentAttemptSqlDao.inTransaction(new Transaction<PaymentAttempt, PaymentAttemptSqlDao>() {
@Override
- public PaymentAttempt inTransaction(PaymentSqlDao transactional, TransactionStatus status) throws Exception {
+ public PaymentAttempt inTransaction(PaymentAttemptSqlDao transactional, TransactionStatus status) throws Exception {
transactional.insertPaymentAttempt(paymentAttempt, context);
- PaymentAttempt savedPaymentAttempt = transactional.getPaymentAttemptById(paymentAttempt.getPaymentAttemptId().toString());
- UUID historyRecordId = UUID.randomUUID();
- transactional.insertPaymentAttemptHistory(historyRecordId.toString(), paymentAttempt, context);
- AuditSqlDao auditSqlDao = transactional.become(AuditSqlDao.class);
- auditSqlDao.insertAuditFromTransaction("payment_attempt", historyRecordId.toString(),
- ChangeType.INSERT, context);
+ PaymentAttempt savedPaymentAttempt = transactional.getPaymentAttemptById(paymentAttempt.getId().toString());
+
+ Long recordId = transactional.getRecordId(paymentAttempt.getId().toString());
+ EntityHistory<PaymentAttempt> history = new EntityHistory<PaymentAttempt>(paymentAttempt.getId(), recordId, paymentAttempt, ChangeType.INSERT);
+ transactional.insertHistoryFromTransaction(history, context);
+
+ Long historyRecordId = transactional.getHistoryRecordId(recordId);
+ EntityAudit audit = new EntityAudit(TableName.PAYMENT_ATTEMPTS, historyRecordId, ChangeType.INSERT);
+ transactional.insertAuditFromTransaction(audit, context);
return savedPaymentAttempt;
}
});
@@ -70,16 +77,19 @@ public class AuditedPaymentDao implements PaymentDao {
@Override
public PaymentAttempt createPaymentAttempt(final Invoice invoice, final CallContext context) {
- return sqlDao.inTransaction(new Transaction<PaymentAttempt, PaymentSqlDao>() {
+ return paymentAttemptSqlDao.inTransaction(new Transaction<PaymentAttempt, PaymentAttemptSqlDao>() {
@Override
- public PaymentAttempt inTransaction(PaymentSqlDao transactional, TransactionStatus status) throws Exception {
- final PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+ public PaymentAttempt inTransaction(PaymentAttemptSqlDao transactional, TransactionStatus status) throws Exception {
+ final PaymentAttempt paymentAttempt = new DefaultPaymentAttempt(UUID.randomUUID(), invoice);
transactional.insertPaymentAttempt(paymentAttempt, context);
- UUID historyRecordId = UUID.randomUUID();
- transactional.insertPaymentAttemptHistory(historyRecordId.toString(), paymentAttempt, context);
- AuditSqlDao auditSqlDao = transactional.become(AuditSqlDao.class);
- auditSqlDao.insertAuditFromTransaction("payment_attempt", historyRecordId.toString(),
- ChangeType.INSERT, context);
+
+ Long recordId = transactional.getRecordId(paymentAttempt.getId().toString());
+ EntityHistory<PaymentAttempt> history = new EntityHistory<PaymentAttempt>(paymentAttempt.getId(), recordId, paymentAttempt, ChangeType.INSERT);
+ transactional.insertHistoryFromTransaction(history, context);
+
+ Long historyRecordId = transactional.getHistoryRecordId(recordId);
+ EntityAudit audit = new EntityAudit(TableName.PAYMENT_ATTEMPTS, historyRecordId, ChangeType.INSERT);
+ transactional.insertAuditFromTransaction(audit, context);
return paymentAttempt;
}
@@ -87,16 +97,18 @@ public class AuditedPaymentDao implements PaymentDao {
}
@Override
- public void savePaymentInfo(final PaymentInfo info, final CallContext context) {
- sqlDao.inTransaction(new Transaction<Void, PaymentSqlDao>() {
+ public void savePaymentInfo(final PaymentInfoEvent info, final CallContext context) {
+ paymentSqlDao.inTransaction(new Transaction<Void, PaymentSqlDao>() {
@Override
public Void inTransaction(PaymentSqlDao transactional, TransactionStatus status) throws Exception {
transactional.insertPaymentInfo(info, context);
- UUID historyRecordId = UUID.randomUUID();
- transactional.insertPaymentInfoHistory(historyRecordId.toString(), info, context);
- AuditSqlDao auditSqlDao = transactional.become(AuditSqlDao.class);
- auditSqlDao.insertAuditFromTransaction("payment", historyRecordId.toString(),
- ChangeType.INSERT, context);
+ Long recordId = transactional.getRecordId(info.getId().toString());
+ EntityHistory<PaymentInfoEvent> history = new EntityHistory<PaymentInfoEvent>(info.getId(), recordId, info, ChangeType.INSERT);
+ transactional.insertHistoryFromTransaction(history, context);
+
+ Long historyRecordId = transactional.getHistoryRecordId(recordId);
+ EntityAudit audit = new EntityAudit(TableName.PAYMENTS, historyRecordId, ChangeType.INSERT);
+ transactional.insertAuditFromTransaction(audit, context);
return null;
}
@@ -104,17 +116,19 @@ public class AuditedPaymentDao implements PaymentDao {
}
@Override
- public void updatePaymentAttemptWithPaymentId(final UUID paymentAttemptId, final String paymentId, final CallContext context) {
- sqlDao.inTransaction(new Transaction<Void, PaymentSqlDao>() {
+ public void updatePaymentAttemptWithPaymentId(final UUID paymentAttemptId, final UUID id, final CallContext context) {
+ paymentAttemptSqlDao.inTransaction(new Transaction<Void, PaymentAttemptSqlDao>() {
@Override
- public Void inTransaction(PaymentSqlDao transactional, TransactionStatus status) throws Exception {
- transactional.updatePaymentAttemptWithPaymentId(paymentAttemptId.toString(), paymentId, context);
+ public Void inTransaction(PaymentAttemptSqlDao transactional, TransactionStatus status) throws Exception {
+ transactional.updatePaymentAttemptWithPaymentId(paymentAttemptId.toString(), id.toString(), context);
PaymentAttempt paymentAttempt = transactional.getPaymentAttemptById(paymentAttemptId.toString());
- UUID historyRecordId = UUID.randomUUID();
- transactional.insertPaymentAttemptHistory(historyRecordId.toString(), paymentAttempt, context);
- AuditSqlDao auditSqlDao = transactional.become(AuditSqlDao.class);
- auditSqlDao.insertAuditFromTransaction("payment_attempt", historyRecordId.toString(),
- ChangeType.UPDATE, context);
+ Long recordId = transactional.getRecordId(paymentAttemptId.toString());
+ EntityHistory<PaymentAttempt> history = new EntityHistory<PaymentAttempt>(paymentAttemptId, recordId, paymentAttempt, ChangeType.UPDATE);
+ transactional.insertHistoryFromTransaction(history, context);
+
+ Long historyRecordId = transactional.getHistoryRecordId(recordId);
+ EntityAudit audit = new EntityAudit(TableName.PAYMENT_ATTEMPTS, historyRecordId, ChangeType.UPDATE);
+ transactional.insertAuditFromTransaction(audit, context);
return null;
}
@@ -122,18 +136,21 @@ public class AuditedPaymentDao implements PaymentDao {
}
@Override
- public void updatePaymentInfo(final String type, final String paymentId, final String cardType,
+ public void updatePaymentInfo(final String type, final UUID paymentId, final String cardType,
final String cardCountry, final CallContext context) {
- sqlDao.inTransaction(new Transaction<Void, PaymentSqlDao>() {
+ paymentSqlDao.inTransaction(new Transaction<Void, PaymentSqlDao>() {
@Override
public Void inTransaction(PaymentSqlDao transactional, TransactionStatus status) throws Exception {
- transactional.updatePaymentInfo(type, paymentId, cardType, cardCountry, context);
- PaymentInfo paymentInfo = transactional.getPaymentInfo(paymentId);
- UUID historyRecordId = UUID.randomUUID();
- transactional.insertPaymentInfoHistory(historyRecordId.toString(), paymentInfo, context);
- AuditSqlDao auditSqlDao = transactional.become(AuditSqlDao.class);
- auditSqlDao.insertAuditFromTransaction("payments", historyRecordId.toString(),
- ChangeType.UPDATE, context);
+ transactional.updatePaymentInfo(type, paymentId.toString(), cardType, cardCountry, context);
+ PaymentInfoEvent paymentInfo = transactional.getPaymentInfo(paymentId.toString());
+
+ Long recordId = transactional.getRecordId(paymentId.toString());
+ EntityHistory<PaymentInfoEvent> history = new EntityHistory<PaymentInfoEvent>(paymentInfo.getId(), recordId, paymentInfo, ChangeType.UPDATE);
+ transactional.insertHistoryFromTransaction(history, context);
+
+ Long historyRecordId = transactional.getHistoryRecordId(recordId);
+ EntityAudit audit = new EntityAudit(TableName.PAYMENT_HISTORY, historyRecordId, ChangeType.UPDATE);
+ transactional.insertAuditFromTransaction(audit, context);
return null;
}
@@ -141,11 +158,20 @@ public class AuditedPaymentDao implements PaymentDao {
}
@Override
- public List<PaymentInfo> getPaymentInfo(List<String> invoiceIds) {
+ public List<PaymentInfoEvent> getPaymentInfoList(List<String> invoiceIds) {
+ if (invoiceIds == null || invoiceIds.size() == 0) {
+ return ImmutableList.<PaymentInfoEvent>of();
+ } else {
+ return paymentSqlDao.getPaymentInfoList(invoiceIds);
+ }
+ }
+
+ @Override
+ public PaymentInfoEvent getLastPaymentInfo(List<String> invoiceIds) {
if (invoiceIds == null || invoiceIds.size() == 0) {
- return ImmutableList.<PaymentInfo>of();
+ return null;
} else {
- return sqlDao.getPaymentInfos(invoiceIds);
+ return paymentSqlDao.getLastPaymentInfo(invoiceIds);
}
}
@@ -154,18 +180,18 @@ public class AuditedPaymentDao implements PaymentDao {
if (invoiceIds == null || invoiceIds.size() == 0) {
return ImmutableList.<PaymentAttempt>of();
} else {
- return sqlDao.getPaymentAttemptsForInvoiceIds(invoiceIds);
+ return paymentAttemptSqlDao.getPaymentAttemptsForInvoiceIds(invoiceIds);
}
}
@Override
public PaymentAttempt getPaymentAttemptById(UUID paymentAttemptId) {
- return sqlDao.getPaymentAttemptById(paymentAttemptId.toString());
+ return paymentAttemptSqlDao.getPaymentAttemptById(paymentAttemptId.toString());
}
@Override
- public PaymentInfo getPaymentInfoForPaymentAttemptId(String paymentAttemptIdStr) {
- return sqlDao.getPaymentInfoForPaymentAttemptId(paymentAttemptIdStr);
+ public PaymentInfoEvent getPaymentInfoForPaymentAttemptId(String paymentAttemptIdStr) {
+ return paymentSqlDao.getPaymentInfoForPaymentAttemptId(paymentAttemptIdStr);
}
}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptHistoryBinder.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptHistoryBinder.java
new file mode 100644
index 0000000..569ef40
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptHistoryBinder.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.dao;
+
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.util.dao.BinderBase;
+import com.ning.billing.util.dao.EntityHistory;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(PaymentAttemptHistoryBinder.PaymentAttemptHistoryBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface PaymentAttemptHistoryBinder {
+ public static class PaymentAttemptHistoryBinderFactory extends BinderBase implements BinderFactory {
+ @Override
+ public Binder<PaymentAttemptHistoryBinder, EntityHistory<PaymentAttempt>> build(Annotation annotation) {
+ return new Binder<PaymentAttemptHistoryBinder, EntityHistory<PaymentAttempt>>() {
+ @Override
+ public void bind(SQLStatement q, PaymentAttemptHistoryBinder bind, EntityHistory<PaymentAttempt> history) {
+ q.bind("recordId", history.getValue());
+ q.bind("changeType", history.getChangeType().toString());
+
+ PaymentAttempt paymentAttempt = history.getEntity();
+ q.bind("id", paymentAttempt.getId().toString());
+ q.bind("invoiceId", paymentAttempt.getInvoiceId().toString());
+ q.bind("accountId", paymentAttempt.getAccountId().toString());
+ q.bind("amount", paymentAttempt.getAmount());
+ q.bind("currency", paymentAttempt.getCurrency().toString());
+ q.bind("invoiceDate", getDate(paymentAttempt.getInvoiceDate()));
+ q.bind("paymentAttemptDate", getDate(paymentAttempt.getPaymentAttemptDate()));
+ q.bind("paymentId", paymentAttempt.getPaymentId() == null ? null : paymentAttempt.getPaymentId().toString());
+ q.bind("retryCount", paymentAttempt.getRetryCount());
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptSqlDao.java
new file mode 100644
index 0000000..a1fed90
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentAttemptSqlDao.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.dao;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.payment.api.DefaultPaymentAttempt;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
+import com.ning.billing.util.dao.BinderBase;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.MapperBase;
+import com.ning.billing.util.entity.dao.UpdatableEntitySqlDao;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+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 org.skife.jdbi.v2.unstable.BindIn;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.UUID;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(PaymentAttemptSqlDao.PaymentAttemptMapper.class)
+public interface PaymentAttemptSqlDao extends Transactional<PaymentAttemptSqlDao>, UpdatableEntitySqlDao<PaymentAttempt>, CloseMe {
+ @SqlUpdate
+ void insertPaymentAttempt(@Bind(binder = PaymentAttemptBinder.class) PaymentAttempt paymentAttempt,
+ @CallContextBinder CallContext context);
+
+ @SqlQuery
+ PaymentAttempt getPaymentAttemptForPaymentId(@Bind("paymentId") String paymentId);
+
+ @SqlQuery
+ PaymentAttempt getPaymentAttemptById(@Bind("id") String paymentAttemptId);
+
+ @SqlQuery
+ List<PaymentAttempt> getPaymentAttemptsForInvoiceId(@Bind("invoiceId") String invoiceId);
+
+ @SqlQuery
+ List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(@BindIn("invoiceIds") List<String> invoiceIds);
+
+
+ @SqlUpdate
+ void updatePaymentAttemptWithPaymentId(@Bind("id") String paymentAttemptId,
+ @Bind("payment_id") String paymentId,
+ @CallContextBinder CallContext context);
+
+ @SqlUpdate
+ void updatePaymentAttemptWithRetryInfo(@Bind("id") String paymentAttemptId,
+ @Bind("retry_count") int retryCount,
+ @CallContextBinder CallContext context);
+
+ @Override
+ @SqlUpdate
+ public void insertHistoryFromTransaction(@PaymentAttemptHistoryBinder final EntityHistory<PaymentAttempt> account,
+ @CallContextBinder final CallContext context);
+
+ public static class PaymentAttemptMapper extends MapperBase implements ResultSetMapper<PaymentAttempt> {
+ @Override
+ public PaymentAttempt map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
+
+ UUID paymentAttemptId = getUUID(rs, "id");
+ UUID invoiceId = getUUID(rs, "invoice_id");
+ UUID accountId = getUUID(rs, "account_id");
+ BigDecimal amount = rs.getBigDecimal("amount");
+ Currency currency = Currency.valueOf(rs.getString("currency"));
+ DateTime invoiceDate = getDate(rs, "invoice_date");
+ DateTime paymentAttemptDate = getDate(rs, "payment_attempt_date");
+ UUID paymentId = getUUID(rs, "payment_id");
+ Integer retryCount = rs.getInt("retry_count");
+ DateTime createdDate = getDate(rs, "created_date");
+ DateTime updatedDate = getDate(rs, "updated_date");
+
+ return new DefaultPaymentAttempt(paymentAttemptId,
+ invoiceId,
+ accountId,
+ amount,
+ currency,
+ invoiceDate,
+ paymentAttemptDate,
+ paymentId,
+ retryCount,
+ createdDate, updatedDate);
+ }
+ }
+
+ public static final class PaymentAttemptBinder extends BinderBase implements Binder<Bind, PaymentAttempt> {
+ @Override
+ public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, PaymentAttempt paymentAttempt) {
+ stmt.bind("id", paymentAttempt.getId().toString());
+ stmt.bind("invoiceId", paymentAttempt.getInvoiceId().toString());
+ stmt.bind("accountId", paymentAttempt.getAccountId().toString());
+ stmt.bind("amount", paymentAttempt.getAmount());
+ stmt.bind("currency", paymentAttempt.getCurrency().toString());
+ stmt.bind("invoiceDate", getDate(paymentAttempt.getInvoiceDate()));
+ stmt.bind("paymentAttemptDate", getDate(paymentAttempt.getPaymentAttemptDate()));
+ stmt.bind("paymentId", paymentAttempt.getPaymentId() == null ? null : paymentAttempt.getPaymentId().toString());
+ stmt.bind("retryCount", paymentAttempt.getRetryCount());
+ }
+ }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
index ce6adf6..581ed71 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
@@ -21,7 +21,7 @@ import java.util.UUID;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentInfoEvent;
import com.ning.billing.util.callcontext.CallContext;
public interface PaymentDao {
@@ -29,19 +29,21 @@ public interface PaymentDao {
PaymentAttempt createPaymentAttempt(Invoice invoice, CallContext context);
PaymentAttempt createPaymentAttempt(PaymentAttempt paymentAttempt, CallContext context);
- void savePaymentInfo(PaymentInfo right, CallContext context);
+ void savePaymentInfo(PaymentInfoEvent right, CallContext context);
- PaymentAttempt getPaymentAttemptForPaymentId(String paymentId);
+ PaymentAttempt getPaymentAttemptForPaymentId(UUID paymentId);
List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(List<String> invoiceIds);
- void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, String paymentId, CallContext context);
+ void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, UUID paymentId, CallContext context);
List<PaymentAttempt> getPaymentAttemptsForInvoiceId(String invoiceId);
- void updatePaymentInfo(String paymentMethodType, String paymentId, String cardType, String cardCountry, CallContext context);
+ void updatePaymentInfo(String paymentMethodType, UUID paymentId, String cardType, String cardCountry, CallContext context);
- List<PaymentInfo> getPaymentInfo(List<String> invoiceIds);
+ List<PaymentInfoEvent> getPaymentInfoList(List<String> invoiceIds);
+
+ PaymentInfoEvent getLastPaymentInfo(List<String> invoiceIds);
PaymentAttempt getPaymentAttemptById(UUID paymentAttemptId);
- PaymentInfo getPaymentInfoForPaymentAttemptId(String paymentAttemptId);
+ PaymentInfoEvent getPaymentInfoForPaymentAttemptId(String paymentAttemptId);
}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentHistoryBinder.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentHistoryBinder.java
new file mode 100644
index 0000000..2ec9fc7
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentHistoryBinder.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.payment.dao;
+
+import com.ning.billing.payment.api.PaymentInfoEvent;
+import com.ning.billing.util.dao.BinderBase;
+import com.ning.billing.util.dao.EntityHistory;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(PaymentHistoryBinder.PaymentHistoryBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface PaymentHistoryBinder {
+ public static class PaymentHistoryBinderFactory extends BinderBase implements BinderFactory {
+ @Override
+ public Binder<PaymentHistoryBinder, EntityHistory<PaymentInfoEvent>> build(Annotation annotation) {
+ return new Binder<PaymentHistoryBinder, EntityHistory<PaymentInfoEvent>>() {
+ @Override
+ public void bind(SQLStatement q, PaymentHistoryBinder bind, EntityHistory<PaymentInfoEvent> history) {
+ q.bind("recordId", history.getValue());
+ q.bind("changeType", history.getChangeType().toString());
+
+ PaymentInfoEvent paymentInfo = history.getEntity();
+ q.bind("id", paymentInfo.getId().toString());
+ q.bind("amount", paymentInfo.getAmount());
+ q.bind("refund_amount", paymentInfo.getRefundAmount());
+ q.bind("payment_number", paymentInfo.getPaymentNumber());
+ q.bind("bank_identification_number", paymentInfo.getBankIdentificationNumber());
+ q.bind("status", paymentInfo.getStatus());
+ q.bind("payment_type", paymentInfo.getType());
+ q.bind("reference_id", paymentInfo.getReferenceId());
+ q.bind("payment_method_id", paymentInfo.getPaymentMethodId());
+ q.bind("payment_method", paymentInfo.getPaymentMethod());
+ q.bind("card_type", paymentInfo.getCardType());
+ q.bind("card_country", paymentInfo.getCardCountry());
+ q.bind("effective_date", getDate(paymentInfo.getEffectiveDate()));
+ }
+ };
+ }
+ }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
index 96d7e0b..bdd9829 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
@@ -25,7 +25,9 @@ import java.util.UUID;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.CallContextBinder;
import com.ning.billing.util.dao.BinderBase;
+import com.ning.billing.util.dao.EntityHistory;
import com.ning.billing.util.dao.MapperBase;
+import com.ning.billing.util.entity.dao.UpdatableEntitySqlDao;
import org.joda.time.DateTime;
import org.skife.jdbi.v2.SQLStatement;
import org.skife.jdbi.v2.StatementContext;
@@ -36,125 +38,48 @@ import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
import org.skife.jdbi.v2.tweak.ResultSetMapper;
import org.skife.jdbi.v2.unstable.BindIn;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.DefaultPaymentInfoEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
@ExternalizedSqlViaStringTemplate3()
-@RegisterMapper({PaymentSqlDao.PaymentAttemptMapper.class, PaymentSqlDao.PaymentInfoMapper.class})
-public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, CloseMe, Transmogrifier {
- @SqlUpdate
- void insertPaymentAttempt(@Bind(binder = PaymentAttemptBinder.class) PaymentAttempt paymentAttempt,
- @CallContextBinder CallContext context);
-
- @SqlUpdate
- void insertPaymentAttemptHistory(@Bind("historyRecordId") final String historyRecordId,
- @Bind(binder = PaymentAttemptBinder.class) final PaymentAttempt paymentAttempt,
- @CallContextBinder final CallContext context);
-
- @SqlQuery
- PaymentAttempt getPaymentAttemptForPaymentId(@Bind("payment_id") String paymentId);
-
- @SqlQuery
- PaymentAttempt getPaymentAttemptById(@Bind("payment_attempt_id") String paymentAttemptId);
-
+@RegisterMapper(PaymentSqlDao.PaymentInfoMapper.class)
+public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, UpdatableEntitySqlDao<PaymentInfoEvent>, CloseMe {
@SqlQuery
- List<PaymentAttempt> getPaymentAttemptsForInvoiceId(@Bind("invoice_id") String invoiceId);
-
- @SqlQuery
- List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(@BindIn("invoiceIds") List<String> invoiceIds);
-
- @SqlQuery
- PaymentInfo getPaymentInfoForPaymentAttemptId(@Bind("payment_attempt_id") String paymentAttemptId);
-
- @SqlUpdate
- void updatePaymentAttemptWithPaymentId(@Bind("payment_attempt_id") String paymentAttemptId,
- @Bind("payment_id") String paymentId,
- @CallContextBinder CallContext context);
-
- @SqlUpdate
- void updatePaymentAttemptWithRetryInfo(@Bind("payment_attempt_id") String paymentAttemptId,
- @Bind("retry_count") int retryCount,
- @CallContextBinder CallContext context);
+ PaymentInfoEvent getPaymentInfoForPaymentAttemptId(@Bind("payment_attempt_id") String paymentAttemptId);
@SqlUpdate
void updatePaymentInfo(@Bind("payment_method") String paymentMethod,
- @Bind("payment_id") String paymentId,
+ @Bind("id") String paymentId,
@Bind("card_type") String cardType,
@Bind("card_country") String cardCountry,
@CallContextBinder CallContext context);
@SqlQuery
- List<PaymentInfo> getPaymentInfos(@BindIn("invoiceIds") final List<String> invoiceIds);
+ List<PaymentInfoEvent> getPaymentInfoList(@BindIn("invoiceIds") final List<String> invoiceIds);
- @SqlUpdate
- void insertPaymentInfo(@Bind(binder = PaymentInfoBinder.class) final PaymentInfo paymentInfo,
- @CallContextBinder final CallContext context);
+ @SqlQuery
+ PaymentInfoEvent getLastPaymentInfo(@BindIn("invoiceIds") final List<String> invoiceIds);
@SqlUpdate
- void insertPaymentInfoHistory(@Bind("historyRecordId") final String historyRecordId,
- @Bind(binder = PaymentInfoBinder.class) final PaymentInfo paymentInfo,
- @CallContextBinder final CallContext context);
+ void insertPaymentInfo(@Bind(binder = PaymentInfoBinder.class) final PaymentInfoEvent paymentInfo,
+ @CallContextBinder final CallContext context);
@SqlQuery
- PaymentInfo getPaymentInfo(@Bind("paymentId") final String paymentId);
-
- public static final class PaymentAttemptBinder extends BinderBase implements Binder<Bind, PaymentAttempt> {
- @Override
- public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, PaymentAttempt paymentAttempt) {
- stmt.bind("payment_attempt_id", paymentAttempt.getPaymentAttemptId().toString());
- stmt.bind("invoice_id", paymentAttempt.getInvoiceId().toString());
- stmt.bind("account_id", paymentAttempt.getAccountId().toString());
- stmt.bind("amount", paymentAttempt.getAmount());
- stmt.bind("currency", paymentAttempt.getCurrency().toString());
- stmt.bind("invoice_dt", getDate(paymentAttempt.getInvoiceDate()));
- stmt.bind("payment_attempt_dt", getDate(paymentAttempt.getPaymentAttemptDate()));
- stmt.bind("payment_id", paymentAttempt.getPaymentId());
- stmt.bind("retry_count", paymentAttempt.getRetryCount());
- stmt.bind("created_dt", getDate(paymentAttempt.getCreatedDate()));
- stmt.bind("updated_dt", getDate(paymentAttempt.getUpdatedDate()));
- }
- }
+ PaymentInfoEvent getPaymentInfo(@Bind("id") final String paymentId);
- public static class PaymentAttemptMapper extends MapperBase implements ResultSetMapper<PaymentAttempt> {
- @Override
- public PaymentAttempt map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
-
- UUID paymentAttemptId = UUID.fromString(rs.getString("payment_attempt_id"));
- UUID invoiceId = UUID.fromString(rs.getString("invoice_id"));
- UUID accountId = UUID.fromString(rs.getString("account_id"));
- BigDecimal amount = rs.getBigDecimal("amount");
- Currency currency = Currency.valueOf(rs.getString("currency"));
- DateTime invoiceDate = getDate(rs, "invoice_dt");
- DateTime paymentAttemptDate = getDate(rs, "payment_attempt_dt");
- String paymentId = rs.getString("payment_id");
- Integer retryCount = rs.getInt("retry_count");
- DateTime createdDate = getDate(rs, "created_dt");
- DateTime updatedDate = getDate(rs, "updated_dt");
-
- return new PaymentAttempt(paymentAttemptId,
- invoiceId,
- accountId,
- amount,
- currency,
- invoiceDate,
- paymentAttemptDate,
- paymentId,
- retryCount,
- createdDate,
- updatedDate);
- }
- }
+ @Override
+ @SqlUpdate
+ public void insertHistoryFromTransaction(@PaymentHistoryBinder final EntityHistory<PaymentInfoEvent> account,
+ @CallContextBinder final CallContext context);
- public static final class PaymentInfoBinder extends BinderBase implements Binder<Bind, PaymentInfo> {
+ public static final class PaymentInfoBinder extends BinderBase implements Binder<Bind, PaymentInfoEvent> {
@Override
- public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, PaymentInfo paymentInfo) {
- stmt.bind("payment_id", paymentInfo.getPaymentId().toString());
+ public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, PaymentInfoEvent paymentInfo) {
+ stmt.bind("id", paymentInfo.getId().toString());
stmt.bind("amount", paymentInfo.getAmount());
stmt.bind("refund_amount", paymentInfo.getRefundAmount());
stmt.bind("payment_number", paymentInfo.getPaymentNumber());
@@ -166,17 +91,14 @@ public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, CloseMe, Tr
stmt.bind("payment_method", paymentInfo.getPaymentMethod());
stmt.bind("card_type", paymentInfo.getCardType());
stmt.bind("card_country", paymentInfo.getCardCountry());
- stmt.bind("effective_dt", getDate(paymentInfo.getEffectiveDate()));
- stmt.bind("created_dt", getDate(paymentInfo.getCreatedDate()));
- stmt.bind("updated_dt", getDate(paymentInfo.getUpdatedDate()));
+ stmt.bind("effective_date", getDate(paymentInfo.getEffectiveDate()));
}
}
- public static class PaymentInfoMapper extends MapperBase implements ResultSetMapper<PaymentInfo> {
+ public static class PaymentInfoMapper extends MapperBase implements ResultSetMapper<PaymentInfoEvent> {
@Override
- public PaymentInfo map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
-
- String paymentId = rs.getString("payment_id");
+ public PaymentInfoEvent map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
+ UUID id = getUUID(rs, "id");
BigDecimal amount = rs.getBigDecimal("amount");
BigDecimal refundAmount = rs.getBigDecimal("refund_amount");
String paymentNumber = rs.getString("payment_number");
@@ -187,12 +109,12 @@ public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, CloseMe, Tr
String paymentMethodId = rs.getString("payment_method_id");
String paymentMethod = rs.getString("payment_method");
String cardType = rs.getString("card_type");
- String cardCountry = rs.getString("card_country");
- DateTime effectiveDate = getDate(rs, "effective_dt");
- DateTime createdDate = getDate(rs, "created_dt");
- DateTime updatedDate = getDate(rs, "updated_dt");
+ String cardCountry = rs.getString("card_country");
+ DateTime effectiveDate = getDate(rs, "effective_date");
- return new PaymentInfo(paymentId,
+ UUID userToken = null; //rs.getString("user_token") != null ? UUID.fromString(rs.getString("user_token")) : null;
+
+ return new DefaultPaymentInfoEvent(id,
amount,
refundAmount,
bankIdentificationNumber,
@@ -204,9 +126,8 @@ public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, CloseMe, Tr
paymentMethod,
cardType,
cardCountry,
- effectiveDate,
- createdDate,
- updatedDate);
+ userToken,
+ effectiveDate);
}
}
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java
index 280d1e0..94a97db 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java
@@ -25,21 +25,22 @@ import org.joda.time.DateTimeZone;
import com.ning.billing.account.api.Account;
import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.DefaultPaymentErrorEvent;
+import com.ning.billing.payment.api.DefaultPaymentInfoEvent;
import com.ning.billing.payment.api.Either;
-import com.ning.billing.payment.api.PaymentError;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
import com.ning.billing.payment.api.PaymentMethodInfo;
import com.ning.billing.payment.api.PaymentProviderAccount;
public class NoOpPaymentProviderPlugin implements PaymentProviderPlugin {
@Override
- public Either<PaymentError, PaymentInfo> processInvoice(Account account, Invoice invoice) {
- PaymentInfo payment = new PaymentInfo.Builder()
- .setPaymentId(UUID.randomUUID().toString())
+ public Either<PaymentErrorEvent, PaymentInfoEvent> processInvoice(Account account, Invoice invoice) {
+ PaymentInfoEvent payment = new DefaultPaymentInfoEvent.Builder()
+ .setId(UUID.randomUUID())
.setAmount(invoice.getBalance())
.setStatus("Processed")
- .setCreatedDate(new DateTime(DateTimeZone.UTC))
.setEffectiveDate(new DateTime(DateTimeZone.UTC))
.setType("Electronic")
.build();
@@ -47,25 +48,25 @@ public class NoOpPaymentProviderPlugin implements PaymentProviderPlugin {
}
@Override
- public Either<PaymentError, PaymentInfo> getPaymentInfo(String paymentId) {
+ public Either<PaymentErrorEvent, PaymentInfoEvent> getPaymentInfo(String paymentId) {
return Either.right(null);
}
@Override
- public Either<PaymentError, String> createPaymentProviderAccount(Account account) {
- return Either.left(new PaymentError("unsupported",
- "Account creation not supported in this plugin",
+ public Either<PaymentErrorEvent, String> createPaymentProviderAccount(Account account) {
+ return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("unsupported",
+ "ACCOUNT creation not supported in this plugin",
account.getId(),
- null));
+ null, null));
}
@Override
- public Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
+ public Either<PaymentErrorEvent, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
return Either.right(null);
}
@Override
- public Either<PaymentError, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+ public Either<PaymentErrorEvent, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
return Either.right(null);
}
@@ -74,38 +75,44 @@ public class NoOpPaymentProviderPlugin implements PaymentProviderPlugin {
}
@Override
- public Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+ public Either<PaymentErrorEvent, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
return Either.right(paymentMethod);
}
@Override
- public Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId) {
+ public Either<PaymentErrorEvent, Void> deletePaymentMethod(String accountKey, String paymentMethodId) {
return Either.right(null);
}
@Override
- public Either<PaymentError, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId) {
+ public Either<PaymentErrorEvent, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId) {
return Either.right(null);
}
@Override
- public Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(final String accountKey) {
+ public Either<PaymentErrorEvent, List<PaymentMethodInfo>> getPaymentMethods(final String accountKey) {
return Either.right(Arrays.<PaymentMethodInfo>asList());
}
@Override
- public Either<PaymentError, Void> updatePaymentGateway(String accountKey) {
+ public Either<PaymentErrorEvent, Void> updatePaymentGateway(String accountKey) {
return Either.right(null);
}
@Override
- public Either<PaymentError, Void> updatePaymentProviderAccountExistingContact(Account account) {
+ public Either<PaymentErrorEvent, Void> updatePaymentProviderAccountExistingContact(Account account) {
return Either.right(null);
}
@Override
- public Either<PaymentError, Void> updatePaymentProviderAccountWithNewContact(Account account) {
+ public Either<PaymentErrorEvent, Void> updatePaymentProviderAccountWithNewContact(Account account) {
return Either.right(null);
}
+ @Override
+ public List<Either<PaymentErrorEvent, PaymentInfoEvent>> processRefund(Account account) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
}
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java
index 7a9ae71..2daa160 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java
@@ -21,26 +21,27 @@ import java.util.List;
import com.ning.billing.account.api.Account;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.payment.api.Either;
-import com.ning.billing.payment.api.PaymentError;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
import com.ning.billing.payment.api.PaymentMethodInfo;
import com.ning.billing.payment.api.PaymentProviderAccount;
public interface PaymentProviderPlugin {
- Either<PaymentError, PaymentInfo> processInvoice(Account account, Invoice invoice);
- Either<PaymentError, String> createPaymentProviderAccount(Account account);
+ Either<PaymentErrorEvent, PaymentInfoEvent> processInvoice(Account account, Invoice invoice);
+ Either<PaymentErrorEvent, String> createPaymentProviderAccount(Account account);
- Either<PaymentError, PaymentInfo> getPaymentInfo(String paymentId);
- Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey);
- Either<PaymentError, Void> updatePaymentGateway(String accountKey);
+ Either<PaymentErrorEvent, PaymentInfoEvent> getPaymentInfo(String paymentId);
+ Either<PaymentErrorEvent, PaymentProviderAccount> getPaymentProviderAccount(String accountKey);
+ Either<PaymentErrorEvent, Void> updatePaymentGateway(String accountKey);
- Either<PaymentError, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId);
- Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountKey);
- Either<PaymentError, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod);
- Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo);
- Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId);
+ Either<PaymentErrorEvent, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId);
+ Either<PaymentErrorEvent, List<PaymentMethodInfo>> getPaymentMethods(String accountKey);
+ Either<PaymentErrorEvent, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod);
+ Either<PaymentErrorEvent, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo);
+ Either<PaymentErrorEvent, Void> deletePaymentMethod(String accountKey, String paymentMethodId);
- Either<PaymentError, Void> updatePaymentProviderAccountExistingContact(Account account);
- Either<PaymentError, Void> updatePaymentProviderAccountWithNewContact(Account account);
+ Either<PaymentErrorEvent, Void> updatePaymentProviderAccountExistingContact(Account account);
+ Either<PaymentErrorEvent, Void> updatePaymentProviderAccountWithNewContact(Account account);
+ List<Either<PaymentErrorEvent, PaymentInfoEvent>> processRefund(Account account);
}
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPluginRegistry.java b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPluginRegistry.java
index fc9c149..bdad7c6 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPluginRegistry.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPluginRegistry.java
@@ -22,7 +22,8 @@ import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;
import com.google.inject.Inject;
-import com.ning.billing.payment.setup.PaymentConfig;
+import com.ning.billing.config.PaymentConfig;
+
public class PaymentProviderPluginRegistry {
private final String defaultPlugin;
diff --git a/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
index e8034a1..c7c359d 100644
--- a/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
@@ -18,27 +18,34 @@ package com.ning.billing.payment;
import java.util.Arrays;
import java.util.List;
+import java.util.UUID;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.DefaultCallContext;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.clock.Clock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
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.invoice.api.InvoiceCreationNotification;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.payment.api.DefaultPaymentErrorEvent;
import com.ning.billing.payment.api.Either;
import com.ning.billing.payment.api.PaymentApi;
-import com.ning.billing.payment.api.PaymentError;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentApiException;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
import com.ning.billing.util.bus.Bus;
import com.ning.billing.util.bus.Bus.EventBusException;
+import com.ning.billing.util.bus.BusEvent.BusEventType;
+import com.ning.billing.util.bus.BusEvent;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContext;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.Clock;
public class RequestProcessor {
public static final String PAYMENT_PROVIDER_KEY = "paymentProvider";
@@ -51,36 +58,49 @@ public class RequestProcessor {
@Inject
public RequestProcessor(Clock clock,
- AccountUserApi accountUserApi,
- PaymentApi paymentApi,
- PaymentProviderPluginRegistry pluginRegistry,
- Bus eventBus) {
+ AccountUserApi accountUserApi,
+ PaymentApi paymentApi,
+ PaymentProviderPluginRegistry pluginRegistry,
+ Bus eventBus) {
this.clock = clock;
this.accountUserApi = accountUserApi;
this.paymentApi = paymentApi;
this.eventBus = eventBus;
}
+
+ private void postPaymentEvent(BusEvent ev, UUID accountId) {
+ if (ev == null) {
+ return;
+ }
+ try {
+ eventBus.post(ev);
+ } catch (EventBusException e) {
+ log.error("Failed to post Payment event event for account {} ", accountId, e);
+ }
+ }
@Subscribe
- public void receiveInvoice(InvoiceCreationNotification event) {
+ public void receiveInvoice(InvoiceCreationEvent event) {
log.info("Received invoice creation notification for account {} and invoice {}", event.getAccountId(), event.getInvoiceId());
+ PaymentErrorEvent errorEvent = null;
try {
final Account account = accountUserApi.getAccountById(event.getAccountId());
-
- if (account == null) {
- log.info("could not process invoice payment: could not find a valid account for event {}", event);
- }
- else {
+ if (account != null) {
CallContext context = new DefaultCallContext("PaymentRequestProcessor", CallOrigin.INTERNAL, UserType.SYSTEM, clock);
- List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account, Arrays.asList(event.getInvoiceId().toString()), context);
- if (!results.isEmpty()) {
- Either<PaymentError, PaymentInfo> result = results.get(0);
- eventBus.post(result.isLeft() ? result.getLeft() : result.getRight());
- }
+ List<PaymentInfoEvent> results = paymentApi.createPayment(account, Arrays.asList(event.getInvoiceId().toString()), context);
+ PaymentInfoEvent infoEvent = (!results.isEmpty()) ? results.get(0) : null;
+ postPaymentEvent(infoEvent, account.getId());
+ return;
+ } else {
+ errorEvent = new DefaultPaymentErrorEvent(null, "Failed to retrieve account", event.getAccountId(), null, null);
}
+ } catch(AccountApiException e) {
+ log.error("Failed to process invoice payment", e);
+ errorEvent = new DefaultPaymentErrorEvent(null, e.getMessage(), event.getAccountId(), null, null);
+ } catch (PaymentApiException e) {
+ log.error("Failed to process invoice payment", e);
+ errorEvent = new DefaultPaymentErrorEvent(null, e.getMessage(), event.getAccountId(), null, null);
}
- catch (EventBusException ex) {
- throw new RuntimeException(ex);
- }
+ postPaymentEvent(errorEvent, event.getAccountId());
}
}
diff --git a/payment/src/main/java/com/ning/billing/payment/RetryService.java b/payment/src/main/java/com/ning/billing/payment/RetryService.java
index d03b241..2563638 100644
--- a/payment/src/main/java/com/ning/billing/payment/RetryService.java
+++ b/payment/src/main/java/com/ning/billing/payment/RetryService.java
@@ -24,16 +24,20 @@ import com.ning.billing.util.callcontext.DefaultCallContext;
import com.ning.billing.util.callcontext.UserType;
import com.ning.billing.util.clock.Clock;
import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
+import com.ning.billing.config.PaymentConfig;
import com.ning.billing.lifecycle.KillbillService;
import com.ning.billing.lifecycle.LifecycleHandlerType;
import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentApiException;
import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentInfoEvent;
import com.ning.billing.payment.api.PaymentStatus;
-import com.ning.billing.payment.setup.PaymentConfig;
+
import com.ning.billing.util.notificationq.NotificationKey;
import com.ning.billing.util.notificationq.NotificationQueue;
import com.ning.billing.util.notificationq.NotificationQueueService;
@@ -41,6 +45,9 @@ import com.ning.billing.util.notificationq.NotificationQueueService.Notification
import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
public class RetryService implements KillbillService {
+
+ private static final Logger log = LoggerFactory.getLogger(RetryService.class);
+
public static final String SERVICE_NAME = "retry-service";
public static final String QUEUE_NAME = "retry-events";
@@ -91,7 +98,7 @@ public class RetryService implements KillbillService {
}
public void scheduleRetry(PaymentAttempt paymentAttempt, DateTime timeOfRetry) {
- final String id = paymentAttempt.getPaymentAttemptId().toString();
+ final String id = paymentAttempt.getId().toString();
NotificationKey key = new NotificationKey() {
@Override
@@ -99,19 +106,21 @@ public class RetryService implements KillbillService {
return id;
}
};
- retryQueue.recordFutureNotification(timeOfRetry, key);
+
+ if (retryQueue != null) {
+ retryQueue.recordFutureNotification(timeOfRetry, key);
+ }
}
private void retry(String paymentAttemptId, CallContext context) {
- PaymentInfo paymentInfo = paymentApi.getPaymentInfoForPaymentAttemptId(paymentAttemptId);
-
- if (paymentInfo != null && PaymentStatus.Processed.equals(PaymentStatus.valueOf(paymentInfo.getStatus()))) {
- // update payment attempt with success and notify invoice api of payment
- System.out.println("Found processed payment");
- }
- else {
- System.out.println("Creating payment for payment attempt " + paymentAttemptId);
+ try {
+ PaymentInfoEvent paymentInfo = paymentApi.getPaymentInfoForPaymentAttemptId(paymentAttemptId);
+ if (paymentInfo != null && PaymentStatus.Processed.equals(PaymentStatus.valueOf(paymentInfo.getStatus()))) {
+ return;
+ }
paymentApi.createPaymentForPaymentAttempt(UUID.fromString(paymentAttemptId), context);
+ } catch (PaymentApiException e) {
+ log.error(String.format("Failed to retry payment for %s"), e);
}
}
}
diff --git a/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java b/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
index 0c769fd..de4bf3c 100644
--- a/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
+++ b/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
@@ -21,6 +21,7 @@ import java.util.Properties;
import org.skife.config.ConfigurationObjectFactory;
import com.google.inject.AbstractModule;
+import com.ning.billing.config.PaymentConfig;
import com.ning.billing.payment.RequestProcessor;
import com.ning.billing.payment.RetryService;
import com.ning.billing.payment.api.DefaultPaymentApi;
diff --git a/payment/src/main/resources/com/ning/billing/payment/dao/PaymentAttemptSqlDao.sql.stg b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
new file mode 100644
index 0000000..15302fe
--- /dev/null
+++ b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
@@ -0,0 +1,106 @@
+group paymentAttemptSqlDao;
+
+paymentAttemptFields(prefix) ::= <<
+ <prefix>id,
+ <prefix>invoice_id,
+ <prefix>account_id,
+ <prefix>amount,
+ <prefix>currency,
+ <prefix>payment_id,
+ <prefix>payment_attempt_date,
+ <prefix>invoice_date,
+ <prefix>retry_count,
+ <prefix>created_by,
+ <prefix>created_date,
+ <prefix>updated_by,
+ <prefix>updated_date
+>>
+
+insertPaymentAttempt() ::= <<
+ INSERT INTO payment_attempts (<paymentAttemptFields()>)
+ VALUES (:id, :invoiceId, :accountId, :amount, :currency, :paymentId,
+ :paymentAttemptDate, :invoiceDate, :retryCount, :userName, :createdDate, :userName, :createdDate);
+>>
+
+getPaymentAttemptForPaymentId() ::= <<
+ SELECT <paymentAttemptFields()>
+ FROM payment_attempts
+ WHERE payment_id = :paymentId;
+>>
+
+getPaymentAttemptById() ::= <<
+ SELECT <paymentAttemptFields()>
+ FROM payment_attempts
+ WHERE id = :id;
+>>
+
+getPaymentAttemptsForInvoiceIds(invoiceIds) ::= <<
+ SELECT <paymentAttemptFields()>
+ FROM payment_attempts
+ WHERE invoice_id in (<invoiceIds>);
+>>
+
+getPaymentAttemptsForInvoiceId() ::= <<
+ SELECT <paymentAttemptFields()>
+ FROM payment_attempts
+ WHERE invoice_id = :invoiceId;
+>>
+
+updatePaymentAttemptWithPaymentId() ::= <<
+ UPDATE payment_attempts
+ SET payment_id = :payment_id,
+ updated_by = :userName,
+ updated_date = :updatedDate
+ WHERE id = :id;
+>>
+
+historyFields(prefix) ::= <<
+ record_id,
+ id,
+ account_id,
+ invoice_id,
+ amount,
+ currency,
+ payment_attempt_date,
+ payment_id,
+ retry_count,
+ invoice_date,
+ created_by,
+ created_date,
+ updated_by,
+ updated_date
+>>
+
+insertHistoryFromTransaction() ::= <<
+ INSERT INTO payment_attempt_history (<historyFields()>)
+ VALUES (:recordId, :id, :accountId, :invoiceId, :amount, :currency, :paymentAttemptDate, :paymentId,
+ :retryCount, :invoiceDate, :userName, :createdDate, :userName, :updatedDate);
+>>
+
+getRecordId() ::= <<
+ SELECT record_id
+ FROM payment_attempts
+ WHERE id = :id;
+>>
+
+getHistoryRecordId() ::= <<
+ SELECT MAX(history_record_id)
+ FROM payment_attempt_history
+ WHERE record_id = :recordId;
+>>
+
+auditFields(prefix) ::= <<
+ <prefix>table_name,
+ <prefix>record_id,
+ <prefix>change_type,
+ <prefix>change_date,
+ <prefix>changed_by,
+ <prefix>reason_code,
+ <prefix>comments,
+ <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+ INSERT INTO audit_log(<auditFields()>)
+ VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
diff --git a/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
index 87c8d8f..3f125ab 100644
--- a/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
+++ b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
@@ -1,23 +1,7 @@
group PaymentSqlDao;
-paymentAttemptFields(prefix) ::= <<
- <prefix>payment_attempt_id,
- <prefix>invoice_id,
- <prefix>account_id,
- <prefix>amount,
- <prefix>currency,
- <prefix>payment_id,
- <prefix>payment_attempt_dt,
- <prefix>invoice_dt,
- <prefix>retry_count,
- <prefix>created_by,
- <prefix>created_dt,
- <prefix>updated_by,
- <prefix>updated_dt
->>
-
paymentInfoFields(prefix) ::= <<
- <prefix>payment_id,
+ <prefix>id,
<prefix>amount,
<prefix>refund_amount,
<prefix>bank_identification_number,
@@ -29,69 +13,18 @@ paymentInfoFields(prefix) ::= <<
<prefix>payment_method,
<prefix>card_type,
<prefix>card_country,
- <prefix>effective_dt,
+ <prefix>effective_date,
<prefix>created_by,
- <prefix>created_dt,
+ <prefix>created_date,
<prefix>updated_by,
- <prefix>updated_dt
->>
-
-insertPaymentAttempt() ::= <<
- INSERT INTO payment_attempts (<paymentAttemptFields()>)
- VALUES (:payment_attempt_id, :invoice_id, :account_id, :amount, :currency, :payment_id,
- :payment_attempt_dt, :invoice_dt, :retry_count, :userName, :createdDate, :userName, :createdDate);
->>
-
-insertPaymentAttemptHistory() ::= <<
- INSERT INTO payment_attempt_history (history_record_id, <paymentAttemptFields()>)
- VALUES (:historyRecordId, :payment_attempt_id, :invoice_id, :account_id, :amount, :currency, :payment_id,
- :payment_attempt_dt, :invoice_dt, :retry_count, :userName, :createdDate, :userName, :createdDate);
->>
-
-getPaymentAttemptForPaymentId() ::= <<
- SELECT <paymentAttemptFields()>
- FROM payment_attempts
- WHERE payment_id = :payment_id
->>
-
-getPaymentAttemptById() ::= <<
- SELECT <paymentAttemptFields()>
- FROM payment_attempts
- WHERE payment_attempt_id = :payment_attempt_id
->>
-
-getPaymentAttemptsForInvoiceIds(invoiceIds) ::= <<
- SELECT <paymentAttemptFields()>
- FROM payment_attempts
- WHERE invoice_id in (<invoiceIds>)
->>
-
-getPaymentAttemptsForInvoiceId() ::= <<
- SELECT <paymentAttemptFields()>
- FROM payment_attempts
- WHERE invoice_id = :invoice_id
->>
-
-updatePaymentAttemptWithPaymentId() ::= <<
- UPDATE payment_attempts
- SET payment_id = :payment_id,
- updated_by = :userName,
- updated_dt = :updatedDate
- WHERE payment_attempt_id = :payment_attempt_id
+ <prefix>updated_date
>>
insertPaymentInfo() ::= <<
INSERT INTO payments (<paymentInfoFields()>)
- VALUES (:payment_id, :amount, :refund_amount, :bank_identification_number, :payment_number,
+ VALUES (:id, :amount, :refund_amount, :bank_identification_number, :payment_number,
:payment_type, :status, :reference_id, :payment_method_id, :payment_method, :card_type,
- :card_country, :effective_dt, :userName, :createdDate, :userName, :createdDate);
->>
-
-insertPaymentInfoHistory() ::= <<
- INSERT INTO payment_history (history_record_id, <paymentInfoFields()>)
- VALUES (:historyRecordId, :payment_id, :amount, :refund_amount, :bank_identification_number, :payment_number,
- :payment_type, :status, :reference_id, :payment_method_id, :payment_method, :card_type,
- :card_country, :effective_dt, :userName, :createdDate, :userName, :createdDate);
+ :card_country, :effective_date, :userName, :createdDate, :userName, :createdDate);
>>
updatePaymentInfo() ::= <<
@@ -100,26 +33,92 @@ updatePaymentInfo() ::= <<
card_type = :card_type,
card_country = :card_country,
updated_by = :userName,
- updated_dt = :updatedDate
- WHERE payment_id = :payment_id
+ updated_date = :updatedDate
+ WHERE id = :id
>>
-getPaymentInfos(invoiceIds) ::= <<
+getPaymentInfoList(invoiceIds) ::= <<
SELECT <paymentInfoFields("p.")>
FROM payments p, payment_attempts pa
- WHERE pa.invoice_id in (<invoiceIds>)
- AND pa.payment_id = p.payment_id
+ WHERE pa.invoice_id in (<invoiceIds>)
+ AND pa.payment_id = p.id
+>>
+
+getLastPaymentInfo(invoiceIds) ::= <<
+ SELECT <paymentInfoFields("p.")>
+ FROM payments p, payment_attempts pa
+ WHERE pa.invoice_id in (<invoiceIds>)
+ AND pa.payment_id = p.id
+ ORDER BY p.created_date DESC
+ LIMIT 1;
>>
getPaymentInfoForPaymentAttemptId() ::= <<
SELECT <paymentInfoFields("p.")>
FROM payments p, payment_attempts pa
- WHERE pa.payment_attempt_id = :payment_attempt_id
- AND pa.payment_id = p.payment_id
+ WHERE pa.payment_attempt_id = :payment_attempt_id
+ AND pa.payment_id = p.id
>>
getPaymentInfo() ::= <<
SELECT <paymentInfoFields()>
FROM payments
- WHERE payment_id = :paymentId
+ WHERE id = :id
+>>
+
+historyFields(prefix) ::= <<
+ record_id,
+ id,
+ amount,
+ refund_amount,
+ payment_number,
+ bank_identification_number,
+ status,
+ reference_id,
+ payment_type,
+ payment_method_id,
+ payment_method,
+ card_type,
+ card_country,
+ effective_date,
+ created_by,
+ created_date,
+ updated_by,
+ updated_date
+>>
+
+insertHistoryFromTransaction() ::= <<
+ INSERT INTO payment_history (<historyFields()>)
+ VALUES (:recordId, :id, :amount, :refund_amount, :bank_identification_number, :payment_number,
+ :payment_type, :status, :reference_id, :payment_method_id, :payment_method, :card_type,
+ :card_country, :effective_date, :userName, :createdDate, :userName, :updatedDate);
+>>
+
+getRecordId() ::= <<
+ SELECT record_id
+ FROM payments
+ WHERE id = :id;
+>>
+
+getHistoryRecordId() ::= <<
+ SELECT MAX(history_record_id)
+ FROM payment_history
+ WHERE record_id = :recordId;
>>
+
+auditFields(prefix) ::= <<
+ <prefix>table_name,
+ <prefix>record_id,
+ <prefix>change_type,
+ <prefix>change_date,
+ <prefix>changed_by,
+ <prefix>reason_code,
+ <prefix>comments,
+ <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+ INSERT INTO audit_log(<auditFields()>)
+ VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
diff --git a/payment/src/main/resources/com/ning/billing/payment/ddl.sql b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
index 3a344f9..c77945f 100644
--- a/payment/src/main/resources/com/ning/billing/payment/ddl.sql
+++ b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
@@ -1,81 +1,90 @@
DROP TABLE IF EXISTS payment_attempts;
CREATE TABLE payment_attempts (
- payment_attempt_id char(36) COLLATE utf8_bin NOT NULL,
- account_id char(36) COLLATE utf8_bin NOT NULL,
- invoice_id char(36) COLLATE utf8_bin NOT NULL,
- amount decimal(8,2),
- currency char(3),
- payment_attempt_dt datetime NOT NULL,
- payment_id varchar(36) COLLATE utf8_bin,
- retry_count tinyint,
- invoice_dt datetime NOT NULL,
- created_by varchar(50) NOT NULL,
- created_dt datetime NOT NULL,
- updated_by varchar(50) NOT NULL,
- updated_dt datetime NOT NULL,
- PRIMARY KEY (payment_attempt_id)
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ id char(36) NOT NULL,
+ account_id char(36) COLLATE utf8_bin NOT NULL,
+ invoice_id char(36) COLLATE utf8_bin NOT NULL,
+ amount decimal(8,2),
+ currency char(3),
+ payment_attempt_date datetime NOT NULL,
+ payment_id varchar(36) COLLATE utf8_bin,
+ retry_count tinyint,
+ invoice_date datetime NOT NULL,
+ created_by varchar(50) NOT NULL,
+ created_date datetime NOT NULL,
+ updated_by varchar(50) NOT NULL,
+ updated_date datetime NOT NULL,
+ PRIMARY KEY (record_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
+CREATE UNIQUE INDEX payment_attempts_id ON payment_attempts(id);
+CREATE INDEX payment_attempts_account_id_invoice_id ON payment_attempts(account_id, invoice_id);
DROP TABLE IF EXISTS payment_attempt_history;
CREATE TABLE payment_attempt_history (
- history_record_id char(36) NOT NULL,
- payment_attempt_id char(36) COLLATE utf8_bin NOT NULL,
- account_id char(36) COLLATE utf8_bin NOT NULL,
- invoice_id char(36) COLLATE utf8_bin NOT NULL,
- amount decimal(8,2),
- currency char(3),
- payment_attempt_dt datetime NOT NULL,
- payment_id varchar(36) COLLATE utf8_bin,
- retry_count tinyint,
- invoice_dt datetime NOT NULL,
- created_by varchar(50) NOT NULL,
- created_dt datetime NOT NULL,
- updated_by varchar(50) NOT NULL,
- updated_dt datetime NOT NULL,
- PRIMARY KEY (history_record_id)
+ history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ record_id int(11) unsigned NOT NULL,
+ id char(36) NOT NULL,
+ account_id char(36) COLLATE utf8_bin NOT NULL,
+ invoice_id char(36) COLLATE utf8_bin NOT NULL,
+ amount decimal(8,2),
+ currency char(3),
+ payment_attempt_date datetime NOT NULL,
+ payment_id varchar(36) COLLATE utf8_bin,
+ retry_count tinyint,
+ invoice_date datetime NOT NULL,
+ created_by varchar(50) NOT NULL,
+ created_date datetime NOT NULL,
+ updated_by varchar(50) NOT NULL,
+ updated_date datetime NOT NULL,
+ PRIMARY KEY (history_record_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
+CREATE INDEX payment_attempt_history_record_id ON payment_attempt_history(record_id);
DROP TABLE IF EXISTS payments;
CREATE TABLE payments (
- payment_id varchar(36) COLLATE utf8_bin NOT NULL,
- amount decimal(8,2),
- refund_amount decimal(8,2),
- payment_number varchar(36) COLLATE utf8_bin,
- bank_identification_number varchar(36) COLLATE utf8_bin,
- status varchar(20) COLLATE utf8_bin,
- reference_id varchar(36) COLLATE utf8_bin,
- payment_type varchar(20) COLLATE utf8_bin,
- payment_method_id varchar(36) COLLATE utf8_bin,
- payment_method varchar(20) COLLATE utf8_bin,
- card_type varchar(20) COLLATE utf8_bin,
- card_country varchar(50) COLLATE utf8_bin,
- effective_dt datetime,
- created_by varchar(50) NOT NULL,
- created_dt datetime NOT NULL,
- updated_by varchar(50) NOT NULL,
- updated_dt datetime NOT NULL,
- PRIMARY KEY (payment_id)
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ id char(36) NOT NULL,
+ amount decimal(8,2),
+ refund_amount decimal(8,2),
+ payment_number varchar(36) COLLATE utf8_bin,
+ bank_identification_number varchar(36) COLLATE utf8_bin,
+ status varchar(20) COLLATE utf8_bin,
+ reference_id varchar(36) COLLATE utf8_bin,
+ payment_type varchar(20) COLLATE utf8_bin,
+ payment_method_id varchar(36) COLLATE utf8_bin,
+ payment_method varchar(20) COLLATE utf8_bin,
+ card_type varchar(20) COLLATE utf8_bin,
+ card_country varchar(50) COLLATE utf8_bin,
+ effective_date datetime,
+ created_by varchar(50) NOT NULL,
+ created_date datetime NOT NULL,
+ updated_by varchar(50) NOT NULL,
+ updated_date datetime NOT NULL,
+ PRIMARY KEY (record_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
+CREATE UNIQUE INDEX payments_id ON payments(id);
DROP TABLE IF EXISTS payment_history;
CREATE TABLE payment_history (
- history_record_id char(36) NOT NULL,
- payment_id varchar(36) COLLATE utf8_bin NOT NULL,
- amount decimal(8,2),
- refund_amount decimal(8,2),
- payment_number varchar(36) COLLATE utf8_bin,
- bank_identification_number varchar(36) COLLATE utf8_bin,
- status varchar(20) COLLATE utf8_bin,
- reference_id varchar(36) COLLATE utf8_bin,
- payment_type varchar(20) COLLATE utf8_bin,
- payment_method_id varchar(36) COLLATE utf8_bin,
- payment_method varchar(20) COLLATE utf8_bin,
- card_type varchar(20) COLLATE utf8_bin,
- card_country varchar(50) COLLATE utf8_bin,
- effective_dt datetime,
- created_by varchar(50) NOT NULL,
- created_dt datetime NOT NULL,
- updated_by varchar(50) NOT NULL,
- updated_dt datetime NOT NULL,
- PRIMARY KEY (history_record_id)
+ history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ record_id int(11) unsigned NOT NULL,
+ id char(36) NOT NULL,
+ amount decimal(8,2),
+ refund_amount decimal(8,2),
+ payment_number varchar(36) COLLATE utf8_bin,
+ bank_identification_number varchar(36) COLLATE utf8_bin,
+ status varchar(20) COLLATE utf8_bin,
+ reference_id varchar(36) COLLATE utf8_bin,
+ payment_type varchar(20) COLLATE utf8_bin,
+ payment_method_id varchar(36) COLLATE utf8_bin,
+ payment_method varchar(20) COLLATE utf8_bin,
+ card_type varchar(20) COLLATE utf8_bin,
+ card_country varchar(50) COLLATE utf8_bin,
+ effective_date datetime,
+ created_by varchar(50) NOT NULL,
+ created_date datetime NOT NULL,
+ updated_by varchar(50) NOT NULL,
+ updated_date datetime NOT NULL,
+ PRIMARY KEY (history_record_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
+CREATE INDEX payment_history_record_id ON payment_history(record_id);
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestEventJson.java b/payment/src/test/java/com/ning/billing/payment/api/TestEventJson.java
new file mode 100644
index 0000000..198e986
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestEventJson.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.payment.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+
+public class TestEventJson {
+
+
+ private ObjectMapper mapper = new ObjectMapper();
+
+ @BeforeTest(groups= {"fast"})
+ public void setup() {
+ mapper = new ObjectMapper();
+ mapper.disable(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS);
+ }
+
+ @Test(groups= {"fast"})
+ public void testPaymentErrorEvent() throws Exception {
+ PaymentErrorEvent e = new DefaultPaymentErrorEvent("credit card", "Failed payment", UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID());
+ String json = mapper.writeValueAsString(e);
+
+ Class<?> claz = Class.forName(DefaultPaymentErrorEvent.class.getName());
+ Object obj = mapper.readValue(json, claz);
+ Assert.assertTrue(obj.equals(e));
+ }
+
+ @Test(groups= {"fast"})
+ public void testPaymentInfoEvent() throws Exception {
+ PaymentInfoEvent e = new DefaultPaymentInfoEvent(UUID.randomUUID(), new BigDecimal(12), new BigDecimal(12.9), "BNP", "eeert", "success",
+ "credit", "ref", "paypal", "paypal", "", "", UUID.randomUUID(), new DateTime());
+
+ String json = mapper.writeValueAsString(e);
+
+ Class<?> clazz = Class.forName(DefaultPaymentInfoEvent.class.getName());
+ Object obj = mapper.readValue(json, clazz);
+ Assert.assertTrue(obj.equals(e));
+ }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestMockPaymentApi.java b/payment/src/test/java/com/ning/billing/payment/api/TestMockPaymentApi.java
index 7c005ee..0ff39d2 100644
--- a/payment/src/test/java/com/ning/billing/payment/api/TestMockPaymentApi.java
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestMockPaymentApi.java
@@ -16,16 +16,17 @@
package com.ning.billing.payment.api;
-import com.google.inject.Inject;
-import com.ning.billing.util.clock.Clock;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
-import com.ning.billing.account.glue.AccountModuleWithMocks;
-import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import com.google.inject.Inject;
+import com.ning.billing.mock.glue.MockJunctionModule;
import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.glue.CallContextModule;
-@Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
+@Guice(modules = { PaymentTestModuleWithMocks.class, MockClockModule.class, MockJunctionModule.class, CallContextModule.class })
@Test(groups = "fast")
public class TestMockPaymentApi extends TestPaymentApi {
@Inject
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
index ad0bd03..c7f925b 100644
--- a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
@@ -22,15 +22,11 @@ import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.DefaultCallContext;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.clock.Clock;
import org.apache.commons.lang.RandomStringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
@@ -40,15 +36,20 @@ import org.testng.annotations.Test;
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.user.AccountBuilder;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.payment.MockRecurringInvoiceItem;
import com.ning.billing.payment.TestHelper;
import com.ning.billing.util.bus.Bus;
import com.ning.billing.util.bus.Bus.EventBusException;
-import com.ning.billing.util.entity.EntityPersistenceException;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContext;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.Clock;
public abstract class TestPaymentApi {
@Inject
@@ -57,6 +58,8 @@ public abstract class TestPaymentApi {
protected PaymentApi paymentApi;
@Inject
protected TestHelper testHelper;
+ @Inject
+ protected InvoicePaymentApi invoicePaymentApi;
protected CallContext context;
@@ -76,15 +79,19 @@ public abstract class TestPaymentApi {
}
@Test(enabled=true)
- public void testCreateCreditCardPayment() throws AccountApiException, EntityPersistenceException {
+ public void testCreateCreditCardPayment() throws Exception {
+ ((ZombieControl)invoicePaymentApi).addResult("notifyOfPaymentAttempt", BrainDeadProxyFactory.ZOMBIE_VOID);
+
final DateTime now = new DateTime(DateTimeZone.UTC);
final Account account = testHelper.createTestCreditCardAccount();
final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
- final BigDecimal amount = new BigDecimal("10.00");
+ final BigDecimal amount = new BigDecimal("10.0011");
final UUID subscriptionId = UUID.randomUUID();
+ final UUID bundleId = UUID.randomUUID();
- invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(), account.getId(),
+ invoice.addInvoiceItem(new MockRecurringInvoiceItem(invoice.getId(), account.getId(),
subscriptionId,
+ bundleId,
"test plan", "test phase",
now,
now.plusMonths(1),
@@ -92,37 +99,36 @@ public abstract class TestPaymentApi {
new BigDecimal("1.0"),
Currency.USD));
- List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account.getExternalKey(), Arrays.asList(invoice.getId().toString()), context);
+ List<PaymentInfoEvent> results = paymentApi.createPayment(account.getExternalKey(), Arrays.asList(invoice.getId().toString()), context);
assertEquals(results.size(), 1);
- assertTrue(results.get(0).isRight());
- PaymentInfo paymentInfo = results.get(0).getRight();
+ PaymentInfoEvent paymentInfo = results.get(0);
- assertNotNull(paymentInfo.getPaymentId());
- assertTrue(paymentInfo.getAmount().compareTo(amount) == 0);
+ assertNotNull(paymentInfo.getId());
+ assertTrue(paymentInfo.getAmount().compareTo(amount.setScale(2, RoundingMode.HALF_EVEN)) == 0);
assertNotNull(paymentInfo.getPaymentNumber());
assertFalse(paymentInfo.getStatus().equals("Error"));
- PaymentAttempt paymentAttempt = paymentApi.getPaymentAttemptForPaymentId(paymentInfo.getPaymentId());
+ PaymentAttempt paymentAttempt = paymentApi.getPaymentAttemptForPaymentId(paymentInfo.getId());
assertNotNull(paymentAttempt);
- assertNotNull(paymentAttempt.getPaymentAttemptId());
+ assertNotNull(paymentAttempt.getId());
assertEquals(paymentAttempt.getInvoiceId(), invoice.getId());
- assertTrue(paymentAttempt.getAmount().compareTo(amount) == 0);
+ assertTrue(paymentAttempt.getAmount().compareTo(amount.setScale(2, RoundingMode.HALF_EVEN)) == 0);
assertEquals(paymentAttempt.getCurrency(), Currency.USD);
- assertEquals(paymentAttempt.getPaymentId(), paymentInfo.getPaymentId());
+ assertEquals(paymentAttempt.getPaymentId(), paymentInfo.getId());
DateTime nowTruncated = now.withMillisOfSecond(0).withSecondOfMinute(0);
DateTime paymentAttemptDateTruncated = paymentAttempt.getPaymentAttemptDate().withMillisOfSecond(0).withSecondOfMinute(0);
assertEquals(paymentAttemptDateTruncated.compareTo(nowTruncated), 0);
- List<PaymentInfo> paymentInfos = paymentApi.getPaymentInfo(Arrays.asList(invoice.getId().toString()));
+ List<PaymentInfoEvent> paymentInfos = paymentApi.getPaymentInfoList(Arrays.asList(invoice.getId().toString()));
assertNotNull(paymentInfos);
assertTrue(paymentInfos.size() > 0);
- PaymentInfo paymentInfoFromGet = paymentInfos.get(0);
+ PaymentInfoEvent paymentInfoFromGet = paymentInfos.get(0);
assertEquals(paymentInfo.getAmount(), paymentInfoFromGet.getAmount());
assertEquals(paymentInfo.getRefundAmount(), paymentInfoFromGet.getRefundAmount());
- assertEquals(paymentInfo.getPaymentId(), paymentInfoFromGet.getPaymentId());
+ assertEquals(paymentInfo.getId(), paymentInfoFromGet.getId());
assertEquals(paymentInfo.getPaymentNumber(), paymentInfoFromGet.getPaymentNumber());
assertEquals(paymentInfo.getStatus(), paymentInfoFromGet.getStatus());
assertEquals(paymentInfo.getBankIdentificationNumber(), paymentInfoFromGet.getBankIdentificationNumber());
@@ -135,7 +141,7 @@ public abstract class TestPaymentApi {
}
- private PaymentProviderAccount setupAccountWithPaypalPaymentMethod() throws AccountApiException, EntityPersistenceException {
+ private PaymentProviderAccount setupAccountWithPaypalPaymentMethod() throws Exception {
final Account account = testHelper.createTestPayPalAccount();
paymentApi.createPaymentProviderAccount(account, context);
@@ -146,90 +152,42 @@ public abstract class TestPaymentApi {
.setEmail(account.getEmail())
.setDefaultMethod(true)
.build();
- Either<PaymentError, String> paymentMethodIdOrError = paymentApi.addPaymentMethod(accountKey, paymentMethod, context);
-
- assertTrue(paymentMethodIdOrError.isRight());
- assertNotNull(paymentMethodIdOrError.getRight());
-
- Either<PaymentError, PaymentMethodInfo> paymentMethodInfoOrError = paymentApi.getPaymentMethod(accountKey, paymentMethodIdOrError.getRight());
+ String paymentMethodId = paymentApi.addPaymentMethod(accountKey, paymentMethod, context);
- assertTrue(paymentMethodInfoOrError.isRight());
- assertNotNull(paymentMethodInfoOrError.getRight());
+ PaymentMethodInfo paymentMethodInfo = paymentApi.getPaymentMethod(accountKey, paymentMethodId);
- Either<PaymentError, PaymentProviderAccount> accountOrError = paymentApi.getPaymentProviderAccount(accountKey);
-
- assertTrue(accountOrError.isRight());
-
- return accountOrError.getRight();
+ return paymentApi.getPaymentProviderAccount(accountKey);
}
@Test(enabled=true)
- public void testCreatePaypalPaymentMethod() throws AccountApiException, EntityPersistenceException {
+ public void testCreatePaypalPaymentMethod() throws Exception {
PaymentProviderAccount account = setupAccountWithPaypalPaymentMethod();
assertNotNull(account);
- Either<PaymentError, List<PaymentMethodInfo>> paymentMethodsOrError = paymentApi.getPaymentMethods(account.getAccountKey());
+ paymentApi.getPaymentMethods(account.getAccountKey());
}
@Test(enabled=true)
- public void testUpdatePaymentProviderAccountContact() throws AccountApiException, EntityPersistenceException {
+ public void testUpdatePaymentProviderAccountContact() throws Exception {
final Account account = testHelper.createTestPayPalAccount();
paymentApi.createPaymentProviderAccount(account, context);
- String newName = "Tester " + RandomStringUtils.randomAlphanumeric(10);
- String newNumber = "888-888-" + RandomStringUtils.randomNumeric(4);
-
- final Account accountToUpdate = new AccountBuilder(account.getId())
- .name(newName)
- .firstNameLength(newName.length())
- .externalKey(account.getExternalKey())
- .phone(newNumber)
- .email(account.getEmail())
- .currency(account.getCurrency())
- .billingCycleDay(account.getBillCycleDay())
- .build();
-
- Either<PaymentError, Void> voidOrError = paymentApi.updatePaymentProviderAccountContact(accountToUpdate.getExternalKey(), context);
- assertTrue(voidOrError.isRight());
+ Account updatedAccount = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+ ZombieControl zombieAccount = (ZombieControl) updatedAccount;
+ zombieAccount.addResult("getId", account.getId());
+ zombieAccount.addResult("getName", "Tester " + RandomStringUtils.randomAlphanumeric(10));
+ zombieAccount.addResult("getFirstNameLength", 6);
+ zombieAccount.addResult("getExternalKey", account.getExternalKey());
+ zombieAccount.addResult("getPhone", "888-888-" + RandomStringUtils.randomNumeric(4));
+ zombieAccount.addResult("getEmail", account.getEmail());
+ zombieAccount.addResult("getCurrency", account.getCurrency());
+ zombieAccount.addResult("getBillCycleDay", account.getBillCycleDay());
+
+ paymentApi.updatePaymentProviderAccountContact(updatedAccount.getExternalKey(), context);
}
@Test(enabled=true)
- public void testCannotDeleteDefaultPaymentMethod() throws AccountApiException, EntityPersistenceException {
+ public void testCannotDeleteDefaultPaymentMethod() throws Exception {
PaymentProviderAccount account = setupAccountWithPaypalPaymentMethod();
-
- Either<PaymentError, Void> errorOrVoid = paymentApi.deletePaymentMethod(account.getAccountKey(), account.getDefaultPaymentMethodId(), context);
-
- assertTrue(errorOrVoid.isLeft());
+ paymentApi.deletePaymentMethod(account.getAccountKey(), account.getDefaultPaymentMethodId(), context);
}
-
- @Test(enabled=true)
- public void testDeleteNonDefaultPaymentMethod() throws AccountApiException, EntityPersistenceException {
- final Account account = testHelper.createTestPayPalAccount();
- paymentApi.createPaymentProviderAccount(account, context);
-
- String accountKey = account.getExternalKey();
-
- PaypalPaymentMethodInfo paymentMethod1 = new PaypalPaymentMethodInfo.Builder().setDefaultMethod(false).setBaid("12345").setEmail(account.getEmail()).build();
- Either<PaymentError, String> paymentMethodIdOrError1 = paymentApi.addPaymentMethod(accountKey, paymentMethod1, context);
-
- assertTrue(paymentMethodIdOrError1.isRight());
- assertNotNull(paymentMethodIdOrError1.getRight());
-
- PaypalPaymentMethodInfo paymentMethod2 = new PaypalPaymentMethodInfo.Builder().setDefaultMethod(true).setBaid("12345").setEmail(account.getEmail()).build();
-
- Either<PaymentError, String> paymentMethodIdOrError2 = paymentApi.addPaymentMethod(accountKey, paymentMethod2, context);
-
- assertTrue(paymentMethodIdOrError2.isRight());
- assertNotNull(paymentMethodIdOrError2.getRight());
-
- Either<PaymentError, List<PaymentMethodInfo>> paymentMethodsOrError = paymentApi.getPaymentMethods(accountKey);
-
- assertTrue(paymentMethodsOrError.isRight());
-
- Either<PaymentError, Void> errorOrVoid1 = paymentApi.deletePaymentMethod(accountKey, paymentMethodIdOrError1.getRight(), context);
- Either<PaymentError, Void> errorOrVoid2 = paymentApi.deletePaymentMethod(accountKey, paymentMethodIdOrError2.getRight(), context);
-
- assertTrue(errorOrVoid1.isRight());
- assertTrue(errorOrVoid2.isLeft());
- }
-
}
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
index 7c8239f..3f90466 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
@@ -23,21 +23,23 @@ import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
+import com.ning.billing.payment.api.DefaultPaymentAttempt;
import com.ning.billing.util.callcontext.CallContext;
import org.apache.commons.collections.CollectionUtils;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.DefaultPaymentInfoEvent;
import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentInfoEvent;
public class MockPaymentDao implements PaymentDao {
- private final Map<String, PaymentInfo> payments = new ConcurrentHashMap<String, PaymentInfo>();
+ private final Map<UUID, PaymentInfoEvent> payments = new ConcurrentHashMap<UUID, PaymentInfoEvent>();
private final Map<UUID, PaymentAttempt> paymentAttempts = new ConcurrentHashMap<UUID, PaymentAttempt>();
@Override
- public PaymentAttempt getPaymentAttemptForPaymentId(String paymentId) {
+ public PaymentAttempt getPaymentAttemptForPaymentId(UUID paymentId) {
for (PaymentAttempt paymentAttempt : paymentAttempts.values()) {
if (paymentId.equals(paymentAttempt.getPaymentId())) {
return paymentAttempt;
@@ -48,39 +50,39 @@ public class MockPaymentDao implements PaymentDao {
@Override
public PaymentAttempt createPaymentAttempt(Invoice invoice, CallContext context) {
- PaymentAttempt updatedPaymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice.getId(), invoice.getAccountId(),
+ PaymentAttempt updatedPaymentAttempt = new DefaultPaymentAttempt(UUID.randomUUID(), invoice.getId(), invoice.getAccountId(),
invoice.getBalance(), invoice.getCurrency(), invoice.getInvoiceDate(),
- null, null, null, context.getCreatedDate(), context.getUpdatedDate());
+ null, null, null, null, null);
- paymentAttempts.put(updatedPaymentAttempt.getPaymentAttemptId(), updatedPaymentAttempt);
+ paymentAttempts.put(updatedPaymentAttempt.getId(), updatedPaymentAttempt);
return updatedPaymentAttempt;
}
@Override
public PaymentAttempt createPaymentAttempt(PaymentAttempt paymentAttempt, CallContext context) {
- PaymentAttempt updatedPaymentAttempt = new PaymentAttempt(paymentAttempt.getPaymentAttemptId(),
+ PaymentAttempt updatedPaymentAttempt = new DefaultPaymentAttempt(paymentAttempt.getId(),
paymentAttempt.getInvoiceId(),
paymentAttempt.getAccountId(), paymentAttempt.getAmount(), paymentAttempt.getCurrency(),
paymentAttempt.getInvoiceDate(), paymentAttempt.getPaymentAttemptDate(),
paymentAttempt.getPaymentId(), paymentAttempt.getRetryCount(),
- context.getCreatedDate(), context.getUpdatedDate());
+ paymentAttempt.getCreatedDate(), paymentAttempt.getUpdatedDate());
- paymentAttempts.put(updatedPaymentAttempt.getPaymentAttemptId(), updatedPaymentAttempt);
+ paymentAttempts.put(updatedPaymentAttempt.getId(), updatedPaymentAttempt);
return updatedPaymentAttempt;
}
@Override
- public void savePaymentInfo(PaymentInfo paymentInfo, CallContext context) {
- payments.put(paymentInfo.getPaymentId(), paymentInfo);
+ public void savePaymentInfo(PaymentInfoEvent paymentInfo, CallContext context) {
+ payments.put(paymentInfo.getId(), paymentInfo);
}
@Override
- public void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, String paymentId, CallContext context) {
+ public void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, UUID paymentId, CallContext context) {
PaymentAttempt existingPaymentAttempt = paymentAttempts.get(paymentAttemptId);
if (existingPaymentAttempt != null) {
- paymentAttempts.put(existingPaymentAttempt.getPaymentAttemptId(),
- existingPaymentAttempt.cloner().setPaymentId(paymentId).build());
+ paymentAttempts.put(existingPaymentAttempt.getId(),
+ ((DefaultPaymentAttempt) existingPaymentAttempt).cloner().setPaymentId(paymentId).build());
}
}
@@ -96,29 +98,28 @@ public class MockPaymentDao implements PaymentDao {
}
@Override
- public void updatePaymentInfo(String paymentMethodType, String paymentId, String cardType, String cardCountry, CallContext context) {
- PaymentInfo existingPayment = payments.get(paymentId);
+ public void updatePaymentInfo(String paymentMethodType, UUID paymentId, String cardType, String cardCountry, CallContext context) {
+ DefaultPaymentInfoEvent existingPayment = (DefaultPaymentInfoEvent) payments.get(paymentId);
if (existingPayment != null) {
- PaymentInfo payment = existingPayment.cloner()
+ PaymentInfoEvent payment = existingPayment.cloner()
.setPaymentMethod(paymentMethodType)
.setCardType(cardType)
.setCardCountry(cardCountry)
- .setUpdatedDate(context.getUpdatedDate())
.build();
payments.put(paymentId, payment);
}
}
@Override
- public List<PaymentInfo> getPaymentInfo(List<String> invoiceIds) {
+ public List<PaymentInfoEvent> getPaymentInfoList(List<String> invoiceIds) {
List<PaymentAttempt> attempts = getPaymentAttemptsForInvoiceIds(invoiceIds);
- List<PaymentInfo> paymentsToReturn = new ArrayList<PaymentInfo>(invoiceIds.size());
+ List<PaymentInfoEvent> paymentsToReturn = new ArrayList<PaymentInfoEvent>(invoiceIds.size());
for (final PaymentAttempt attempt : attempts) {
- paymentsToReturn.addAll(Collections2.filter(payments.values(), new Predicate<PaymentInfo>() {
+ paymentsToReturn.addAll(Collections2.filter(payments.values(), new Predicate<PaymentInfoEvent>() {
@Override
- public boolean apply(PaymentInfo input) {
- return input.getPaymentId().equals(attempt.getPaymentId());
+ public boolean apply(PaymentInfoEvent input) {
+ return input.getId().equals(attempt.getPaymentId());
}
}));
}
@@ -126,6 +127,20 @@ public class MockPaymentDao implements PaymentDao {
}
@Override
+ public PaymentInfoEvent getLastPaymentInfo(List<String> invoiceIds) {
+ List<PaymentInfoEvent> payments = getPaymentInfoList(invoiceIds);
+ PaymentInfoEvent lastPayment = null;
+
+ for (PaymentInfoEvent payment : payments) {
+ if ((lastPayment == null) || (payment.getEffectiveDate().isAfter(lastPayment.getEffectiveDate()))) {
+ lastPayment = payment;
+ }
+ }
+
+ return lastPayment;
+ }
+
+ @Override
public List<PaymentAttempt> getPaymentAttemptsForInvoiceIds(List<String> invoiceIds) {
List<PaymentAttempt> paymentAttempts = new ArrayList<PaymentAttempt>(invoiceIds.size());
for (String invoiceId : invoiceIds) {
@@ -143,7 +158,7 @@ public class MockPaymentDao implements PaymentDao {
}
@Override
- public PaymentInfo getPaymentInfoForPaymentAttemptId(String paymentAttemptId) {
+ public PaymentInfoEvent getPaymentInfoForPaymentAttemptId(String paymentAttemptId) {
// TODO Auto-generated method stub
return null;
}
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
index 472fc2f..40b92ef 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
@@ -21,12 +21,12 @@ import java.util.Arrays;
import java.util.List;
import java.util.UUID;
+import com.ning.billing.payment.api.DefaultPaymentAttempt;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.CallOrigin;
import com.ning.billing.util.callcontext.DefaultCallContext;
import com.ning.billing.util.callcontext.TestCallContext;
import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.ClockMock;
import com.ning.billing.util.clock.DefaultClock;
import org.testng.Assert;
@@ -34,8 +34,9 @@ import org.testng.annotations.Test;
import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.payment.api.DefaultPaymentInfoEvent;
import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentInfoEvent;
public abstract class TestPaymentDao {
protected PaymentDao paymentDao;
@@ -43,7 +44,7 @@ public abstract class TestPaymentDao {
@Test
public void testCreatePayment() {
- PaymentInfo paymentInfo = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString())
+ PaymentInfoEvent paymentInfo = new DefaultPaymentInfoEvent.Builder().setId(UUID.randomUUID())
.setAmount(BigDecimal.TEN)
.setStatus("Processed")
.setBankIdentificationNumber("1234")
@@ -59,7 +60,7 @@ public abstract class TestPaymentDao {
@Test
public void testUpdatePaymentInfo() {
- PaymentInfo paymentInfo = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString())
+ PaymentInfoEvent paymentInfo = new DefaultPaymentInfoEvent.Builder().setId(UUID.randomUUID())
.setAmount(BigDecimal.TEN)
.setStatus("Processed")
.setBankIdentificationNumber("1234")
@@ -67,20 +68,18 @@ public abstract class TestPaymentDao {
.setPaymentMethodId("12345")
.setReferenceId("12345")
.setType("Electronic")
- .setCreatedDate(new DefaultClock().getUTCNow())
- .setUpdatedDate(new DefaultClock().getUTCNow())
.setEffectiveDate(new DefaultClock().getUTCNow())
.build();
CallContext context = new TestCallContext("PaymentTests");
paymentDao.savePaymentInfo(paymentInfo, context);
- paymentDao.updatePaymentInfo("CreditCard", paymentInfo.getPaymentId(), "Visa", "US", context);
+ paymentDao.updatePaymentInfo("CreditCard", paymentInfo.getId(), "Visa", "US", context);
}
@Test
public void testUpdatePaymentAttempt() {
- PaymentAttempt paymentAttempt = new PaymentAttempt.Builder().setPaymentAttemptId(UUID.randomUUID())
- .setPaymentId(UUID.randomUUID().toString())
+ PaymentAttempt paymentAttempt = new DefaultPaymentAttempt.Builder().setPaymentAttemptId(UUID.randomUUID())
+ .setPaymentId(UUID.randomUUID())
.setInvoiceId(UUID.randomUUID())
.setAccountId(UUID.randomUUID())
.setAmount(BigDecimal.TEN)
@@ -96,14 +95,14 @@ public abstract class TestPaymentDao {
final UUID invoiceId = UUID.randomUUID();
final UUID paymentAttemptId = UUID.randomUUID();
final UUID accountId = UUID.randomUUID();
- final String paymentId = UUID.randomUUID().toString();
+ final UUID paymentId = UUID.randomUUID();
final BigDecimal invoiceAmount = BigDecimal.TEN;
// Move the clock backwards to test the updated_date field (see below)
ClockMock clock = new ClockMock();
CallContext thisContext = new DefaultCallContext("Payment Tests", CallOrigin.TEST, UserType.TEST, clock);
- PaymentAttempt originalPaymentAttempt = new PaymentAttempt(paymentAttemptId, invoiceId, accountId, invoiceAmount, Currency.USD, clock.getUTCNow(), clock.getUTCNow(), paymentId, 0);
+ PaymentAttempt originalPaymentAttempt = new DefaultPaymentAttempt(paymentAttemptId, invoiceId, accountId, invoiceAmount, Currency.USD, clock.getUTCNow(), clock.getUTCNow(), paymentId, 0, null, null);
PaymentAttempt attempt = paymentDao.createPaymentAttempt(originalPaymentAttempt, thisContext);
List<PaymentAttempt> attemptsFromGet = paymentDao.getPaymentAttemptsForInvoiceId(invoiceId.toString());
@@ -114,11 +113,11 @@ public abstract class TestPaymentDao {
Assert.assertEquals(attempt, attempt3);
- PaymentAttempt attempt4 = paymentDao.getPaymentAttemptById(attempt3.getPaymentAttemptId());
+ PaymentAttempt attempt4 = paymentDao.getPaymentAttemptById(attempt3.getId());
Assert.assertEquals(attempt3, attempt4);
- PaymentInfo originalPaymentInfo = new PaymentInfo.Builder().setPaymentId(paymentId)
+ PaymentInfoEvent originalPaymentInfo = new DefaultPaymentInfoEvent.Builder().setId(paymentId)
.setAmount(invoiceAmount)
.setStatus("Processed")
.setBankIdentificationNumber("1234")
@@ -126,19 +125,18 @@ public abstract class TestPaymentDao {
.setPaymentMethodId("12345")
.setReferenceId("12345")
.setType("Electronic")
- .setCreatedDate(clock.getUTCNow())
- .setUpdatedDate(clock.getUTCNow())
.setEffectiveDate(clock.getUTCNow())
.build();
paymentDao.savePaymentInfo(originalPaymentInfo, thisContext);
- PaymentInfo paymentInfo = paymentDao.getPaymentInfo(Arrays.asList(invoiceId.toString())).get(0);
+ PaymentInfoEvent paymentInfo = paymentDao.getPaymentInfoList(Arrays.asList(invoiceId.toString())).get(0);
Assert.assertEquals(paymentInfo, originalPaymentInfo);
clock.setDeltaFromReality(60 * 60 * 1000); // move clock forward one hour
- paymentDao.updatePaymentInfo(originalPaymentInfo.getPaymentMethod(), originalPaymentInfo.getPaymentId(), originalPaymentInfo.getCardType(), originalPaymentInfo.getCardCountry(), thisContext);
- paymentInfo = paymentDao.getPaymentInfo(Arrays.asList(invoiceId.toString())).get(0);
- Assert.assertEquals(paymentInfo.getCreatedDate().compareTo(attempt.getCreatedDate()), 0);
- Assert.assertTrue(paymentInfo.getUpdatedDate().isAfter(originalPaymentInfo.getUpdatedDate()));
+ paymentDao.updatePaymentInfo(originalPaymentInfo.getPaymentMethod(), originalPaymentInfo.getId(), originalPaymentInfo.getCardType(), originalPaymentInfo.getCardCountry(), thisContext);
+ paymentInfo = paymentDao.getPaymentInfoList(Arrays.asList(invoiceId.toString())).get(0);
+ // TODO: replace these asserts
+// Assert.assertEquals(paymentInfo.getCreatedDate().compareTo(attempt.getCreatedDate()), 0);
+// Assert.assertTrue(paymentInfo.getUpdatedDate().isAfter(originalPaymentInfo.getUpdatedDate()));
}
}
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java
index 84e3e5a..7c2fe65 100644
--- a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java
@@ -33,9 +33,11 @@ public class TestPaymentDaoWithEmbeddedDb extends TestPaymentDao {
@BeforeClass(groups = { "slow", "database" })
public void startMysql() throws IOException {
final String paymentddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+ final String utilddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
helper.startMysql();
helper.initDb(paymentddl);
+ helper.initDb(utilddl);
}
@AfterClass(groups = { "slow", "database" })
diff --git a/payment/src/test/java/com/ning/billing/payment/MockInvoice.java b/payment/src/test/java/com/ning/billing/payment/MockInvoice.java
new file mode 100644
index 0000000..20c0446
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/MockInvoice.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import com.ning.billing.util.dao.ObjectType;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.entity.ExtendedEntityBase;
+
+public class MockInvoice extends ExtendedEntityBase implements Invoice {
+ private final List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
+ private final List<InvoicePayment> payments = new ArrayList<InvoicePayment>();
+ private final UUID accountId;
+ private final Integer invoiceNumber;
+ private final DateTime invoiceDate;
+ private final DateTime targetDate;
+ private final Currency currency;
+ private final boolean migrationInvoice;
+
+ // used to create a new invoice
+ public MockInvoice(UUID accountId, DateTime invoiceDate, DateTime targetDate, Currency currency) {
+ this(UUID.randomUUID(), accountId, null, invoiceDate, targetDate, currency, false);
+ }
+
+ // used to hydrate invoice from persistence layer
+ public MockInvoice(UUID invoiceId, UUID accountId, @Nullable Integer invoiceNumber, DateTime invoiceDate,
+ DateTime targetDate, Currency currency, boolean isMigrationInvoice) {
+ super(invoiceId);
+ this.accountId = accountId;
+ this.invoiceNumber = invoiceNumber;
+ this.invoiceDate = invoiceDate;
+ this.targetDate = targetDate;
+ this.currency = currency;
+ this.migrationInvoice = isMigrationInvoice;
+ }
+
+ @Override
+ public boolean addInvoiceItem(final InvoiceItem item) {
+ return invoiceItems.add(item);
+ }
+
+ @Override
+ public boolean addInvoiceItems(final List<InvoiceItem> items) {
+ return this.invoiceItems.addAll(items);
+ }
+
+ @Override
+ public List<InvoiceItem> getInvoiceItems() {
+ return invoiceItems;
+ }
+
+ @Override
+ public <T extends InvoiceItem> List<InvoiceItem> getInvoiceItems(Class<T> clazz) {
+ List<InvoiceItem> results = new ArrayList<InvoiceItem>();
+ for (InvoiceItem item : invoiceItems) {
+ if ( clazz.isInstance(item) ) {
+ results.add(item);
+ }
+ }
+ return results;
+ }
+
+ @Override
+ public int getNumberOfItems() {
+ return invoiceItems.size();
+ }
+
+ @Override
+ public boolean addPayment(final InvoicePayment payment) {
+ return payments.add(payment);
+ }
+
+ @Override
+ public boolean addPayments(final List<InvoicePayment> payments) {
+ return this.payments.addAll(payments);
+ }
+
+ @Override
+ public List<InvoicePayment> getPayments() {
+ return payments;
+ }
+
+ @Override
+ public int getNumberOfPayments() {
+ return payments.size();
+ }
+
+ @Override
+ public UUID getId() {
+ return id;
+ }
+
+ @Override
+ public UUID getAccountId() {
+ return accountId;
+ }
+
+ /**
+ * null until retrieved from the database
+ * @return the invoice number
+ */
+ @Override
+ public Integer getInvoiceNumber() {
+ return invoiceNumber;
+ }
+
+ @Override
+ public DateTime getInvoiceDate() {
+ return invoiceDate;
+ }
+
+ @Override
+ public DateTime getTargetDate() {
+ return targetDate;
+ }
+
+ @Override
+ public Currency getCurrency() {
+ return currency;
+ }
+
+ @Override
+ public boolean isMigrationInvoice() {
+ return migrationInvoice;
+ }
+
+ @Override
+ public DateTime getLastPaymentAttempt() {
+ DateTime lastPaymentAttempt = null;
+
+ for (final InvoicePayment paymentAttempt : payments) {
+ DateTime paymentAttemptDate = paymentAttempt.getPaymentAttemptDate();
+ if (lastPaymentAttempt == null) {
+ lastPaymentAttempt = paymentAttemptDate;
+ }
+
+ if (lastPaymentAttempt.isBefore(paymentAttemptDate)) {
+ lastPaymentAttempt = paymentAttemptDate;
+ }
+ }
+
+ return lastPaymentAttempt;
+ }
+
+ @Override
+ public BigDecimal getAmountPaid() {
+ BigDecimal amountPaid = BigDecimal.ZERO;
+ for (final InvoicePayment payment : payments) {
+ if (payment.getAmount() != null) {
+ amountPaid = amountPaid.add(payment.getAmount());
+ }
+ }
+ return amountPaid;
+ }
+
+ @Override
+ public BigDecimal getTotalAmount() {
+ BigDecimal result = BigDecimal.ZERO;
+
+ for(InvoiceItem i : invoiceItems) {
+ result = result.add(i.getAmount());
+ }
+ return result;
+ }
+
+ @Override
+ public BigDecimal getBalance() {
+ return getTotalAmount().subtract(getAmountPaid());
+ }
+
+ @Override
+ public boolean isDueForPayment(final DateTime targetDate, final int numberOfDays) {
+ if (getTotalAmount().compareTo(BigDecimal.ZERO) == 0) {
+ return false;
+ }
+
+ DateTime lastPaymentAttempt = getLastPaymentAttempt();
+ if (lastPaymentAttempt == null) {
+ return true;
+ }
+
+ return !lastPaymentAttempt.plusDays(numberOfDays).isAfter(targetDate);
+ }
+
+ @Override
+ public String toString() {
+ return "DefaultInvoice [items=" + invoiceItems + ", payments=" + payments + ", id=" + id + ", accountId=" + accountId + ", invoiceDate=" + invoiceDate + ", targetDate=" + targetDate + ", currency=" + currency + ", amountPaid=" + getAmountPaid() + ", lastPaymentAttempt=" + getLastPaymentAttempt() + "]";
+ }
+
+ @Override
+ public ObjectType getObjectType() {
+ return ObjectType.RECURRING_INVOICE_ITEM;
+ }
+
+ @Override
+ public void saveFieldValue(String fieldName, String fieldValue, CallContext context) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void saveFields(List<CustomField> fields, CallContext context) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clearPersistedFields(CallContext context) {
+ throw new UnsupportedOperationException();
+ }
+}
+
diff --git a/payment/src/test/java/com/ning/billing/payment/MockInvoiceCreationEvent.java b/payment/src/test/java/com/ning/billing/payment/MockInvoiceCreationEvent.java
new file mode 100644
index 0000000..9a700cd
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/MockInvoiceCreationEvent.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.util.bus.BusEvent.BusEventType;
+
+public class MockInvoiceCreationEvent implements InvoiceCreationEvent {
+
+ private final UUID invoiceId;
+ private final UUID accountId;
+ private final BigDecimal amountOwed;
+ private final Currency currency;
+ private final DateTime invoiceCreationDate;
+ private final UUID userToken;
+
+ @JsonCreator
+ public MockInvoiceCreationEvent(@JsonProperty("invoiceId") UUID invoiceId,
+ @JsonProperty("accountId") UUID accountId,
+ @JsonProperty("amountOwed") BigDecimal amountOwed,
+ @JsonProperty("currency") Currency currency,
+ @JsonProperty("invoiceCreationDate") DateTime invoiceCreationDate,
+ @JsonProperty("userToken") UUID userToken) {
+ this.invoiceId = invoiceId;
+ this.accountId = accountId;
+ this.amountOwed = amountOwed;
+ this.currency = currency;
+ this.invoiceCreationDate = invoiceCreationDate;
+ this.userToken = userToken;
+ }
+
+ @JsonIgnore
+ @Override
+ public BusEventType getBusEventType() {
+ return BusEventType.INVOICE_CREATION;
+ }
+
+ @Override
+ public UUID getUserToken() {
+ return userToken;
+ }
+
+ @Override
+ public UUID getInvoiceId() {
+ return invoiceId;
+ }
+
+ @Override
+ public UUID getAccountId() {
+ return accountId;
+ }
+
+ @Override
+ public BigDecimal getAmountOwed() {
+ return amountOwed;
+ }
+
+ @Override
+ public Currency getCurrency() {
+ return currency;
+ }
+
+ @Override
+ public DateTime getInvoiceCreationDate() {
+ return invoiceCreationDate;
+ }
+
+ @Override
+ public String toString() {
+ return "DefaultInvoiceCreationNotification [invoiceId=" + invoiceId + ", accountId=" + accountId + ", amountOwed=" + amountOwed + ", currency=" + currency + ", invoiceCreationDate=" + invoiceCreationDate + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((accountId == null) ? 0 : accountId.hashCode());
+ result = prime * result
+ + ((amountOwed == null) ? 0 : amountOwed.hashCode());
+ result = prime * result
+ + ((currency == null) ? 0 : currency.hashCode());
+ result = prime
+ * result
+ + ((invoiceCreationDate == null) ? 0 : invoiceCreationDate
+ .hashCode());
+ result = prime * result
+ + ((invoiceId == null) ? 0 : invoiceId.hashCode());
+ result = prime * result
+ + ((userToken == null) ? 0 : userToken.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ MockInvoiceCreationEvent other = (MockInvoiceCreationEvent) obj;
+ if (accountId == null) {
+ if (other.accountId != null)
+ return false;
+ } else if (!accountId.equals(other.accountId))
+ return false;
+ if (amountOwed == null) {
+ if (other.amountOwed != null)
+ return false;
+ } else if (!amountOwed.equals(other.amountOwed))
+ return false;
+ if (currency != other.currency)
+ return false;
+ if (invoiceCreationDate == null) {
+ if (other.invoiceCreationDate != null)
+ return false;
+ } else if (invoiceCreationDate.compareTo(other.invoiceCreationDate) != 0)
+ return false;
+ if (invoiceId == null) {
+ if (other.invoiceId != null)
+ return false;
+ } else if (!invoiceId.equals(other.invoiceId))
+ return false;
+ if (userToken == null) {
+ if (other.userToken != null)
+ return false;
+ } else if (!userToken.equals(other.userToken))
+ return false;
+ return true;
+ }
+
+
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/MockRecurringInvoiceItem.java b/payment/src/test/java/com/ning/billing/payment/MockRecurringInvoiceItem.java
new file mode 100644
index 0000000..93c90fa
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/MockRecurringInvoiceItem.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.util.entity.EntityBase;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+public class MockRecurringInvoiceItem extends EntityBase implements InvoiceItem {
+ private final BigDecimal rate;
+ private final UUID reversedItemId;
+ protected final UUID invoiceId;
+ protected final UUID accountId;
+ protected final UUID subscriptionId;
+ protected final UUID bundleId;
+ protected final String planName;
+ protected final String phaseName;
+ protected final DateTime startDate;
+ protected final DateTime endDate;
+ protected final BigDecimal amount;
+ protected final Currency currency;
+
+
+ public MockRecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+ DateTime startDate, DateTime endDate,
+ BigDecimal amount, BigDecimal rate,
+ Currency currency) {
+ this(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, rate, null);
+
+ }
+
+ public MockRecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+ DateTime startDate, DateTime endDate,
+ BigDecimal amount, BigDecimal rate,
+ Currency currency, UUID reversedItemId) {
+ this(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate,
+ amount, currency, rate, reversedItemId);
+ }
+
+ public MockRecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+ DateTime startDate, DateTime endDate,
+ BigDecimal amount, BigDecimal rate,
+ Currency currency) {
+ this(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, rate, null);
+
+ }
+
+ public MockRecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+ DateTime startDate, DateTime endDate,
+ BigDecimal amount, BigDecimal rate,
+ Currency currency, UUID reversedItemId) {
+ this(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, rate, reversedItemId);
+ }
+ public MockRecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+ DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency, BigDecimal rate, UUID reversedItemId){
+ this(UUID.randomUUID(), invoiceId, accountId, bundleId, subscriptionId, planName, phaseName,
+ startDate, endDate, amount, currency, rate, reversedItemId);
+ }
+
+
+ public MockRecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, @Nullable UUID bundleId, @Nullable UUID subscriptionId, String planName, String phaseName,
+ DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency,
+ BigDecimal rate, UUID reversedItemId) {
+ super(id);
+ this.invoiceId = invoiceId;
+ this.accountId = accountId;
+ this.subscriptionId = subscriptionId;
+ this.bundleId = bundleId;
+ this.planName = planName;
+ this.phaseName = phaseName;
+ this.startDate = startDate;
+ this.endDate = endDate;
+ this.amount = amount;
+ this.currency = currency;
+ this.rate = rate;
+ this.reversedItemId = reversedItemId;
+ }
+
+ @Override
+ public UUID getId() {
+ return id;
+ }
+
+ @Override
+ public UUID getInvoiceId() {
+ return invoiceId;
+ }
+
+ @Override
+ public UUID getBundleId() {
+ return bundleId;
+ }
+
+ public UUID getAccountId() {
+ return accountId;
+ }
+
+ @Override
+ public UUID getSubscriptionId() {
+ return subscriptionId;
+ }
+
+ @Override
+ public String getPlanName() {
+ return planName;
+ }
+
+ @Override
+ public String getPhaseName() {
+ return phaseName;
+ }
+
+ @Override
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
+ @Override
+ public DateTime getStartDate() {
+ return startDate;
+ }
+
+ @Override
+ public DateTime getEndDate() {
+ return endDate;
+ }
+
+ @Override
+ public Currency getCurrency() {
+ return currency;
+ }
+ @Override
+ public InvoiceItem asReversingItem() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public String getDescription() {
+ return String.format("%s from %s to %s", phaseName, startDate.toString(), endDate.toString());
+ }
+
+ public UUID getReversedItemId() {
+ return reversedItemId;
+ }
+
+ public boolean reversesItem() {
+ return (reversedItemId != null);
+ }
+
+ public BigDecimal getRate() {
+ return rate;
+ }
+
+ @Override
+ public int compareTo(InvoiceItem item) {
+ if (item == null) {
+ return -1;
+ }
+ if (!(item instanceof MockRecurringInvoiceItem)) {
+ return -1;
+ }
+
+ MockRecurringInvoiceItem that = (MockRecurringInvoiceItem) item;
+ int compareAccounts = getAccountId().compareTo(that.getAccountId());
+ if (compareAccounts == 0 && bundleId != null) {
+ int compareBundles = getBundleId().compareTo(that.getBundleId());
+ if (compareBundles == 0 && subscriptionId != null) {
+
+ int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
+ if (compareSubscriptions == 0) {
+ int compareStartDates = getStartDate().compareTo(that.getStartDate());
+ if (compareStartDates == 0) {
+ return getEndDate().compareTo(that.getEndDate());
+ } else {
+ return compareStartDates;
+ }
+ } else {
+ return compareSubscriptions;
+ }
+ } else {
+ return compareBundles;
+ }
+ } else {
+ return compareAccounts;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ MockRecurringInvoiceItem that = (MockRecurringInvoiceItem) o;
+
+ if (accountId.compareTo(that.accountId) != 0) return false;
+ if (amount.compareTo(that.amount) != 0) return false;
+ if (currency != that.currency) return false;
+ if (startDate.compareTo(that.startDate) != 0) return false;
+ if (endDate.compareTo(that.endDate) != 0) return false;
+ if (!phaseName.equals(that.phaseName)) return false;
+ if (!planName.equals(that.planName)) return false;
+ if (rate.compareTo(that.rate) != 0) return false;
+ if (reversedItemId != null ? !reversedItemId.equals(that.reversedItemId) : that.reversedItemId != null)
+ return false;
+ if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null)
+ return false;
+ if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = accountId.hashCode();
+ result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
+ result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
+ result = 31 * result + planName.hashCode();
+ result = 31 * result + phaseName.hashCode();
+ result = 31 * result + startDate.hashCode();
+ result = 31 * result + endDate.hashCode();
+ result = 31 * result + amount.hashCode();
+ result = 31 * result + rate.hashCode();
+ result = 31 * result + currency.hashCode();
+ result = 31 * result + (reversedItemId != null ? reversedItemId.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(phaseName).append(", ");
+ sb.append(startDate.toString()).append(", ");
+ sb.append(endDate.toString()).append(", ");
+ sb.append(amount.toString()).append(", ");
+ sb.append("subscriptionId = ").append(subscriptionId == null ? null : subscriptionId.toString()).append(", ");
+ sb.append("bundleId = ").append(bundleId == null ? null : bundleId.toString()).append(", ");
+
+ return sb.toString();
+ }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
index 47713bb..f3cb154 100644
--- a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -24,26 +24,27 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
-import com.google.inject.Inject;
-import com.ning.billing.util.clock.Clock;
import org.apache.commons.lang.RandomStringUtils;
-import org.joda.time.DateTime;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
+import com.google.inject.Inject;
import com.ning.billing.account.api.Account;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.payment.api.CreditCardPaymentMethodInfo;
+import com.ning.billing.payment.api.DefaultPaymentErrorEvent;
+import com.ning.billing.payment.api.DefaultPaymentInfoEvent;
import com.ning.billing.payment.api.Either;
-import com.ning.billing.payment.api.PaymentError;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
import com.ning.billing.payment.api.PaymentMethodInfo;
import com.ning.billing.payment.api.PaymentProviderAccount;
import com.ning.billing.payment.api.PaypalPaymentMethodInfo;
+import com.ning.billing.util.clock.Clock;
public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
private final AtomicBoolean makeNextInvoiceFail = new AtomicBoolean(false);
- private final Map<String, PaymentInfo> payments = new ConcurrentHashMap<String, PaymentInfo>();
+ private final Map<UUID, PaymentInfoEvent> payments = new ConcurrentHashMap<UUID, PaymentInfoEvent>();
private final Map<String, PaymentProviderAccount> accounts = new ConcurrentHashMap<String, PaymentProviderAccount>();
private final Map<String, PaymentMethodInfo> paymentMethods = new ConcurrentHashMap<String, PaymentMethodInfo>();
private final Clock clock;
@@ -58,32 +59,31 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
}
@Override
- public Either<PaymentError, PaymentInfo> processInvoice(Account account, Invoice invoice) {
+ public Either<PaymentErrorEvent, PaymentInfoEvent> processInvoice(Account account, Invoice invoice) {
if (makeNextInvoiceFail.getAndSet(false)) {
- return Either.left(new PaymentError("unknown", "test error", account.getId(), invoice.getId()));
+ return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("unknown", "test error", account.getId(), invoice.getId(), null));
}
else {
- PaymentInfo payment = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString())
+ PaymentInfoEvent payment = new DefaultPaymentInfoEvent.Builder().setId(UUID.randomUUID())
.setAmount(invoice.getBalance())
.setStatus("Processed")
.setBankIdentificationNumber("1234")
- .setCreatedDate(clock.getUTCNow())
.setEffectiveDate(clock.getUTCNow())
.setPaymentNumber("12345")
.setReferenceId("12345")
.setType("Electronic")
.build();
- payments.put(payment.getPaymentId(), payment);
+ payments.put(payment.getId(), payment);
return Either.right(payment);
}
}
@Override
- public Either<PaymentError, PaymentInfo> getPaymentInfo(String paymentId) {
- PaymentInfo payment = payments.get(paymentId);
+ public Either<PaymentErrorEvent, PaymentInfoEvent> getPaymentInfo(String paymentId) {
+ PaymentInfoEvent payment = payments.get(paymentId);
if (payment == null) {
- return Either.left(new PaymentError("notfound", "No payment found for id " + paymentId, null, null));
+ return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("notfound", "No payment found for id " + paymentId, null, null, null));
}
else {
return Either.right(payment);
@@ -91,7 +91,7 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
}
@Override
- public Either<PaymentError, String> createPaymentProviderAccount(Account account) {
+ public Either<PaymentErrorEvent, String> createPaymentProviderAccount(Account account) {
if (account != null) {
String id = String.valueOf(RandomStringUtils.randomAlphanumeric(10));
accounts.put(account.getExternalKey(),
@@ -102,22 +102,22 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
return Either.right(id);
}
else {
- return Either.left(new PaymentError("unknown", "Did not get account to create payment provider account", null, null));
+ return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("unknown", "Did not get account to create payment provider account", null, null, null));
}
}
@Override
- public Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
+ public Either<PaymentErrorEvent, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
if (accountKey != null) {
return Either.right(accounts.get(accountKey));
}
else {
- return Either.left(new PaymentError("unknown", "Did not get account for accountKey " + accountKey, null, null));
+ return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("unknown", "Did not get account for accountKey " + accountKey, null, null, null));
}
}
@Override
- public Either<PaymentError, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+ public Either<PaymentErrorEvent, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
if (paymentMethod != null) {
PaymentProviderAccount account = accounts.get(accountKey);
@@ -144,7 +144,7 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
realPaymentMethod = new CreditCardPaymentMethodInfo.Builder(ccPaymentMethod).setId(paymentMethodId).build();
}
if (realPaymentMethod == null) {
- return Either.left(new PaymentError("unsupported", "Payment method " + paymentMethod.getType() + " not supported by the plugin", null, null));
+ return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("unsupported", "Payment method " + paymentMethod.getType() + " not supported by the plugin", null, null, null));
}
else {
if (shouldBeDefault) {
@@ -155,11 +155,11 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
}
}
else {
- return Either.left(new PaymentError("noaccount", "Could not retrieve account for accountKey " + accountKey, null, null));
+ return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("noaccount", "Could not retrieve account for accountKey " + accountKey, null, null, null));
}
}
else {
- return Either.left(new PaymentError("unknown", "Could not create add payment method " + paymentMethod + " for " + accountKey, null, null));
+ return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("unknown", "Could not create add payment method " + paymentMethod + " for " + accountKey, null, null, null));
}
}
@@ -190,7 +190,7 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
}
@Override
- public Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+ public Either<PaymentErrorEvent, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
if (paymentMethod != null) {
PaymentMethodInfo realPaymentMethod = null;
@@ -203,7 +203,7 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
realPaymentMethod = new CreditCardPaymentMethodInfo.Builder(ccPaymentMethod).build();
}
if (realPaymentMethod == null) {
- return Either.left(new PaymentError("unsupported", "Payment method " + paymentMethod.getType() + " not supported by the plugin", null, null));
+ return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("unsupported", "Payment method " + paymentMethod.getType() + " not supported by the plugin", null, null, null));
}
else {
paymentMethods.put(paymentMethod.getId(), paymentMethod);
@@ -211,37 +211,37 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
}
}
else {
- return Either.left(new PaymentError("unknown", "Could not create add payment method " + paymentMethod + " for " + accountKey, null, null));
+ return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("unknown", "Could not create add payment method " + paymentMethod + " for " + accountKey, null, null, null));
}
}
@Override
- public Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId) {
+ public Either<PaymentErrorEvent, Void> deletePaymentMethod(String accountKey, String paymentMethodId) {
PaymentMethodInfo paymentMethodInfo = paymentMethods.get(paymentMethodId);
if (paymentMethodInfo != null) {
if (Boolean.FALSE.equals(paymentMethodInfo.getDefaultMethod()) || paymentMethodInfo.getDefaultMethod() == null) {
if (paymentMethods.remove(paymentMethodId) == null) {
- return Either.left(new PaymentError("unknown", "Did not get any result back", null, null));
+ return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("unknown", "Did not get any result back", null, null, null));
}
}
else {
- return Either.left(new PaymentError("error", "Cannot delete default payment method", null, null));
+ return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("error", "Cannot delete default payment method", null, null, null));
}
}
return Either.right(null);
}
@Override
- public Either<PaymentError, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId) {
+ public Either<PaymentErrorEvent, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId) {
if (paymentMethodId == null) {
- return Either.left(new PaymentError("unknown", "Could not retrieve payment method for paymentMethodId " + paymentMethodId, null, null));
+ return Either.left((PaymentErrorEvent) new DefaultPaymentErrorEvent("unknown", "Could not retrieve payment method for paymentMethodId " + paymentMethodId, null, null, null));
}
return Either.right(paymentMethods.get(paymentMethodId));
}
@Override
- public Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(final String accountKey) {
+ public Either<PaymentErrorEvent, List<PaymentMethodInfo>> getPaymentMethods(final String accountKey) {
Collection<PaymentMethodInfo> filteredPaymentMethods = Collections2.filter(paymentMethods.values(), new Predicate<PaymentMethodInfo>() {
@Override
@@ -254,20 +254,23 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
}
@Override
- public Either<PaymentError, Void> updatePaymentGateway(String accountKey) {
+ public Either<PaymentErrorEvent, Void> updatePaymentGateway(String accountKey) {
return Either.right(null);
}
@Override
- public Either<PaymentError, Void> updatePaymentProviderAccountExistingContact(Account account) {
+ public Either<PaymentErrorEvent, Void> updatePaymentProviderAccountExistingContact(Account account) {
// nothing to do here
return Either.right(null);
}
@Override
- public Either<PaymentError, Void> updatePaymentProviderAccountWithNewContact(Account account) {
- // nothing to do here
+ public Either<PaymentErrorEvent, Void> updatePaymentProviderAccountWithNewContact(Account account) {
return Either.right(null);
}
+ @Override
+ public List<Either<PaymentErrorEvent, PaymentInfoEvent>> processRefund(Account account) {
+ return null;
+ }
}
diff --git a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
index 97aa31e..da38b34 100644
--- a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
@@ -20,7 +20,8 @@ import org.apache.commons.collections.MapUtils;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Provider;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
+import com.ning.billing.config.PaymentConfig;
+import com.ning.billing.junction.api.BillingApi;
import com.ning.billing.mock.BrainDeadProxyFactory;
import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
import com.ning.billing.util.bus.Bus;
@@ -29,10 +30,10 @@ import com.ning.billing.util.notificationq.DefaultNotificationQueueService;
import com.ning.billing.util.notificationq.NotificationQueueService;
public class PaymentTestModuleWithEmbeddedDb extends PaymentModule {
- public static class MockProvider implements Provider<EntitlementBillingApi> {
+ public static class MockProvider implements Provider<BillingApi> {
@Override
- public EntitlementBillingApi get() {
- return BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementBillingApi.class);
+ public BillingApi get() {
+ return BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
}
}
@@ -50,7 +51,6 @@ public class PaymentTestModuleWithEmbeddedDb extends PaymentModule {
protected void configure() {
super.configure();
bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
- bind(EntitlementBillingApi.class).toProvider(MockProvider.class);
bind(NotificationQueueService.class).to(DefaultNotificationQueueService.class).asEagerSingleton();
}
}
diff --git a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
index c8f79bc..14f2ab9 100644
--- a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
@@ -20,25 +20,23 @@ import org.apache.commons.collections.MapUtils;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Provider;
-import com.ning.billing.account.dao.AccountDao;
-import com.ning.billing.account.dao.MockAccountDao;
-import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
-import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.invoice.dao.MockInvoiceDao;
+import com.ning.billing.config.PaymentConfig;
+import com.ning.billing.junction.api.BillingApi;
import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.glue.MockInvoiceModule;
+import com.ning.billing.mock.glue.MockNotificationQueueModule;
+import com.ning.billing.mock.glue.TestDbiModule;
import com.ning.billing.payment.dao.MockPaymentDao;
import com.ning.billing.payment.dao.PaymentDao;
import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
-import com.ning.billing.util.bus.Bus;
-import com.ning.billing.util.bus.InMemoryBus;
-import com.ning.billing.util.notificationq.MockNotificationQueueService;
-import com.ning.billing.util.notificationq.NotificationQueueService;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.BusModule.BusType;
public class PaymentTestModuleWithMocks extends PaymentModule {
- public static class MockProvider implements Provider<EntitlementBillingApi> {
+ public static class MockProvider implements Provider<BillingApi> {
@Override
- public EntitlementBillingApi get() {
- return BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementBillingApi.class);
+ public BillingApi get() {
+ return BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
}
}
@@ -46,7 +44,7 @@ public class PaymentTestModuleWithMocks extends PaymentModule {
public PaymentTestModuleWithMocks() {
super(MapUtils.toProperties(ImmutableMap.of("killbill.payment.provider.default", "my-mock",
- "killbill.payment.engine.events.off", "false")));
+ "killbill.payment.engine.events.off", "false")));
}
@Override
@@ -62,12 +60,9 @@ public class PaymentTestModuleWithMocks extends PaymentModule {
@Override
protected void configure() {
super.configure();
- bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
- bind(MockAccountDao.class).asEagerSingleton();
- bind(AccountDao.class).to(MockAccountDao.class);
- bind(MockInvoiceDao.class).asEagerSingleton();
- bind(InvoiceDao.class).to(MockInvoiceDao.class);
- bind(EntitlementBillingApi.class).toProvider( MockProvider.class );
- bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
+ install(new BusModule(BusType.MEMORY));
+ install(new MockNotificationQueueModule());
+ install(new MockInvoiceModule());
+ install(new TestDbiModule());
}
}
diff --git a/payment/src/test/java/com/ning/billing/payment/TestHelper.java b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
index 6112fc2..33dc142 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestHelper.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
@@ -16,83 +16,70 @@
package com.ning.billing.payment;
-import java.math.BigDecimal;
import java.util.UUID;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.callcontext.CallContextFactory;
-import com.ning.billing.util.entity.EntityPersistenceException;
import org.apache.commons.lang.RandomStringUtils;
import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
import com.google.inject.Inject;
import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.user.AccountBuilder;
-import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.account.api.AccountUserApi;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.invoice.model.DefaultInvoice;
-import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.entity.EntityPersistenceException;
public class TestHelper {
- protected final AccountDao accountDao;
- protected final InvoiceDao invoiceDao;
+ protected final AccountUserApi accountUserApi;
+ protected final InvoicePaymentApi invoicePaymentApi;
private final CallContext context;
+ private final Bus eventBus;
@Inject
- public TestHelper(CallContextFactory factory, AccountDao accountDao, InvoiceDao invoiceDao) {
- this.accountDao = accountDao;
- this.invoiceDao = invoiceDao;
+ public TestHelper(CallContextFactory factory, AccountUserApi accountUserApi, InvoicePaymentApi invoicePaymentApi, Bus eventBus) {
+ this.eventBus = eventBus;
+ this.accountUserApi = accountUserApi;
+ this.invoicePaymentApi = invoicePaymentApi;
context = factory.createCallContext("Princess Buttercup", CallOrigin.TEST, UserType.TEST);
}
// These helper methods can be overridden in a plugin implementation
public Account createTestCreditCardAccount() throws EntityPersistenceException {
- final String name = "First" + RandomStringUtils.randomAlphanumeric(5) + " " + "Last" + RandomStringUtils.randomAlphanumeric(5);
- final String externalKey = RandomStringUtils.randomAlphanumeric(10);
- final Account account = new AccountBuilder(UUID.randomUUID()).name(name)
- .firstNameLength(name.length())
- .externalKey(externalKey)
- .phone("123-456-7890")
- .email("ccuser" + RandomStringUtils.randomAlphanumeric(8) + "@example.com")
- .currency(Currency.USD)
- .billingCycleDay(1)
- .build();
- accountDao.create(account, context);
+ final Account account = createTestAccount("ccuser" + RandomStringUtils.randomAlphanumeric(8) + "@example.com");
+ ((ZombieControl)accountUserApi).addResult("getAccountById", account);
+ ((ZombieControl)accountUserApi).addResult("getAccountByKey", account);
return account;
}
public Account createTestPayPalAccount() throws EntityPersistenceException {
- final String name = "First" + RandomStringUtils.randomAlphanumeric(5) + " " + "Last" + RandomStringUtils.randomAlphanumeric(5);
- final String externalKey = RandomStringUtils.randomAlphanumeric(10);
- final Account account = new AccountBuilder(UUID.randomUUID()).name(name)
- .firstNameLength(name.length())
- .externalKey(externalKey)
- .phone("123-456-7890")
- .email("ppuser@example.com")
- .currency(Currency.USD)
- .billingCycleDay(1)
- .build();
- accountDao.create(account, context);
+ final Account account = createTestAccount("ppuser@example.com");
+ ((ZombieControl)accountUserApi).addResult("getAccountById", account);
+ ((ZombieControl)accountUserApi).addResult("getAccountByKey", account);
return account;
}
public Invoice createTestInvoice(Account account,
DateTime targetDate,
Currency currency,
- InvoiceItem... items) {
- Invoice invoice = new DefaultInvoice(account.getId(), new DateTime(), targetDate, currency);
+ InvoiceItem... items) throws EventBusException {
+ Invoice invoice = new MockInvoice(account.getId(), new DateTime(), targetDate, currency);
for (InvoiceItem item : items) {
- if (item instanceof RecurringInvoiceItem) {
- RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
- invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
+ if (item instanceof MockRecurringInvoiceItem) {
+ MockRecurringInvoiceItem recurringInvoiceItem = (MockRecurringInvoiceItem) item;
+ invoice.addInvoiceItem(new MockRecurringInvoiceItem(invoice.getId(),
account.getId(),
+ recurringInvoiceItem.getBundleId(),
recurringInvoiceItem.getSubscriptionId(),
recurringInvoiceItem.getPlanName(),
recurringInvoiceItem.getPhaseName(),
@@ -103,17 +90,34 @@ public class TestHelper {
recurringInvoiceItem.getCurrency()));
}
}
- invoiceDao.create(invoice, context);
+
+ // invoiceTestApi.create(invoice, context);
+ ((ZombieControl)invoicePaymentApi).addResult("getInvoice", invoice);
+ InvoiceCreationEvent event = new MockInvoiceCreationEvent(invoice.getId(), invoice.getAccountId(),
+ invoice.getBalance(), invoice.getCurrency(),
+ invoice.getInvoiceDate(),
+ context.getUserToken());
+
+ eventBus.post(event);
return invoice;
}
- public Invoice createTestInvoice(Account account) {
- final DateTime now = new DateTime(DateTimeZone.UTC);
- final UUID subscriptionId = UUID.randomUUID();
- final BigDecimal amount = new BigDecimal("10.00");
- final InvoiceItem item = new RecurringInvoiceItem(null, account.getId(), subscriptionId, "test plan", "test phase", now, now.plusMonths(1),
- amount, new BigDecimal("1.0"), Currency.USD);
+ public Account createTestAccount(String email) {
+ final String name = "First" + RandomStringUtils.randomAlphanumeric(5) + " " + "Last" + RandomStringUtils.randomAlphanumeric(5);
+ final String externalKey = RandomStringUtils.randomAlphanumeric(10);
- return createTestInvoice(account, now, Currency.USD, item);
+ Account account = BrainDeadProxyFactory.createBrainDeadProxyFor(Account.class);
+ ZombieControl zombie = (ZombieControl) account;
+ zombie.addResult("getId", UUID.randomUUID());
+ zombie.addResult("getExternalKey", externalKey);
+ zombie.addResult("getName", name);
+ zombie.addResult("getFirstNameLength", 10);
+ zombie.addResult("getPhone", "123-456-7890");
+ zombie.addResult("getEmail", email);
+ zombie.addResult("getCurrency", Currency.USD);
+ zombie.addResult("getBillCycleDay", 1);
+ zombie.addResult("getPaymentProviderName", "");
+
+ return account;
}
}
diff --git a/payment/src/test/java/com/ning/billing/payment/TestRetryService.java b/payment/src/test/java/com/ning/billing/payment/TestRetryService.java
index 1379267..2c519d0 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestRetryService.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestRetryService.java
@@ -25,6 +25,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.UUID;
+import com.ning.billing.payment.api.DefaultPaymentAttempt;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.CallOrigin;
import com.ning.billing.util.callcontext.DefaultCallContext;
@@ -39,30 +40,32 @@ import org.testng.annotations.Test;
import com.google.inject.Inject;
import com.ning.billing.account.api.Account;
-import com.ning.billing.account.glue.AccountModuleWithMocks;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.config.PaymentConfig;
import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
-import com.ning.billing.invoice.model.RecurringInvoiceItem;
-import com.ning.billing.payment.api.Either;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.mock.glue.MockJunctionModule;
import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentApiException;
import com.ning.billing.payment.api.PaymentAttempt;
-import com.ning.billing.payment.api.PaymentError;
-import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentInfoEvent;
import com.ning.billing.payment.api.PaymentStatus;
import com.ning.billing.payment.dao.PaymentDao;
import com.ning.billing.payment.provider.MockPaymentProviderPlugin;
import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
-import com.ning.billing.payment.setup.PaymentConfig;
import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
import com.ning.billing.util.bus.Bus;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.glue.CallContextModule;
import com.ning.billing.util.notificationq.MockNotificationQueue;
import com.ning.billing.util.notificationq.Notification;
import com.ning.billing.util.notificationq.NotificationQueueService;
-@Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
+@Guice(modules = { PaymentTestModuleWithMocks.class, MockClockModule.class, MockJunctionModule.class, CallContextModule.class })
@Test(groups = "fast")
public class TestRetryService {
@Inject
@@ -72,6 +75,8 @@ public class TestRetryService {
@Inject
private PaymentApi paymentApi;
@Inject
+ private InvoicePaymentApi invoicePaymentApi;
+ @Inject
private TestHelper testHelper;
@Inject
private PaymentProviderPluginRegistry registry;
@@ -102,6 +107,8 @@ public class TestRetryService {
mockPaymentProviderPlugin = (MockPaymentProviderPlugin)registry.getPlugin(null);
mockNotificationQueue = (MockNotificationQueue)notificationQueueService.getNotificationQueue(RetryService.SERVICE_NAME, RetryService.QUEUE_NAME);
context = new DefaultCallContext("RetryServiceTests", CallOrigin.INTERNAL, UserType.TEST, clock);
+ ((ZombieControl)invoicePaymentApi).addResult("notifyOfPaymentAttempt", BrainDeadProxyFactory.ZOMBIE_VOID);
+
}
@AfterMethod(alwaysRun = true)
@@ -116,12 +123,14 @@ public class TestRetryService {
final Invoice invoice = testHelper.createTestInvoice(account, clock.getUTCNow(), Currency.USD);
final BigDecimal amount = new BigDecimal("10.00");
final UUID subscriptionId = UUID.randomUUID();
+ final UUID bundleId = UUID.randomUUID();
final DateTime startDate = clock.getUTCNow();
final DateTime endDate = startDate.plusMonths(1);
- invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
+ invoice.addInvoiceItem(new MockRecurringInvoiceItem(invoice.getId(),
account.getId(),
subscriptionId,
+ bundleId,
"test plan", "test phase",
startDate,
endDate,
@@ -130,11 +139,13 @@ public class TestRetryService {
Currency.USD));
mockPaymentProviderPlugin.makeNextInvoiceFail();
-
- List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account.getExternalKey(), Arrays.asList(invoice.getId().toString()), context);
-
- assertEquals(results.size(), 1);
- assertTrue(results.get(0).isLeft());
+ boolean failed = false;
+ try {
+ paymentApi.createPayment(account.getExternalKey(), Arrays.asList(invoice.getId().toString()), context);
+ } catch (PaymentApiException e) {
+ failed = true;
+ }
+ assertTrue(failed);
List<Notification> pendingNotifications = mockNotificationQueue.getPendingEvents();
@@ -144,7 +155,7 @@ public class TestRetryService {
List<PaymentAttempt> paymentAttempts = paymentApi.getPaymentAttemptsForInvoiceId(invoice.getId().toString());
assertNotNull(paymentAttempts);
- assertEquals(notification.getNotificationKey(), paymentAttempts.get(0).getPaymentAttemptId().toString());
+ assertEquals(notification.getNotificationKey(), paymentAttempts.get(0).getId().toString());
DateTime expectedRetryDate = paymentAttempts.get(0).getPaymentAttemptDate().plusDays(paymentConfig.getPaymentRetryDays().get(0));
@@ -157,12 +168,14 @@ public class TestRetryService {
final Invoice invoice = testHelper.createTestInvoice(account, clock.getUTCNow(), Currency.USD);
final BigDecimal amount = new BigDecimal("10.00");
final UUID subscriptionId = UUID.randomUUID();
+ final UUID bundleId = UUID.randomUUID();
final DateTime now = clock.getUTCNow();
- invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
+ invoice.addInvoiceItem(new MockRecurringInvoiceItem(invoice.getId(),
account.getId(),
subscriptionId,
+ bundleId,
"test plan", "test phase",
now,
now.plusMonths(1),
@@ -172,7 +185,7 @@ public class TestRetryService {
int numberOfDays = paymentConfig.getPaymentRetryDays().get(0);
DateTime nextRetryDate = now.plusDays(numberOfDays);
- PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice).cloner()
+ PaymentAttempt paymentAttempt = new DefaultPaymentAttempt(UUID.randomUUID(), invoice).cloner()
.setRetryCount(1)
.setPaymentAttemptDate(now)
.build();
@@ -185,14 +198,14 @@ public class TestRetryService {
List<Notification> pendingNotifications = mockNotificationQueue.getPendingEvents();
assertEquals(pendingNotifications.size(), 0);
- List<PaymentInfo> paymentInfoList = paymentApi.getPaymentInfo(Arrays.asList(invoice.getId().toString()));
+ List<PaymentInfoEvent> paymentInfoList = paymentApi.getPaymentInfoList(Arrays.asList(invoice.getId().toString()));
assertEquals(paymentInfoList.size(), 1);
- PaymentInfo paymentInfo = paymentInfoList.get(0);
+ PaymentInfoEvent paymentInfo = paymentInfoList.get(0);
assertEquals(paymentInfo.getStatus(), PaymentStatus.Processed.toString());
List<PaymentAttempt> updatedAttempts = paymentApi.getPaymentAttemptsForInvoiceId(invoice.getId().toString());
- assertEquals(paymentInfo.getPaymentId(), updatedAttempts.get(0).getPaymentId());
+ assertEquals(paymentInfo.getId(), updatedAttempts.get(0).getPaymentId());
}
}
pom.xml 108(+92 -16)
diff --git a/pom.xml b/pom.xml
index a08995c..f8d46a3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -44,11 +44,42 @@
<module>catalog</module>
<module>entitlement</module>
<module>invoice</module>
+ <module>junction</module>
+ <module>overdue</module>
<module>payment</module>
<module>util</module>
+ <module>jaxrs</module>
+ <module>server</module>
</modules>
<dependencyManagement>
<dependencies>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>jsr311-api</artifactId>
+ <version>1.1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-beatrix</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-beatrix</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-analytics</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-jaxrs</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>com.ning.billing</groupId>
<artifactId>killbill-api</artifactId>
@@ -63,6 +94,11 @@
</dependency>
<dependency>
<groupId>com.ning.billing</groupId>
+ <artifactId>killbill-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
<artifactId>killbill-account</artifactId>
<version>${project.version}</version>
</dependency>
@@ -75,6 +111,18 @@
</dependency>
<dependency>
<groupId>com.ning.billing</groupId>
+ <artifactId>killbill-junction</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-junction</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>
@@ -128,6 +176,18 @@
</dependency>
<dependency>
<groupId>com.ning.billing</groupId>
+ <artifactId>killbill-overdue</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-overdue</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>
@@ -136,17 +196,17 @@
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
- <version>1.9.2</version>
+ <version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
- <version>1.9.2</version>
+ <version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
- <version>1.9.2</version>
+ <version>1.9.5</version>
</dependency>
<dependency>
<groupId>com.jolbox</groupId>
@@ -195,14 +255,24 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-email</artifactId>
+ <version>1.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.directory.studio</groupId>
+ <artifactId>org.apache.commons.io</artifactId>
+ <version>2.1</version>
+ </dependency>
+ <dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
- <version>2.0.1</version>
+ <version>2.0</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
- <version>2.5</version>
+ <version>2.6</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
@@ -235,32 +305,32 @@
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi</artifactId>
- <version>2.31.2</version>
+ <version>2.32</version>
</dependency>
<dependency>
<groupId>org.skife.config</groupId>
<artifactId>config-magic</artifactId>
- <version>0.9</version>
+ <version>0.13</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
- <version>1.6.3</version>
+ <version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
- <version>1.6.3</version>
+ <version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
- <version>1.6.3</version>
+ <version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
- <version>1.6.3</version>
+ <version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
@@ -269,6 +339,11 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>com.samskivert</groupId>
+ <artifactId>jmustache</artifactId>
+ <version>1.5</version>
+ </dependency>
+ <dependency>
<groupId>com.jayway.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>1.3.3</version>
@@ -373,6 +448,7 @@
<exclude>**/.project</exclude>
<exclude>.git/**</exclude>
<exclude>.gitignore</exclude>
+ <exclude>**/.classpath</exclude>
<exclude>ignore/**</exclude>
<exclude>API.txt</exclude>
<exclude>RELEASE.sh</exclude>
@@ -394,12 +470,12 @@
<exclude>**/*.dont-let-git-remove-this-directory</exclude>
<exclude>**/test-output/**</exclude>
<exclude>**/bin/**</exclude>
+ <exclude>**/target/**</exclude>
+ <exclude>**/.settings/**</exclude>
<exclude>.travis.yml</exclude>
- <!-- until we merge from server branch we disable rat for those -->
- <exclude>jaxrs/**</exclude>
- <exclude>server/**</exclude>
<exclude>bin/**</exclude>
-
+ <!-- exclude mustache template files -->
+ <exclude>**/*.mustache</exclude>
</excludes>
</configuration>
</execution>
@@ -483,7 +559,7 @@
<systemPropertyVariables>
<log4j.configuration>file:${project.basedir}/src/test/resources/log4j.xml</log4j.configuration>
<com.ning.billing.dbi.test.useLocalDb>true</com.ning.billing.dbi.test.useLocalDb>
- <com.ning.billing.dbi.jdbc.url>jdbc:mysql://127.0.0.1:3306/test_killbill</com.ning.billing.dbi.jdbc.url>
+ <com.ning.billing.dbi.jdbc.url>jdbc:mysql://127.0.0.1:3306/killbill</com.ning.billing.dbi.jdbc.url>
<file.encoding>UTF-8</file.encoding>
<user.timezone>GMT</user.timezone>
</systemPropertyVariables>
server/pom.xml 434(+434 -0)
diff --git a/server/pom.xml b/server/pom.xml
new file mode 100644
index 0000000..e740c10
--- /dev/null
+++ b/server/pom.xml
@@ -0,0 +1,434 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ~ Copyright 2010-2011 Ning, Inc. ~ ~ Ning licenses this file to you
+ under the Apache License, version 2.0 ~ (the "License"); you may not use
+ this file except in compliance with the ~ License. You may obtain a copy
+ of the License at: ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless
+ required by applicable law or agreed to in writing, software ~ distributed
+ under the License is distributed on an "AS IS" BASIS, WITHOUT ~ WARRANTIES
+ OR CONDITIONS OF ANY KIND, either express or implied. See the ~ License for
+ the specific language governing permissions and limitations ~ under the License. -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill</artifactId>
+ <version>0.1.11-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <artifactId>killbill-server</artifactId>
+ <name>killbill-server</name>
+ <packaging>war</packaging>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <guice.version>3.0</guice.version>
+ <jersey.version>1.12</jersey.version>
+ <jetty.version>8.1.2.v20120308</jetty.version>
+ <logback.version>1.0.1</logback.version>
+ <metrics.version>2.1.2</metrics.version>
+ <slf4j.version>1.6.4</slf4j.version>
+ <skeleton.version>0.1.2</skeleton.version>
+ <async-http-client.version>1.6.5</async-http-client.version>
+ </properties>
+
+ <dependencies>
+ <!-- Jetty provided scope -->
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-http</artifactId>
+ <version>${jetty.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-io</artifactId>
+ <version>${jetty.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <version>${jetty.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${jetty.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-deploy</artifactId>
+ <version>${jetty.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-jmx</artifactId>
+ <version>${jetty.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-xml</artifactId>
+ <version>${jetty.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- NOT in master POM; include version as well -->
+ <dependency>
+ <groupId>org.weakref</groupId>
+ <artifactId>jmxutils</artifactId>
+ <version>1.12</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.inject.extensions</groupId>
+ <artifactId>guice-servlet</artifactId>
+ <version>${guice.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.yammer.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ <version>${metrics.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.yammer.metrics</groupId>
+ <artifactId>metrics-guice</artifactId>
+ <version>${metrics.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${jersey.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey.contribs</groupId>
+ <artifactId>jersey-guice</artifactId>
+ <version>${jersey.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>3.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.jetty</groupId>
+ <artifactId>ning-service-skeleton-base</artifactId>
+ <version>${skeleton.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.jetty</groupId>
+ <artifactId>ning-service-skeleton-jdbi</artifactId>
+ <version>${skeleton.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.jetty</groupId>
+ <artifactId>ning-service-skeleton-log4j</artifactId>
+ <version>${skeleton.version}</version>
+ <classifier>selfcontained</classifier>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ <version>${logback.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>${logback.version}</version>
+ </dependency>
+
+ <!-- FROM MASTER POM / LIBRARY -->
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-account</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-jaxrs</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-beatrix</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-entitlement</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-invoice</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-payment</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-catalog</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-analytics</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-junction</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>11.0.2</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.inject</groupId>
+ <artifactId>guice</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency><!-- Needed by jmxutils -->
+ <groupId>com.google.inject.extensions</groupId>
+ <artifactId>guice-multibindings</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.antlr</groupId>
+ <artifactId>stringtemplate</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.skife.config</groupId>
+ <artifactId>config-magic</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>jsr311-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>joda-time</groupId>
+ <artifactId>joda-time</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning</groupId>
+ <artifactId>async-http-client</artifactId>
+ <version>${async-http-client.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-util</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-beatrix</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-payment</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-mxj</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-mxj-db-files</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <resources>
+ <resource>
+ <directory>${basedir}/src/main/resources</directory>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <groupId>com.ning.maven.plugins</groupId>
+ <artifactId>maven-dependency-versions-check-plugin</artifactId>
+ <version>2.0.2</version>
+ <configuration>
+ <failBuildInCaseOfConflict>true</failBuildInCaseOfConflict>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>verify</phase>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <!-- To make eclipse happy -->
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>2.5</version>
+ </plugin>
+ <plugin>
+ <groupId>com.ning.maven.plugins</groupId>
+ <artifactId>maven-duplicate-finder-plugin</artifactId>
+ <version>1.0.2</version>
+ <configuration>
+ <failBuildInCaseOfConflict>false</failBuildInCaseOfConflict>
+ <!-- That's for Jetty -->
+ <ignoredResources>
+ <ignoredResource>about.html</ignoredResource>
+ </ignoredResources>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>verify</phase>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.3.2</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <version>2.3</version>
+ <executions>
+ <execution>
+ <id>analyze</id>
+ <goals>
+ <goal>analyze-only</goal>
+ </goals>
+ <configuration>
+ <ignoreNonCompile>true</ignoreNonCompile>
+ <failOnWarning>false</failOnWarning>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ <version>2.2.1</version>
+ <configuration>
+ <mavenExecutorId>forked-path</mavenExecutorId>
+ </configuration>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.scm</groupId>
+ <artifactId>maven-scm-provider-gitexe</artifactId>
+ <version>1.4</version>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-utils</artifactId>
+ <version>1.5.9</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ <plugin>
+ <!-- TODO: fix for http://jira.codehaus.org/browse/MSITE-286? -->
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-site-plugin</artifactId>
+ <version>3.0</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-war-plugin</artifactId>
+ <version>2.1.1</version>
+ <configuration>
+ <attachClasses>true</attachClasses>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>jetty-maven-plugin</artifactId>
+ <version>${jetty.version}</version>
+ <dependencies>
+ <dependency><!-- For LogLevelCounterAppender -->
+ <groupId>com.ning.jetty</groupId>
+ <artifactId>ning-service-skeleton-log4j</artifactId>
+ <version>${skeleton.version}</version>
+ <classifier>selfcontained</classifier>
+ <scope>runtime</scope>
+ </dependency>
+ <!-- Needed to redirect Jetty logs to slf4j -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ <version>${logback.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>${logback.version}</version>
+ </dependency>
+ </dependencies>
+ <configuration>
+ <scanIntervalSeconds>60</scanIntervalSeconds>
+ <systemProperties>
+ <systemProperty>
+ <name>logback.configurationFile</name>
+ <value>file:${basedir}/src/main/resources/logback.xml</value>
+ </systemProperty>
+ </systemProperties>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/server/src/main/java/com/ning/billing/server/healthchecks/KillbillHealthcheck.java b/server/src/main/java/com/ning/billing/server/healthchecks/KillbillHealthcheck.java
new file mode 100644
index 0000000..62c79bf
--- /dev/null
+++ b/server/src/main/java/com/ning/billing/server/healthchecks/KillbillHealthcheck.java
@@ -0,0 +1,45 @@
+/*
+ * 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.server.healthchecks;
+
+import com.yammer.metrics.core.HealthCheck;
+import org.weakref.jmx.Managed;
+
+public class KillbillHealthcheck extends HealthCheck
+{
+ public KillbillHealthcheck()
+ {
+ super(KillbillHealthcheck.class.getSimpleName());
+ }
+
+ @Override
+ public Result check()
+ {
+ try {
+ // STEPH obviously needs more than that
+ return Result.healthy();
+ }
+ catch (Exception e) {
+ return Result.unhealthy(e);
+ }
+ }
+
+ @Managed(description = "Basic killbill healthcheck")
+ public boolean isHealthy()
+ {
+ return check().isHealthy();
+ }
+}
diff --git a/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java b/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
new file mode 100644
index 0000000..1aaef9b
--- /dev/null
+++ b/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.server.listeners;
+
+
+import com.ning.billing.beatrix.lifecycle.DefaultLifecycle;
+import com.ning.billing.jaxrs.util.KillbillEventHandler;
+import com.ning.billing.server.config.KillbillServerConfig;
+import com.ning.billing.server.healthchecks.KillbillHealthcheck;
+import com.ning.billing.server.modules.KillbillServerModule;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.BusService;
+import com.ning.jetty.base.modules.ServerModuleBuilder;
+import com.ning.jetty.core.listeners.SetupServer;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletContextEvent;
+
+public class KillbillGuiceListener extends SetupServer
+{
+ public static final Logger logger = LoggerFactory.getLogger(KillbillGuiceListener.class);
+
+ private DefaultLifecycle killbillLifecycle;
+ private BusService killbillBusService;
+ private KillbillEventHandler killbilleventHandler;
+
+
+ protected Module getModule() {
+ return new KillbillServerModule();
+ }
+
+ @Override
+ public void contextInitialized(ServletContextEvent event)
+ {
+
+
+ final ServerModuleBuilder builder = new ServerModuleBuilder()
+ .addConfig(KillbillServerConfig.class)
+ .addHealthCheck(KillbillHealthcheck.class)
+ .addJMXExport(KillbillHealthcheck.class)
+ .addModule(getModule())
+ .addJerseyResource("com.ning.billing.jaxrs.resources");
+
+
+ guiceModule = builder.build();
+
+ super.contextInitialized(event);
+
+ logger.info("KillbillLifecycleListener : contextInitialized");
+ Injector theInjector = injector(event);
+ killbillLifecycle = theInjector.getInstance(DefaultLifecycle.class);
+ killbillBusService = theInjector.getInstance(BusService.class);
+ killbilleventHandler = theInjector.getInstance(KillbillEventHandler.class);
+
+ /*
+ ObjectMapper mapper = theInjector.getInstance(ObjectMapper.class);
+ mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy());
+*/
+
+ //
+ // Fire all Startup levels up to service start
+ //
+ killbillLifecycle.fireStartupSequencePriorEventRegistration();
+ //
+ // Perform Bus registration
+ //
+ try {
+ killbillBusService.getBus().register(killbilleventHandler);
+ }
+ catch (Bus.EventBusException e) {
+ logger.error("Failed to register for event notifications, this is bad exiting!", e);
+ System.exit(1);
+ }
+ // Let's start!
+ killbillLifecycle.fireStartupSequencePostEventRegistration();
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce)
+ {
+ super.contextDestroyed(sce);
+
+ logger.info("IrsKillbillListener : contextDestroyed");
+ // Stop services
+ // Guice error, no need to fill the screen with useless stack traces
+ if (killbillLifecycle == null) {
+ return;
+ }
+
+ killbillLifecycle.fireShutdownSequencePriorEventUnRegistration();
+
+ try {
+ killbillBusService.getBus().unregister(killbilleventHandler);
+ }
+ catch (Bus.EventBusException e) {
+ logger.warn("Failed to unregister for event notifications", e);
+ }
+
+ // Complete shutdown sequence
+ killbillLifecycle.fireShutdownSequencePostEventUnRegistration();
+ }
+}
diff --git a/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
new file mode 100644
index 0000000..d6c3691
--- /dev/null
+++ b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
@@ -0,0 +1,98 @@
+/*
+ * 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.server.modules;
+
+import com.ning.billing.util.email.EmailModule;
+import com.ning.billing.util.email.templates.TemplateModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
+import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.account.glue.AccountModule;
+import com.ning.billing.analytics.setup.AnalyticsModule;
+import com.ning.billing.beatrix.glue.BeatrixModule;
+import com.ning.billing.catalog.glue.CatalogModule;
+import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
+import com.ning.billing.invoice.glue.DefaultInvoiceModule;
+import com.ning.billing.jaxrs.resources.AccountResource;
+import com.ning.billing.jaxrs.resources.BundleResource;
+import com.ning.billing.jaxrs.resources.InvoiceResource;
+import com.ning.billing.jaxrs.resources.PaymentResource;
+import com.ning.billing.jaxrs.resources.SubscriptionResource;
+import com.ning.billing.jaxrs.resources.TagResource;
+import com.ning.billing.jaxrs.util.KillbillEventHandler;
+import com.ning.billing.jaxrs.util.TagHelper;
+import com.ning.billing.junction.glue.DefaultJunctionModule;
+import com.ning.billing.payment.setup.PaymentModule;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.CallContextModule;
+import com.ning.billing.util.glue.ClockModule;
+import com.ning.billing.util.glue.FieldStoreModule;
+import com.ning.billing.util.glue.NotificationQueueModule;
+import com.ning.billing.util.glue.TagStoreModule;
+import com.ning.jetty.jdbi.guice.providers.DBIProvider;
+
+public class KillbillServerModule extends AbstractModule
+{
+ @Override
+ protected void configure() {
+ configureDao();
+ configureResources();
+ installKillbillModules();
+ }
+
+ protected void configureDao() {
+ bind(IDBI.class).to(DBI.class).asEagerSingleton();
+ bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+ }
+
+ protected void configureResources() {
+ bind(TagHelper.class).asEagerSingleton();
+ bind(AccountResource.class).asEagerSingleton();
+ bind(BundleResource.class).asEagerSingleton();
+ bind(SubscriptionResource.class).asEagerSingleton();
+ bind(InvoiceResource.class).asEagerSingleton();
+ bind(PaymentResource.class).asEagerSingleton();
+ bind(TagResource.class).asEagerSingleton();
+ bind(KillbillEventHandler.class).asEagerSingleton();
+ }
+
+ protected void installClock() {
+ install(new ClockModule());
+ }
+
+ protected void installKillbillModules() {
+ install(new EmailModule());
+ install(new GlobalLockerModule());
+ install(new FieldStoreModule());
+ install(new TagStoreModule());
+ install(new CatalogModule());
+ install(new BusModule());
+ install(new NotificationQueueModule());
+ install(new CallContextModule());
+ install(new AccountModule());
+ install(new DefaultInvoiceModule());
+ install(new TemplateModule());
+ install(new DefaultEntitlementModule());
+ install(new AnalyticsModule());
+ install(new PaymentModule());
+ install(new BeatrixModule());
+ install(new DefaultJunctionModule());
+ installClock();
+ }
+}
diff --git a/server/src/main/jetty-config/contexts/root.xml b/server/src/main/jetty-config/contexts/root.xml
new file mode 100755
index 0000000..cdf71e1
--- /dev/null
+++ b/server/src/main/jetty-config/contexts/root.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+ <Set name="contextPath">/</Set>
+ <Set name="war">
+ <SystemProperty name="xn.jetty.webapp.path" default="webapps/root"/>
+ </Set>
+ <Set name="defaultsDescriptor">
+ <SystemProperty name="xn.jetty.webapps.defaultsDescriptor" default="etc/webdefault.xml"/>
+ </Set>
+</Configure>
server/src/main/jetty-config/etc/webdefault.xml 186(+186 -0)
diff --git a/server/src/main/jetty-config/etc/webdefault.xml b/server/src/main/jetty-config/etc/webdefault.xml
new file mode 100755
index 0000000..19ae6ff
--- /dev/null
+++ b/server/src/main/jetty-config/etc/webdefault.xml
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app
+ xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+ metadata-complete="true"
+ version="2.5">
+ <description>
+ Default web.xml file.
+ This file is applied to a Web application before it's own WEB_INF/web.xml file
+ </description>
+ <context-param>
+ <param-name>org.mortbay.jetty.webapp.NoTLDJarPattern</param-name>
+ <param-value>
+ start.jar|ant-.*\.jar|dojo-.*\.jar|jetty-.*\.jar|jsp-api-.*\.jar|junit-.*\.jar|servlet-api-.*\.jar|dnsns\.jar|rt\.jar|jsse\.jar|tools\.jar|sunpkcs11\.jar|sunjce_provider\.jar|xerces.*\.jar
+ </param-value>
+ </context-param>
+ <locale-encoding-mapping-list>
+ <locale-encoding-mapping>
+ <locale>ar</locale>
+ <encoding>ISO-8859-6</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>be</locale>
+ <encoding>ISO-8859-5</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>bg</locale>
+ <encoding>ISO-8859-5</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>ca</locale>
+ <encoding>ISO-8859-1</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>cs</locale>
+ <encoding>ISO-8859-2</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>da</locale>
+ <encoding>ISO-8859-1</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>de</locale>
+ <encoding>ISO-8859-1</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>el</locale>
+ <encoding>ISO-8859-7</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>en</locale>
+ <encoding>ISO-8859-1</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>es</locale>
+ <encoding>ISO-8859-1</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>et</locale>
+ <encoding>ISO-8859-1</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>fi</locale>
+ <encoding>ISO-8859-1</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>fr</locale>
+ <encoding>ISO-8859-1</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>hr</locale>
+ <encoding>ISO-8859-2</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>hu</locale>
+ <encoding>ISO-8859-2</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>is</locale>
+ <encoding>ISO-8859-1</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>it</locale>
+ <encoding>ISO-8859-1</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>iw</locale>
+ <encoding>ISO-8859-8</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>ja</locale>
+ <encoding>Shift_JIS</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>ko</locale>
+ <encoding>EUC-KR</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>lt</locale>
+ <encoding>ISO-8859-2</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>lv</locale>
+ <encoding>ISO-8859-2</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>mk</locale>
+ <encoding>ISO-8859-5</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>nl</locale>
+ <encoding>ISO-8859-1</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>no</locale>
+ <encoding>ISO-8859-1</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>pl</locale>
+ <encoding>ISO-8859-2</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>pt</locale>
+ <encoding>ISO-8859-1</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>ro</locale>
+ <encoding>ISO-8859-2</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>ru</locale>
+ <encoding>ISO-8859-5</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>sh</locale>
+ <encoding>ISO-8859-5</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>sk</locale>
+ <encoding>ISO-8859-2</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>sl</locale>
+ <encoding>ISO-8859-2</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>sq</locale>
+ <encoding>ISO-8859-2</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>sr</locale>
+ <encoding>ISO-8859-5</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>sv</locale>
+ <encoding>ISO-8859-1</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>tr</locale>
+ <encoding>ISO-8859-9</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>uk</locale>
+ <encoding>ISO-8859-5</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>zh</locale>
+ <encoding>GB2312</encoding>
+ </locale-encoding-mapping>
+ <locale-encoding-mapping>
+ <locale>zh_TW</locale>
+ <encoding>Big5</encoding>
+ </locale-encoding-mapping>
+ </locale-encoding-mapping-list>
+
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>Disable TRACE</web-resource-name>
+ <url-pattern>/</url-pattern>
+ <http-method>TRACE</http-method>
+ </web-resource-collection>
+ <auth-constraint/>
+ </security-constraint>
+</web-app>
+
server/src/main/jetty-config/ning-jetty-conf.xml 122(+122 -0)
diff --git a/server/src/main/jetty-config/ning-jetty-conf.xml b/server/src/main/jetty-config/ning-jetty-conf.xml
new file mode 100644
index 0000000..60764c5
--- /dev/null
+++ b/server/src/main/jetty-config/ning-jetty-conf.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+ <!-- =============================================================== -->
+ <!-- Setup MBean Server early -->
+ <!-- =============================================================== -->
+ <Call id="MBeanServer" class="java.lang.management.ManagementFactory" name="getPlatformMBeanServer"/>
+
+ <New id="MBeanContainer" class="org.eclipse.jetty.jmx.MBeanContainer">
+ <Arg>
+ <Ref id="MBeanServer"/>
+ </Arg>
+ </New>
+
+ <Get id="Container" name="container">
+ <Call name="addEventListener">
+ <Arg>
+ <Ref id="MBeanContainer"/>
+ </Arg>
+ </Call>
+ </Get>
+
+ <!-- =========================================================== -->
+ <!-- Server Thread Pool -->
+ <!-- =========================================================== -->
+ <Set name="ThreadPool">
+ <!-- Default queued blocking threadpool -->
+ <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
+ <Set name="minThreads">
+ <SystemProperty name="xn.server.threads.min" default="10"/>
+ </Set>
+ <Set name="maxThreads">
+ <SystemProperty name="xn.server.threads.max" default="200"/>
+ </Set>
+ </New>
+ </Set>
+
+ <!-- =========================================================== -->
+ <!-- Set connectors -->
+ <!-- =========================================================== -->
+
+ <!-- Use this connector if NIO is not available. -->
+ <Call name="addConnector">
+ <Arg>
+ <New class="org.eclipse.jetty.server.bio.SocketConnector">
+ <Set name="host">
+ <SystemProperty name="xn.server.ip"/>
+ </Set>
+ <Set name="port">
+ <SystemProperty name="xn.server.port" default="8080"/>
+ </Set>
+ <Set name="maxIdleTime">300000</Set>
+ <Set name="Acceptors">2</Set>
+ <Set name="statsOn">true</Set>
+ <Set name="confidentialPort">
+ <SystemProperty name="xn.server.ssl.port" default="8443"/>
+ </Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <Set name="handler">
+ <New class="org.eclipse.jetty.server.handler.StatisticsHandler">
+ <Set name="handler">
+ <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
+ <Set name="handlers">
+ <Array type="org.eclipse.jetty.server.Handler">
+ <Item>
+ <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
+ </Item>
+ <Item>
+ <New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
+ </Item>
+ <Item>
+ <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler"/>
+ </Item>
+ </Array>
+ </Set>
+ </New>
+ </Set>
+ </New>
+ </Set>
+
+ <Ref id="RequestLog">
+ <Set name="requestLog">
+ <New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
+ <Arg>
+ <SystemProperty name="jetty.logs" default="./logs"/>/yyyy_mm_dd.request.log
+ </Arg>
+ <Set name="retainDays">30</Set>
+ <Set name="append">true</Set>
+ <Set name="extended">false</Set>
+ <Set name="LogTimeZone">GMT</Set>
+ </New>
+ </Set>
+ </Ref>
+
+ <Call name="addLifeCycle">
+ <Arg>
+ <New class="org.eclipse.jetty.deploy.ContextDeployer">
+ <Set name="contexts">
+ <Ref id="Contexts"/>
+ </Set>
+ <Set name="configurationDir">
+ <SystemProperty name="xn.jetty.contextDir" default="contexts"/>
+ </Set>
+ <Set name="scanInterval">1</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- =========================================================== -->
+ <!-- extra options -->
+ <!-- =========================================================== -->
+ <Set name="stopAtShutdown">true</Set>
+ <Set name="sendServerVersion">false</Set>
+ <Set name="sendDateHeader">true</Set>
+ <Set name="gracefulShutdown">
+ <SystemProperty name="xn.jetty.gracefulShutdownTimeoutInMs" default="1000"/>
+ </Set>
+</Configure>
server/src/main/resources/catalog-demo.xml 641(+641 -0)
diff --git a/server/src/main/resources/catalog-demo.xml b/server/src/main/resources/catalog-demo.xml
new file mode 100644
index 0000000..8a97d57
--- /dev/null
+++ b/server/src/main/resources/catalog-demo.xml
@@ -0,0 +1,641 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+ ~ Copyright 2010-2011 Ning, Inc.
+ ~
+ ~ Ning licenses this file to you under the Apache License, version 2.0
+ ~ (the "License"); you may not use this file except in compliance with the
+ ~ License. You may obtain a copy of the License at:
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ ~ License for the specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+<!--
+Use cases covered so far:
+ Tiered Product (Pistol/Shotgun/Assault-Rifle)
+ Multiple changeEvent plan policies
+ Multiple PlanAlignment (see below, trial add-on alignments and rescue discount package)
+ Product transition rules
+ Add on (Scopes, Hoster)
+ Multi-pack addon (Extra-Ammo)
+ Addon Trial aligned to base plan (holster-monthly-regular)
+ Addon Trial aligned to creation (holster-monthly-special)
+ Rescue discount package (assault-rifle-annual-rescue)
+ Plan phase with a reccurring and a one off (refurbish-maintenance)
+ Phan with more than 2 phase (gunclub discount plans)
+
+Use Cases to do:
+ Tiered Add On
+ Riskfree period
+
+
+
+ -->
+<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+
+ <effectiveDate>2011-01-01T00:00:00+00:00</effectiveDate>
+ <catalogName>Firearms</catalogName>
+
+ <currencies>
+ <currency>USD</currency>
+ <currency>EUR</currency>
+ <currency>GBP</currency>
+ </currencies>
+
+ <products>
+ <product name="Pistol">
+ <category>BASE</category>
+ </product>
+ <product name="Shotgun">
+ <category>BASE</category>
+ <available>
+ <addonProduct>Telescopic-Scope</addonProduct>
+ <addonProduct>Laser-Scope</addonProduct>
+ </available>
+ </product>
+ <product name="Assault-Rifle">
+ <category>BASE</category>
+ <included>
+ <addonProduct>Telescopic-Scope</addonProduct>
+ </included>
+ <available>
+ <addonProduct>Laser-Scope</addonProduct>
+ </available>
+ </product>
+ <product name="Telescopic-Scope">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Laser-Scope">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Holster">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Extra-Ammo">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Refurbish-Maintenance">
+ <category>ADD_ON</category>
+ </product>
+ </products>
+
+ <rules>
+ <changePolicy>
+ <changePolicyCase>
+ <phaseType>TRIAL</phaseType>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <toProduct>Assault-Rifle</toProduct>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromProduct>Pistol</fromProduct>
+ <toProduct>Shotgun</toProduct>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <toPriceList>rescue</toPriceList>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromBillingPeriod>MONTHLY</fromBillingPeriod>
+ <toBillingPeriod>ANNUAL</toBillingPeriod>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromBillingPeriod>ANNUAL</fromBillingPeriod>
+ <toBillingPeriod>MONTHLY</toBillingPeriod>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ </changePolicy>
+ <changeAlignment>
+ <changeAlignmentCase>
+ <toPriceList>rescue</toPriceList>
+ <alignment>CHANGE_OF_PLAN</alignment>
+ </changeAlignmentCase>
+ <changeAlignmentCase>
+ <fromPriceList>rescue</fromPriceList>
+ <toPriceList>rescue</toPriceList>
+ <alignment>CHANGE_OF_PRICELIST</alignment>
+ </changeAlignmentCase>
+ <changeAlignmentCase>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </changeAlignmentCase>
+ </changeAlignment>
+ <cancelPolicy>
+ <cancelPolicyCase>
+ <phaseType>TRIAL</phaseType>
+ <policy>IMMEDIATE</policy>
+ </cancelPolicyCase>
+ <cancelPolicyCase>
+ <policy>END_OF_TERM</policy>
+ </cancelPolicyCase>
+ </cancelPolicy>
+ <createAlignment>
+ <createAlignmentCase>
+ <product>Laser-Scope</product>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </createAlignmentCase>
+ <createAlignmentCase>
+ <alignment>START_OF_BUNDLE</alignment>
+ </createAlignmentCase>
+ </createAlignment>
+ <billingAlignment>
+ <billingAlignmentCase>
+ <productCategory>ADD_ON</productCategory>
+ <alignment>BUNDLE</alignment>
+ </billingAlignmentCase>
+ <billingAlignmentCase>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <alignment>SUBSCRIPTION</alignment>
+ </billingAlignmentCase>
+ <billingAlignmentCase>
+ <alignment>ACCOUNT</alignment>
+ </billingAlignmentCase>
+ </billingAlignment>
+ <priceList>
+ <priceListCase>
+ <fromPriceList>rescue</fromPriceList>
+ <toPriceList>DEFAULT</toPriceList>
+ </priceListCase>
+ </priceList>
+ </rules>
+
+ <plans>
+ <plan name="pistol-monthly">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice> <!-- empty price implies $0 -->
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>GBP</currency><value>29.95</value></price>
+ <price><currency>EUR</currency><value>29.95</value></price>
+ <price><currency>USD</currency><value>29.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-monthly">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice></fixedPrice>
+ <!-- no price implies $0 -->
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ <number>-1</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>249.95</value></price>
+ <price><currency>EUR</currency><value>149.95</value></price>
+ <price><currency>GBP</currency><value>169.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-monthly">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>599.95</value></price>
+ <price><currency>EUR</currency><value>349.95</value></price>
+ <price><currency>GBP</currency><value>399.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-annual">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-annual">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>2399.95</value></price>
+ <price><currency>EUR</currency><value>1499.95</value></price>
+ <price><currency>GBP</currency><value>1699.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>5999.95</value></price>
+ <price><currency>EUR</currency><value>3499.95</value></price>
+ <price><currency>GBP</currency><value>3999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-annual-gunclub-discount">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>9.95</value></price>
+ <price><currency>EUR</currency><value>9.95</value></price>
+ <price><currency>GBP</currency><value>9.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-annual-gunclub-discount">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>19.95</value></price>
+ <price><currency>EUR</currency><value>49.95</value></price>
+ <price><currency>GBP</currency><value>69.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>2399.95</value></price>
+ <price><currency>EUR</currency><value>1499.95</value></price>
+ <price><currency>GBP</currency><value>1699.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual-gunclub-discount">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>99.95</value></price>
+ <price><currency>EUR</currency><value>99.95</value></price>
+ <price><currency>GBP</currency><value>99.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>5999.95</value></price>
+ <price><currency>EUR</currency><value>3499.95</value></price>
+ <price><currency>GBP</currency><value>3999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="laser-scope-monthly">
+ <product>Laser-Scope</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>1</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>999.95</value></price>
+ <price><currency>EUR</currency><value>499.95</value></price>
+ <price><currency>GBP</currency><value>999.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>1999.95</value></price>
+ <price><currency>EUR</currency><value>1499.95</value></price>
+ <price><currency>GBP</currency><value>1999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="telescopic-scope-monthly">
+ <product>Telescopic-Scope</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>1</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>399.95</value></price>
+ <price><currency>EUR</currency><value>299.95</value></price>
+ <price><currency>GBP</currency><value>399.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>999.95</value></price>
+ <price><currency>EUR</currency><value>499.95</value></price>
+ <price><currency>GBP</currency><value>999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="extra-ammo-monthly">
+ <product>Extra-Ammo</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>999.95</value></price>
+ <price><currency>EUR</currency><value>499.95</value></price>
+ <price><currency>GBP</currency><value>999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ <plansAllowedInBundle>-1</plansAllowedInBundle> <!-- arbitrary number of these (multipack) -->
+ </plan>
+ <plan name="holster-monthly-regular">
+ <product>Holster</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="holster-monthly-special">
+ <product>Holster</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual-rescue">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>YEARS</unit>
+ <number>1</number>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>5999.95</value></price>
+ <price><currency>EUR</currency><value>3499.95</value></price>
+ <price><currency>GBP</currency><value>3999.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>5999.95</value></price>
+ <price><currency>EUR</currency><value>3499.95</value></price>
+ <price><currency>GBP</currency><value>3999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="refurbish-maintenance">
+ <product>Refurbish-Maintenance</product>
+ <finalPhase type="FIXEDTERM">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>12</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ <fixedPrice>
+ <price><currency>USD</currency><value>599.95</value></price>
+ <price><currency>EUR</currency><value>599.95</value></price>
+ <price><currency>GBP</currency><value>599.95</value></price>
+ </fixedPrice>
+ </finalPhase>
+ </plan>
+ </plans>
+ <priceLists>
+ <defaultPriceList name="DEFAULT">
+ <plans>
+ <plan>pistol-monthly</plan>
+ <plan>shotgun-monthly</plan>
+ <plan>assault-rifle-monthly</plan>
+ <plan>pistol-annual</plan>
+ <plan>shotgun-annual</plan>
+ <plan>assault-rifle-annual</plan>
+ <plan>laser-scope-monthly</plan>
+ <plan>telescopic-scope-monthly</plan>
+ <plan>extra-ammo-monthly</plan>
+ <plan>holster-monthly-regular</plan>
+ <plan>refurbish-maintenance</plan>
+ </plans>
+ </defaultPriceList>
+ <childPriceList name="gunclubDiscount">
+ <plans>
+ <plan>pistol-monthly</plan>
+ <plan>shotgun-monthly</plan>
+ <plan>assault-rifle-monthly</plan>
+ <plan>pistol-annual-gunclub-discount</plan>
+ <plan>shotgun-annual-gunclub-discount</plan>
+ <plan>assault-rifle-annual-gunclub-discount</plan>
+ <plan>holster-monthly-special</plan>
+ </plans>
+ </childPriceList>
+ <childPriceList name="rescue">
+ <plans>
+ <plan>assault-rifle-annual-rescue</plan>
+ </plans>
+ </childPriceList>
+ </priceLists>
+
+</catalog>
diff --git a/server/src/main/resources/killbill-server.properties b/server/src/main/resources/killbill-server.properties
new file mode 100644
index 0000000..daaa110
--- /dev/null
+++ b/server/src/main/resources/killbill-server.properties
@@ -0,0 +1,12 @@
+# Use skeleton properties for server and configure killbill database
+com.ning.jetty.jdbi.url=jdbc:mysql://127.0.0.1:3306/killbill
+com.ning.jetty.jdbi.user=root
+com.ning.jetty.jdbi.password=root
+
+killbill.catalog.uri=file:src/main/resources/catalog-demo.xml
+
+killbill.entitlement.dao.claim.time=60000
+killbill.entitlement.dao.ready.max=1
+killbill.entitlement.engine.notifications.sleep=500
+user.timezone=UTC
+
server/src/main/resources/logback.xml 13(+13 -0)
diff --git a/server/src/main/resources/logback.xml b/server/src/main/resources/logback.xml
new file mode 100644
index 0000000..3087885
--- /dev/null
+++ b/server/src/main/resources/logback.xml
@@ -0,0 +1,13 @@
+<configuration>
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <!-- encoders are assigned the type
+ ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
+ <encoder>
+ <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <root level="info">
+ <appender-ref ref="STDOUT" />
+ </root>
+</configuration>
server/src/main/webapp/WEB-INF/web.xml 35(+35 -0)
diff --git a/server/src/main/webapp/WEB-INF/web.xml b/server/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..9891d30
--- /dev/null
+++ b/server/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+ version="2.5">
+ <display-name>irs</display-name>
+ <filter>
+ <!-- Guice emulates Servlet API with DI -->
+ <filter-name>guiceFilter</filter-name>
+ <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
+ </filter>
+ <filter-mapping>
+ <filter-name>guiceFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+ <listener>
+ <!-- Jersey insists on using java.util.logging (JUL) -->
+ <listener-class>com.ning.jetty.core.listeners.SetupJULBridge</listener-class>
+ </listener>
+ <listener>
+ <!-- Context listener: called at startup time and creates the injector -->
+ <listener-class>com.ning.billing.server.listeners.KillbillGuiceListener</listener-class>
+ </listener>
+ <!-- ServletHandler#handle requires a backend servlet, it won't be used though (handled by Guice) -->
+ <servlet>
+ <servlet-name>log-invalid-resources</servlet-name>
+ <servlet-class>com.ning.jetty.core.servlets.LogInvalidResourcesServlet</servlet-class>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>log-invalid-resources</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+</web-app>
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java b/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
new file mode 100644
index 0000000..414f2a0
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
@@ -0,0 +1,204 @@
+/*
+ * 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.jaxrs;
+
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+
+import javax.ws.rs.core.Response.Status;
+
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.PropertyNamingStrategy;
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.AccountTimelineJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.CustomFieldJson;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
+import com.ning.billing.jaxrs.json.TagDefinitionJson;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.http.client.Response;
+
+
+public class TestAccount extends TestJaxrsBase {
+
+ private static final Logger log = LoggerFactory.getLogger(TestAccount.class);
+
+
+
+
+ @Test(groups="slow", enabled=true)
+ public void testAccountOk() throws Exception {
+
+ AccountJson input = createAccount("xoxo", "shdgfhwe", "xoxo@yahoo.com");
+
+ // Retrieves by external key
+ Map<String, String> queryParams = new HashMap<String, String>();
+ queryParams.put(BaseJaxrsResource.QUERY_EXTERNAL_KEY, "shdgfhwe");
+ Response response = doGet(BaseJaxrsResource.ACCOUNTS_PATH, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ String baseJson = response.getResponseBody();
+ AccountJson objFromJson = mapper.readValue(baseJson, AccountJson.class);
+ Assert.assertTrue(objFromJson.equals(input));
+
+ // Update Account
+ AccountJson newInput = new AccountJson(objFromJson.getAccountId(),
+ "zozo", 4, objFromJson.getExternalKey(), "rr@google.com", 18, "EUR", "none", "UTC", "bl1", "bh2", "", "ca", "usa", "415-255-2991");
+ baseJson = mapper.writeValueAsString(newInput);
+ final String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + objFromJson.getAccountId();
+ response = doPut(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ baseJson = response.getResponseBody();
+ objFromJson = mapper.readValue(baseJson, AccountJson.class);
+ Assert.assertTrue(objFromJson.equals(newInput));
+ }
+
+
+ @Test(groups="slow", enabled=true)
+ public void testUpdateNonExistentAccount() throws Exception {
+ AccountJson input = getAccountJson("xoxo", "shghaahwe", "xoxo@yahoo.com");
+ String baseJson = mapper.writeValueAsString(input);
+ final String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + input.getAccountId();
+ Response response = doPut(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+ String body = response.getResponseBody();
+ Assert.assertEquals(body, "");
+ }
+
+
+ @Test(groups="slow", enabled=true)
+ public void testAccountNonExistent() throws Exception {
+ final String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/99999999-b103-42f3-8b6e-dd244f1d0747";
+ Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+ }
+
+ @Test(groups="slow", enabled=true)
+ public void testAccountBadAccountId() throws Exception {
+ final String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/yo";
+ Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.NOT_FOUND.getStatusCode());
+ }
+
+ @Test(groups="slow", enabled=true)
+ public void testAccountTimeline() throws Exception {
+
+ clock.setTime(new DateTime(2012, 4, 25, 0, 3, 42, 0));
+
+
+ AccountJson accountJson = createAccount("poney", "shdddqgfhwe", "poney@yahoo.com");
+ assertNotNull(accountJson);
+
+ BundleJsonNoSubsciptions bundleJson = createBundle(accountJson.getAccountId(), "996599");
+ assertNotNull(bundleJson);
+
+ SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), "Shotgun", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), true);
+ assertNotNull(subscriptionJson);
+
+ // MOVE AFTER TRIAL
+ clock.addMonths(3);
+
+ crappyWaitForLackOfProperSynchonization();
+
+
+ final String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + accountJson.getAccountId() + "/" + BaseJaxrsResource.TIMELINE;
+
+ Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ String baseJson = response.getResponseBody();
+ AccountTimelineJson objFromJson = mapper.readValue(baseJson, AccountTimelineJson.class);
+ assertNotNull(objFromJson);
+ log.info(baseJson);
+
+ Assert.assertEquals(objFromJson.getPayments().size(), 3);
+ Assert.assertEquals(objFromJson.getInvoices().size(), 4);
+ Assert.assertEquals(objFromJson.getBundles().size(), 1);
+ Assert.assertEquals(objFromJson.getBundles().get(0).getSubscriptions().size(), 1);
+ Assert.assertEquals(objFromJson.getBundles().get(0).getSubscriptions().get(0).getEvents().size(), 2);
+ }
+
+ @Test(groups="slow", enabled=false)
+ public void testAccountWithTags() throws Exception {
+ //Create Tag definition
+ TagDefinitionJson input = new TagDefinitionJson("yoyo", "nothing more to say");
+ String baseJson = mapper.writeValueAsString(input);
+ Response response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+ AccountJson accountJson = createAccount("couroucoucou", "shdwdsqgfhwe", "couroucoucou@yahoo.com");
+ assertNotNull(accountJson);
+
+ Map<String, String> queryParams = new HashMap<String, String>();
+ queryParams.put(BaseJaxrsResource.QUERY_TAGS, input.getName());
+ String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + BaseJaxrsResource.TAGS + "/" + accountJson.getAccountId() ;
+ response = doPost(uri, null, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+ /*
+ * STEPH Some how Location returns the ID twice (confused) :
+ * Location: http://127.0.0.1:8080/1.0/kb/accounts/tags/ebb5f830-6f0a-4521-9553-521d173169be/ebb5f830-6f0a-4521-9553-521d173169be
+ *
+ String location = response.getHeader("Location");
+ Assert.assertNotNull(location);
+
+ // Retrieves by Id based on Location returned
+ response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ */
+
+ }
+
+ @Test(groups="slow", enabled=false)
+ public void testAccountWithCustomFields() throws Exception {
+
+ AccountJson accountJson = createAccount("carafe", "shdwhwgaz", "carafe@yahoo.com");
+ assertNotNull(accountJson);
+
+ List<CustomFieldJson> customFields = new LinkedList<CustomFieldJson>();
+ customFields.add(new CustomFieldJson("1", "value1"));
+ customFields.add(new CustomFieldJson("2", "value2"));
+ customFields.add(new CustomFieldJson("3", "value3"));
+ String baseJson = mapper.writeValueAsString(customFields);
+
+ String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + BaseJaxrsResource.CUSTOM_FIELDS + "/" + accountJson.getAccountId() ;
+ Response response = doPost(uri,baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+ String location = response.getHeader("Location");
+ Assert.assertNotNull(location);
+
+ // Retrieves by Id based on Location returned
+ response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+ }
+}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java b/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
new file mode 100644
index 0000000..7795d0c
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
@@ -0,0 +1,116 @@
+/*
+ * 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.jaxrs;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.http.client.Response;
+
+public class TestBundle extends TestJaxrsBase {
+
+ private static final Logger log = LoggerFactory.getLogger(TestBundle.class);
+
+
+
+ @Test(groups="slow", enabled=true)
+ public void testBundleOk() throws Exception {
+
+ AccountJson accountJson = createAccount("xlxl", "shdgfhkkl", "xlxl@yahoo.com");
+ BundleJsonNoSubsciptions bundleJson = createBundle(accountJson.getAccountId(), "12345");
+
+ // Retrieves by external key
+ Map<String, String> queryParams = new HashMap<String, String>();
+ queryParams.put(BaseJaxrsResource.QUERY_EXTERNAL_KEY, "12345");
+ Response response = doGet(BaseJaxrsResource.BUNDLES_PATH, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ String baseJson = response.getResponseBody();
+ BundleJsonNoSubsciptions objFromJson = mapper.readValue(baseJson, BundleJsonNoSubsciptions.class);
+ Assert.assertTrue(objFromJson.equals(bundleJson));
+ }
+
+
+ @Test(groups="slow", enabled=true)
+ public void testBundleFromAccount() throws Exception {
+
+ AccountJson accountJson = createAccount("xaxa", "saagfhkkl", "xaxa@yahoo.com");
+ BundleJsonNoSubsciptions bundleJson1 = createBundle(accountJson.getAccountId(), "156567");
+ BundleJsonNoSubsciptions bundleJson2 = createBundle(accountJson.getAccountId(), "265658");
+
+ String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + accountJson.getAccountId().toString() + "/" + BaseJaxrsResource.BUNDLES;
+ Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ String baseJson = response.getResponseBody();
+ List<BundleJsonNoSubsciptions> objFromJson = mapper.readValue(baseJson, new TypeReference<List<BundleJsonNoSubsciptions>>() {});
+
+ Collections.sort(objFromJson, new Comparator<BundleJsonNoSubsciptions>() {
+ @Override
+ public int compare(BundleJsonNoSubsciptions o1, BundleJsonNoSubsciptions o2) {
+ return o1.getExternalKey().compareTo(o2.getExternalKey());
+ }
+ });
+ Assert.assertEquals(objFromJson.get(0), bundleJson1);
+ Assert.assertEquals(objFromJson.get(1), bundleJson2);
+ }
+
+ @Test(groups="slow", enabled=true)
+ public void testBundleNonExistent() throws Exception {
+ AccountJson accountJson = createAccount("dfdf", "dfdfgfhkkl", "dfdf@yahoo.com");
+
+ String uri = BaseJaxrsResource.BUNDLES_PATH + "/99999999-b103-42f3-8b6e-dd244f1d0747";
+ Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+
+
+ // Retrieves by external key
+ Map<String, String> queryParams = new HashMap<String, String>();
+ queryParams.put(BaseJaxrsResource.QUERY_EXTERNAL_KEY, "56566");
+ response = doGet(BaseJaxrsResource.BUNDLES_PATH, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+
+
+ uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + accountJson.getAccountId().toString() + "/" + BaseJaxrsResource.BUNDLES;
+ response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ String baseJson = response.getResponseBody();
+ List<BundleJsonNoSubsciptions> objFromJson = mapper.readValue(baseJson, new TypeReference<List<BundleJsonNoSubsciptions>>() {});
+ Assert.assertNotNull(objFromJson);
+ Assert.assertEquals(objFromJson.size(), 0);
+ }
+
+ @Test(groups="slow", enabled=true)
+ public void testAppNonExistent() throws Exception {
+ String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/99999999-b103-42f3-8b6e-dd244f1d0747/" + BaseJaxrsResource.BUNDLES;
+ Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+ }
+
+
+}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
new file mode 100644
index 0000000..aba67b8
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
@@ -0,0 +1,126 @@
+/*
+ * 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.jaxrs;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.InvoiceJsonSimple;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.http.client.Response;
+
+public class TestInvoice extends TestJaxrsBase {
+
+ private final DateTimeFormatter DATE_TIME_FORMATTER = ISODateTimeFormat.dateTime();
+
+ private static final Logger log = LoggerFactory.getLogger(TestInvoice.class);
+
+
+ @Test(groups="slow", enabled=true)
+ public void testInvoiceOk() throws Exception {
+
+ DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+
+ AccountJson accountJson = createAccount("poupou", "qhddffrwe", "poupou@yahoo.com");
+ assertNotNull(accountJson);
+
+ BundleJsonNoSubsciptions bundleJson = createBundle(accountJson.getAccountId(), "9967599");
+ assertNotNull(bundleJson);
+
+ SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), "Shotgun", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), true);
+ assertNotNull(subscriptionJson);
+
+ // MOVE AFTER TRIAL
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(3).plusDays(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ crappyWaitForLackOfProperSynchonization();
+
+ String uri = BaseJaxrsResource.INVOICES_PATH;
+ Map<String, String> queryParams = new HashMap<String, String>();
+ queryParams.put(BaseJaxrsResource.QUERY_ACCOUNT_ID, accountJson.getAccountId());
+
+ Response response = doGet(uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ String baseJson = response.getResponseBody();
+ List<InvoiceJsonSimple> objFromJson = mapper.readValue(baseJson, new TypeReference<List<InvoiceJsonSimple>>() {});
+ assertNotNull(objFromJson);
+ log.info(baseJson);
+ assertEquals(objFromJson.size(), 4);
+
+ // Check we can retrieve an individual invoice
+ uri = BaseJaxrsResource.INVOICES_PATH + "/" + objFromJson.get(0).getInvoiceId();
+ response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ baseJson = response.getResponseBody();
+ InvoiceJsonSimple firstInvoiceJson = mapper.readValue(baseJson, InvoiceJsonSimple.class);
+ assertNotNull(objFromJson);
+ assertEquals(firstInvoiceJson, objFromJson.get(0));
+
+ // Then create a dryRun Invoice
+ DateTime futureDate = clock.getUTCNow().plusMonths(1).plusDays(3);
+ uri = BaseJaxrsResource.INVOICES_PATH;
+ queryParams.put(BaseJaxrsResource.QUERY_TARGET_DATE, futureDate.toString());
+ queryParams.put(BaseJaxrsResource.QUERY_DRY_RUN, "true");
+ response = doPost(uri, null, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ baseJson = response.getResponseBody();
+ InvoiceJsonSimple futureInvoice = mapper.readValue(baseJson, InvoiceJsonSimple.class);
+ assertNotNull(futureInvoice);
+ log.info(baseJson);
+
+ // The one more time with no DryRun
+ queryParams.remove(BaseJaxrsResource.QUERY_DRY_RUN);
+ response = doPost(uri, null, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+ String location = response.getHeader("Location");
+ Assert.assertNotNull(location);
+
+ // Check again # invoices, should be 5 this time
+ uri = BaseJaxrsResource.INVOICES_PATH;
+ response = doGet(uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ baseJson = response.getResponseBody();
+ objFromJson = mapper.readValue(baseJson, new TypeReference<List<InvoiceJsonSimple>>() {});
+ assertNotNull(objFromJson);
+ log.info(baseJson);
+ assertEquals(objFromJson.size(), 5);
+ }
+}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
new file mode 100644
index 0000000..0a0d619
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -0,0 +1,497 @@
+/*
+ * 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.jaxrs;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import javax.ws.rs.core.Response.Status;
+
+import com.ning.billing.util.email.EmailModule;
+import com.ning.billing.util.email.templates.TemplateModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
+import org.apache.commons.io.IOUtils;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.joda.JodaModule;
+import com.google.inject.Module;
+import com.ning.billing.account.glue.AccountModule;
+import com.ning.billing.analytics.setup.AnalyticsModule;
+import com.ning.billing.api.TestApiListener;
+import com.ning.billing.beatrix.glue.BeatrixModule;
+import com.ning.billing.beatrix.integration.TestIntegration;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.glue.CatalogModule;
+import com.ning.billing.config.PaymentConfig;
+import com.ning.billing.dbi.DBIProvider;
+import com.ning.billing.dbi.DbiConfig;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
+import com.ning.billing.invoice.glue.DefaultInvoiceModule;
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.billing.junction.glue.DefaultJunctionModule;
+import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
+import com.ning.billing.payment.setup.PaymentModule;
+import com.ning.billing.server.listeners.KillbillGuiceListener;
+import com.ning.billing.server.modules.KillbillServerModule;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.CallContextModule;
+import com.ning.billing.util.glue.FieldStoreModule;
+import com.ning.billing.util.glue.NotificationQueueModule;
+import com.ning.billing.util.glue.TagStoreModule;
+import com.ning.http.client.AsyncCompletionHandler;
+import com.ning.http.client.AsyncHttpClient;
+import com.ning.http.client.AsyncHttpClient.BoundRequestBuilder;
+import com.ning.http.client.ListenableFuture;
+import com.ning.http.client.Response;
+import com.ning.jetty.core.CoreConfig;
+import com.ning.jetty.core.server.HttpServer;
+
+public class TestJaxrsBase {
+
+ private final static String PLUGIN_NAME = "noop";
+
+ protected static final int DEFAULT_HTTP_TIMEOUT_SEC = 500000; // 5;
+
+ protected static final Map<String, String> DEFAULT_EMPTY_QUERY = new HashMap<String, String>();
+
+ private static final Logger log = LoggerFactory.getLogger(TestJaxrsBase.class);
+
+ public static final String HEADER_CONTENT_TYPE = "Content-type";
+ public static final String CONTENT_TYPE = "application/json";
+
+ private static TestKillbillGuiceListener listener;
+
+
+ private MysqlTestingHelper helper;
+ private HttpServer server;
+
+ protected CoreConfig config;
+ protected AsyncHttpClient httpClient;
+ protected ObjectMapper mapper;
+ protected ClockMock clock;
+ protected TestApiListener busHandler;
+
+ // Context informtation to be passed around
+ private static final String createdBy = "Toto";
+ private static final String reason = "i am god";
+ private static final String comment = "no comment";
+
+ public static void loadSystemPropertiesFromClasspath(final String resource) {
+ final URL url = TestJaxrsBase.class.getResource(resource);
+ assertNotNull(url);
+ try {
+ System.getProperties().load(url.openStream());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static class TestKillbillGuiceListener extends KillbillGuiceListener {
+
+ private final MysqlTestingHelper helper;
+ private final Clock clock;
+
+ public TestKillbillGuiceListener(final MysqlTestingHelper helper, final Clock clock) {
+ super();
+ this.helper = helper;
+ this.clock = clock;
+ }
+ @Override
+ protected Module getModule() {
+ return new TestKillbillServerModule(helper, clock);
+ }
+
+ //
+ // Listener is created once before Suite and keeps pointer to helper and clock so they can get
+ // reset for each test Class-- needed in order to ONLY start Jetty once across all the test classes
+ // while still being able to start mysql before Jetty is started
+ //
+ public MysqlTestingHelper getMysqlTestingHelper() {
+ return helper;
+ }
+
+ public Clock getClock() {
+ return clock;
+ }
+ }
+
+ public static class TestKillbillServerModule extends KillbillServerModule {
+
+ private final MysqlTestingHelper helper;
+ private final Clock clock;
+
+ public TestKillbillServerModule(final MysqlTestingHelper helper, final Clock clock) {
+ super();
+ this.helper = helper;
+ this.clock = clock;
+ }
+
+ @Override
+ protected void installClock() {
+ bind(Clock.class).toInstance(clock);
+ }
+
+
+ private static final class PaymentMockModule extends PaymentModule {
+ @Override
+ protected void installPaymentProviderPlugins(PaymentConfig config) {
+ install(new MockPaymentProviderPluginModule(PLUGIN_NAME));
+ }
+ }
+
+ protected void installKillbillModules(){
+
+ /*
+ * For a lack of getting module override working, copy all install modules from parent class...
+ *
+ super.installKillbillModules();
+ Modules.override(new com.ning.billing.payment.setup.PaymentModule()).with(new PaymentMockModule());
+ */
+ install(new EmailModule());
+ install(new GlobalLockerModule());
+ install(new FieldStoreModule());
+ install(new TagStoreModule());
+ install(new CatalogModule());
+ install(new BusModule());
+ install(new NotificationQueueModule());
+ install(new CallContextModule());
+ install(new AccountModule());
+ install(new DefaultInvoiceModule());
+ install(new TemplateModule());
+ install(new DefaultEntitlementModule());
+ install(new AnalyticsModule());
+ install(new PaymentMockModule());
+ install(new BeatrixModule());
+ install(new DefaultJunctionModule());
+ installClock();
+ }
+
+ @Override
+ protected void configureDao() {
+ bind(MysqlTestingHelper.class).toInstance(helper);
+ if (helper.isUsingLocalInstance()) {
+ bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+ final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
+ bind(DbiConfig.class).toInstance(config);
+ } else {
+ final IDBI dbi = helper.getDBI();
+ bind(IDBI.class).toInstance(dbi);
+ }
+ }
+ }
+
+ @BeforeMethod(groups="slow")
+ public void cleanupBeforeMethod() {
+ busHandler.reset();
+ helper.cleanupAllTables();
+ }
+
+ @BeforeClass(groups="slow")
+ public void setupClass() throws IOException {
+ loadConfig();
+ httpClient = new AsyncHttpClient();
+ mapper = new ObjectMapper();
+ mapper.registerModule(new JodaModule());
+ mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+
+ //mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy());
+
+ busHandler = new TestApiListener(null);
+ this.helper = listener.getMysqlTestingHelper();
+ this.clock = (ClockMock) listener.getClock();
+ }
+
+ private void setupMySQL() throws IOException {
+ final String accountDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
+ final String entitlementDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+ final String invoiceDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+ final String paymentDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+ final String utilDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+ final String analyticsDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/analytics/ddl.sql"));
+ final String junctionDdl = IOUtils.toString(TestIntegration.class.getResourceAsStream("/com/ning/billing/junction/ddl.sql"));
+
+ helper.startMysql();
+
+ helper.initDb(accountDdl);
+ helper.initDb(entitlementDdl);
+ helper.initDb(invoiceDdl);
+ helper.initDb(paymentDdl);
+ helper.initDb(utilDdl);
+ helper.initDb(analyticsDdl);
+ helper.initDb(junctionDdl);
+ }
+
+
+ private void loadConfig() {
+ if (config == null) {
+ config = new ConfigurationObjectFactory(System.getProperties()).build(CoreConfig.class);
+ }
+ }
+
+ @BeforeSuite(groups="slow")
+ public void setup() throws Exception {
+
+ loadSystemPropertiesFromClasspath("/killbill.properties");
+ loadConfig();
+
+ this.helper = new MysqlTestingHelper();
+ this.clock = new ClockMock();
+ listener = new TestKillbillGuiceListener(helper, clock);
+ server = new HttpServer();
+
+ final Iterable<EventListener> eventListeners = new Iterable<EventListener>() {
+ @Override
+ public Iterator<EventListener> iterator() {
+ ArrayList<EventListener> array = new ArrayList<EventListener>();
+ array.add(listener);
+ return array.iterator();
+ }
+ };
+ server.configure(config, eventListeners, new HashMap<FilterHolder, String>());
+
+ setupMySQL();
+ helper.cleanupAllTables();
+
+ server.start();
+ }
+
+ @AfterSuite(groups="slow")
+ public void tearDown() {
+ try {
+ server.stop();
+ } catch (Exception e) {
+
+ }
+ if (helper != null) {
+ helper.stopMysql();
+ }
+ }
+
+
+ protected AccountJson createAccount(String name, String key, String email) throws Exception {
+ AccountJson input = getAccountJson(name, key, email);
+ String baseJson = mapper.writeValueAsString(input);
+ Response response = doPost(BaseJaxrsResource.ACCOUNTS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+ String location = response.getHeader("Location");
+ Assert.assertNotNull(location);
+
+ // Retrieves by Id based on Location returned
+ response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+ baseJson = response.getResponseBody();
+ AccountJson objFromJson = mapper.readValue(baseJson, AccountJson.class);
+ Assert.assertNotNull(objFromJson);
+ return objFromJson;
+ }
+
+
+
+ protected BundleJsonNoSubsciptions createBundle(String accountId, String key) throws Exception {
+ BundleJsonNoSubsciptions input = new BundleJsonNoSubsciptions(null, accountId, key, null);
+ String baseJson = mapper.writeValueAsString(input);
+ Response response = doPost(BaseJaxrsResource.BUNDLES_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+ String location = response.getHeader("Location");
+ Assert.assertNotNull(location);
+
+ // Retrieves by Id based on Location returned
+ response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+ baseJson = response.getResponseBody();
+ BundleJsonNoSubsciptions objFromJson = mapper.readValue(baseJson, BundleJsonNoSubsciptions.class);
+ Assert.assertTrue(objFromJson.equalsNoId(input));
+ return objFromJson;
+ }
+
+ protected SubscriptionJsonNoEvents createSubscription(final String bundleId, final String productName, final String productCategory, final String billingPeriod, final boolean waitCompletion) throws Exception {
+
+ SubscriptionJsonNoEvents input = new SubscriptionJsonNoEvents(null, bundleId, null, productName, productCategory, billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ String baseJson = mapper.writeValueAsString(input);
+
+
+ Map<String, String> queryParams = waitCompletion ? getQueryParamsForCallCompletion("5") : DEFAULT_EMPTY_QUERY;
+ Response response = doPost(BaseJaxrsResource.SUBSCRIPTIONS_PATH, baseJson, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+ String location = response.getHeader("Location");
+ Assert.assertNotNull(location);
+
+ // Retrieves by Id based on Location returned
+
+ response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+ baseJson = response.getResponseBody();
+ SubscriptionJsonNoEvents objFromJson = mapper.readValue(baseJson, SubscriptionJsonNoEvents.class);
+ Assert.assertTrue(objFromJson.equalsNoId(input));
+ return objFromJson;
+ }
+
+ protected Map<String, String> getQueryParamsForCallCompletion(final String timeoutSec) {
+ Map<String, String> queryParams = new HashMap<String, String>();
+ queryParams.put(BaseJaxrsResource.QUERY_CALL_COMPLETION, "true");
+ queryParams.put(BaseJaxrsResource.QUERY_CALL_TIMEOUT, timeoutSec);
+ return queryParams;
+ }
+
+ //
+ // HTTP CLIENT HELPERS
+ //
+ protected Response doPost(final String uri, final String body, final Map<String, String> queryParams, final int timeoutSec) {
+ BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("POST", getUrlFromUri(uri), queryParams);
+ if (body != null) {
+ builder.setBody(body);
+ } else {
+ builder.setBody("{}");
+ }
+ return executeAndWait(builder, timeoutSec, true);
+ }
+
+ protected Response doPut(final String uri, final String body, final Map<String, String> queryParams, final int timeoutSec) {
+ final String url = String.format("http://%s:%d%s", config.getServerHost(), config.getServerPort(), uri);
+ BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("PUT", url, queryParams);
+ if (body != null) {
+ builder.setBody(body);
+ } else {
+ builder.setBody("{}");
+ }
+ return executeAndWait(builder, timeoutSec, true);
+ }
+
+ protected Response doDelete(final String uri, final Map<String, String> queryParams, final int timeoutSec) {
+ final String url = String.format("http://%s:%d%s", config.getServerHost(), config.getServerPort(), uri);
+ BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("DELETE", url, queryParams);
+ return executeAndWait(builder, timeoutSec, true);
+ }
+
+ protected Response doGet(final String uri, final Map<String, String> queryParams, final int timeoutSec) {
+ final String url = String.format("http://%s:%d%s", config.getServerHost(), config.getServerPort(), uri);
+ return doGetWithUrl(url, queryParams, timeoutSec);
+ }
+
+ protected Response doGetWithUrl(final String url, final Map<String, String> queryParams, final int timeoutSec) {
+ BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("GET", url, queryParams);
+ return executeAndWait(builder, timeoutSec, false);
+ }
+
+ private Response executeAndWait(final BoundRequestBuilder builder, final int timeoutSec, final boolean addContextHeader) {
+
+ if (addContextHeader) {
+ builder.addHeader(BaseJaxrsResource.HDR_CREATED_BY, createdBy);
+ builder.addHeader(BaseJaxrsResource.HDR_REASON, reason);
+ builder.addHeader(BaseJaxrsResource.HDR_COMMENT, comment);
+ }
+
+ Response response = null;
+ try {
+ ListenableFuture<Response> futureStatus =
+ builder.execute(new AsyncCompletionHandler<Response>() {
+ @Override
+ public Response onCompleted(Response response) throws Exception {
+ return response;
+ }
+ });
+ response = futureStatus.get(timeoutSec, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ Assert.fail(e.getMessage());
+ }
+ Assert.assertNotNull(response);
+ return response;
+ }
+
+ private String getUrlFromUri(final String uri) {
+ return String.format("http://%s:%d%s", config.getServerHost(), config.getServerPort(), uri);
+ }
+
+ private BoundRequestBuilder getBuilderWithHeaderAndQuery(final String verb, final String url, final Map<String, String> queryParams) {
+ BoundRequestBuilder builder = null;
+ if (verb.equals("GET")) {
+ builder = httpClient.prepareGet(url);
+ } else if (verb.equals("POST")) {
+ builder = httpClient.preparePost(url);
+ } else if (verb.equals("PUT")) {
+ builder = httpClient.preparePut(url);
+ } else if (verb.equals("DELETE")) {
+ builder = httpClient.prepareDelete(url);
+ }
+ builder.addHeader(HEADER_CONTENT_TYPE, CONTENT_TYPE);
+ for (Entry<String, String> q : queryParams.entrySet()) {
+ builder.addQueryParameter(q.getKey(), q.getValue());
+ }
+ return builder;
+ }
+
+ public AccountJson getAccountJson(final String name, final String externalKey, final String email) {
+ String accountId = UUID.randomUUID().toString();
+ int length = 4;
+ int billCycleDay = 12;
+ String currency = "USD";
+ String paymentProvider = "noop";
+ String timeZone = "UTC";
+ String address1 = "12 rue des ecoles";
+ String address2 = "Poitier";
+ String company = "Renault";
+ String state = "Poitou";
+ String country = "France";
+ String phone = "81 53 26 56";
+
+ AccountJson accountJson = new AccountJson(accountId, name, length, externalKey, email, billCycleDay, currency, paymentProvider, timeZone, address1, address2, company, state, country, phone);
+ return accountJson;
+ }
+
+ /**
+ *
+ * We could implement a ClockResource in jaxrs with the ability to sync on user token
+ * but until we have a strong need for it, this is in the TODO list...
+ */
+ protected void crappyWaitForLackOfProperSynchonization() throws Exception {
+ Thread.sleep(5000);
+ }
+
+}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java b/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
new file mode 100644
index 0000000..d43c858
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
@@ -0,0 +1,132 @@
+/*
+ * 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.jaxrs;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+import java.util.UUID;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.http.client.Response;
+
+public class TestSubscription extends TestJaxrsBase {
+
+ private static final Logger log = LoggerFactory.getLogger(TestSubscription.class);
+
+ private static final String CALL_COMPLETION_TIMEOUT_SEC = "5";
+
+
+ @Test(groups="slow", enabled=true)
+ public void testSubscriptionInTrialOk() throws Exception {
+
+ DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ AccountJson accountJson = createAccount("xil", "shdxilhkkl", "xil@yahoo.com");
+ BundleJsonNoSubsciptions bundleJson = createBundle(accountJson.getAccountId(), "99999");
+
+ String productName = "Shotgun";
+ BillingPeriod term = BillingPeriod.MONTHLY;
+
+ SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), productName, ProductCategory.BASE.toString(), term.toString(), true);
+ Assert.assertNotNull(subscriptionJson.getChargedThroughDate());
+ Assert.assertEquals(subscriptionJson.getChargedThroughDate(), subscriptionJson.getStartDate().plusDays(30));
+
+ String uri = BaseJaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId().toString();
+
+
+ // Retrieves with GET
+ Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ String baseJson = response.getResponseBody();
+ SubscriptionJsonNoEvents objFromJson = mapper.readValue(baseJson, SubscriptionJsonNoEvents.class);
+ Assert.assertTrue(objFromJson.equals(subscriptionJson));
+
+ // Change plan IMM
+ String newProductName = "Assault-Rifle";
+
+ SubscriptionJsonNoEvents newInput = new SubscriptionJsonNoEvents(subscriptionJson.getSubscriptionId(),
+ subscriptionJson.getBundleId(),
+ null,
+ newProductName,
+ subscriptionJson.getProductCategory(),
+ subscriptionJson.getBillingPeriod(),
+ subscriptionJson.getPriceList(), null);
+ baseJson = mapper.writeValueAsString(newInput);
+
+ Map<String, String> queryParams = getQueryParamsForCallCompletion(CALL_COMPLETION_TIMEOUT_SEC);
+ response = doPut(uri, baseJson, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ baseJson = response.getResponseBody();
+ objFromJson = mapper.readValue(baseJson, SubscriptionJsonNoEvents.class);
+ assertTrue(objFromJson.equals(newInput));
+
+ // MOVE AFTER TRIAL
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(3).plusDays(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ crappyWaitForLackOfProperSynchonization();
+
+ //
+ // Cancel EOT
+ uri = BaseJaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId().toString();
+ response = doDelete(uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+ // Uncancel
+ uri = BaseJaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId().toString() + "/uncancel";
+ response = doPut(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ }
+
+ @Test(groups="slow", enabled=true)
+ public void testWithNonExistentSubscription() throws Exception {
+ String uri = BaseJaxrsResource.SUBSCRIPTIONS_PATH + "/" + UUID.randomUUID().toString();
+ SubscriptionJsonNoEvents subscriptionJson = new SubscriptionJsonNoEvents(null, UUID.randomUUID().toString(), null, "Pistol", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ String baseJson = mapper.writeValueAsString(subscriptionJson);
+
+ Response response = doPut(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+ String body = response.getResponseBody();
+ Assert.assertEquals(body, "");
+
+ response = doDelete(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+ body = response.getResponseBody();
+ Assert.assertEquals(body, "");
+
+ response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+ }
+
+}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestTag.java b/server/src/test/java/com/ning/billing/jaxrs/TestTag.java
new file mode 100644
index 0000000..c1f460a
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestTag.java
@@ -0,0 +1,114 @@
+/*
+ * 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.jaxrs;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.List;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.ning.billing.jaxrs.json.TagDefinitionJson;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.http.client.Response;
+
+public class TestTag extends TestJaxrsBase {
+
+ private static final Logger log = LoggerFactory.getLogger(TestTag.class);
+
+ @Test(groups="slow", enabled=true)
+ public void testTagDefinitionOk() throws Exception {
+
+ TagDefinitionJson input = new TagDefinitionJson("blue", "realxing color");
+ String baseJson = mapper.writeValueAsString(input);
+ Response response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+ String location = response.getHeader("Location");
+ assertNotNull(location);
+
+ // Retrieves by Id based on Location returned
+ response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+ baseJson = response.getResponseBody();
+ TagDefinitionJson objFromJson = mapper.readValue(baseJson, TagDefinitionJson.class);
+ assertNotNull(objFromJson);
+ assertEquals(objFromJson, input);
+ }
+
+ @Test(groups="slow", enabled=true)
+ public void testMultipleTagDefinitionOk() throws Exception {
+
+ Response response = doGet(BaseJaxrsResource.TAG_DEFINITIONS_PATH, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ String baseJson = response.getResponseBody();
+
+ List<TagDefinitionJson> objFromJson = mapper.readValue(baseJson, new TypeReference<List<TagDefinitionJson>>() {});
+ int sizeSystemTag = (objFromJson == null || objFromJson.size() == 0) ? 0 : objFromJson.size();
+
+ TagDefinitionJson input = new TagDefinitionJson("blue", "realxing color");
+ baseJson = mapper.writeValueAsString(input);
+ response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+ input = new TagDefinitionJson("red", "hot color");
+ baseJson = mapper.writeValueAsString(input);
+ response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+ input = new TagDefinitionJson("yellow", "vibrant color");
+ baseJson = mapper.writeValueAsString(input);
+ response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+ input = new TagDefinitionJson("green", "super realxing color");
+ baseJson = mapper.writeValueAsString(input);
+ response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+ response = doGet(BaseJaxrsResource.TAG_DEFINITIONS_PATH, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ baseJson = response.getResponseBody();
+
+ objFromJson = mapper.readValue(baseJson, new TypeReference<List<TagDefinitionJson>>() {});
+ assertNotNull(objFromJson);
+ assertEquals(objFromJson.size(), 4 + sizeSystemTag);
+
+ // STEPH currently broken Tag API does not work as expected...
+
+ /*
+ String uri = BaseJaxrsResource.TAG_DEFINITIONS_PATH + "/green";
+ response = doDelete(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+
+ response = doGet(BaseJaxrsResource.TAG_DEFINITIONS_PATH, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+ baseJson = response.getResponseBody();
+
+ objFromJson = mapper.readValue(baseJson, new TypeReference<List<TagDefinitionJson>>() {});
+ assertNotNull(objFromJson);
+ assertEquals(objFromJson.size(), 3 + sizeSystemTag);
+ */
+ }
+
+}
server/src/test/resources/catalog-weapons.xml 641(+641 -0)
diff --git a/server/src/test/resources/catalog-weapons.xml b/server/src/test/resources/catalog-weapons.xml
new file mode 100644
index 0000000..8a97d57
--- /dev/null
+++ b/server/src/test/resources/catalog-weapons.xml
@@ -0,0 +1,641 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+ ~ Copyright 2010-2011 Ning, Inc.
+ ~
+ ~ Ning licenses this file to you under the Apache License, version 2.0
+ ~ (the "License"); you may not use this file except in compliance with the
+ ~ License. You may obtain a copy of the License at:
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ ~ License for the specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+<!--
+Use cases covered so far:
+ Tiered Product (Pistol/Shotgun/Assault-Rifle)
+ Multiple changeEvent plan policies
+ Multiple PlanAlignment (see below, trial add-on alignments and rescue discount package)
+ Product transition rules
+ Add on (Scopes, Hoster)
+ Multi-pack addon (Extra-Ammo)
+ Addon Trial aligned to base plan (holster-monthly-regular)
+ Addon Trial aligned to creation (holster-monthly-special)
+ Rescue discount package (assault-rifle-annual-rescue)
+ Plan phase with a reccurring and a one off (refurbish-maintenance)
+ Phan with more than 2 phase (gunclub discount plans)
+
+Use Cases to do:
+ Tiered Add On
+ Riskfree period
+
+
+
+ -->
+<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+
+ <effectiveDate>2011-01-01T00:00:00+00:00</effectiveDate>
+ <catalogName>Firearms</catalogName>
+
+ <currencies>
+ <currency>USD</currency>
+ <currency>EUR</currency>
+ <currency>GBP</currency>
+ </currencies>
+
+ <products>
+ <product name="Pistol">
+ <category>BASE</category>
+ </product>
+ <product name="Shotgun">
+ <category>BASE</category>
+ <available>
+ <addonProduct>Telescopic-Scope</addonProduct>
+ <addonProduct>Laser-Scope</addonProduct>
+ </available>
+ </product>
+ <product name="Assault-Rifle">
+ <category>BASE</category>
+ <included>
+ <addonProduct>Telescopic-Scope</addonProduct>
+ </included>
+ <available>
+ <addonProduct>Laser-Scope</addonProduct>
+ </available>
+ </product>
+ <product name="Telescopic-Scope">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Laser-Scope">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Holster">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Extra-Ammo">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Refurbish-Maintenance">
+ <category>ADD_ON</category>
+ </product>
+ </products>
+
+ <rules>
+ <changePolicy>
+ <changePolicyCase>
+ <phaseType>TRIAL</phaseType>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <toProduct>Assault-Rifle</toProduct>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromProduct>Pistol</fromProduct>
+ <toProduct>Shotgun</toProduct>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <toPriceList>rescue</toPriceList>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromBillingPeriod>MONTHLY</fromBillingPeriod>
+ <toBillingPeriod>ANNUAL</toBillingPeriod>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromBillingPeriod>ANNUAL</fromBillingPeriod>
+ <toBillingPeriod>MONTHLY</toBillingPeriod>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ </changePolicy>
+ <changeAlignment>
+ <changeAlignmentCase>
+ <toPriceList>rescue</toPriceList>
+ <alignment>CHANGE_OF_PLAN</alignment>
+ </changeAlignmentCase>
+ <changeAlignmentCase>
+ <fromPriceList>rescue</fromPriceList>
+ <toPriceList>rescue</toPriceList>
+ <alignment>CHANGE_OF_PRICELIST</alignment>
+ </changeAlignmentCase>
+ <changeAlignmentCase>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </changeAlignmentCase>
+ </changeAlignment>
+ <cancelPolicy>
+ <cancelPolicyCase>
+ <phaseType>TRIAL</phaseType>
+ <policy>IMMEDIATE</policy>
+ </cancelPolicyCase>
+ <cancelPolicyCase>
+ <policy>END_OF_TERM</policy>
+ </cancelPolicyCase>
+ </cancelPolicy>
+ <createAlignment>
+ <createAlignmentCase>
+ <product>Laser-Scope</product>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </createAlignmentCase>
+ <createAlignmentCase>
+ <alignment>START_OF_BUNDLE</alignment>
+ </createAlignmentCase>
+ </createAlignment>
+ <billingAlignment>
+ <billingAlignmentCase>
+ <productCategory>ADD_ON</productCategory>
+ <alignment>BUNDLE</alignment>
+ </billingAlignmentCase>
+ <billingAlignmentCase>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <alignment>SUBSCRIPTION</alignment>
+ </billingAlignmentCase>
+ <billingAlignmentCase>
+ <alignment>ACCOUNT</alignment>
+ </billingAlignmentCase>
+ </billingAlignment>
+ <priceList>
+ <priceListCase>
+ <fromPriceList>rescue</fromPriceList>
+ <toPriceList>DEFAULT</toPriceList>
+ </priceListCase>
+ </priceList>
+ </rules>
+
+ <plans>
+ <plan name="pistol-monthly">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice> <!-- empty price implies $0 -->
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>GBP</currency><value>29.95</value></price>
+ <price><currency>EUR</currency><value>29.95</value></price>
+ <price><currency>USD</currency><value>29.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-monthly">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice></fixedPrice>
+ <!-- no price implies $0 -->
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ <number>-1</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>249.95</value></price>
+ <price><currency>EUR</currency><value>149.95</value></price>
+ <price><currency>GBP</currency><value>169.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-monthly">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>599.95</value></price>
+ <price><currency>EUR</currency><value>349.95</value></price>
+ <price><currency>GBP</currency><value>399.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-annual">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-annual">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>2399.95</value></price>
+ <price><currency>EUR</currency><value>1499.95</value></price>
+ <price><currency>GBP</currency><value>1699.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>5999.95</value></price>
+ <price><currency>EUR</currency><value>3499.95</value></price>
+ <price><currency>GBP</currency><value>3999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-annual-gunclub-discount">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>9.95</value></price>
+ <price><currency>EUR</currency><value>9.95</value></price>
+ <price><currency>GBP</currency><value>9.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-annual-gunclub-discount">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>19.95</value></price>
+ <price><currency>EUR</currency><value>49.95</value></price>
+ <price><currency>GBP</currency><value>69.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>2399.95</value></price>
+ <price><currency>EUR</currency><value>1499.95</value></price>
+ <price><currency>GBP</currency><value>1699.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual-gunclub-discount">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>99.95</value></price>
+ <price><currency>EUR</currency><value>99.95</value></price>
+ <price><currency>GBP</currency><value>99.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>5999.95</value></price>
+ <price><currency>EUR</currency><value>3499.95</value></price>
+ <price><currency>GBP</currency><value>3999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="laser-scope-monthly">
+ <product>Laser-Scope</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>1</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>999.95</value></price>
+ <price><currency>EUR</currency><value>499.95</value></price>
+ <price><currency>GBP</currency><value>999.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>1999.95</value></price>
+ <price><currency>EUR</currency><value>1499.95</value></price>
+ <price><currency>GBP</currency><value>1999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="telescopic-scope-monthly">
+ <product>Telescopic-Scope</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>1</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>399.95</value></price>
+ <price><currency>EUR</currency><value>299.95</value></price>
+ <price><currency>GBP</currency><value>399.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>999.95</value></price>
+ <price><currency>EUR</currency><value>499.95</value></price>
+ <price><currency>GBP</currency><value>999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="extra-ammo-monthly">
+ <product>Extra-Ammo</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>999.95</value></price>
+ <price><currency>EUR</currency><value>499.95</value></price>
+ <price><currency>GBP</currency><value>999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ <plansAllowedInBundle>-1</plansAllowedInBundle> <!-- arbitrary number of these (multipack) -->
+ </plan>
+ <plan name="holster-monthly-regular">
+ <product>Holster</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="holster-monthly-special">
+ <product>Holster</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual-rescue">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>YEARS</unit>
+ <number>1</number>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>5999.95</value></price>
+ <price><currency>EUR</currency><value>3499.95</value></price>
+ <price><currency>GBP</currency><value>3999.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>5999.95</value></price>
+ <price><currency>EUR</currency><value>3499.95</value></price>
+ <price><currency>GBP</currency><value>3999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="refurbish-maintenance">
+ <product>Refurbish-Maintenance</product>
+ <finalPhase type="FIXEDTERM">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>12</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ <fixedPrice>
+ <price><currency>USD</currency><value>599.95</value></price>
+ <price><currency>EUR</currency><value>599.95</value></price>
+ <price><currency>GBP</currency><value>599.95</value></price>
+ </fixedPrice>
+ </finalPhase>
+ </plan>
+ </plans>
+ <priceLists>
+ <defaultPriceList name="DEFAULT">
+ <plans>
+ <plan>pistol-monthly</plan>
+ <plan>shotgun-monthly</plan>
+ <plan>assault-rifle-monthly</plan>
+ <plan>pistol-annual</plan>
+ <plan>shotgun-annual</plan>
+ <plan>assault-rifle-annual</plan>
+ <plan>laser-scope-monthly</plan>
+ <plan>telescopic-scope-monthly</plan>
+ <plan>extra-ammo-monthly</plan>
+ <plan>holster-monthly-regular</plan>
+ <plan>refurbish-maintenance</plan>
+ </plans>
+ </defaultPriceList>
+ <childPriceList name="gunclubDiscount">
+ <plans>
+ <plan>pistol-monthly</plan>
+ <plan>shotgun-monthly</plan>
+ <plan>assault-rifle-monthly</plan>
+ <plan>pistol-annual-gunclub-discount</plan>
+ <plan>shotgun-annual-gunclub-discount</plan>
+ <plan>assault-rifle-annual-gunclub-discount</plan>
+ <plan>holster-monthly-special</plan>
+ </plans>
+ </childPriceList>
+ <childPriceList name="rescue">
+ <plans>
+ <plan>assault-rifle-annual-rescue</plan>
+ </plans>
+ </childPriceList>
+ </priceLists>
+
+</catalog>
diff --git a/server/src/test/resources/killbill.properties b/server/src/test/resources/killbill.properties
new file mode 100644
index 0000000..9aa3e66
--- /dev/null
+++ b/server/src/test/resources/killbill.properties
@@ -0,0 +1,22 @@
+# Use killbill util test properties (DbiProvider/MysqltestingHelper) on the test side configured with killbill
+com.ning.billing.dbi.jdbc.url=jdbc:mysql://127.0.0.1:3306/killbill
+
+killbill.catalog.uri=file:src/test/resources/catalog-weapons.xml
+
+killbill.payment.engine.events.off=false
+killbill.payment.retry.days=8,8,8
+
+user.timezone=UTC
+
+com.ning.core.server.jetty.logPath=/var/tmp/.logs
+
+killbill.payment.engine.notifications.sleep=100
+killbill.invoice.engine.notifications.sleep=100
+killbill.entitlement.engine.notifications.sleep=100
+killbill.billing.util.persistent.bus.sleep=100
+killbill.billing.util.persistent.bus.nbThreads=1
+# Local DB
+#com.ning.billing.dbi.test.useLocalDb=true
+
+
+
server/src/test/resources/log4j.xml 39(+39 -0)
diff --git a/server/src/test/resources/log4j.xml b/server/src/test/resources/log4j.xml
new file mode 100644
index 0000000..0c84cc0
--- /dev/null
+++ b/server/src/test/resources/log4j.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2010-2011 Ning, Inc.
+ ~
+ ~ Ning licenses this file to you under the Apache License, version 2.0
+ ~ (the "License"); you may not use this file except in compliance with the
+ ~ License. You may obtain a copy of the License at:
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ ~ License for the specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+ <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
+ <param name="Target" value="System.out"/>
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%p %d{ISO8601} %X{trace} %t %c %m%n"/>
+ </layout>
+ </appender>
+
+
+ <logger name="com.ning.billing.jaxrs">
+ <level value="debug"/>
+ </logger>
+ <logger name="com.ning.billing.server">
+ <level value="info"/>
+ </logger>
+
+ <root>
+ <priority value="info"/>
+ <appender-ref ref="stdout"/>
+ </root>
+</log4j:configuration>
util/pom.xml 24(+24 -0)
diff --git a/util/pom.xml b/util/pom.xml
index 806c8c6..23d47a3 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -60,11 +60,27 @@
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-core-asl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-mapper-asl</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-email</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.directory.studio</groupId>
+ <artifactId>org.apache.commons.io</artifactId>
+ </dependency>
+ <dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<scope>test</scope>
@@ -83,6 +99,14 @@
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.samskivert</groupId>
+ <artifactId>jmustache</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
</dependencies>
<build>
<plugins>
diff --git a/util/src/main/java/com/ning/billing/util/bus/dao/BusEventEntry.java b/util/src/main/java/com/ning/billing/util/bus/dao/BusEventEntry.java
new file mode 100644
index 0000000..8e98211
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/bus/dao/BusEventEntry.java
@@ -0,0 +1,98 @@
+/*
+ * 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.bus.dao;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.util.queue.PersistentQueueEntryLifecycle;
+
+public class BusEventEntry implements PersistentQueueEntryLifecycle {
+
+ private final long id;
+ private final String owner;
+ private final String createdOwner;
+ private final DateTime nextAvailable;
+ private final PersistentQueueEntryLifecycleState processingState;
+ private final String busEventClass;
+ private final String busEventJson;
+
+ public BusEventEntry(final long id, final String createdOwner, final String owner, final DateTime nextAvailable, PersistentQueueEntryLifecycleState processingState, final String busEventClass, final String busEventJson) {
+ this.id = id;
+ this.createdOwner = createdOwner;
+ this.owner = owner;
+ this.nextAvailable = nextAvailable;
+ this.processingState = processingState;
+ this.busEventClass = busEventClass;
+ this.busEventJson = busEventJson;
+ }
+
+ public BusEventEntry(final String createdOwner, final String busEventClass, final String busEventJson) {
+ this(0, createdOwner, null, null, null, busEventClass, busEventJson);
+ }
+
+
+
+ public long getId() {
+ return id;
+ }
+
+ public String getBusEventClass() {
+ return busEventClass;
+ }
+
+ public String getBusEventJson() {
+ return busEventJson;
+ }
+
+ @Override
+ public String getOwner() {
+ return owner;
+ }
+
+ @Override
+ public String getCreatedOwner() {
+ return createdOwner;
+ }
+
+ @Override
+ public DateTime getNextAvailableDate() {
+ return nextAvailable;
+ }
+
+ @Override
+ public PersistentQueueEntryLifecycleState getProcessingState() {
+ return processingState;
+ }
+
+ @Override
+ public boolean isAvailableForProcessing(DateTime now) {
+ switch(processingState) {
+ case AVAILABLE:
+ break;
+ case IN_PROCESSING:
+ // Somebody already got the event, not available yet
+ if (nextAvailable.isAfter(now)) {
+ return false;
+ }
+ break;
+ case PROCESSED:
+ return false;
+ default:
+ throw new RuntimeException(String.format("Unkwnon IEvent processing state %s", processingState));
+ }
+ return true;
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/bus/dao/PersistentBusSqlDao.java b/util/src/main/java/com/ning/billing/util/bus/dao/PersistentBusSqlDao.java
new file mode 100644
index 0000000..ca98098
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/bus/dao/PersistentBusSqlDao.java
@@ -0,0 +1,96 @@
+/*
+ * 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.bus.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.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.dao.BinderBase;
+import com.ning.billing.util.dao.MapperBase;
+import com.ning.billing.util.queue.PersistentQueueEntryLifecycle.PersistentQueueEntryLifecycleState;
+
+@ExternalizedSqlViaStringTemplate3()
+public interface PersistentBusSqlDao extends Transactional<PersistentBusSqlDao>, CloseMe {
+
+
+ @SqlQuery
+ @Mapper(PersistentBusSqlMapper.class)
+ public BusEventEntry getNextBusEventEntry(@Bind("max") int max, @Bind("owner") String owner, @Bind("now") Date now);
+
+ @SqlUpdate
+ public int claimBusEvent(@Bind("owner") String owner, @Bind("nextAvailable") Date nextAvailable,
+ @Bind("recordId") Long id, @Bind("now") Date now);
+
+ @SqlUpdate
+ public void clearBusEvent(@Bind("recordId") Long id, @Bind("owner") String owner);
+
+ @SqlUpdate
+ public void removeBusEventsById(@Bind("recordId") Long id);
+
+ @SqlUpdate
+ public void insertBusEvent(@Bind(binder = PersistentBusSqlBinder.class) BusEventEntry evt);
+
+ @SqlUpdate
+ public void insertClaimedHistory(@Bind("ownerId") String owner, @Bind("claimedDate") Date claimedDate,
+ @Bind("busEventId") long id);
+
+
+ public static class PersistentBusSqlBinder extends BinderBase implements Binder<Bind, BusEventEntry> {
+
+ @Override
+ public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, BusEventEntry evt) {
+ stmt.bind("className", evt.getBusEventClass());
+ stmt.bind("eventJson", evt.getBusEventJson());
+ stmt.bind("createdDate", getDate(new DateTime()));
+ stmt.bind("creatingOwner", evt.getCreatedOwner());
+ stmt.bind("processingAvailableDate", getDate(evt.getNextAvailableDate()));
+ stmt.bind("processingOwner", evt.getOwner());
+ stmt.bind("processingState", PersistentQueueEntryLifecycleState.AVAILABLE.toString());
+ }
+ }
+
+ public static class PersistentBusSqlMapper extends MapperBase implements ResultSetMapper<BusEventEntry> {
+
+ @Override
+ public BusEventEntry map(int index, ResultSet r, StatementContext ctx)
+ throws SQLException {
+
+ final Long recordId = r.getLong("record_id");
+ final String className = r.getString("class_name");
+ final String createdOwner = r.getString("creating_owner");
+ final String eventJson = r.getString("event_json");
+ final DateTime nextAvailableDate = getDate(r, "processing_available_date");
+ final String processingOwner = r.getString("processing_owner");
+ final PersistentQueueEntryLifecycleState processingState = PersistentQueueEntryLifecycleState.valueOf(r.getString("processing_state"));
+
+ return new BusEventEntry(recordId, createdOwner, processingOwner, nextAvailableDate, processingState, className, eventJson);
+ }
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/bus/DefaultBusService.java b/util/src/main/java/com/ning/billing/util/bus/DefaultBusService.java
index 2a572ec..ad287e4 100644
--- a/util/src/main/java/com/ning/billing/util/bus/DefaultBusService.java
+++ b/util/src/main/java/com/ning/billing/util/bus/DefaultBusService.java
@@ -22,8 +22,13 @@ import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
public class DefaultBusService implements BusService {
- private final static String EVENT_BUS_SERVICE = "bus-service";
-
+
+ public final static String EVENT_BUS_GROUP_NAME = "bus-grp";
+ public final static String EVENT_BUS_TH_NAME = "bus-th";
+
+ public final static String EVENT_BUS_SERVICE = "bus-service";
+ public final static String EVENT_BUS_IDENTIFIER = EVENT_BUS_SERVICE;
+
private final Bus eventBus;
@Inject
diff --git a/util/src/main/java/com/ning/billing/util/bus/InMemoryBus.java b/util/src/main/java/com/ning/billing/util/bus/InMemoryBus.java
index 31105c8..5974bfd 100644
--- a/util/src/main/java/com/ning/billing/util/bus/InMemoryBus.java
+++ b/util/src/main/java/com/ning/billing/util/bus/InMemoryBus.java
@@ -28,9 +28,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class InMemoryBus implements Bus {
- private final static String EVENT_BUS_IDENTIFIER = "bus-service";
- private final static String EVENT_BUS_GROUP_NAME = "bus-grp";
- private final static String EVENT_BUS_TH_NAME = "bus-th";
+
private static final Logger log = LoggerFactory.getLogger(InMemoryBus.class);
@@ -64,15 +62,15 @@ public class InMemoryBus implements Bus {
public InMemoryBus() {
- final ThreadGroup group = new ThreadGroup(EVENT_BUS_GROUP_NAME);
+ final ThreadGroup group = new ThreadGroup(DefaultBusService.EVENT_BUS_GROUP_NAME);
Executor executor = Executors.newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
- return new Thread(group, r, EVENT_BUS_TH_NAME);
+ return new Thread(group, r, DefaultBusService.EVENT_BUS_TH_NAME);
}
});
- this.delegate = new EventBusDelegate(EVENT_BUS_IDENTIFIER, group, executor);
+ this.delegate = new EventBusDelegate(DefaultBusService.EVENT_BUS_IDENTIFIER, group, executor);
this.isInitialized = new AtomicBoolean(false);
}
diff --git a/util/src/main/java/com/ning/billing/util/bus/PersistentBus.java b/util/src/main/java/com/ning/billing/util/bus/PersistentBus.java
new file mode 100644
index 0000000..92b35d7
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/bus/PersistentBus.java
@@ -0,0 +1,197 @@
+/*
+ * 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.bus;
+
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.eventbus.EventBus;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import com.ning.billing.config.PersistentQueueConfig;
+import com.ning.billing.util.Hostname;
+import com.ning.billing.util.bus.dao.BusEventEntry;
+import com.ning.billing.util.bus.dao.PersistentBusSqlDao;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.queue.PersistentQueueBase;
+
+
+public class PersistentBus extends PersistentQueueBase implements Bus {
+
+ private final static long DELTA_IN_PROCESSING_TIME_MS = 1000L * 60L * 5L; // 5 minutes
+ private final static int MAX_BUS_EVENTS = 1;
+
+ private static final Logger log = LoggerFactory.getLogger(PersistentBus.class);
+
+ private final PersistentBusSqlDao dao;
+
+ private final ObjectMapper objectMapper;
+ private final EventBusDelegate eventBusDelegate;
+ private final Clock clock;
+ private final String hostname;
+
+
+
+ private static final class EventBusDelegate extends EventBus {
+ public EventBusDelegate(String busName) {
+ super(busName);
+ }
+
+ // STEPH we can't override the method because EventHandler is package private scope
+ // Logged a bug against guava (Issue 981)
+ /*
+ @Override
+ protected void dispatch(Object event, EventHandler wrapper) {
+ try {
+ wrapper.handleEvent(event);
+ } catch (InvocationTargetException e) {
+ logger.log(Level.SEVERE,
+ "Could not dispatch event: " + event + " to handler " + wrapper, e);
+ }
+ }
+ */
+ }
+
+ @Inject
+ public PersistentBus(final IDBI dbi, final Clock clock, final PersistentBusConfig config) {
+
+ super("Bus", Executors.newFixedThreadPool(config.getNbThreads(), new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(new ThreadGroup(DefaultBusService.EVENT_BUS_GROUP_NAME),
+ r,
+ DefaultBusService.EVENT_BUS_TH_NAME);
+ }
+ }), config.getNbThreads(), config);
+ this.dao = dbi.onDemand(PersistentBusSqlDao.class);
+ this.clock = clock;
+ this.objectMapper = new ObjectMapper();
+ this.objectMapper.disable(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS);
+ this.eventBusDelegate = new EventBusDelegate("Killbill EventBus");
+ this.hostname = Hostname.get();
+ }
+
+ @Override
+ public void start() {
+ startQueue();
+ }
+
+ @Override
+ public void stop() {
+ stopQueue();
+ }
+
+ @Override
+ public int doProcessEvents() {
+
+ List<BusEventEntry> events = getNextBusEvent();
+ if (events.size() == 0) {
+ return 0;
+ }
+
+ int result = 0;
+ for (final BusEventEntry cur : events) {
+ BusEvent evt = deserializeBusEvent(cur.getBusEventClass(), cur.getBusEventJson());
+ result++;
+ // STEPH exception handling is done by GUAVA-- logged a bug Issue-780
+ eventBusDelegate.post(evt);
+ dao.clearBusEvent(cur.getId(), hostname);
+ }
+ return result;
+ }
+
+ private BusEvent deserializeBusEvent(final String className, final String json) {
+ try {
+ Class<?> claz = Class.forName(className);
+ return (BusEvent) objectMapper.readValue(json, claz);
+ } catch (Exception e) {
+ log.error(String.format("Failed to deserialize json object %s for class %s", json, className), e);
+ return null;
+ }
+ }
+
+
+ private List<BusEventEntry> getNextBusEvent() {
+
+ final Date now = clock.getUTCNow().toDate();
+ final Date nextAvailable = clock.getUTCNow().plus(DELTA_IN_PROCESSING_TIME_MS).toDate();
+
+ BusEventEntry input = dao.getNextBusEventEntry(MAX_BUS_EVENTS, hostname, now);
+ if (input == null) {
+ return Collections.emptyList();
+ }
+
+ final boolean claimed = (dao.claimBusEvent(hostname, nextAvailable, input.getId(), now) == 1);
+ if (claimed) {
+ dao.insertClaimedHistory(hostname, now, input.getId());
+ return Collections.singletonList(input);
+ }
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void register(Object handlerInstance) throws EventBusException {
+ eventBusDelegate.register(handlerInstance);
+ }
+
+ @Override
+ public void unregister(Object handlerInstance) throws EventBusException {
+ eventBusDelegate.unregister(handlerInstance);
+ }
+
+ @Override
+ public void post(final BusEvent event) throws EventBusException {
+ dao.inTransaction(new Transaction<Void, PersistentBusSqlDao>() {
+ @Override
+ public Void inTransaction(PersistentBusSqlDao transactional,
+ TransactionStatus status) throws Exception {
+ postFromTransaction(event, transactional);
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public void postFromTransaction(final BusEvent event, Transmogrifier transmogrifier)
+ throws EventBusException {
+ PersistentBusSqlDao transactional = transmogrifier.become(PersistentBusSqlDao.class);
+ postFromTransaction(event, transactional);
+ }
+
+ private void postFromTransaction(BusEvent event, PersistentBusSqlDao transactional) {
+ try {
+ String json = objectMapper.writeValueAsString(event);
+ BusEventEntry entry = new BusEventEntry(hostname, event.getClass().getName(), json);
+ transactional.insertBusEvent(entry);
+ } catch (Exception e) {
+ log.error("Failed to post BusEvent " + event.toString(), e);
+ }
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/bus/PersistentBusConfig.java b/util/src/main/java/com/ning/billing/util/bus/PersistentBusConfig.java
new file mode 100644
index 0000000..a2e9879
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/bus/PersistentBusConfig.java
@@ -0,0 +1,33 @@
+/*
+ * 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.bus;
+
+import org.skife.config.Config;
+import org.skife.config.Default;
+
+import com.ning.billing.config.PersistentQueueConfig;
+
+public interface PersistentBusConfig extends PersistentQueueConfig {
+
+ @Override
+ @Config("killbill.billing.util.persistent.bus.sleep")
+ @Default("500")
+ public long getSleepTimeMs();
+
+ @Config("killbill.billing.util.persistent.bus.nbThreads")
+ @Default("3")
+ public int getNbThreads();
+}
diff --git a/util/src/main/java/com/ning/billing/util/callcontext/CallContextBase.java b/util/src/main/java/com/ning/billing/util/callcontext/CallContextBase.java
index 8164f76..35dc92f 100644
--- a/util/src/main/java/com/ning/billing/util/callcontext/CallContextBase.java
+++ b/util/src/main/java/com/ning/billing/util/callcontext/CallContextBase.java
@@ -16,15 +16,25 @@
package com.ning.billing.util.callcontext;
+import java.util.UUID;
+
public abstract class CallContextBase implements CallContext {
+
+ private final UUID userToken;
private final String userName;
private final CallOrigin callOrigin;
private final UserType userType;
+
public CallContextBase(String userName, CallOrigin callOrigin, UserType userType) {
+ this(userName, callOrigin, userType, null);
+ }
+
+ public CallContextBase(String userName, CallOrigin callOrigin, UserType userType, UUID userToken) {
this.userName = userName;
this.callOrigin = callOrigin;
this.userType = userType;
+ this.userToken = userToken;
}
@Override
@@ -41,4 +51,9 @@ public abstract class CallContextBase implements CallContext {
public UserType getUserType() {
return userType;
}
+
+ @Override
+ public UUID getUserToken() {
+ return userToken;
+ }
}
diff --git a/util/src/main/java/com/ning/billing/util/callcontext/CallContextBinder.java b/util/src/main/java/com/ning/billing/util/callcontext/CallContextBinder.java
index 3ac8a98..41dd68a 100644
--- a/util/src/main/java/com/ning/billing/util/callcontext/CallContextBinder.java
+++ b/util/src/main/java/com/ning/billing/util/callcontext/CallContextBinder.java
@@ -37,9 +37,10 @@ public @interface CallContextBinder {
return new Binder<CallContextBinder, CallContext>() {
@Override
public void bind(SQLStatement q, CallContextBinder bind, CallContext callContext) {
- q.bind("userName", callContext.getUserName());
+ q.bind("userName", callContext.getUserName());
q.bind("createdDate", callContext.getCreatedDate().toDate());
q.bind("updatedDate", callContext.getUpdatedDate().toDate());
+ q.bind("userToken", (callContext.getUserToken() != null) ? callContext.getUserToken().toString() : null);
}
};
}
diff --git a/util/src/main/java/com/ning/billing/util/callcontext/CallContextFactory.java b/util/src/main/java/com/ning/billing/util/callcontext/CallContextFactory.java
index 6ffc554..d9f18c9 100644
--- a/util/src/main/java/com/ning/billing/util/callcontext/CallContextFactory.java
+++ b/util/src/main/java/com/ning/billing/util/callcontext/CallContextFactory.java
@@ -16,10 +16,14 @@
package com.ning.billing.util.callcontext;
+import java.util.UUID;
+
import org.joda.time.DateTime;
public interface CallContextFactory {
- CallContext createCallContext(String userName, CallOrigin callOrigin, UserType userType);
+ CallContext createCallContext(String userName, CallOrigin callOrigin, UserType userType, UUID userToken);
+
+ CallContext createCallContext(String userName, CallOrigin callOrigin, UserType userType);
CallContext createMigrationCallContext(String userName, CallOrigin callOrigin, UserType userType, DateTime createdDate, DateTime updatedDate);
diff --git a/util/src/main/java/com/ning/billing/util/callcontext/DefaultCallContext.java b/util/src/main/java/com/ning/billing/util/callcontext/DefaultCallContext.java
index 57dc682..4cde143 100644
--- a/util/src/main/java/com/ning/billing/util/callcontext/DefaultCallContext.java
+++ b/util/src/main/java/com/ning/billing/util/callcontext/DefaultCallContext.java
@@ -16,17 +16,24 @@
package com.ning.billing.util.callcontext;
+import java.util.UUID;
+
import com.ning.billing.util.clock.Clock;
import org.joda.time.DateTime;
public class DefaultCallContext extends CallContextBase {
+
private final Clock clock;
- public DefaultCallContext(String userName, CallOrigin callOrigin, UserType userType, Clock clock) {
- super(userName, callOrigin, userType);
+ public DefaultCallContext(final String userName, final CallOrigin callOrigin, final UserType userType, final UUID userToken, final Clock clock) {
+ super(userName, callOrigin, userType, userToken);
this.clock = clock;
}
+ public DefaultCallContext(String userName, CallOrigin callOrigin, UserType userType, Clock clock) {
+ this(userName, callOrigin, userType, null, clock);
+ }
+
@Override
public DateTime getCreatedDate() {
return clock.getUTCNow();
diff --git a/util/src/main/java/com/ning/billing/util/callcontext/DefaultCallContextFactory.java b/util/src/main/java/com/ning/billing/util/callcontext/DefaultCallContextFactory.java
index f75574c..52ac313 100644
--- a/util/src/main/java/com/ning/billing/util/callcontext/DefaultCallContextFactory.java
+++ b/util/src/main/java/com/ning/billing/util/callcontext/DefaultCallContextFactory.java
@@ -16,6 +16,8 @@
package com.ning.billing.util.callcontext;
+import java.util.UUID;
+
import com.google.inject.Inject;
import com.ning.billing.util.clock.Clock;
import org.joda.time.DateTime;
@@ -29,8 +31,13 @@ public class DefaultCallContextFactory implements CallContextFactory {
}
@Override
+ public CallContext createCallContext(String userName, CallOrigin callOrigin, UserType userType, UUID userToken) {
+ return new DefaultCallContext(userName, callOrigin, userType, userToken, clock);
+ }
+
+ @Override
public CallContext createCallContext(String userName, CallOrigin callOrigin, UserType userType) {
- return new DefaultCallContext(userName, callOrigin, userType, clock);
+ return createCallContext(userName, callOrigin, userType, null);
}
@Override
diff --git a/util/src/main/java/com/ning/billing/util/clock/Clock.java b/util/src/main/java/com/ning/billing/util/clock/Clock.java
index b41a36d..6b65aac 100644
--- a/util/src/main/java/com/ning/billing/util/clock/Clock.java
+++ b/util/src/main/java/com/ning/billing/util/clock/Clock.java
@@ -25,6 +25,5 @@ public interface Clock {
public DateTime getUTCNow();
-
//public DateTime addDuration(DateTime input, IDuration duration);
}
diff --git a/util/src/main/java/com/ning/billing/util/clock/DefaultClock.java b/util/src/main/java/com/ning/billing/util/clock/DefaultClock.java
index 8280a15..057a58c 100644
--- a/util/src/main/java/com/ning/billing/util/clock/DefaultClock.java
+++ b/util/src/main/java/com/ning/billing/util/clock/DefaultClock.java
@@ -36,6 +36,13 @@ public class DefaultClock implements Clock {
return getNow(DateTimeZone.UTC);
}
+ public static DateTime toUTCDateTime(DateTime input) {
+ if (input == null) {
+ return null;
+ }
+ DateTime result = input.toDateTime(DateTimeZone.UTC);
+ return truncateMs(result);
+ }
public static DateTime truncateMs(DateTime input) {
return input.minus(input.getMillisOfSecond());
diff --git a/util/src/main/java/com/ning/billing/util/config/XMLLoader.java b/util/src/main/java/com/ning/billing/util/config/XMLLoader.java
index d0487b8..9d35352 100644
--- a/util/src/main/java/com/ning/billing/util/config/XMLLoader.java
+++ b/util/src/main/java/com/ning/billing/util/config/XMLLoader.java
@@ -71,6 +71,18 @@ public class XMLLoader {
return null;
}
}
+
+ public static <T> T getObjectFromStreamNoValidation(InputStream stream, Class<T> clazz) throws SAXException, InvalidConfigException, JAXBException, IOException, TransformerException {
+ Object o = unmarshaller(clazz).unmarshal(stream);
+ if (clazz.isInstance(o)) {
+ @SuppressWarnings("unchecked")
+ T castObject = (T)o;
+ return castObject;
+ } else {
+ return null;
+ }
+ }
+
public static <T extends ValidatingConfig<T>> void validate(URI uri, T c) throws ValidationException {
c.initialize(c, uri);
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
index b7d2de0..b918fac 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
@@ -16,66 +16,90 @@
package com.ning.billing.util.customfield.dao;
-import com.ning.billing.util.ChangeType;
-import com.ning.billing.util.callcontext.CallContext;
+import com.google.inject.Inject;
import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.customfield.CustomFieldHistory;
+import com.ning.billing.util.dao.AuditedCollectionDaoBase;
+import com.ning.billing.util.dao.TableName;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
+import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.UUID;
+public class AuditedCustomFieldDao extends AuditedCollectionDaoBase<CustomField> implements CustomFieldDao {
+ private final CustomFieldSqlDao dao;
-public class AuditedCustomFieldDao implements CustomFieldDao {
- @Override
- public void saveFields(Transmogrifier dao, UUID objectId, String objectType, List<CustomField> fields, CallContext context) {
- CustomFieldSqlDao customFieldSqlDao = dao.become(CustomFieldSqlDao.class);
-
- // get list of existing fields
- List<CustomField> existingFields = customFieldSqlDao.load(objectId.toString(), objectType);
- List<CustomField> fieldsToUpdate = new ArrayList<CustomField>();
-
- // sort into fields to update (fieldsToUpdate), fields to add (fields), and fields to delete (existingFields)
- Iterator<CustomField> fieldIterator = fields.iterator();
- while (fieldIterator.hasNext()) {
- CustomField field = fieldIterator.next();
-
- Iterator<CustomField> existingFieldIterator = existingFields.iterator();
- while (existingFieldIterator.hasNext()) {
- CustomField existingField = existingFieldIterator.next();
- if (field.getName().equals(existingField.getName())) {
- // if the tags match, remove from both lists
- fieldsToUpdate.add(field);
- fieldIterator.remove();
- existingFieldIterator.remove();
- }
- }
- }
-
- customFieldSqlDao.batchInsertFromTransaction(objectId.toString(), objectType, fields, context);
- customFieldSqlDao.batchUpdateFromTransaction(objectId.toString(), objectType, fieldsToUpdate, context);
- customFieldSqlDao.batchDeleteFromTransaction(objectId.toString(), objectType, existingFields, context);
-
- List<CustomFieldHistory> fieldHistories = new ArrayList<CustomFieldHistory>();
- fieldHistories.addAll(convertToHistoryEntry(fields, ChangeType.INSERT));
- fieldHistories.addAll(convertToHistoryEntry(fieldsToUpdate, ChangeType.UPDATE));
- fieldHistories.addAll(convertToHistoryEntry(existingFields, ChangeType.DELETE));
-
- CustomFieldHistorySqlDao historyDao = dao.become(CustomFieldHistorySqlDao.class);
- historyDao.batchAddHistoryFromTransaction(objectId.toString(), objectType, fieldHistories, context);
-
- CustomFieldAuditSqlDao auditDao = dao.become(CustomFieldAuditSqlDao.class);
- auditDao.batchInsertAuditLogFromTransaction(objectId.toString(), objectType, fieldHistories, context);
+ @Inject
+ public AuditedCustomFieldDao(final IDBI dbi) {
+ dao = dbi.onDemand(CustomFieldSqlDao.class);
}
- private List<CustomFieldHistory> convertToHistoryEntry(List<CustomField> fields, ChangeType changeType) {
- List<CustomFieldHistory> result = new ArrayList<CustomFieldHistory>();
+ @Override
+ protected TableName getTableName() {
+ return TableName.CUSTOM_FIELD_HISTORY;
+ }
- for (CustomField field : fields) {
- result.add(new CustomFieldHistory(field, changeType));
- }
+ @Override
+ protected UpdatableEntityCollectionSqlDao<CustomField> transmogrifyDao(Transmogrifier transactionalDao) {
+ return transactionalDao.become(CustomFieldSqlDao.class);
+ }
- return result;
+ @Override
+ protected UpdatableEntityCollectionSqlDao<CustomField> getSqlDao() {
+ return dao;
}
+
+// @Override
+// public void saveEntitiesFromTransaction(Transmogrifier dao, UUID objectId, ObjectType objectType, List<CustomField> fields, CallContext context) {
+// CustomFieldSqlDao customFieldSqlDao = dao.become(CustomFieldSqlDao.class);
+//
+// // get list of existing fields
+// List<CustomField> existingFields = customFieldSqlDao.load(objectId.toString(), objectType);
+// List<CustomField> fieldsToUpdate = new ArrayList<CustomField>();
+//
+// // sort into fields to update (fieldsToUpdate), fields to add (fields), and fields to delete (existingFields)
+// Iterator<CustomField> fieldIterator = fields.iterator();
+// while (fieldIterator.hasNext()) {
+// CustomField field = fieldIterator.next();
+//
+// Iterator<CustomField> existingFieldIterator = existingFields.iterator();
+// while (existingFieldIterator.hasNext()) {
+// CustomField existingField = existingFieldIterator.next();
+// if (field.getName().equals(existingField.getName())) {
+// // if the tagStore match, remove from both lists
+// fieldsToUpdate.add(field);
+// fieldIterator.remove();
+// existingFieldIterator.remove();
+// }
+// }
+// }
+//
+// customFieldSqlDao.batchInsertFromTransaction(objectId.toString(), objectType, fields, context);
+// customFieldSqlDao.batchUpdateFromTransaction(objectId.toString(), objectType, fieldsToUpdate, context);
+//
+// // get all custom fields (including those that are about to be deleted) from the database in order to get the record ids
+// List<Mapper> recordIds = customFieldSqlDao.getRecordIds(objectId.toString(), objectType);
+// Map<UUID, Long> recordIdMap = new HashMap<UUID, Long>();
+// for (Mapper recordId : recordIds) {
+// recordIdMap.put(recordId.getId(), recordId.getRecordId());
+// }
+//
+// customFieldSqlDao.batchDeleteFromTransaction(objectId.toString(), objectType, existingFields, context);
+//
+// List<MappedEntity<CustomField>> fieldHistories = new ArrayList<MappedEntity<CustomField>>();
+// fieldHistories.addAll(convertToHistory(fields, recordIdMap, ChangeType.INSERT));
+// fieldHistories.addAll(convertToHistory(fieldsToUpdate, recordIdMap, ChangeType.UPDATE));
+// fieldHistories.addAll(convertToHistory(existingFields, recordIdMap, ChangeType.DELETE));
+//
+// customFieldSqlDao.batchAddHistoryFromTransaction(objectId.toString(), objectType, fieldHistories, context);
+// customFieldSqlDao.batchInsertAuditLogFromTransaction(TableName.CUSTOM_FIELD_HISTORY, objectId.toString(), objectType, fieldHistories, context);
+// }
+//
+// private List<MappedEntity<CustomField>> convertToHistory(List<CustomField> fields, Map<UUID, Long> recordIds, ChangeType changeType) {
+// List<MappedEntity<CustomField>> result = new ArrayList<MappedEntity<CustomField>>();
+//
+// for (CustomField field : fields) {
+// result.add(new MappedEntity<CustomField>(recordIds.get(field.getId()), field, changeType));
+// }
+//
+// return result;
+// }
}
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldDao.java
index a987c4d..66cd734 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldDao.java
@@ -16,13 +16,8 @@
package com.ning.billing.util.customfield.dao;
-import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.customfield.CustomField;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import com.ning.billing.util.dao.AuditedCollectionDao;
-import java.util.List;
-import java.util.UUID;
-
-public interface CustomFieldDao {
- void saveFields(Transmogrifier dao, UUID objectId, String objectType, List<CustomField> fields, CallContext context);
+public interface CustomFieldDao extends AuditedCollectionDao<CustomField> {
}
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java
index 733bfeb..e73dbf4 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java
@@ -20,9 +20,10 @@ import java.util.List;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.customfield.CustomFieldBinder;
-import com.ning.billing.util.customfield.CustomFieldMapper;
-import com.ning.billing.util.entity.UpdatableEntityCollectionDao;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.ObjectTypeBinder;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlBatch;
import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
@@ -33,25 +34,33 @@ import com.ning.billing.util.customfield.CustomField;
@ExternalizedSqlViaStringTemplate3
@RegisterMapper(CustomFieldMapper.class)
-public interface CustomFieldSqlDao extends UpdatableEntityCollectionDao<CustomField>, Transactional<CustomFieldSqlDao>, Transmogrifier {
+public interface CustomFieldSqlDao extends UpdatableEntityCollectionSqlDao<CustomField>,
+ Transactional<CustomFieldSqlDao>, Transmogrifier {
@Override
@SqlBatch(transactional=false)
- public void batchInsertFromTransaction(@Bind("objectId") final String objectId,
- @Bind("objectType") final String objectType,
- @CustomFieldBinder final List<CustomField> entities,
- @CallContextBinder final CallContext context);
+ public void insertFromTransaction(@Bind("objectId") final String objectId,
+ @ObjectTypeBinder final ObjectType objectType,
+ @CustomFieldBinder final List<CustomField> entities,
+ @CallContextBinder final CallContext context);
@Override
@SqlBatch(transactional=false)
- public void batchUpdateFromTransaction(@Bind("objectId") final String objectId,
- @Bind("objectType") final String objectType,
- @CustomFieldBinder final List<CustomField> entities,
- @CallContextBinder final CallContext context);
+ public void updateFromTransaction(@Bind("objectId") final String objectId,
+ @ObjectTypeBinder final ObjectType objectType,
+ @CustomFieldBinder final List<CustomField> entities,
+ @CallContextBinder final CallContext context);
@Override
@SqlBatch(transactional=false)
- public void batchDeleteFromTransaction(@Bind("objectId") final String objectId,
- @Bind("objectType") final String objectType,
- @CustomFieldBinder final List<CustomField> entities,
- @CallContextBinder final CallContext context);
+ public void deleteFromTransaction(@Bind("objectId") final String objectId,
+ @ObjectTypeBinder final ObjectType objectType,
+ @CustomFieldBinder final List<CustomField> entities,
+ @CallContextBinder final CallContext context);
+
+ @Override
+ @SqlBatch(transactional=false)
+ public void addHistoryFromTransaction(@Bind("objectId") final String objectId,
+ @ObjectTypeBinder final ObjectType objectType,
+ @CustomFieldHistoryBinder final List<EntityHistory<CustomField>> entities,
+ @CallContextBinder final CallContext context);
}
diff --git a/util/src/main/java/com/ning/billing/util/customfield/DefaultFieldStore.java b/util/src/main/java/com/ning/billing/util/customfield/DefaultFieldStore.java
index 4a31dd4..8698787 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/DefaultFieldStore.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/DefaultFieldStore.java
@@ -17,14 +17,16 @@
package com.ning.billing.util.customfield;
import java.util.UUID;
-import com.ning.billing.util.entity.EntityCollectionBase;
+
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.entity.collection.EntityCollectionBase;
public class DefaultFieldStore extends EntityCollectionBase<CustomField> implements FieldStore {
- public DefaultFieldStore(UUID objectId, String objectType) {
+ public DefaultFieldStore(UUID objectId, ObjectType objectType) {
super(objectId, objectType);
}
- public static DefaultFieldStore create(UUID objectId, String objectType) {
+ public static DefaultFieldStore create(UUID objectId, ObjectType objectType) {
return new DefaultFieldStore(objectId, objectType);
}
diff --git a/util/src/main/java/com/ning/billing/util/customfield/StringCustomField.java b/util/src/main/java/com/ning/billing/util/customfield/StringCustomField.java
index f60093c..074f322 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/StringCustomField.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/StringCustomField.java
@@ -18,7 +18,6 @@ package com.ning.billing.util.customfield;
import java.util.UUID;
import com.ning.billing.util.entity.UpdatableEntityBase;
-import org.joda.time.DateTime;
public class StringCustomField extends UpdatableEntityBase implements CustomField {
private String name;
@@ -30,9 +29,8 @@ public class StringCustomField extends UpdatableEntityBase implements CustomFiel
this.value = value;
}
- public StringCustomField(UUID id, String createdBy, DateTime createdDate,
- String updatedBy, DateTime updatedDate, String name, String value) {
- super(id, createdBy, createdDate, updatedBy, updatedDate);
+ public StringCustomField(UUID id, String name, String value) {
+ super(id);
this.name = name;
this.value = value;
}
@@ -51,4 +49,24 @@ public class StringCustomField extends UpdatableEntityBase implements CustomFiel
public void setValue(String value) {
this.value = value;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ StringCustomField that = (StringCustomField) o;
+
+ if (name != null ? !name.equals(that.name) : that.name != null) return false;
+ //if (value != null ? !value.equals(that.value) : that.value != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name != null ? name.hashCode() : 0;
+ result = 31 * result + (value != null ? value.hashCode() : 0);
+ return result;
+ }
}
diff --git a/util/src/main/java/com/ning/billing/util/dao/AuditBinder.java b/util/src/main/java/com/ning/billing/util/dao/AuditBinder.java
new file mode 100644
index 0000000..a518578
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/AuditBinder.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.dao;
+
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(AuditBinder.AuditBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface AuditBinder {
+ public static class AuditBinderFactory implements BinderFactory {
+ @Override
+ public Binder build(Annotation annotation) {
+ return new Binder<AuditBinder, EntityAudit>() {
+ @Override
+ public void bind(SQLStatement q, AuditBinder bind, EntityAudit audit) {
+ q.bind("tableName", audit.getTableName().toString());
+ q.bind("recordId", audit.getRecordId());
+ q.bind("changeType", audit.getChangeType().toString());
+ }
+ };
+ }
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDao.java b/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDao.java
new file mode 100644
index 0000000..d757f70
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDao.java
@@ -0,0 +1,33 @@
+/*
+ * 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.dao;
+
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.entity.Entity;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface AuditedCollectionDao<T extends Entity> {
+ void saveEntitiesFromTransaction(Transmogrifier transactionalDao, UUID objectId, ObjectType objectType,
+ List<T> entities, CallContext context);
+
+ List<T> loadEntities(UUID objectId, ObjectType objectType);
+
+ List<T> loadEntitiesFromTransaction(Transmogrifier dao, UUID objectId, ObjectType objectType);
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDaoBase.java b/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDaoBase.java
new file mode 100644
index 0000000..da6df68
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDaoBase.java
@@ -0,0 +1,137 @@
+/*
+ * 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.dao;
+
+import com.ning.billing.util.ChangeType;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.entity.Entity;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+public abstract class AuditedCollectionDaoBase<T extends Entity> implements AuditedCollectionDao<T> {
+ @Override
+ public void saveEntitiesFromTransaction(Transmogrifier transactionalDao, UUID objectId, ObjectType objectType, List<T> entities, CallContext context) {
+ UpdatableEntityCollectionSqlDao<T> dao = transmogrifyDao(transactionalDao);
+
+ // get list of existing entities
+ List<T> existingEntities = dao.load(objectId.toString(), objectType);
+ List<T> entitiesToUpdate = new ArrayList<T>();
+
+ // sort into entities to update (entitiesToUpdate), entities to add (entities), and entities to delete (existingEntities)
+ Iterator<T> entityIterator = entities.iterator();
+ while (entityIterator.hasNext()) {
+ T entity = entityIterator.next();
+
+ Iterator<T> existingEntityIterator = existingEntities.iterator();
+ while (existingEntityIterator.hasNext()) {
+ T existingEntity = existingEntityIterator.next();
+ if (entity.equals(existingEntity)) {
+ // if the entities match, remove from both lists
+ entitiesToUpdate.add(entity);
+ entityIterator.remove();
+ existingEntityIterator.remove();
+ }
+ }
+ }
+
+ dao.insertFromTransaction(objectId.toString(), objectType, entities, context);
+ dao.updateFromTransaction(objectId.toString(), objectType, entitiesToUpdate, context);
+
+ // get all custom entities (including those that are about to be deleted) from the database in order to get the record ids
+ List<Mapper<UUID, Long>> recordIds = dao.getRecordIds(objectId.toString(), objectType);
+ Map<UUID, Long> recordIdMap = convertToHistoryMap(recordIds);
+
+ dao.deleteFromTransaction(objectId.toString(), objectType, existingEntities, context);
+
+ List<EntityHistory<T>> entityHistories = new ArrayList<EntityHistory<T>>();
+ entityHistories.addAll(convertToHistory(entities, recordIdMap, ChangeType.INSERT));
+ entityHistories.addAll(convertToHistory(entitiesToUpdate, recordIdMap, ChangeType.UPDATE));
+ entityHistories.addAll(convertToHistory(existingEntities, recordIdMap, ChangeType.DELETE));
+
+ Long maxHistoryRecordId = dao.getMaxHistoryRecordId();
+ dao.addHistoryFromTransaction(objectId.toString(), objectType, entityHistories, context);
+
+ // have to fetch history record ids to update audit log
+ List<Mapper<Long, Long>> historyRecordIds = dao.getHistoryRecordIds(maxHistoryRecordId);
+ Map<Long, Long> historyRecordIdMap = convertToAuditMap(historyRecordIds);
+ List<EntityAudit> entityAudits = convertToAudits(entityHistories, historyRecordIdMap);
+
+ dao.insertAuditFromTransaction(entityAudits, context);
+ }
+
+ @Override
+ public List<T> loadEntities(final UUID objectId, final ObjectType objectType) {
+ UpdatableEntityCollectionSqlDao thisDao = getSqlDao();
+ return thisDao.load(objectId.toString(), objectType);
+ }
+
+ @Override
+ public List<T> loadEntitiesFromTransaction(final Transmogrifier dao, final UUID objectId, final ObjectType objectType) {
+ UpdatableEntityCollectionSqlDao<T> thisDao = transmogrifyDao(dao);
+ return thisDao.load(objectId.toString(), objectType);
+ }
+
+ protected List<EntityHistory<T>> convertToHistory(List<T> entities, Map<UUID, Long> recordIds, ChangeType changeType) {
+ List<EntityHistory<T>> histories = new ArrayList<EntityHistory<T>>();
+
+ for (T entity : entities) {
+ UUID id = entity.getId();
+ histories.add(new EntityHistory<T>(id, recordIds.get(id), entity, changeType));
+ }
+
+ return histories;
+ }
+
+ protected List<EntityAudit> convertToAudits(List<EntityHistory<T>> histories, Map<Long, Long> historyRecordIds) {
+ List<EntityAudit> audits = new ArrayList<EntityAudit>();
+
+ for (EntityHistory<T> history : histories) {
+ Long recordId = history.getValue();
+ Long historyRecordId = historyRecordIds.get(recordId);
+ audits.add(new EntityAudit(getTableName(), historyRecordId, history.getChangeType()));
+ }
+
+ return audits;
+ }
+
+ protected Map<UUID, Long> convertToHistoryMap(List<Mapper<UUID, Long>> recordIds) {
+ Map<UUID, Long> recordIdMap = new HashMap<UUID, Long>();
+ for (Mapper<UUID, Long> recordId : recordIds) {
+ recordIdMap.put(recordId.getKey(), recordId.getValue());
+ }
+ return recordIdMap;
+ }
+
+ protected Map<Long, Long> convertToAuditMap(List<Mapper<Long, Long>> historyRecordIds) {
+ Map<Long, Long> historyRecordIdMap = new HashMap<Long, Long>();
+ for (Mapper<Long, Long> historyRecordId : historyRecordIds) {
+ historyRecordIdMap.put(historyRecordId.getKey(), historyRecordId.getValue());
+ }
+ return historyRecordIdMap;
+ }
+
+ protected abstract TableName getTableName();
+ protected abstract UpdatableEntityCollectionSqlDao<T> transmogrifyDao(Transmogrifier transactionalDao);
+ protected abstract UpdatableEntityCollectionSqlDao<T> getSqlDao();
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/CollectionHistorySqlDao.java b/util/src/main/java/com/ning/billing/util/dao/CollectionHistorySqlDao.java
new file mode 100644
index 0000000..d3a01d5
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/CollectionHistorySqlDao.java
@@ -0,0 +1,37 @@
+/*
+ * 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.dao;
+
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.entity.Entity;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface CollectionHistorySqlDao<T extends Entity> {
+ @SqlBatch(transactional = false)
+ public void addHistoryFromTransaction(String objectId, ObjectType objectType,
+ List<EntityHistory<T>> histories,
+ CallContext context);
+
+ @SqlUpdate
+ public void addHistoryFromTransaction(String objectId, ObjectType objectType,
+ EntityHistory<T> history,
+ CallContext context);
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/EntityAudit.java b/util/src/main/java/com/ning/billing/util/dao/EntityAudit.java
new file mode 100644
index 0000000..15280fa
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/EntityAudit.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.util.dao;
+
+import com.ning.billing.util.ChangeType;
+
+public class EntityAudit {
+ private final TableName tableName;
+ private final Long recordId;
+ private final ChangeType changeType;
+
+ public EntityAudit(TableName tableName, Long recordId, ChangeType changeType) {
+ this.tableName = tableName;
+ this.recordId = recordId;
+ this.changeType = changeType;
+ }
+
+ public TableName getTableName() {
+ return tableName;
+ }
+
+ public Long getRecordId() {
+ return recordId;
+ }
+
+ public ChangeType getChangeType() {
+ return changeType;
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/EntityHistory.java b/util/src/main/java/com/ning/billing/util/dao/EntityHistory.java
new file mode 100644
index 0000000..536de35
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/EntityHistory.java
@@ -0,0 +1,28 @@
+/*
+ * 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.dao;
+
+import com.ning.billing.util.ChangeType;
+import com.ning.billing.util.entity.Entity;
+
+import java.util.UUID;
+
+public class EntityHistory<T extends Entity> extends MappedEntity<T, UUID, Long> {
+ public EntityHistory(UUID id, Long recordId, T entity, ChangeType changeType) {
+ super(new Mapper<UUID, Long>(id, recordId), entity, changeType);
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/HistoryRecordIdMapper.java b/util/src/main/java/com/ning/billing/util/dao/HistoryRecordIdMapper.java
new file mode 100644
index 0000000..3d6e3f5
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/HistoryRecordIdMapper.java
@@ -0,0 +1,33 @@
+/*
+ * 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.dao;
+
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class HistoryRecordIdMapper extends MapperBase implements ResultSetMapper<Mapper> {
+ @Override
+ public Mapper<Long, Long> map(int index, ResultSet resultSet, StatementContext ctx) throws SQLException {
+ Long recordId = resultSet.getLong("record_id");
+ Long historyRecordId = resultSet.getLong("history_record_id");
+
+ return new Mapper<Long, Long>(recordId, historyRecordId);
+ }
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/dao/Mapper.java b/util/src/main/java/com/ning/billing/util/dao/Mapper.java
new file mode 100644
index 0000000..e4e178c
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/Mapper.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.dao;
+
+public class Mapper<K, V> {
+ private final K key;
+ private final V value;
+
+ public Mapper(K key, V value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public K getKey() {
+ return key;
+ }
+
+ public V getValue() {
+ return value;
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/MapperBase.java b/util/src/main/java/com/ning/billing/util/dao/MapperBase.java
index d10e2a0..119b727 100644
--- a/util/src/main/java/com/ning/billing/util/dao/MapperBase.java
+++ b/util/src/main/java/com/ning/billing/util/dao/MapperBase.java
@@ -22,11 +22,16 @@ import org.joda.time.DateTimeZone;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
-import java.util.Date;
+import java.util.UUID;
public abstract class MapperBase {
protected DateTime getDate(ResultSet rs, String fieldName) throws SQLException {
final Timestamp resultStamp = rs.getTimestamp(fieldName);
return rs.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
}
+
+ protected UUID getUUID(ResultSet resultSet, String fieldName) throws SQLException {
+ String result = resultSet.getString(fieldName);
+ return result == null ? null : UUID.fromString(result);
+ }
}
diff --git a/util/src/main/java/com/ning/billing/util/dao/ObjectTypeBinder.java b/util/src/main/java/com/ning/billing/util/dao/ObjectTypeBinder.java
new file mode 100644
index 0000000..18f0efc
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/ObjectTypeBinder.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.util.dao;
+
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(ObjectTypeBinder.ObjectTypeBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface ObjectTypeBinder {
+ public static class ObjectTypeBinderFactory implements BinderFactory {
+ public Binder build(Annotation annotation) {
+ return new Binder<ObjectTypeBinder, ObjectType>() {
+ public void bind(SQLStatement q, ObjectTypeBinder bind, ObjectType objectType) {
+ q.bind("objectType", objectType.getObjectName());
+ }
+ };
+ }
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/RecordIdMapper.java b/util/src/main/java/com/ning/billing/util/dao/RecordIdMapper.java
new file mode 100644
index 0000000..35cdaf3
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/RecordIdMapper.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.dao;
+
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+public class RecordIdMapper extends MapperBase implements ResultSetMapper<Mapper> {
+ @Override
+ public Mapper<UUID, Long> map(int index, ResultSet resultSet, StatementContext ctx) throws SQLException {
+ UUID id = getUUID(resultSet, "id");
+ Long recordId = resultSet.getLong("record_id");
+
+ return new Mapper<UUID, Long>(id, recordId);
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/TableName.java b/util/src/main/java/com/ning/billing/util/dao/TableName.java
new file mode 100644
index 0000000..28a070a
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/TableName.java
@@ -0,0 +1,45 @@
+/*
+ * 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.dao;
+
+public enum TableName {
+ ACCOUNT("accounts"),
+ ACCOUNT_HISTORY("account_history"),
+ ACCOUNT_EMAIL_HISTORY("account_email_history"),
+ BUNDLES("bundles"),
+ CUSTOM_FIELD_HISTORY("custom_field_history"),
+ ENTITLEMENT_EVENTS("entitlement_events"),
+ FIXED_INVOICE_ITEMS("fixed_invoice_items"),
+ INVOICE_PAYMENTS("invoice_payments"),
+ INVOICES("invoices"),
+ PAYMENT_ATTEMPTS("payment_attempts"),
+ PAYMENT_HISTORY("payment_history"),
+ PAYMENTS("payments"),
+ RECURRING_INVOICE_ITEMS("recurring_invoice_items"),
+ SUBSCRIPTIONS("subscriptions"),
+ TAG_HISTORY("tag_history");
+
+ private final String tableName;
+
+ TableName(String tableName) {
+ this.tableName = tableName;
+ }
+
+ public String getTableName() {
+ return tableName;
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/dao/TableNameBinder.java b/util/src/main/java/com/ning/billing/util/dao/TableNameBinder.java
new file mode 100644
index 0000000..a93d915
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/dao/TableNameBinder.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.util.dao;
+
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(TableNameBinder.TableNameBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface TableNameBinder {
+ public static class TableNameBinderFactory implements BinderFactory {
+ public Binder build(Annotation annotation) {
+ return new Binder<TableNameBinder, TableName>() {
+ public void bind(SQLStatement q, TableNameBinder bind, TableName tableName) {
+ q.bind("tableName", tableName.getTableName());
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/email/DefaultEmailSender.java b/util/src/main/java/com/ning/billing/util/email/DefaultEmailSender.java
new file mode 100644
index 0000000..b4e3587
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/DefaultEmailSender.java
@@ -0,0 +1,68 @@
+/*
+ * 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.email;
+
+import java.util.List;
+
+import org.apache.commons.mail.EmailException;
+import org.apache.commons.mail.HtmlEmail;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+
+public class DefaultEmailSender implements EmailSender {
+ private final Logger log = LoggerFactory.getLogger(EmailSender.class);
+ private final EmailConfig config;
+
+ @Inject
+ public DefaultEmailSender(EmailConfig emailConfig) {
+ this.config = emailConfig;
+ }
+
+ @Override
+ public void sendSecureEmail(List<String> to, List<String> cc, String subject, String htmlBody) throws EmailApiException {
+ HtmlEmail email;
+ try {
+ email = new HtmlEmail();
+
+ email.setSmtpPort(config.getSmtpPort());
+ email.setAuthentication(config.getSmtpUserName(), config.getSmtpPassword());
+ email.setHostName(config.getSmtpServerName());
+ email.setFrom(config.getSmtpUserName());
+ email.setSubject(subject);
+ email.setHtmlMsg(htmlBody);
+
+ if (to != null) {
+ for (String recipient : to) {
+ email.addTo(recipient);
+ }
+ }
+
+ if (cc != null) {
+ for (String recipient : cc) {
+ email.addCc(recipient);
+ }
+ }
+
+ email.setSSL(true);
+ email.send();
+ } catch (EmailException ee) {
+ log.warn("Failed to send e-mail", ee);
+ }
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/email/EmailConfig.java b/util/src/main/java/com/ning/billing/util/email/EmailConfig.java
new file mode 100644
index 0000000..bcbf5e5
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/EmailConfig.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.email;
+
+import com.ning.billing.config.KillbillConfig;
+import org.skife.config.Config;
+import org.skife.config.Default;
+
+import java.util.Locale;
+
+public interface EmailConfig extends KillbillConfig {
+ @Config("mail.smtp.host")
+ @Default("smtp.gmail.com")
+ public String getSmtpServerName();
+
+ @Config("mail.smtp.port")
+ @Default("465")
+ public int getSmtpPort();
+
+ @Config("mail.smtp.user")
+ @Default("killbill.ning@gmail.com")
+ public String getSmtpUserName();
+
+ @Config("mail.smtp.password")
+ @Default("killbill@ning!")
+ public String getSmtpPassword();
+}
diff --git a/util/src/main/java/com/ning/billing/util/email/EmailModule.java b/util/src/main/java/com/ning/billing/util/email/EmailModule.java
new file mode 100644
index 0000000..5bb252a
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/EmailModule.java
@@ -0,0 +1,32 @@
+/*
+ * 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.email;
+
+import com.google.inject.AbstractModule;
+import org.skife.config.ConfigurationObjectFactory;
+
+public class EmailModule extends AbstractModule {
+ protected void installEmailConfig() {
+ EmailConfig config = new ConfigurationObjectFactory(System.getProperties()).build(EmailConfig.class);
+ bind(EmailConfig.class).toInstance(config);
+ }
+
+ @Override
+ protected void configure() {
+ installEmailConfig();
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java b/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java
new file mode 100644
index 0000000..dba8757
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java
@@ -0,0 +1,42 @@
+/*
+ * 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.email.templates;
+
+import com.samskivert.mustache.Mustache;
+import com.samskivert.mustache.Template;
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.util.Map;
+
+public class MustacheTemplateEngine implements TemplateEngine {
+ @Override
+ public String executeTemplate(String templateName, Map<String, Object> data) throws IOException {
+ String templateText = getTemplateText(templateName);
+ Template template = Mustache.compiler().compile(templateText);
+ return template.execute(data);
+ }
+
+ private String getTemplateText(String templateName) throws IOException {
+ InputStream templateStream = this.getClass().getResourceAsStream(templateName + ".mustache");
+ StringWriter writer = new StringWriter();
+ IOUtils.copy(templateStream, writer, "UTF-8");
+ return writer.toString();
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/email/templates/TemplateEngine.java b/util/src/main/java/com/ning/billing/util/email/templates/TemplateEngine.java
new file mode 100644
index 0000000..9a47008
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/templates/TemplateEngine.java
@@ -0,0 +1,24 @@
+/*
+ * 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.email.templates;
+
+import java.io.IOException;
+import java.util.Map;
+
+public interface TemplateEngine {
+ public String executeTemplate(String templateName, Map<String, Object> data) throws IOException;
+}
diff --git a/util/src/main/java/com/ning/billing/util/email/templates/TemplateModule.java b/util/src/main/java/com/ning/billing/util/email/templates/TemplateModule.java
new file mode 100644
index 0000000..86a3124
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/email/templates/TemplateModule.java
@@ -0,0 +1,28 @@
+/*
+ * 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.email.templates;
+
+import com.google.inject.AbstractModule;
+
+public class TemplateModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ bind(TemplateEngine.class).to(MustacheTemplateEngine.class).asEagerSingleton();
+ }
+
+}
diff --git a/util/src/main/java/com/ning/billing/util/entity/collection/dao/UpdatableEntityCollectionSqlDao.java b/util/src/main/java/com/ning/billing/util/entity/collection/dao/UpdatableEntityCollectionSqlDao.java
new file mode 100644
index 0000000..ae52521
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/entity/collection/dao/UpdatableEntityCollectionSqlDao.java
@@ -0,0 +1,51 @@
+/*
+ * 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.entity.collection.dao;
+
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
+import com.ning.billing.util.dao.AuditSqlDao;
+import com.ning.billing.util.dao.CollectionHistorySqlDao;
+import com.ning.billing.util.dao.HistoryRecordIdMapper;
+import com.ning.billing.util.dao.Mapper;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.ObjectTypeBinder;
+import com.ning.billing.util.entity.Entity;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+
+import java.util.List;
+
+public interface UpdatableEntityCollectionSqlDao<T extends Entity> extends EntityCollectionSqlDao<T>,
+ CollectionHistorySqlDao<T>,
+ AuditSqlDao {
+ @SqlBatch(transactional=false)
+ public void updateFromTransaction(@Bind("objectId") final String objectId,
+ @ObjectTypeBinder final ObjectType objectType,
+ @BindBean final List<T> entities,
+ @CallContextBinder final CallContext context);
+
+ @SqlQuery
+ public long getMaxHistoryRecordId();
+
+ @SqlQuery
+ @RegisterMapper(HistoryRecordIdMapper.class)
+ public List<Mapper<Long, Long>> getHistoryRecordIds(@Bind("maxHistoryRecordId") final long maxHistoryRecordId);
+}
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/EntityDao.java b/util/src/main/java/com/ning/billing/util/entity/dao/EntityDao.java
new file mode 100644
index 0000000..1d2d173
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/EntityDao.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.entity.dao;
+
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.entity.Entity;
+import com.ning.billing.util.entity.EntityPersistenceException;
+
+public interface EntityDao<T extends Entity> {
+ public void create(final T entity, final CallContext context) throws EntityPersistenceException;
+
+ public T getById(final UUID id);
+
+ public List<T> get();
+
+ public void test();
+}
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/UpdatableEntitySqlDao.java b/util/src/main/java/com/ning/billing/util/entity/dao/UpdatableEntitySqlDao.java
new file mode 100644
index 0000000..59bc69e
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/UpdatableEntitySqlDao.java
@@ -0,0 +1,33 @@
+/*
+ * 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.entity.dao;
+
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.dao.AuditSqlDao;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.entity.Entity;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+
+// this interface needs to be extended by an interface that provides (externalized) sql and object binders and mappers
+public interface UpdatableEntitySqlDao<T extends Entity> extends EntitySqlDao<T>, AuditSqlDao {
+ @SqlUpdate
+ public void update(final T entity, final CallContext context);
+
+ @SqlUpdate
+ public void insertHistoryFromTransaction(final EntityHistory<T> account,
+ final CallContext context);
+}
diff --git a/util/src/main/java/com/ning/billing/util/entity/EntityBase.java b/util/src/main/java/com/ning/billing/util/entity/EntityBase.java
index 4de0312..6a03828 100644
--- a/util/src/main/java/com/ning/billing/util/entity/EntityBase.java
+++ b/util/src/main/java/com/ning/billing/util/entity/EntityBase.java
@@ -22,35 +22,19 @@ import java.util.UUID;
public abstract class EntityBase implements Entity {
protected final UUID id;
- protected final String createdBy;
- protected final DateTime createdDate;
// used to hydrate objects
- public EntityBase(UUID id, String createdBy, DateTime createdDate) {
+ public EntityBase(UUID id) {
this.id = id;
- this.createdBy = createdBy;
- this.createdDate = createdDate;
}
// used to create new objects
public EntityBase() {
this.id = UUID.randomUUID();
- this.createdBy = null;
- this.createdDate = null;
}
@Override
public UUID getId() {
return id;
}
-
- @Override
- public String getCreatedBy() {
- return createdBy;
- }
-
- @Override
- public DateTime getCreatedDate() {
- return createdDate;
- }
}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/entity/ExtendedEntityBase.java b/util/src/main/java/com/ning/billing/util/entity/ExtendedEntityBase.java
index 0017397..c598893 100644
--- a/util/src/main/java/com/ning/billing/util/entity/ExtendedEntityBase.java
+++ b/util/src/main/java/com/ning/billing/util/entity/ExtendedEntityBase.java
@@ -21,32 +21,34 @@ import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.customfield.Customizable;
import com.ning.billing.util.customfield.DefaultFieldStore;
import com.ning.billing.util.customfield.FieldStore;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.DefaultControlTag;
import com.ning.billing.util.tag.DefaultTagStore;
import com.ning.billing.util.tag.DescriptiveTag;
import com.ning.billing.util.tag.Tag;
import com.ning.billing.util.tag.TagDefinition;
import com.ning.billing.util.tag.TagStore;
import com.ning.billing.util.tag.Taggable;
-import org.joda.time.DateTime;
-import javax.annotation.Nullable;
+import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public abstract class ExtendedEntityBase extends EntityBase implements Customizable, Taggable {
protected final FieldStore fields;
- protected final TagStore tags;
+ protected final TagStore tagStore;
public ExtendedEntityBase() {
super();
- this.fields = DefaultFieldStore.create(getId(), getObjectName());
- this.tags = new DefaultTagStore(id, getObjectName());
+ this.fields = DefaultFieldStore.create(getId(), getObjectType());
+ this.tagStore = new DefaultTagStore(id, getObjectType());
}
- public ExtendedEntityBase(final UUID id, @Nullable final String createdBy, @Nullable final DateTime createdDate) {
- super(id, createdBy, createdDate);
- this.fields = DefaultFieldStore.create(getId(), getObjectName());
- this.tags = new DefaultTagStore(id, getObjectName());
+ public ExtendedEntityBase(final UUID id) {
+ super(id);
+ this.fields = DefaultFieldStore.create(getId(), getObjectType());
+ this.tagStore = new DefaultTagStore(id, getObjectType());
}
@Override
@@ -78,49 +80,71 @@ public abstract class ExtendedEntityBase extends EntityBase implements Customiza
@Override
public List<Tag> getTagList() {
- return tags.getEntityList();
+ return tagStore.getEntityList();
}
@Override
- public boolean hasTag(final String tagName) {
- return tags.containsTag(tagName);
+ public boolean hasTag(final TagDefinition tagDefinition) {
+ return tagStore.containsTagForDefinition(tagDefinition);
}
+ @Override
+ public boolean hasTag(ControlTagType controlTagType) {
+ return tagStore.containsTagForControlTagType(controlTagType);
+ }
+
@Override
public void addTag(final TagDefinition definition) {
Tag tag = new DescriptiveTag(definition);
- tags.add(tag) ;
+ tagStore.add(tag) ;
}
+ @Override
+ public void addTags(final List<Tag> tags) {
+ this.tagStore.add(tags);
+ }
+
@Override
- public void addTags(final List<Tag> tags) {
- if (tags != null) {
- this.tags.add(tags);
+ public void addTagsFromDefinitions(final List<TagDefinition> tagDefinitions) {
+ if (tagStore != null) {
+ List<Tag> tags = new ArrayList<Tag>();
+ if (tagDefinitions != null) {
+ for (TagDefinition tagDefinition : tagDefinitions) {
+ try {
+ ControlTagType controlTagType = ControlTagType.valueOf(tagDefinition.getName());
+ tags.add(new DefaultControlTag(controlTagType));
+ } catch (IllegalArgumentException ex) {
+ tags.add(new DescriptiveTag(tagDefinition));
+ }
+ }
+ }
+
+ this.tagStore.add(tags);
}
}
@Override
public void clearTags() {
- this.tags.clear();
+ this.tagStore.clear();
}
@Override
- public void removeTag(final TagDefinition definition) {
- tags.remove(definition.getName());
+ public void removeTag(final TagDefinition tagDefinition) {
+ tagStore.remove(tagDefinition);
}
@Override
public boolean generateInvoice() {
- return tags.generateInvoice();
+ return tagStore.generateInvoice();
}
@Override
public boolean processPayment() {
- return tags.processPayment();
+ return tagStore.processPayment();
}
@Override
- public abstract String getObjectName();
+ public abstract ObjectType getObjectType();
@Override
public abstract void saveFieldValue(String fieldName, String fieldValue, CallContext context);
diff --git a/util/src/main/java/com/ning/billing/util/entity/UpdatableEntityBase.java b/util/src/main/java/com/ning/billing/util/entity/UpdatableEntityBase.java
index 3aaffbc..625aaec 100644
--- a/util/src/main/java/com/ning/billing/util/entity/UpdatableEntityBase.java
+++ b/util/src/main/java/com/ning/billing/util/entity/UpdatableEntityBase.java
@@ -16,33 +16,14 @@
package com.ning.billing.util.entity;
-import org.joda.time.DateTime;
-
import java.util.UUID;
public abstract class UpdatableEntityBase extends EntityBase implements UpdatableEntity {
- private final String updatedBy;
- private final DateTime updatedDate;
-
public UpdatableEntityBase() {
super();
- this.updatedBy = null;
- this.updatedDate = null;
- }
-
- public UpdatableEntityBase(UUID id, String createdBy, DateTime createdDate, String updatedBy, DateTime updatedDate) {
- super(id, createdBy, createdDate);
- this.updatedBy = updatedBy;
- this.updatedDate = updatedDate;
- }
-
- @Override
- public String getUpdatedBy() {
- return updatedBy;
}
- @Override
- public DateTime getUpdatedDate() {
- return updatedDate;
+ public UpdatableEntityBase(UUID id) {
+ super(id);
}
}
diff --git a/util/src/main/java/com/ning/billing/util/glue/BusModule.java b/util/src/main/java/com/ning/billing/util/glue/BusModule.java
index d6f7a37..2793208 100644
--- a/util/src/main/java/com/ning/billing/util/glue/BusModule.java
+++ b/util/src/main/java/com/ning/billing/util/glue/BusModule.java
@@ -16,19 +16,65 @@
package com.ning.billing.util.glue;
+import org.skife.config.ConfigurationObjectFactory;
+
import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
+import com.ning.billing.config.EntitlementConfig;
+import com.ning.billing.config.PersistentQueueConfig;
import com.ning.billing.util.bus.DefaultBusService;
import com.ning.billing.util.bus.Bus;
import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.bus.PersistentBusConfig;
import com.ning.billing.util.bus.InMemoryBus;
+import com.ning.billing.util.bus.PersistentBus;
public class BusModule extends AbstractModule {
+ private final BusType type;
+
+ public BusModule() {
+ super();
+ type = BusType.PERSISTENT;
+ }
+
+ public BusModule(BusType type) {
+ super();
+ this.type = type;
+ }
+
+ public enum BusType {
+ MEMORY,
+ PERSISTENT
+ }
+
@Override
protected void configure() {
bind(BusService.class).to(DefaultBusService.class);
- bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
-
+ switch(type) {
+ case MEMORY:
+ configureInMemoryEventBus();
+ break;
+ case PERSISTENT:
+ configurePersistentEventBus();
+ break;
+ default:
+ new RuntimeException("Unrecognized EventBus type " + type);
+ }
+
}
+ protected void configurePersistentBusConfig() {
+ final PersistentBusConfig config = new ConfigurationObjectFactory(System.getProperties()).build(PersistentBusConfig.class);
+ bind(PersistentBusConfig.class).toInstance(config);
+ }
+
+ private void configurePersistentEventBus() {
+ configurePersistentBusConfig();
+ bind(Bus.class).to(PersistentBus.class).asEagerSingleton();
+ }
+
+ private void configureInMemoryEventBus() {
+ bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+ }
}
diff --git a/util/src/main/java/com/ning/billing/util/glue/RealImplementation.java b/util/src/main/java/com/ning/billing/util/glue/RealImplementation.java
new file mode 100644
index 0000000..d72a698
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/glue/RealImplementation.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.glue;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+
+import com.google.inject.BindingAnnotation;
+
+/**
+ * This annotation is used to bing classes that are being intercepted in junction.
+ *
+ * The real implementation of the class is bound in Guice with this parameter, the Blocking implementation
+ * is left unannotated.
+ *
+ */
+@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD,LOCAL_VARIABLE }) @Retention(RUNTIME)
+public @interface RealImplementation {
+
+}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java b/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
index 3d0a8b1..cbfc564 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
@@ -39,7 +39,7 @@ 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;
+import com.ning.billing.util.queue.PersistentQueueEntryLifecycle.PersistentQueueEntryLifecycleState;
@ExternalizedSqlViaStringTemplate3()
public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, CloseMe {
@@ -49,31 +49,36 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
//
@SqlQuery
@Mapper(NotificationSqlMapper.class)
- public List<Notification> getReadyNotifications(@Bind("now") Date now, @Bind("max") int max, @Bind("queue_name") String queueName);
+ public List<Notification> getReadyNotifications(@Bind("now") Date now, @Bind("owner") String owner, @Bind("max") int max, @Bind("queueName") String queueName);
@SqlUpdate
- public int claimNotification(@Bind("owner") String owner, @Bind("next_available") Date nextAvailable, @Bind("id") long id, @Bind("now") Date now);
+ public int claimNotification(@Bind("owner") String owner, @Bind("nextAvailable") Date nextAvailable,
+ @Bind("id") String id, @Bind("now") Date now);
@SqlUpdate
- public void clearNotification(@Bind("id") long id, @Bind("owner") String owner);
+ public void clearNotification(@Bind("id") String id, @Bind("owner") String owner);
@SqlUpdate
+ public void removeNotificationsByKey(@Bind("notificationKey") String key);
+
+ @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 void insertClaimedHistory(@Bind("ownerId") String ownerId, @Bind("claimedDate") Date claimedDate, @Bind("notificationId") String notificationId);
public static class NotificationSqlDaoBinder extends BinderBase implements Binder<Bind, Notification> {
@Override
public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, Notification evt) {
- stmt.bind("notification_id", evt.getUUID().toString());
- stmt.bind("created_dt", getDate(new DateTime()));
- stmt.bind("notification_key", evt.getNotificationKey());
- stmt.bind("effective_dt", getDate(evt.getEffectiveDate()));
- stmt.bind("queue_name", evt.getQueueName());
- stmt.bind("processing_available_dt", getDate(evt.getNextAvailableDate()));
- stmt.bind("processing_owner", evt.getOwner());
- stmt.bind("processing_state", NotificationLifecycleState.AVAILABLE.toString());
+ stmt.bind("id", evt.getId().toString());
+ stmt.bind("createdDate", getDate(new DateTime()));
+ stmt.bind("creatingOwner", evt.getCreatedOwner());
+ stmt.bind("notificationKey", evt.getNotificationKey());
+ stmt.bind("effectiveDate", getDate(evt.getEffectiveDate()));
+ stmt.bind("queueName", evt.getQueueName());
+ stmt.bind("processingAvailableDate", getDate(evt.getNextAvailableDate()));
+ stmt.bind("processingOwner", evt.getOwner());
+ stmt.bind("processingState", PersistentQueueEntryLifecycleState.AVAILABLE.toString());
}
}
@@ -83,16 +88,17 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
public Notification map(int index, ResultSet r, StatementContext ctx)
throws SQLException {
- final long id = r.getLong("id");
- final UUID uuid = UUID.fromString(r.getString("notification_id"));
+ final Long ordering = r.getLong("record_id");
+ final UUID id = getUUID(r, "id");
+ final String createdOwner = r.getString("creating_owner");
final String notificationKey = r.getString("notification_key");
final String queueName = r.getString("queue_name");
- final DateTime effectiveDate = getDate(r, "effective_dt");
- final DateTime nextAvailableDate = getDate(r, "processing_available_dt");
+ final DateTime effectiveDate = getDate(r, "effective_date");
+ final DateTime nextAvailableDate = getDate(r, "processing_available_date");
final String processingOwner = r.getString("processing_owner");
- final NotificationLifecycleState processingState = NotificationLifecycleState.valueOf(r.getString("processing_state"));
+ final PersistentQueueEntryLifecycleState processingState = PersistentQueueEntryLifecycleState.valueOf(r.getString("processing_state"));
- return new DefaultNotification(id, uuid, processingOwner, queueName, nextAvailableDate,
+ return new DefaultNotification(ordering, id, createdOwner, processingOwner, queueName, nextAvailableDate,
processingState, notificationKey, effectiveDate);
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java
index 26e6c4e..c71343d 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java
@@ -18,27 +18,27 @@ package com.ning.billing.util.notificationq;
import java.util.UUID;
+import com.ning.billing.util.entity.EntityBase;
import org.joda.time.DateTime;
-public class DefaultNotification implements Notification {
-
- private final long id;
- private final UUID uuid;
+public class DefaultNotification extends EntityBase implements Notification {
+ private final long ordering;
private final String owner;
+ private final String createdOwner;
private final String queueName;
private final DateTime nextAvailableDate;
- private final NotificationLifecycleState lifecycleState;
+ private final PersistentQueueEntryLifecycleState lifecycleState;
private final String notificationKey;
private final DateTime effectiveDate;
- public DefaultNotification(long id, UUID uuid, String owner, String queueName, DateTime nextAvailableDate,
- NotificationLifecycleState lifecycleState,
+ public DefaultNotification(long ordering, UUID id, String createdOwner, String owner, String queueName, DateTime nextAvailableDate,
+ PersistentQueueEntryLifecycleState lifecycleState,
String notificationKey, DateTime effectiveDate) {
- super();
- this.id = id;
- this.uuid = uuid;
+ super(id);
+ this.ordering = ordering;
this.owner = owner;
+ this.createdOwner = createdOwner;
this.queueName = queueName;
this.nextAvailableDate = nextAvailableDate;
this.lifecycleState = lifecycleState;
@@ -46,17 +46,12 @@ public class DefaultNotification implements Notification {
this.effectiveDate = effectiveDate;
}
- @Override
- public long getId() {
- return id;
- }
-
- public DefaultNotification(String queueName, String notificationKey, DateTime effectiveDate) {
- this(-1L, UUID.randomUUID(), null, queueName, null, NotificationLifecycleState.AVAILABLE, notificationKey, effectiveDate);
+ public DefaultNotification(String queueName, String createdOwner, String notificationKey, DateTime effectiveDate) {
+ this(-1L, UUID.randomUUID(), createdOwner, null, queueName, null, PersistentQueueEntryLifecycleState.AVAILABLE, notificationKey, effectiveDate);
}
@Override
- public UUID getUUID() {
- return uuid;
+ public Long getOrdering() {
+ return ordering;
}
@Override
@@ -70,7 +65,7 @@ public class DefaultNotification implements Notification {
}
@Override
- public NotificationLifecycleState getProcessingState() {
+ public PersistentQueueEntryLifecycleState getProcessingState() {
return lifecycleState;
}
@@ -108,4 +103,8 @@ public class DefaultNotification implements Notification {
return queueName;
}
+ @Override
+ public String getCreatedOwner() {
+ return createdOwner;
+ }
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
index c0c88fe..6b50914 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
@@ -19,11 +19,13 @@ package com.ning.billing.util.notificationq;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
+import java.util.UUID;
import org.joda.time.DateTime;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import com.ning.billing.config.NotificationConfig;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
@@ -39,10 +41,10 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
}
@Override
- protected int doProcessEvents(final int sequenceId) {
+ public int doProcessEvents() {
logDebug("ENTER doProcessEvents");
- List<Notification> notifications = getReadyNotifications(sequenceId);
+ List<Notification> notifications = getReadyNotifications();
if (notifications.size() == 0) {
logDebug("EXIT doProcessEvents");
return 0;
@@ -54,19 +56,19 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
for (final Notification cur : notifications) {
nbProcessedEvents.incrementAndGet();
logDebug("handling notification %s, key = %s for time %s",
- cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate());
+ cur.getId(), cur.getNotificationKey(), cur.getEffectiveDate());
handler.handleReadyNotification(cur.getNotificationKey(), cur.getEffectiveDate());
result++;
clearNotification(cur);
logDebug("done handling notification %s, key = %s for time %s",
- cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate());
+ cur.getId(), cur.getNotificationKey(), cur.getEffectiveDate());
}
return result;
}
@Override
public void recordFutureNotification(DateTime futureNotificationTime, NotificationKey notificationKey) {
- Notification notification = new DefaultNotification(getFullQName(), notificationKey.toString(), futureNotificationTime);
+ Notification notification = new DefaultNotification(getFullQName(), hostname, notificationKey.toString(), futureNotificationTime);
dao.insertNotification(notification);
}
@@ -74,32 +76,32 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
public void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao,
final DateTime futureNotificationTime, final NotificationKey notificationKey) {
NotificationSqlDao transactionalNotificationDao = transactionalDao.become(NotificationSqlDao.class);
- Notification notification = new DefaultNotification(getFullQName(), notificationKey.toString(), futureNotificationTime);
+ Notification notification = new DefaultNotification(getFullQName(), hostname, notificationKey.toString(), futureNotificationTime);
transactionalNotificationDao.insertNotification(notification);
}
private void clearNotification(final Notification cleared) {
- dao.clearNotification(cleared.getId(), hostname);
+ dao.clearNotification(cleared.getId().toString(), hostname);
}
- private List<Notification> getReadyNotifications(final int seqId) {
+ private List<Notification> getReadyNotifications() {
final Date now = clock.getUTCNow().toDate();
- final Date nextAvailable = clock.getUTCNow().plus(config.getDaoClaimTimeMs()).toDate();
+ final Date nextAvailable = clock.getUTCNow().plus(CLAIM_TIME_MS).toDate();
- List<Notification> input = dao.getReadyNotifications(now, config.getDaoMaxReadyEvents(), getFullQName());
+ List<Notification> input = dao.getReadyNotifications(now, hostname, CLAIM_TIME_MS, getFullQName());
List<Notification> claimedNotifications = new ArrayList<Notification>();
for (Notification cur : input) {
logDebug("about to claim notification %s, key = %s for time %s",
- cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate());
- final boolean claimed = (dao.claimNotification(hostname, nextAvailable, cur.getId(), now) == 1);
+ cur.getId(), cur.getNotificationKey(), cur.getEffectiveDate());
+ final boolean claimed = (dao.claimNotification(hostname, nextAvailable, cur.getId().toString(), now) == 1);
logDebug("claimed notification %s, key = %s for time %s result = %s",
- cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate(), Boolean.valueOf(claimed));
+ cur.getId(), cur.getNotificationKey(), cur.getEffectiveDate(), Boolean.valueOf(claimed));
if (claimed) {
claimedNotifications.add(cur);
- dao.insertClaimedHistory(seqId, hostname, now, cur.getUUID().toString());
+ dao.insertClaimedHistory(hostname, now, cur.getId().toString());
}
}
@@ -118,4 +120,10 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
log.debug(String.format("Thread %d [queue = %s] %s", Thread.currentThread().getId(), getFullQName(), realDebug));
}
}
+
+ @Override
+ public void removeNotificationsByKey(UUID key) {
+ dao.removeNotificationsByKey(key.toString());
+
+ }
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java
index 3b96ee4..bf8652c 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java
@@ -18,6 +18,7 @@ package com.ning.billing.util.notificationq;
import org.skife.jdbi.v2.IDBI;
import com.google.inject.Inject;
+import com.ning.billing.config.NotificationConfig;
import com.ning.billing.util.clock.Clock;
public class DefaultNotificationQueueService extends NotificationQueueServiceBase {
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/Notification.java b/util/src/main/java/com/ning/billing/util/notificationq/Notification.java
index d59098b..37caa4e 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/Notification.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/Notification.java
@@ -16,22 +16,17 @@
package com.ning.billing.util.notificationq;
-import java.util.UUID;
+import com.ning.billing.util.entity.Entity;
+import com.ning.billing.util.queue.PersistentQueueEntryLifecycle;
import org.joda.time.DateTime;
-
-public interface Notification extends NotificationLifecycle {
-
- public long getId();
-
- public UUID getUUID();
+public interface Notification extends PersistentQueueEntryLifecycle, Entity {
+ public Long getOrdering();
public String getNotificationKey();
public DateTime getEffectiveDate();
public String getQueueName();
-
-
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
index fb88d4c..f28ff63 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
@@ -16,59 +16,58 @@
package com.ning.billing.util.notificationq;
+import java.util.UUID;
+
import org.joda.time.DateTime;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+import com.ning.billing.util.queue.QueueLifecycle;
+
+public interface NotificationQueue extends QueueLifecycle {
-public interface NotificationQueue {
+ /**
+ *
+ * Record the need to be called back when the notification is ready
+ *
+ * @param futureNotificationTime the time at which the notification is ready
+ * @param notificationKey the key for that notification
+ */
+ public void recordFutureNotification(final DateTime futureNotificationTime, final NotificationKey notificationKey);
- /**
- *
- * Record the need to be called back when the notification is ready
- *
- * @param futureNotificationTime the time at which the notification is ready
- * @param notificationKey the key for that notification
- */
- public void recordFutureNotification(final DateTime futureNotificationTime, final NotificationKey notificationKey);
+ /**
+ *
+ * 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);
- /**
- *
- * 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);
+
+ /**
+ * Remove all notifications associated with this key
+ *
+ * @param key
+ */
+ public void removeNotificationsByKey(UUID key);
- /**
- * 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.
- *
- * @return the number of entries we processed
- */
- public int processReadyNotification();
+ /**
+ * 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.
+ *
+ * @return the number of entries we processed
+ */
+ public int processReadyNotification();
- /**
- * Stops the queue. Blocks until queue is completely stopped.
- *
- * @see NotificationQueueHandler.completedQueueStop to be notified when the notification thread exited
- */
- public void stopQueue();
+ /**
+ *
+ * @return the name of that queue
+ */
+ public String getFullQName();
- /**
- * Starts the queue. Blocks until queue has completely started.
- *
- * @see NotificationQueueHandler.completedQueueStart to be notified when the notification thread started
- */
- public void startQueue();
- /**
- *
- * @return the name of that queue
- */
- public String getFullQName();
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
index a4f4c97..cd3c0c2 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
@@ -17,60 +17,45 @@
package com.ning.billing.util.notificationq;
import java.lang.Thread.UncaughtExceptionHandler;
-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.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.ning.billing.config.NotificationConfig;
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.queue.PersistentQueueBase;
-public abstract class NotificationQueueBase implements NotificationQueue {
+public abstract class NotificationQueueBase extends PersistentQueueBase implements NotificationQueue {
protected final static Logger log = LoggerFactory.getLogger(NotificationQueueBase.class);
- private static final long MAX_NOTIFICATION_THREAD_WAIT_MS = 10000; // 10 secs
- private static final long NOTIFICATION_THREAD_WAIT_INCREMENT_MS = 1000; // 1 sec
- private static final long NANO_TO_MS = (1000 * 1000);
+ public final static int CLAIM_TIME_MS = (5 * 60 * 1000); // 5 minutes
+
+ private final static String NOTIFICATION_THREAD_PREFIX = "Notification-";
+ private final static int NB_THREADS = 1;
- protected static final String NOTIFICATION_THREAD_PREFIX = "Notification-";
- protected final long STOP_WAIT_TIMEOUT_MS = 60000;
-
- protected final String svcName;
- protected final String queueName;
+
+ private final String svcName;
+ private 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;
-
- private boolean startedComplete = false;
- private boolean stoppedComplete = false;
-
// Package visibility on purpose
NotificationQueueBase(final Clock clock, final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config) {
- this.clock = clock;
- this.svcName = svcName;
- this.queueName = queueName;
- this.handler = handler;
- this.config = config;
- this.hostname = Hostname.get();
+ super(svcName, Executors.newFixedThreadPool(1, new ThreadFactory() {
- this.executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread th = new Thread(r);
@@ -83,103 +68,36 @@ public abstract class NotificationQueueBase implements NotificationQueue {
});
return th;
}
- });
- }
+ }), NB_THREADS, config);
-
- @Override
- public int processReadyNotification() {
- return doProcessEvents(sequenceId.incrementAndGet());
+ this.clock = clock;
+ this.svcName = svcName;
+ this.queueName = queueName;
+ this.handler = handler;
+ this.config = config;
+ this.hostname = Hostname.get();
+ this.nbProcessedEvents = new AtomicLong();
}
-
@Override
- public void stopQueue() {
+ public void startQueue() {
if (config.isNotificationProcessingOff()) {
- 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);
- }
- }
- waitForNotificationStopCompletion();
+ super.startQueue();
}
-
+
@Override
- public void startQueue() {
-
- this.isProcessingEvents = true;
- this.nbProcessedEvents = new AtomicLong();
-
-
+ public void stopQueue() {
if (config.isNotificationProcessingOff()) {
- log.warn(String.format("KILLBILL NOTIFICATION PROCESSING FOR SVC %s IS OFF !!!", getFullQName()));
- 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
- 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 {
- completedQueueStop();
- log.info(String.format("NotificationQueue thread %s [%d] exited...",
- Thread.currentThread().getName(),
- Thread.currentThread().getId()));
- }
- }
+ super.stopQueue();
+ }
- private void sleepALittle() throws InterruptedException {
- Thread.sleep(config.getNotificationSleepTimeMs());
- }
- });
- waitForNotificationStartCompletion();
+ @Override
+ public int processReadyNotification() {
+ return doProcessEvents();
}
@Override
@@ -187,61 +105,12 @@ public abstract class NotificationQueueBase implements NotificationQueue {
return getFullQName();
}
- private void completedQueueStop() {
- synchronized (this) {
- stoppedComplete = true;
- this.notifyAll();
- }
- }
-
- private void completedQueueStart() {
- synchronized (this) {
- startedComplete = true;
- this.notifyAll();
- }
- }
-
- private void waitForNotificationStartCompletion() {
- waitForNotificationEventCompletion(true);
- }
-
- private void waitForNotificationStopCompletion() {
- waitForNotificationEventCompletion(false);
- }
-
- private void waitForNotificationEventCompletion(boolean startEvent) {
-
- long ini = System.nanoTime();
- synchronized(this) {
- do {
- if ((startEvent ? startedComplete : stoppedComplete)) {
- break;
- }
- try {
- this.wait(NOTIFICATION_THREAD_WAIT_INCREMENT_MS);
- } catch (InterruptedException e ) {
- Thread.currentThread().interrupt();
- throw new NotificationError(e);
- }
- } while (!(startEvent ? startedComplete : stoppedComplete) &&
- (System.nanoTime() - ini) / NANO_TO_MS < MAX_NOTIFICATION_THREAD_WAIT_MS);
-
- if (!(startEvent ? startedComplete : stoppedComplete)) {
- log.error("Could not {} notification thread in {} msec !!!",
- (startEvent ? "start" : "stop"),
- MAX_NOTIFICATION_THREAD_WAIT_MS);
- throw new NotificationError("Failed to start service!!");
- }
- log.info("Notification thread has been {} in {} ms",
- (startEvent ? "started" : "stopped"),
- (System.nanoTime() - ini) / NANO_TO_MS);
- }
- }
@Override
public String getFullQName() {
return svcName + ":" + queueName;
}
- protected abstract int doProcessEvents(int sequenceId);
+ @Override
+ public abstract int doProcessEvents();
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java
index 4d56b03..828608a 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java
@@ -18,6 +18,8 @@ package com.ning.billing.util.notificationq;
import org.joda.time.DateTime;
+import com.ning.billing.config.NotificationConfig;
+
public interface NotificationQueueService {
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java
index 56423a0..90c27b0 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java
@@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
import com.google.inject.Inject;
+import com.ning.billing.config.NotificationConfig;
import com.ning.billing.util.clock.Clock;
public abstract class NotificationQueueServiceBase implements NotificationQueueService {
diff --git a/util/src/main/java/com/ning/billing/util/queue/PersistentQueueBase.java b/util/src/main/java/com/ning/billing/util/queue/PersistentQueueBase.java
new file mode 100644
index 0000000..0ffe882
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/queue/PersistentQueueBase.java
@@ -0,0 +1,161 @@
+/*
+ * 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.queue;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.config.PersistentQueueConfig;
+
+
+public abstract class PersistentQueueBase implements QueueLifecycle {
+
+ private static final Logger log = LoggerFactory.getLogger(PersistentQueueBase.class);
+
+ private static final long waitTimeoutMs = 15L * 1000L; // 15 seconds
+
+ private final int nbThreads;
+ private final Executor executor;
+ private final String svcName;
+ private final long sleepTimeMs;
+
+ private boolean isProcessingEvents;
+ private int curActiveThreads;
+
+ public PersistentQueueBase(final String svcName, final Executor executor, final int nbThreads, final PersistentQueueConfig config) {
+ this.executor = executor;
+ this.nbThreads = nbThreads;
+ this.svcName = svcName;
+ this.sleepTimeMs = config.getSleepTimeMs();
+ this.isProcessingEvents = false;
+ this.curActiveThreads = 0;
+ }
+
+ @Override
+ public void startQueue() {
+
+ isProcessingEvents = true;
+ curActiveThreads = 0;
+
+ final PersistentQueueBase thePersistentQ = this;
+ final CountDownLatch doneInitialization = new CountDownLatch(nbThreads);
+
+ log.info(String.format("%s: Starting with %d threads",
+ svcName, nbThreads));
+
+ for (int i = 0; i < nbThreads; i++) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+
+ log.info(String.format("%s: Thread %s [%d] starting",
+ svcName,
+ Thread.currentThread().getName(),
+ Thread.currentThread().getId()));
+
+ synchronized(thePersistentQ) {
+ curActiveThreads++;
+ }
+
+ doneInitialization.countDown();
+
+ try {
+ while (true) {
+
+ synchronized(thePersistentQ) {
+ if (!isProcessingEvents) {
+ thePersistentQ.notify();
+ break;
+ }
+ }
+
+ try {
+ doProcessEvents();
+ } catch (Exception e) {
+ log.warn(String.format("%s: Thread %s [%d] got an exception, catching and moving on...",
+ svcName,
+ Thread.currentThread().getName(),
+ Thread.currentThread().getId()), e);
+ }
+ sleepALittle();
+ }
+ } catch (InterruptedException e) {
+ log.info(String.format("%s: Thread %s got interrupted, exting... ", svcName, Thread.currentThread().getName()));
+ } catch (Throwable e) {
+ log.error(String.format("%s: Thread %s got an exception, exting... ", svcName, Thread.currentThread().getName()), e);
+ } finally {
+
+ log.info(String.format("%s: Thread %s has exited", svcName, Thread.currentThread().getName()));
+ synchronized(thePersistentQ) {
+ curActiveThreads--;
+ }
+ }
+ }
+
+ private void sleepALittle() throws InterruptedException {
+ Thread.sleep(sleepTimeMs);
+ }
+ });
+ }
+ try {
+ boolean success = doneInitialization.await(sleepTimeMs, TimeUnit.MILLISECONDS);
+ if (!success) {
+
+ log.warn(String.format("%s: Failed to wait for all threads to be started, got %d/%d", svcName, (nbThreads - doneInitialization.getCount()), nbThreads));
+ } else {
+ log.info(String.format("%s: Done waiting for all threads to be started, got %d/%d", svcName, (nbThreads - doneInitialization.getCount()), nbThreads));
+ }
+ } catch (InterruptedException e) {
+ log.warn(String.format("%s: Start sequence, got interrupted", svcName));
+ }
+ }
+
+
+ @Override
+ public void stopQueue() {
+ int remaining = 0;
+ try {
+ synchronized(this) {
+ isProcessingEvents = false;
+ long ini = System.currentTimeMillis();
+ long remainingWaitTimeMs = waitTimeoutMs;
+ while (curActiveThreads > 0 && remainingWaitTimeMs > 0) {
+ wait(1000);
+ remainingWaitTimeMs = waitTimeoutMs - (System.currentTimeMillis() - ini);
+ }
+ remaining = curActiveThreads;
+ }
+
+ } catch (InterruptedException ignore) {
+ log.info(String.format("%s: Stop sequence has been interrupted, remaining active threads = %d", svcName, curActiveThreads));
+ } finally {
+ if (remaining > 0) {
+ log.error(String.format("%s: Stop sequence completed with %d active remaing threads", svcName, curActiveThreads));
+ } else {
+ log.info(String.format("%s: Stop sequence completed with %d active remaing threads", svcName, curActiveThreads));
+ }
+ curActiveThreads = 0;
+ }
+ }
+
+
+ @Override
+ public abstract int doProcessEvents();
+}
diff --git a/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionService.java b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionService.java
index d24ac9a..5865540 100644
--- a/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionService.java
+++ b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionService.java
@@ -21,6 +21,7 @@ import com.ning.billing.util.api.TagDefinitionService;
import com.ning.billing.util.api.TagUserApi;
public class DefaultTagDefinitionService implements TagDefinitionService {
+
private static final String TAG_DEFINITION_SERVICE_NAME = "tag-service";
private final TagUserApi api;
diff --git a/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionUserApi.java b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionUserApi.java
index 53d8841..680fb96 100644
--- a/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionUserApi.java
+++ b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionUserApi.java
@@ -17,7 +17,9 @@
package com.ning.billing.util.tag.api;
import java.util.List;
+import java.util.UUID;
+import com.ning.billing.util.dao.ObjectType;
import org.joda.time.DateTime;
import com.google.inject.Inject;
@@ -46,8 +48,8 @@ public class DefaultTagDefinitionUserApi implements TagUserApi {
}
@Override
- public TagDefinition create(final String name, final String description, final CallContext context) throws TagDefinitionApiException {
- return dao.create(name, description, context);
+ public TagDefinition create(final String definitionName, final String description, final CallContext context) throws TagDefinitionApiException {
+ return dao.create(definitionName, description, context);
}
@Override
@@ -68,24 +70,44 @@ public class DefaultTagDefinitionUserApi implements TagUserApi {
}
@Override
- public Tag createControlTag(String controlTagName) throws TagDefinitionApiException {
- ControlTagType type = null;
- for(ControlTagType t : ControlTagType.values()) {
- if(t.toString().equals(controlTagName)) {
- type = t;
- }
- }
-
- if(type == null) {
- throw new TagDefinitionApiException(ErrorCode.CONTROL_TAG_DOES_NOT_EXIST, controlTagName);
- }
- return new DefaultControlTag(type);
+ public List<Tag> createControlTags(UUID objectId, ObjectType objectType, List<TagDefinition> tagDefinitions) throws TagDefinitionApiException {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
}
@Override
- public Tag createDescriptiveTag(String tagDefinitionName) throws TagDefinitionApiException {
- TagDefinition tagDefinition = getTagDefinition(tagDefinitionName);
-
- return new DescriptiveTag(tagDefinition);
+ public Tag createControlTag(UUID objectId, ObjectType objectType, TagDefinition tagDefinition) throws TagDefinitionApiException {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
}
+
+ @Override
+ public List<Tag> createDescriptiveTags(UUID objectId, ObjectType objectType, List<TagDefinition> tagDefinitions) throws TagDefinitionApiException {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public Tag createDescriptiveTag(UUID objectId, ObjectType objectType, TagDefinition tagDefinition) throws TagDefinitionApiException {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+// @Override
+// public Tag createControlTags(String controlTagName) throws TagDefinitionApiException {
+// ControlTagType type = null;
+// for(ControlTagType t : ControlTagType.values()) {
+// if(t.toString().equals(controlTagName)) {
+// type = t;
+// }
+// }
+//
+// if(type == null) {
+// throw new TagDefinitionApiException(ErrorCode.CONTROL_TAG_DOES_NOT_EXIST, controlTagName);
+// }
+// return new DefaultControlTag(type);
+// }
+//
+// @Override
+// public Tag createDescriptiveTags(List) throws TagDefinitionApiException {
+// TagDefinition tagDefinition = getTagDefinition(tagDefinitionName);
+//
+// return new DescriptiveTag(tagDefinition);
+// }
}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
index 374e0a3..a9a2373 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
@@ -20,8 +20,14 @@ import com.google.inject.Inject;
import com.ning.billing.ErrorCode;
import com.ning.billing.invoice.api.InvoiceApiException;
import com.ning.billing.util.ChangeType;
-import com.ning.billing.util.audit.dao.AuditSqlDao;
import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.dao.AuditedCollectionDaoBase;
+import com.ning.billing.util.dao.EntityAudit;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.Mapper;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.TableName;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
import com.ning.billing.util.tag.Tag;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.Transaction;
@@ -29,11 +35,11 @@ import org.skife.jdbi.v2.TransactionStatus;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
-public class AuditedTagDao implements TagDao {
+public class AuditedTagDao extends AuditedCollectionDaoBase<Tag> implements TagDao {
private final TagSqlDao tagSqlDao;
@Inject
@@ -42,78 +48,31 @@ public class AuditedTagDao implements TagDao {
}
@Override
- public void saveTags(final UUID objectId, final String objectType,
- final List<Tag> tags, final CallContext context) {
- saveTagsFromTransaction(tagSqlDao, objectId, objectType, tags, context);
- }
-
- @Override
- public void saveTagsFromTransaction(final Transmogrifier dao, final UUID objectId, final String objectType,
- final List<Tag> tags, final CallContext context) {
- TagSqlDao tagSqlDao = dao.become(TagSqlDao.class);
-
- // get list of existing tags
- List<Tag> existingTags = tagSqlDao.load(objectId.toString(), objectType);
-
- // sort into tags to update (tagsToUpdate), tags to add (tags), and tags to delete (existingTags)
- Iterator<Tag> tagIterator = tags.iterator();
- while (tagIterator.hasNext()) {
- Tag tag = tagIterator.next();
-
- Iterator<Tag> existingTagIterator = existingTags.iterator();
- while (existingTagIterator.hasNext()) {
- Tag existingTag = existingTagIterator.next();
- if (tag.getTagDefinitionName().equals(existingTag.getTagDefinitionName())) {
- // if the tags match, remove from both lists
- // in the case of tag, this just means the tag remains associated
- tagIterator.remove();
- existingTagIterator.remove();
- }
- }
- }
-
- tagSqlDao.batchInsertFromTransaction(objectId.toString(), objectType, tags, context);
- tagSqlDao.batchDeleteFromTransaction(objectId.toString(), objectType, existingTags, context);
-
- List<String> historyIdsForInsert = getIdList(tags.size());
- tagSqlDao.batchInsertHistoryFromTransaction(objectId.toString(), objectType, historyIdsForInsert, tags, ChangeType.INSERT, context);
- List<String> historyIdsForDelete = getIdList(existingTags.size());
- tagSqlDao.batchInsertHistoryFromTransaction(objectId.toString(), objectType, historyIdsForDelete, existingTags, ChangeType.DELETE, context);
-
- AuditSqlDao auditSqlDao = tagSqlDao.become(AuditSqlDao.class);
- auditSqlDao.insertAuditFromTransaction("tag_history", historyIdsForInsert, ChangeType.INSERT, context);
- auditSqlDao.insertAuditFromTransaction("tag_history", historyIdsForDelete, ChangeType.DELETE, context);
- }
-
- private List<String> getIdList(int size) {
- List<String> results = new ArrayList<String>();
- for (int i = 0; i < size; i++) {
- results.add(UUID.randomUUID().toString());
- }
- return results;
- }
-
- @Override
- public List<Tag> loadTags(final UUID objectId, final String objectType) {
- return tagSqlDao.load(objectId.toString(), objectType);
- }
-
- @Override
- public List<Tag> loadTagsFromTransaction(final Transmogrifier dao, final UUID objectId, final String objectType) {
- TagSqlDao tagSqlDao = dao.become(TagSqlDao.class);
- return tagSqlDao.load(objectId.toString(), objectType);
- }
-
- @Override
- public void addTag(final String tagName, final UUID objectId, final String objectType, final CallContext context) {
+ public void addTag(final String tagName, final UUID objectId, final ObjectType objectType, final CallContext context) {
tagSqlDao.inTransaction(new Transaction<Void, TagSqlDao>() {
@Override
public Void inTransaction(final TagSqlDao tagSqlDao, final TransactionStatus status) throws Exception {
String tagId = UUID.randomUUID().toString();
tagSqlDao.addTagFromTransaction(tagId, tagName, objectId.toString(), objectType, context);
- TagAuditSqlDao auditDao = tagSqlDao.become(TagAuditSqlDao.class);
- auditDao.addTagFromTransaction(tagId, context);
+ Tag tag = tagSqlDao.findTag(tagName, objectId.toString(), objectType);
+ List<Tag> tagList = new ArrayList<Tag>();
+ tagList.add(tag);
+
+ List<Mapper<UUID, Long>> recordIds = tagSqlDao.getRecordIds(objectId.toString(), objectType);
+ Map<UUID, Long> recordIdMap = convertToHistoryMap(recordIds);
+
+ List<EntityHistory<Tag>> entityHistories = new ArrayList<EntityHistory<Tag>>();
+ entityHistories.addAll(convertToHistory(tagList, recordIdMap, ChangeType.INSERT));
+
+ Long maxHistoryRecordId = tagSqlDao.getMaxHistoryRecordId();
+ tagSqlDao.addHistoryFromTransaction(objectId.toString(), objectType, entityHistories, context);
+
+ // have to fetch history record ids to update audit log
+ List<Mapper<Long, Long>> historyRecordIds = tagSqlDao.getHistoryRecordIds(maxHistoryRecordId);
+ Map<Long, Long> historyRecordIdMap = convertToAuditMap(historyRecordIds);
+ List<EntityAudit> entityAudits = convertToAudits(entityHistories, historyRecordIdMap);
+ tagSqlDao.insertAuditFromTransaction(entityAudits, context);
return null;
}
@@ -121,7 +80,7 @@ public class AuditedTagDao implements TagDao {
}
@Override
- public void removeTag(final String tagName, final UUID objectId, final String objectType, final CallContext context) {
+ public void removeTag(final String tagName, final UUID objectId, final ObjectType objectType, final CallContext context) {
tagSqlDao.inTransaction(new Transaction<Void, TagSqlDao>() {
@Override
public Void inTransaction(final TagSqlDao tagSqlDao, final TransactionStatus status) throws Exception {
@@ -131,13 +90,43 @@ public class AuditedTagDao implements TagDao {
throw new InvoiceApiException(ErrorCode.TAG_DOES_NOT_EXIST, tagName);
}
- tagSqlDao.removeTagFromTransaction(tagName, objectId.toString(), objectType, context);
+ List<Tag> tagList = new ArrayList<Tag>();
+ tagList.add(tag);
+
+ List<Mapper<UUID, Long>> recordIds = tagSqlDao.getRecordIds(objectId.toString(), objectType);
+ Map<UUID, Long> recordIdMap = convertToHistoryMap(recordIds);
+
+ tagSqlDao.deleteFromTransaction(objectId.toString(), objectType, tagList, context);
+
+ List<EntityHistory<Tag>> entityHistories = new ArrayList<EntityHistory<Tag>>();
+ entityHistories.addAll(convertToHistory(tagList, recordIdMap, ChangeType.DELETE));
+
+ Long maxHistoryRecordId = tagSqlDao.getMaxHistoryRecordId();
+ tagSqlDao.addHistoryFromTransaction(objectId.toString(), objectType, entityHistories, context);
- TagAuditSqlDao auditDao = tagSqlDao.become(TagAuditSqlDao.class);
- auditDao.removeTagFromTransaction(tag.getId().toString(), context);
+ // have to fetch history record ids to update audit log
+ List<Mapper<Long, Long>> historyRecordIds = tagSqlDao.getHistoryRecordIds(maxHistoryRecordId);
+ Map<Long, Long> historyRecordIdMap = convertToAuditMap(historyRecordIds);
+ List<EntityAudit> entityAudits = convertToAudits(entityHistories, historyRecordIdMap);
+ tagSqlDao.insertAuditFromTransaction(entityAudits, context);
return null;
}
});
}
+
+ @Override
+ protected TableName getTableName() {
+ return TableName.TAG_HISTORY;
+ }
+
+ @Override
+ protected UpdatableEntityCollectionSqlDao<Tag> transmogrifyDao(Transmogrifier transactionalDao) {
+ return transactionalDao.become(TagSqlDao.class);
+ }
+
+ @Override
+ protected UpdatableEntityCollectionSqlDao<Tag> getSqlDao() {
+ return tagSqlDao;
+ }
}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java
index 884dacd..1b6ce2a 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java
@@ -44,7 +44,7 @@ public class DefaultTagDefinitionDao implements TagDefinitionDao {
// add control tag definitions
for (ControlTagType controlTag : ControlTagType.values()) {
- definitionList.add(new DefaultTagDefinition(controlTag.toString(), controlTag.getDescription()));
+ definitionList.add(new DefaultTagDefinition(controlTag.toString(), controlTag.getDescription(), true));
}
return definitionList;
@@ -68,7 +68,7 @@ public class DefaultTagDefinitionDao implements TagDefinitionDao {
throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_ALREADY_EXISTS, definitionName);
}
- TagDefinition definition = new DefaultTagDefinition(definitionName, description);
+ TagDefinition definition = new DefaultTagDefinition(definitionName, description, false);
dao.create(definition, context);
return definition;
}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagBinder.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagBinder.java
index 2416b21..d2d4a6f 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagBinder.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagBinder.java
@@ -22,8 +22,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import com.google.inject.Inject;
-import com.ning.billing.util.clock.Clock;
import org.skife.jdbi.v2.SQLStatement;
import org.skife.jdbi.v2.sqlobject.Binder;
import org.skife.jdbi.v2.sqlobject.BinderFactory;
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagDao.java
index 11594ac..a680862 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagDao.java
@@ -17,6 +17,8 @@
package com.ning.billing.util.tag.dao;
import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.dao.AuditedCollectionDao;
+import com.ning.billing.util.dao.ObjectType;
import com.ning.billing.util.tag.ControlTagType;
import com.ning.billing.util.tag.Tag;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
@@ -24,16 +26,8 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import java.util.List;
import java.util.UUID;
-public interface TagDao {
- void saveTagsFromTransaction(Transmogrifier dao, UUID objectId, String objectType, List<Tag> tags, CallContext context);
+public interface TagDao extends AuditedCollectionDao<Tag> {
+ void addTag(String tagName, UUID objectId, ObjectType objectType, CallContext context);
- void saveTags(UUID objectId, String objectType, List<Tag> tags, CallContext context);
-
- List<Tag> loadTags(UUID objectId, String objectType);
-
- List<Tag> loadTagsFromTransaction(Transmogrifier dao, UUID objectId, String objectType);
-
- void addTag(String tagName, UUID objectId, String objectType, CallContext context);
-
- void removeTag(String tagName, UUID objectId, String objectType, CallContext context);
+ void removeTag(String tagName, UUID objectId, ObjectType objectType, CallContext context);
}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.java
index 1308ae1..efe7587 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.java
@@ -27,10 +27,9 @@ import java.util.UUID;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.entity.EntityDao;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
import com.ning.billing.util.tag.DefaultTagDefinition;
import com.ning.billing.util.tag.TagDefinition;
-import org.joda.time.DateTime;
import org.skife.jdbi.v2.SQLStatement;
import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.sqlobject.Bind;
@@ -45,7 +44,7 @@ import org.skife.jdbi.v2.tweak.ResultSetMapper;
@ExternalizedSqlViaStringTemplate3
@RegisterMapper(TagDefinitionSqlDao.TagDefinitionMapper.class)
-public interface TagDefinitionSqlDao extends EntityDao<TagDefinition> {
+public interface TagDefinitionSqlDao extends EntitySqlDao<TagDefinition> {
@Override
@SqlUpdate
public void create(@TagDefinitionBinder final TagDefinition entity, @CallContextBinder final CallContext context);
@@ -68,9 +67,7 @@ public interface TagDefinitionSqlDao extends EntityDao<TagDefinition> {
UUID id = UUID.fromString(result.getString("id"));
String name = result.getString("name");
String description = result.getString("description");
- String createdBy = result.getString("created_by");
- DateTime createdDate = new DateTime(result.getTimestamp("created_date"));
- return new DefaultTagDefinition(id, createdBy, createdDate, name, description);
+ return new DefaultTagDefinition(id, name, description);
}
}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagHistoryBinder.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagHistoryBinder.java
new file mode 100644
index 0000000..67bb3ca
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagHistoryBinder.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.tag.dao;
+
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.MappedEntity;
+import com.ning.billing.util.tag.Tag;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@BindingAnnotation(TagHistoryBinder.TagHistoryBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface TagHistoryBinder {
+ public static class TagHistoryBinderFactory implements BinderFactory {
+ @Override
+ public Binder build(Annotation annotation) {
+ return new Binder<TagHistoryBinder, EntityHistory<Tag>>() {
+ @Override
+ public void bind(SQLStatement q, TagHistoryBinder bind, EntityHistory<Tag> tagHistory) {
+ q.bind("recordId", tagHistory.getValue());
+ q.bind("changeType", tagHistory.getChangeType().toString());
+ q.bind("id", tagHistory.getId().toString());
+ q.bind("tagDefinitionName", tagHistory.getEntity().getTagDefinitionName());
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagMapper.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagMapper.java
index 211ae5a..986e49d 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagMapper.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagMapper.java
@@ -44,10 +44,8 @@ public class TagMapper extends MapperBase implements ResultSetMapper<Tag> {
if (thisTagType == null) {
UUID id = UUID.fromString(result.getString("id"));
- String createdBy = result.getString("created_by");
- DateTime createdDate = new DateTime(result.getTimestamp("created_date"));
- return new DescriptiveTag(id, createdBy, createdDate, name);
+ return new DescriptiveTag(id, name);
} else {
return new DefaultControlTag(thisTagType);
}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java
index a20e757..ee62b60 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java
@@ -18,10 +18,16 @@ package com.ning.billing.util.tag.dao;
import java.util.List;
-import com.ning.billing.util.ChangeType;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.dao.ChangeTypeBinder;
+import com.ning.billing.util.dao.AuditBinder;
+import com.ning.billing.util.dao.EntityAudit;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.ObjectTypeBinder;
+import com.ning.billing.util.dao.TableName;
+import com.ning.billing.util.dao.TableNameBinder;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlBatch;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
@@ -30,49 +36,54 @@ import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
-import com.ning.billing.util.entity.EntityCollectionDao;
import com.ning.billing.util.tag.Tag;
@ExternalizedSqlViaStringTemplate3
@RegisterMapper(TagMapper.class)
-public interface TagSqlDao extends EntityCollectionDao<Tag>, Transactional<TagSqlDao>, Transmogrifier {
+public interface TagSqlDao extends UpdatableEntityCollectionSqlDao<Tag>, Transactional<TagSqlDao>, Transmogrifier {
@Override
@SqlBatch(transactional=false)
- public void batchInsertFromTransaction(@Bind("objectId") final String objectId,
- @Bind("objectType") final String objectType,
- @TagBinder final List<Tag> entities,
- @CallContextBinder final CallContext context);
+ public void insertFromTransaction(@Bind("objectId") final String objectId,
+ @ObjectTypeBinder final ObjectType objectType,
+ @TagBinder final List<Tag> tags,
+ @CallContextBinder final CallContext context);
+
+ @Override
+ @SqlBatch(transactional=false)
+ public void updateFromTransaction(@Bind("objectId") final String objectId,
+ @ObjectTypeBinder final ObjectType objectType,
+ @TagBinder final List<Tag> tags,
+ @CallContextBinder final CallContext context);
@Override
@SqlBatch(transactional=false)
- public void batchDeleteFromTransaction(@Bind("objectId") final String objectId,
- @Bind("objectType") final String objectType,
- @TagBinder final List<Tag> entities,
- @CallContextBinder final CallContext context);
+ public void deleteFromTransaction(@Bind("objectId") final String objectId,
+ @ObjectTypeBinder final ObjectType objectType,
+ @TagBinder final List<Tag> tags,
+ @CallContextBinder final CallContext context);
- @SqlBatch(transactional = false)
- public void batchInsertHistoryFromTransaction(@Bind("objectId") final String objectId,
- @Bind("objectType") final String objectType,
- @Bind("historyRecordId") final List<String> historyRecordIdList,
- @TagBinder final List<Tag> tags,
- @ChangeTypeBinder final ChangeType changeType,
- @CallContextBinder final CallContext context);
+ @Override
+ @SqlBatch(transactional=false)
+ public void addHistoryFromTransaction(@Bind("objectId") final String objectId,
+ @ObjectTypeBinder final ObjectType objectType,
+ @TagHistoryBinder final List<EntityHistory<Tag>> histories,
+ @CallContextBinder final CallContext context);
@SqlUpdate
public void addTagFromTransaction(@Bind("id") final String tagId,
@Bind("tagDefinitionName") final String tagName,
@Bind("objectId") final String objectId,
- @Bind("objectType") final String objectType,
+ @ObjectTypeBinder final ObjectType objectType,
@CallContextBinder final CallContext context);
@SqlUpdate
public void removeTagFromTransaction(@Bind("tagDefinitionName") final String tagName,
@Bind("objectId") final String objectId,
- @Bind("objectType") final String objectType,
+ @ObjectTypeBinder final ObjectType objectType,
@CallContextBinder final CallContext context);
@SqlQuery
public Tag findTag(@Bind("tagDefinitionName") final String tagName,
@Bind("objectId") final String objectId,
- @Bind("objectType") final String objectType);
+ @ObjectTypeBinder final ObjectType objectType);
}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/tag/DefaultControlTag.java b/util/src/main/java/com/ning/billing/util/tag/DefaultControlTag.java
index 662de60..f41fe12 100644
--- a/util/src/main/java/com/ning/billing/util/tag/DefaultControlTag.java
+++ b/util/src/main/java/com/ning/billing/util/tag/DefaultControlTag.java
@@ -17,7 +17,6 @@
package com.ning.billing.util.tag;
import java.util.UUID;
-import org.joda.time.DateTime;
public class DefaultControlTag extends DescriptiveTag implements ControlTag {
private final ControlTagType controlTagType;
@@ -29,9 +28,8 @@ public class DefaultControlTag extends DescriptiveTag implements ControlTag {
}
// use to hydrate objects when loaded from the persistence layer
- public DefaultControlTag(final UUID id, final String createdBy,
- final DateTime createdDate, final ControlTagType controlTagType) {
- super(id, createdBy, createdDate, controlTagType.toString());
+ public DefaultControlTag(final UUID id, final ControlTagType controlTagType) {
+ super(id, controlTagType.toString());
this.controlTagType = controlTagType;
}
@@ -39,4 +37,21 @@ public class DefaultControlTag extends DescriptiveTag implements ControlTag {
public ControlTagType getControlTagType() {
return controlTagType;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ DefaultControlTag that = (DefaultControlTag) o;
+
+ if (controlTagType != that.controlTagType) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return controlTagType != null ? controlTagType.hashCode() : 0;
+ }
}
diff --git a/util/src/main/java/com/ning/billing/util/tag/DefaultTagDefinition.java b/util/src/main/java/com/ning/billing/util/tag/DefaultTagDefinition.java
index 0eb0ac9..c16a621 100644
--- a/util/src/main/java/com/ning/billing/util/tag/DefaultTagDefinition.java
+++ b/util/src/main/java/com/ning/billing/util/tag/DefaultTagDefinition.java
@@ -18,20 +18,21 @@ package com.ning.billing.util.tag;
import java.util.UUID;
import com.ning.billing.util.entity.EntityBase;
-import org.joda.time.DateTime;
public class DefaultTagDefinition extends EntityBase implements TagDefinition {
private String name;
private String description;
+ private Boolean isControlTag;
- public DefaultTagDefinition(String name, String description) {
+ public DefaultTagDefinition(String name, String description, Boolean isControlTag) {
super();
this.name = name;
this.description = description;
+ this.isControlTag = isControlTag;
}
- public DefaultTagDefinition(UUID id, String createdBy, DateTime createdDate, String name, String description) {
- super(id, createdBy, createdDate);
+ public DefaultTagDefinition(UUID id, String name, String description) {
+ super(id);
this.name = name;
this.description = description;
}
@@ -45,4 +46,9 @@ public class DefaultTagDefinition extends EntityBase implements TagDefinition {
public String getDescription() {
return description;
}
+
+ @Override
+ public Boolean isControlTag() {
+ return isControlTag;
+ }
}
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 eace017..a35b424 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
@@ -17,10 +17,12 @@
package com.ning.billing.util.tag;
import java.util.UUID;
-import com.ning.billing.util.entity.EntityCollectionBase;
+
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.entity.collection.EntityCollectionBase;
public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagStore {
- public DefaultTagStore(final UUID objectId, final String objectType) {
+ public DefaultTagStore(final UUID objectId, final ObjectType objectType) {
super(objectId, objectType);
}
@@ -32,7 +34,7 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
@Override
/***
* Collates the contents of the TagStore to determine if payments should be processed
- * @return true is no tags contraindicate payment processing
+ * @return true if no tags contraindicate payment processing
*/
public boolean processPayment() {
for (Tag tag : entities.values()) {
@@ -49,7 +51,7 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
/***
* Collates the contents of the TagStore to determine if invoices should be generated
- * @return true is no tags contraindicate invoice generation
+ * @return true if no tags contraindicate invoice generation
*/
@Override
public boolean generateInvoice() {
@@ -66,18 +68,30 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
}
@Override
- public void remove(final String tagName) {
- entities.remove(entities.get(tagName));
+ public boolean containsTagForDefinition(final TagDefinition tagDefinition) {
+ for (Tag tag : entities.values()) {
+ if (tag.getTagDefinitionName().equals(tagDefinition.getName())) {
+ return true;
+ }
+ }
+
+ return false;
}
@Override
- public boolean containsTag(final String tagName) {
+ public boolean containsTagForControlTagType(final ControlTagType controlTagType) {
for (Tag tag : entities.values()) {
- if (tag.getTagDefinitionName().equals(tagName)) {
+ if (tag.getTagDefinitionName().equals(controlTagType.toString())) {
return true;
}
}
return false;
}
+
+ @Override
+ public Tag remove(TagDefinition tagDefinition) {
+ Tag tag = entities.get(tagDefinition.getName());
+ return (tag == null) ? null : entities.remove(tag);
+ }
}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/tag/DescriptiveTag.java b/util/src/main/java/com/ning/billing/util/tag/DescriptiveTag.java
index 1ad70fe..2633643 100644
--- a/util/src/main/java/com/ning/billing/util/tag/DescriptiveTag.java
+++ b/util/src/main/java/com/ning/billing/util/tag/DescriptiveTag.java
@@ -18,21 +18,14 @@ package com.ning.billing.util.tag;
import java.util.UUID;
-import com.google.inject.Inject;
-import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.entity.EntityBase;
-import com.ning.billing.util.entity.UpdatableEntityBase;
-import org.joda.time.DateTime;
public class DescriptiveTag extends EntityBase implements Tag {
private final String tagDefinitionName;
- @Inject
- private Clock clock;
-
// use to hydrate objects from the persistence layer
- public DescriptiveTag(UUID id, String createdBy, DateTime createdDate, String tagDefinitionName) {
- super(id, createdBy, createdDate);
+ public DescriptiveTag(UUID id, String tagDefinitionName) {
+ super(id);
this.tagDefinitionName = tagDefinitionName;
}
@@ -52,4 +45,22 @@ public class DescriptiveTag extends EntityBase implements Tag {
public String getTagDefinitionName() {
return tagDefinitionName;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ DescriptiveTag that = (DescriptiveTag) o;
+
+ if (tagDefinitionName != null ? !tagDefinitionName.equals(that.tagDefinitionName) : that.tagDefinitionName != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return tagDefinitionName != null ? tagDefinitionName.hashCode() : 0;
+ }
}
diff --git a/util/src/main/java/com/ning/billing/util/template/translation/DefaultCatalogTranslator.java b/util/src/main/java/com/ning/billing/util/template/translation/DefaultCatalogTranslator.java
new file mode 100644
index 0000000..933b24f
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/template/translation/DefaultCatalogTranslator.java
@@ -0,0 +1,36 @@
+/*
+ * 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.template.translation;
+
+import com.google.inject.Inject;
+
+public class DefaultCatalogTranslator extends DefaultTranslatorBase {
+ @Inject
+ public DefaultCatalogTranslator(TranslatorConfig config) {
+ super(config);
+ }
+
+ @Override
+ protected String getBundlePath() {
+ return "com/ning/billing/util/template/translation/CatalogTranslation";
+ }
+
+ @Override
+ protected String getTranslationType() {
+ return "catalog";
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java b/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java
new file mode 100644
index 0000000..4d1a7b6
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/template/translation/DefaultTranslatorBase.java
@@ -0,0 +1,71 @@
+/*
+ * 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.template.translation;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+public abstract class DefaultTranslatorBase implements Translator {
+ protected final TranslatorConfig config;
+ protected final Logger log = LoggerFactory.getLogger(DefaultTranslatorBase.class);
+
+ @Inject
+ public DefaultTranslatorBase(TranslatorConfig config) {
+ this.config = config;
+ }
+
+ protected abstract String getBundlePath();
+
+ /*
+ * string used for exception handling
+ */
+ protected abstract String getTranslationType();
+
+ @Override
+ public String getTranslation(Locale locale, String originalText) {
+ ResourceBundle bundle = null;
+ try {
+ bundle = ResourceBundle.getBundle(getBundlePath(), locale);
+ } catch (MissingResourceException mrex) {
+ log.warn(String.format(ErrorCode.MISSING_TRANSLATION_RESOURCE.toString(), getTranslationType()));
+ }
+
+ if ((bundle != null) && (bundle.containsKey(originalText))) {
+ return bundle.getString(originalText);
+ } else {
+ try {
+ Locale defaultLocale = new Locale(config.getDefaultLocale());
+ bundle = ResourceBundle.getBundle(getBundlePath(), defaultLocale);
+
+ if ((bundle != null) && (bundle.containsKey(originalText))) {
+ return bundle.getString(originalText);
+ } else {
+ return originalText;
+ }
+ } catch (MissingResourceException mrex) {
+ log.warn(String.format(ErrorCode.MISSING_TRANSLATION_RESOURCE.toString(), getTranslationType()));
+ return originalText;
+ }
+ }
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestBase.java b/util/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestBase.java
new file mode 100644
index 0000000..03c643d
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/userrequest/CompletionUserRequestBase.java
@@ -0,0 +1,156 @@
+/*
+ * 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.userrequest;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeoutException;
+
+import com.ning.billing.account.api.AccountChangeEvent;
+import com.ning.billing.account.api.AccountCreationEvent;
+import com.ning.billing.entitlement.api.user.SubscriptionEvent;
+import com.ning.billing.invoice.api.EmptyInvoiceEvent;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
+import com.ning.billing.util.bus.BusEvent;
+
+public abstract class CompletionUserRequestBase implements CompletionUserRequest {
+
+ private static final long NANO_TO_MILLI_SEC = (1000L * 1000L);
+
+ private final List<BusEvent> events;
+
+ private final UUID userToken;
+ private long timeoutMilliSec;
+
+ private boolean isCompleted;
+ private long initialTimeMilliSec;
+
+
+ public CompletionUserRequestBase(final UUID userToken) {
+ this.events = new LinkedList<BusEvent>();
+ this.userToken = userToken;
+ this.isCompleted = false;
+ }
+
+ @Override
+ public List<BusEvent> waitForCompletion(final long timeoutMilliSec) throws InterruptedException, TimeoutException {
+
+ this.timeoutMilliSec = timeoutMilliSec;
+ initialTimeMilliSec = currentTimeMillis();
+ synchronized(this) {
+ long remainingTimeMillisSec = getRemainingTimeMillis();
+ while (!isCompleted && remainingTimeMillisSec > 0) {
+ wait(remainingTimeMillisSec);
+ if (isCompleted) {
+ break;
+ }
+ remainingTimeMillisSec = getRemainingTimeMillis();
+ }
+ if (!isCompleted) {
+ throw new TimeoutException();
+ }
+ }
+ return events;
+ }
+
+ @Override
+ public void notifyForCompletion() {
+ synchronized(this) {
+ isCompleted = true;
+ notify();
+ }
+ }
+
+ private long currentTimeMillis() {
+ return System.nanoTime() / NANO_TO_MILLI_SEC;
+ }
+
+ private long getRemainingTimeMillis() {
+ return timeoutMilliSec - (currentTimeMillis() - initialTimeMilliSec);
+ }
+
+ @Override
+ public void onBusEvent(BusEvent curEvent) {
+ // Check if this is for us..
+ if (curEvent.getUserToken() == null ||
+ ! curEvent.getUserToken().equals(userToken)) {
+ return;
+ }
+
+ events.add(curEvent);
+
+ switch(curEvent.getBusEventType()) {
+ case ACCOUNT_CREATE:
+ onAccountCreation((AccountCreationEvent) curEvent);
+ break;
+ case ACCOUNT_CHANGE:
+ onAccountChange((AccountChangeEvent) curEvent);
+ break;
+ case SUBSCRIPTION_TRANSITION:
+ onSubscriptionTransition((SubscriptionEvent) curEvent);
+ break;
+ case INVOICE_EMPTY:
+ onEmptyInvoice((EmptyInvoiceEvent) curEvent);
+ break;
+ case INVOICE_CREATION:
+ onInvoiceCreation((InvoiceCreationEvent) curEvent);
+ break;
+ case PAYMENT_INFO:
+ onPaymentInfo((PaymentInfoEvent) curEvent);
+ break;
+ case PAYMENT_ERROR:
+ onPaymentError((PaymentErrorEvent) curEvent);
+ break;
+ default:
+ throw new RuntimeException("Unexpected event type " + curEvent.getBusEventType());
+ }
+ }
+
+ /*
+ *
+ * Default no-op implementation so as to not have to implement all callbacks
+ */
+ @Override
+ public void onAccountCreation(final AccountCreationEvent curEvent) {
+ }
+
+ @Override
+ public void onAccountChange(final AccountChangeEvent curEvent) {
+ }
+
+ @Override
+ public void onSubscriptionTransition(final SubscriptionEvent curEvent) {
+ }
+
+ @Override
+ public void onEmptyInvoice(final EmptyInvoiceEvent curEvent) {
+ }
+
+ @Override
+ public void onInvoiceCreation(final InvoiceCreationEvent curEvent) {
+ }
+
+ @Override
+ public void onPaymentInfo(final PaymentInfoEvent curEvent) {
+ }
+
+ @Override
+ public void onPaymentError(final PaymentErrorEvent curEvent) {
+ }
+}
diff --git a/util/src/main/resources/com/ning/billing/util/audit/dao/AuditSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/audit/dao/AuditSqlDao.sql.stg
index 25cc9d4..f949ba8 100644
--- a/util/src/main/resources/com/ning/billing/util/audit/dao/AuditSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/audit/dao/AuditSqlDao.sql.stg
@@ -7,10 +7,11 @@ fields(prefix) ::= <<
<prefix>change_date,
<prefix>changed_by,
<prefix>reason_code,
- <prefix>comments
+ <prefix>comments,
+ <prefix>user_token
>>
insertAuditFromTransaction() ::= <<
INSERT INTO audit_log(<fields()>)
- VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL);
+ VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, :userToken);
>>
\ No newline at end of file
diff --git a/util/src/main/resources/com/ning/billing/util/bus/dao/PersistentBusSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/bus/dao/PersistentBusSqlDao.sql.stg
new file mode 100644
index 0000000..0cbd4ec
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/bus/dao/PersistentBusSqlDao.sql.stg
@@ -0,0 +1,88 @@
+group PersistentBusSqlDao;
+
+getNextBusEventEntry() ::= <<
+ select
+ record_id
+ , class_name
+ , event_json
+ , created_date
+ , creating_owner
+ , processing_owner
+ , processing_available_date
+ , processing_state
+ from bus_events
+ where
+ processing_state != 'PROCESSED'
+ and processing_state != 'REMOVED'
+ and (processing_owner IS NULL OR processing_available_date \<= :now)
+ order by
+ record_id asc
+ limit :max
+ ;
+>>
+
+
+claimBusEvent() ::= <<
+ update bus_events
+ set
+ processing_owner = :owner
+ , processing_available_date = :nextAvailable
+ , processing_state = 'IN_PROCESSING'
+ where
+ record_id = :recordId
+ and processing_state != 'PROCESSED'
+ and processing_state != 'REMOVED'
+ and (processing_owner IS NULL OR processing_available_date \<= :now)
+ ;
+>>
+
+clearBusEvent() ::= <<
+ update bus_events
+ set
+ processing_state = 'PROCESSED'
+ where
+ record_id = :recordId
+ ;
+>>
+
+removeBusEventsById() ::= <<
+ update bus_events
+ set
+ processing_state = 'REMOVED'
+ where
+ record_id = :recordId
+ ;
+>>
+
+
+insertBusEvent() ::= <<
+ insert into bus_events (
+ class_name
+ , event_json
+ , created_date
+ , creating_owner
+ , processing_owner
+ , processing_available_date
+ , processing_state
+ ) values (
+ :className
+ , :eventJson
+ , :createdDate
+ , :creatingOwner
+ , :processingOwner
+ , :processingAvailableDate
+ , :processingState
+ );
+>>
+
+insertClaimedHistory() ::= <<
+ insert into claimed_bus_events (
+ owner_id
+ , claimed_date
+ , bus_event_id
+ ) values (
+ :ownerId
+ , :claimedDate
+ , :busEventId
+ );
+>>
diff --git a/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
index 1fbf994..018e1ec 100644
--- a/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.sql.stg
@@ -1,17 +1,17 @@
group CustomFieldSqlDao;
-batchInsertFromTransaction() ::= <<
+insertFromTransaction() ::= <<
INSERT INTO custom_fields(id, object_id, object_type, field_name, field_value, created_by, created_date, updated_by, updated_date)
VALUES (:id, :objectId, :objectType, :fieldName, :fieldValue, :userName, :createdDate, :userName, :updatedDate);
>>
-batchUpdateFromTransaction() ::= <<
+updateFromTransaction() ::= <<
UPDATE custom_fields
SET field_value = :fieldValue, updated_by = :userName, updated_date = :updatedDate
WHERE object_id = :objectId AND object_type = :objectType AND field_name = :fieldName;
>>
-batchDeleteFromTransaction() ::= <<
+deleteFromTransaction() ::= <<
DELETE FROM custom_fields
WHERE object_id = :objectId AND object_type = :objectType AND field_name = :fieldName;
>>
@@ -22,6 +22,56 @@ load() ::= <<
WHERE object_id = :objectId AND object_type = :objectType;
>>
+getRecordIds() ::= <<
+ SELECT record_id, id
+ FROM custom_fields
+ WHERE object_id = :objectId AND object_type = :objectType;
+>>
+
+historyFields(prefix) ::= <<
+ <prefix>record_id,
+ <prefix>id,
+ <prefix>object_id,
+ <prefix>object_type,
+ <prefix>field_name,
+ <prefix>field_value,
+ <prefix>updated_by,
+ <prefix>date,
+ <prefix>change_type
+>>
+
+addHistoryFromTransaction() ::= <<
+ INSERT INTO custom_field_history(<historyFields()>)
+ VALUES(:recordId, :id, :objectId, :objectType, :fieldName, :fieldValue, :userName, :updatedDate, :changeType);
+>>
+
+getMaxHistoryRecordId() ::= <<
+ SELECT MAX(history_record_id)
+ FROM custom_field_history;
+>>
+
+getHistoryRecordIds() ::= <<
+ SELECT history_record_id, record_id
+ FROM custom_field_history
+ WHERE history_record_id > :maxHistoryRecordId;
+>>
+
+auditFields(prefix) ::= <<
+ <prefix>table_name,
+ <prefix>record_id,
+ <prefix>change_type,
+ <prefix>change_date,
+ <prefix>changed_by,
+ <prefix>reason_code,
+ <prefix>comments,
+ <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+ INSERT INTO audit_log(<auditFields()>)
+ VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
test() ::= <<
SELECT 1 FROM custom_fields;
>>
util/src/main/resources/com/ning/billing/util/ddl.sql 167(+106 -61)
diff --git a/util/src/main/resources/com/ning/billing/util/ddl.sql b/util/src/main/resources/com/ning/billing/util/ddl.sql
index 77b66c9..9ea7c1a 100644
--- a/util/src/main/resources/com/ning/billing/util/ddl.sql
+++ b/util/src/main/resources/com/ning/billing/util/ddl.sql
@@ -1,120 +1,165 @@
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),
- created_by varchar(50) NOT NULL,
- created_date datetime NOT NULL,
- updated_by varchar(50) DEFAULT NULL,
- updated_date datetime DEFAULT NULL,
- PRIMARY KEY(id)
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ 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),
+ created_by varchar(50) NOT NULL,
+ created_date datetime NOT NULL,
+ updated_by varchar(50) DEFAULT NULL,
+ updated_date datetime DEFAULT NULL,
+ PRIMARY KEY(record_id)
) ENGINE=innodb;
+CREATE UNIQUE INDEX custom_fields_id ON custom_fields(id);
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 custom_field_history;
CREATE TABLE custom_field_history (
- history_id char(36) NOT NULL,
- id char(36) NOT NULL,
- object_id char(36) NOT NULL,
- object_type varchar(30) NOT NULL,
- field_name varchar(30),
- field_value varchar(255),
- updated_by varchar(50) NOT NULL,
- date datetime NOT NULL,
- change_type char(6) NOT NULL
+ history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ record_id int(11) unsigned NOT NULL,
+ id char(36) NOT NULL,
+ object_id char(36) NOT NULL,
+ object_type varchar(30) NOT NULL,
+ field_name varchar(30),
+ field_value varchar(255),
+ updated_by varchar(50) NOT NULL,
+ date datetime NOT NULL,
+ change_type char(6) NOT NULL,
+ PRIMARY KEY(history_record_id)
) ENGINE=innodb;
+CREATE INDEX custom_field_history_record_id ON custom_field_history(record_id);
CREATE INDEX custom_field_history_object_id_object_type ON custom_fields(object_id, object_type);
DROP TABLE IF EXISTS tag_descriptions;
DROP TABLE IF EXISTS tag_definitions;
CREATE TABLE tag_definitions (
- id char(36) NOT NULL,
- name varchar(20) NOT NULL,
- description varchar(200) NOT NULL,
- created_by varchar(50) NOT NULL,
- created_date datetime NOT NULL,
- PRIMARY KEY(id)
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ id char(36) NOT NULL,
+ name varchar(20) NOT NULL,
+ description varchar(200) NOT NULL,
+ created_by varchar(50) NOT NULL,
+ created_date datetime NOT NULL,
+ updated_by varchar(50) NOT NULL,
+ updated_date datetime NOT NULL,
+ PRIMARY KEY(record_id)
) ENGINE=innodb;
+CREATE UNIQUE INDEX tag_definitions_id ON tag_definitions(id);
CREATE UNIQUE INDEX tag_definitions_name ON tag_definitions(name);
DROP TABLE IF EXISTS tag_definition_history;
CREATE TABLE tag_definition_history (
- id char(36) NOT NULL,
- name varchar(30) NOT NULL,
- created_by varchar(50),
- description varchar(200),
- change_type char(6) NOT NULL,
- updated_by varchar(50) NOT NULL,
- date datetime NOT NULL
+ history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ record_id int(11) unsigned NOT NULL,
+ id char(36) NOT NULL,
+ name varchar(30) NOT NULL,
+ created_by varchar(50),
+ description varchar(200),
+ change_type char(6) NOT NULL,
+ updated_by varchar(50) NOT NULL,
+ date datetime NOT NULL,
+ PRIMARY KEY(history_record_id)
) ENGINE=innodb;
CREATE INDEX tag_definition_history_id ON tag_definition_history(id);
+CREATE INDEX tag_definition_history_record_id ON tag_definition_history(record_id);
CREATE INDEX tag_definition_history_name ON tag_definition_history(name);
DROP TABLE IF EXISTS tags;
CREATE TABLE tags (
- id char(36) NOT NULL,
- tag_definition_name varchar(20) NOT NULL,
- object_id char(36) NOT NULL,
- object_type varchar(30) NOT NULL,
- created_by varchar(50) NOT NULL,
- created_date datetime NOT NULL,
- PRIMARY KEY(id)
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ id char(36) NOT NULL,
+ tag_definition_name varchar(20) NOT NULL,
+ object_id char(36) NOT NULL,
+ object_type varchar(30) NOT NULL,
+ created_by varchar(50) NOT NULL,
+ created_date datetime NOT NULL,
+ PRIMARY KEY(record_id)
) ENGINE = innodb;
+CREATE UNIQUE INDEX tags_id ON tags(id);
CREATE INDEX tags_by_object ON tags(object_id);
CREATE UNIQUE INDEX tags_unique ON tags(tag_definition_name, object_id);
DROP TABLE IF EXISTS tag_history;
CREATE TABLE tag_history (
- history_record_id char(36) NOT NULL,
- id char(36) NOT NULL,
- tag_definition_name varchar(20) NOT NULL,
- object_id char(36) NOT NULL,
- object_type varchar(30) NOT NULL,
- change_type char(6) NOT NULL,
- updated_by varchar(50) NOT NULL,
- date datetime NOT NULL
+ history_record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ record_id int(11) unsigned NOT NULL,
+ id char(36) NOT NULL,
+ object_id char(36) NOT NULL,
+ object_type varchar(30) NOT NULL,
+ tag_definition_name varchar(20) NOT NULL,
+ updated_by varchar(50) NOT NULL,
+ date datetime NOT NULL,
+ change_type char(6) NOT NULL,
+ PRIMARY KEY(history_record_id)
) ENGINE = innodb;
+CREATE INDEX tag_history_record_id ON tag_history(record_id);
CREATE INDEX tag_history_by_object ON tags(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,
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ id char(36) NOT NULL,
+ created_date datetime NOT NULL,
notification_key varchar(256) NOT NULL,
- effective_dt datetime NOT NULL,
+ creating_owner char(50) NOT NULL,
+ effective_date datetime NOT NULL,
queue_name char(64) NOT NULL,
processing_owner char(50) DEFAULT NULL,
- processing_available_dt datetime DEFAULT NULL,
+ processing_available_date datetime DEFAULT NULL,
processing_state varchar(14) DEFAULT 'AVAILABLE',
- PRIMARY KEY(id)
+ PRIMARY KEY(record_id)
) ENGINE=innodb;
-CREATE INDEX `idx_comp_where` ON notifications (`effective_dt`, `queue_name`, `processing_state`,`processing_owner`,`processing_available_dt`);
-CREATE INDEX `idx_update` ON notifications (`processing_state`,`processing_owner`,`processing_available_dt`);
-CREATE INDEX `idx_get_ready` ON notifications (`effective_dt`,`created_dt`,`id`);
+CREATE UNIQUE INDEX notifications_id ON notifications(id);
+CREATE INDEX `idx_comp_where` ON notifications (`effective_date`, `queue_name`, `processing_state`,`processing_owner`,`processing_available_date`);
+CREATE INDEX `idx_update` ON notifications (`processing_state`,`processing_owner`,`processing_available_date`);
+CREATE INDEX `idx_get_ready` ON notifications (`effective_date`,`created_date`,`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,
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
owner_id varchar(64) NOT NULL,
- claimed_dt datetime NOT NULL,
+ claimed_date datetime NOT NULL,
notification_id char(36) NOT NULL,
- PRIMARY KEY(id)
+ PRIMARY KEY(record_id)
) ENGINE=innodb;
DROP TABLE IF EXISTS audit_log;
CREATE TABLE audit_log (
id int(11) unsigned NOT NULL AUTO_INCREMENT,
table_name varchar(50) NOT NULL,
- record_id char(36) NOT NULL,
+ record_id int(11) NOT NULL,
change_type char(6) NOT NULL,
change_date datetime NOT NULL,
changed_by varchar(50) NOT NULL,
reason_code varchar(20) DEFAULT NULL,
comments varchar(255) DEFAULT NULL,
+ user_token char(36),
PRIMARY KEY(id)
) ENGINE=innodb;
+CREATE INDEX audit_log_fetch_record ON audit_log(table_name, record_id);
+CREATE INDEX audit_log_user_name ON audit_log(changed_by);
+
+DROP TABLE IF EXISTS bus_events;
+CREATE TABLE bus_events (
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ class_name varchar(128) NOT NULL,
+ event_json varchar(2048) NOT NULL,
+ created_date datetime NOT NULL,
+ creating_owner char(50) NOT NULL,
+ processing_owner char(50) DEFAULT NULL,
+ processing_available_date datetime DEFAULT NULL,
+ processing_state varchar(14) DEFAULT 'AVAILABLE',
+ PRIMARY KEY(record_id)
+) ENGINE=innodb;
+CREATE INDEX `idx_bus_where` ON bus_events (`processing_state`,`processing_owner`,`processing_available_date`);
+
+DROP TABLE IF EXISTS claimed_bus_events;
+CREATE TABLE claimed_bus_events (
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ owner_id varchar(64) NOT NULL,
+ claimed_date datetime NOT NULL,
+ bus_event_id char(36) NOT NULL,
+ PRIMARY KEY(record_id)
+) ENGINE=innodb;
\ No newline at end of file
diff --git a/util/src/main/resources/com/ning/billing/util/email/templates/HtmlInvoiceTemplate.mustache b/util/src/main/resources/com/ning/billing/util/email/templates/HtmlInvoiceTemplate.mustache
new file mode 100644
index 0000000..73c70b3
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/email/templates/HtmlInvoiceTemplate.mustache
@@ -0,0 +1,96 @@
+<html>
+ <head>
+ <style type="text/css">
+ th {align=left; width=225px; border-bottom: solid 2px black;}
+ </style>
+ </head>
+ <body>
+ <h1>{{text.invoiceTitle}}</h1>
+ <table>
+ <tr>
+ <td rowspan=3 width=350px>Insert image here</td>
+ <td width=100px/>
+ <td width=225px/>
+ <td width=225px/>
+ </tr>
+ <tr>
+ <td />
+ <td align=right>{{text.invoiceDate}}</td>
+ <td>{{invoice.formattedInvoiceDate}}</td>
+ </tr>
+ <tr>
+ <td />
+ <td align=right>{{text.invoiceNumber}}</td>
+ <td>{{invoice.invoiceNumber}}</td>
+ </tr>
+ <tr>
+ <td>{{text.companyName}}</td>
+ <td></td>
+ <td align=right>{{text.accountOwnerName}}</td>
+ <td>{{account.name}}</td>
+ </tr>
+ <tr>
+ <td>{{text.companyAddress}}</td>
+ <td />
+ <td />
+ <td>{{account.email}}</td>
+ </tr>
+ <tr>
+ <td>{{text.companyCityProvincePostalCode}}</td>
+ <td />
+ <td />
+ <td>{{account.phone}}</td>
+ </tr>
+ <tr>
+ <td>{{text.companyCountry}}</td>
+ <td />
+ <td />
+ <td />
+ </tr>
+ <tr>
+ <td><{{text.companyUrl}}</td>
+ <td />
+ <td />
+ <td />
+ </tr>
+ </table>
+ <br />
+ <br />
+ <br />
+ <table>
+ <tr>
+ <th>{{text.invoiceItemBundleName}}</td>
+ <th>{{text.invoiceItemDescription}}</td>
+ <th>{{text.invoiceItemServicePeriod}}</td>
+ <th>{{text.invoiceItemAmount}}</td>
+ </tr>
+ {{#invoice.invoiceItems}}
+ <tr>
+ <td>{{description}}</td>
+ <td>{{planName}}</td>
+ <td>{{formattedStartDate}} - {{formattedEndDate}}</td>
+ <td>{{invoice.currency}} {{amount}}</td>
+ </tr>
+ {{/invoice.invoiceItems}}
+ <tr>
+ <td colspan=4 />
+ </tr>
+ <tr>
+ <td colspan=2 />
+ <td align=right><strong>{{text.invoiceAmount}}</strong></td>
+ <td align=right><strong>{{invoice.totalAmount}}</strong></td>
+ </tr>
+ <tr>
+ <td colspan=2 />
+ <td align=right><strong>{{text.invoiceAmountPaid}}</strong></td>
+ <td align=right><strong>{{invoice.amountPaid}}</strong></td>
+ </tr>
+ <tr>
+ <td colspan=2 />
+ <td align=right><strong>{{text.invoiceBalance}}</strong></td>
+ <td align=right><strong>{{invoice.balance}}</strong></td>
+ </tr>
+ </table>
+ </body>
+</html>
+
diff --git a/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
index 7a7ecab..0731adc 100644
--- a/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
@@ -1,45 +1,47 @@
group NotificationSqlDao;
-getReadyNotifications(now, max) ::= <<
+getReadyNotifications() ::= <<
select
- id
- , notification_id
+ record_id
+ , id
, notification_key
- , created_dt
- , effective_dt
+ , created_date
+ , creating_owner
+ , effective_date
, queue_name
, processing_owner
- , processing_available_dt
+ , processing_available_date
, processing_state
from notifications
where
- effective_dt \<= :now
- and queue_name = :queue_name
+ effective_date \<= :now
+ and queue_name = :queueName
and processing_state != 'PROCESSED'
- and (processing_owner IS NULL OR processing_available_dt \<= :now)
+ and processing_state != 'REMOVED'
+ and (processing_owner IS NULL OR processing_available_date \<= :now)
order by
- effective_dt asc
- , created_dt asc
- , id
+ effective_date asc
+ , created_date asc
+ , record_id
limit :max
;
>>
-
-claimNotification(owner, next_available, id, now) ::= <<
+claimNotification() ::= <<
update notifications
set
processing_owner = :owner
- , processing_available_dt = :next_available
+ , processing_available_date = :nextAvailable
, processing_state = 'IN_PROCESSING'
where
id = :id
and processing_state != 'PROCESSED'
- and (processing_owner IS NULL OR processing_available_dt \<= :now)
+ and processing_state != 'REMOVED'
+ and (processing_owner IS NULL OR processing_available_date \<= :now)
;
>>
-clearNotification(id, owner) ::= <<
+clearNotification() ::= <<
update notifications
set
processing_state = 'PROCESSED'
@@ -48,39 +50,47 @@ clearNotification(id, owner) ::= <<
;
>>
+removeNotificationsByKey() ::= <<
+ update notifications
+ set
+ processing_state = 'REMOVED'
+ where
+ notification_key = :notificationKey
+ ;
+>>
+
insertNotification() ::= <<
insert into notifications (
- notification_id
- , notification_key
- , created_dt
- , effective_dt
+ id
+ , notification_key
+ , created_date
+ , creating_owner
+ , effective_date
, queue_name
, processing_owner
- , processing_available_dt
+ , processing_available_date
, processing_state
) values (
- :notification_id
- , :notification_key
- , :created_dt
- , :effective_dt
- , :queue_name
- , :processing_owner
- , :processing_available_dt
- , :processing_state
+ :id
+ , :notificationKey
+ , :createdDate
+ , :creatingOwner
+ , :effectiveDate
+ , :queueName
+ , :processingOwner
+ , :processingAvailableDate
+ , :processingState
);
>>
-
-insertClaimedHistory(sequence_id, owner, hostname, claimed_dt, notification_id) ::= <<
+insertClaimedHistory() ::= <<
insert into claimed_notifications (
- sequence_id
- , owner_id
- , claimed_dt
+ owner_id
+ , claimed_date
, notification_id
) values (
- :sequence_id
- , :owner
- , :claimed_dt
- , :notification_id
+ :ownerId
+ , :claimedDate
+ , :notificationId
);
>>
diff --git a/util/src/main/resources/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg
index 54bdfa9..2a03325 100644
--- a/util/src/main/resources/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg
@@ -5,7 +5,9 @@ fields(prefix) ::= <<
<prefix>name,
<prefix>description,
<prefix>created_by,
- <prefix>created_date
+ <prefix>created_date ,
+ <prefix>updated_by,
+ <prefix>updated_date
>>
get() ::= <<
@@ -15,7 +17,7 @@ get() ::= <<
create() ::= <<
INSERT INTO tag_definitions(<fields()>)
- VALUES(:id, :name, :description, :userName, :createdDate);
+ VALUES(:id, :name, :description, :userName, :createdDate, :userName, :updatedDate);
>>
load() ::= <<
diff --git a/util/src/main/resources/com/ning/billing/util/tag/dao/TagSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/tag/dao/TagSqlDao.sql.stg
index 8f98452..f0a5805 100644
--- a/util/src/main/resources/com/ning/billing/util/tag/dao/TagSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/tag/dao/TagSqlDao.sql.stg
@@ -9,17 +9,12 @@ fields(prefix) ::= <<
<prefix>created_date
>>
-batchInsertFromTransaction() ::= <<
+insertFromTransaction() ::= <<
INSERT INTO tags(<fields()>)
VALUES (:id, :tagDefinitionName, :objectId, :objectType, :userName, :createdDate);
>>
-batchInsertHistoryFromTransaction() ::= <<
- INSERT INTO tag_history (history_record_id, id, tag_definition_name, object_id, object_type, change_type, updated_by, date)
- VALUES (:historyRecordId, :id, :tagDefinitionName, :objectId, :objectType, :changeType, :userName, :updatedDate);
->>
-
-batchDeleteFromTransaction() ::= <<
+deleteFromTransaction() ::= <<
DELETE FROM tags
WHERE tag_definition_name = :tagDefinitionName
AND object_id = :objectId AND object_type = :objectType;
@@ -53,6 +48,55 @@ load() ::= <<
WHERE t.object_id = :objectId AND t.object_type = :objectType;
>>
+getRecordIds() ::= <<
+ SELECT record_id, id
+ FROM tags
+ WHERE object_id = :objectId AND object_type = :objectType;
+>>
+
+historyFields(prefix) ::= <<
+ <prefix>record_id,
+ <prefix>id,
+ <prefix>object_id,
+ <prefix>object_type,
+ <prefix>tag_definition_name,
+ <prefix>updated_by,
+ <prefix>date,
+ <prefix>change_type
+>>
+
+addHistoryFromTransaction() ::= <<
+ INSERT INTO tag_history(<historyFields()>)
+ VALUES(:recordId, :id, :objectId, :objectType, :tagDefinitionName, :userName, :updatedDate, :changeType);
+>>
+
+getMaxHistoryRecordId() ::= <<
+ SELECT MAX(history_record_id)
+ FROM tag_history;
+>>
+
+getHistoryRecordIds() ::= <<
+ SELECT history_record_id, record_id
+ FROM tag_history
+ WHERE history_record_id > :maxHistoryRecordId;
+>>
+
+auditFields(prefix) ::= <<
+ <prefix>table_name,
+ <prefix>record_id,
+ <prefix>change_type,
+ <prefix>change_date,
+ <prefix>changed_by,
+ <prefix>reason_code,
+ <prefix>comments,
+ <prefix>user_token
+>>
+
+insertAuditFromTransaction() ::= <<
+ INSERT INTO audit_log(<auditFields()>)
+ VALUES(:tableName, :recordId, :changeType, :createdDate, :userName, NULL, NULL, NULL);
+>>
+
test() ::= <<
SELECT 1 FROM tags;
>>
diff --git a/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_EN_US.properties b/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_EN_US.properties
new file mode 100644
index 0000000..b05a595
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_EN_US.properties
@@ -0,0 +1,2 @@
+ning-pro = Pro
+ning-plus = Plus
\ No newline at end of file
diff --git a/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_FR_CA.properties b/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_FR_CA.properties
new file mode 100644
index 0000000..e025a88
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/template/translation/CatalogTranslation_FR_CA.properties
@@ -0,0 +1 @@
+ning-plus = Plus en francais
\ No newline at end of file
diff --git a/util/src/main/resources/com/ning/billing/util/template/translation/InvoiceTranslation_EN_US.properties b/util/src/main/resources/com/ning/billing/util/template/translation/InvoiceTranslation_EN_US.properties
new file mode 100644
index 0000000..0162671
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/template/translation/InvoiceTranslation_EN_US.properties
@@ -0,0 +1,20 @@
+invoiceTitle=INVOICE
+invoiceDate=Date:
+invoiceNumber=Invoice #
+invoiceAmount=New Charges
+invoiceAmountPaid=Payment
+invoiceBalance=Balance
+
+accountOwnerName=Network Creator
+
+companyName=Ning, Inc.
+companyAddress=P.O. Box 1622
+companyCityProvincePostalCode=Palo Alto, CA 94302
+companyCountry=USA
+companyUrl=http://www.ning.com
+
+invoiceItemBundleName=NetworkName
+invoiceItemDescription=Description
+invoiceItemServicePeriod=Service Period
+invoiceItemAmount=Amount
+
diff --git a/util/src/test/java/com/ning/billing/api/TestListenerStatus.java b/util/src/test/java/com/ning/billing/api/TestListenerStatus.java
new file mode 100644
index 0000000..23b8174
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/api/TestListenerStatus.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.api;
+
+public interface TestListenerStatus {
+
+ public void failed(String msg);
+
+ public void resetTestListenerStatus();
+}
diff --git a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
index 866289e..c68c759 100644
--- a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
+++ b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
@@ -20,6 +20,7 @@ import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
@@ -27,6 +28,7 @@ import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.tweak.HandleCallback;
+import org.skife.jdbi.v2.util.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
@@ -44,10 +46,12 @@ public class MysqlTestingHelper
private static final Logger log = LoggerFactory.getLogger(MysqlTestingHelper.class);
- private static final String DB_NAME = "test_killbill";
+ private static final String DB_NAME = "killbill";
private static final String USERNAME = "root";
private static final String PASSWORD = "root";
+ // Discover dynamically list of all tables in that database;
+ private List<String> allTables;
private File dbDir;
private MysqldResource mysqldResource;
private int port;
@@ -122,13 +126,46 @@ public class MysqlTestingHelper
}
});
}
+
+ public void cleanupAllTables() {
+ final List<String> tablesToCleanup = fetchAllTables();
+ for (String tableName : tablesToCleanup) {
+ cleanupTable(tableName);
+ }
+ }
+
+ public synchronized List<String> fetchAllTables() {
+
+ if (allTables == null) {
+ final String dbiString = "jdbc:mysql://localhost:" + port + "/information_schema";
+ IDBI cleanupDbi = new DBI(dbiString, USERNAME, PASSWORD);
+
+ final List<String> tables= cleanupDbi.withHandle(new HandleCallback<List<String>>() {
+
+ @Override
+ public List<String> withHandle(Handle h) throws Exception {
+ return h.createQuery("select table_name from tables where table_schema = :table_schema and table_type = 'BASE TABLE';")
+ .bind("table_schema", DB_NAME)
+ .map(new StringMapper())
+ .list();
+ }
+ });
+ allTables = tables;
+ }
+ return allTables;
+ }
+
public void stopMysql()
{
- if (mysqldResource != null) {
- mysqldResource.shutdown();
- FileUtils.deleteQuietly(dbDir);
- log.info("MySQLd stopped");
+ try {
+ if (mysqldResource != null) {
+ mysqldResource.shutdown();
+ FileUtils.deleteQuietly(dbDir);
+ log.info("MySQLd stopped");
+ }
+ } catch (Exception ex) {
+ //fail silently
}
}
diff --git a/util/src/test/java/com/ning/billing/mock/BrainDeadProxyFactory.java b/util/src/test/java/com/ning/billing/mock/BrainDeadProxyFactory.java
index 7404331..7a3d28f 100644
--- a/util/src/test/java/com/ning/billing/mock/BrainDeadProxyFactory.java
+++ b/util/src/test/java/com/ning/billing/mock/BrainDeadProxyFactory.java
@@ -19,12 +19,15 @@ package com.ning.billing.mock;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import bsh.This;
+
public class BrainDeadProxyFactory {
private static final Logger log = LoggerFactory.getLogger(BrainDeadProxyFactory.class);
@@ -39,9 +42,12 @@ public class BrainDeadProxyFactory {
}
@SuppressWarnings("unchecked")
- public static <T> T createBrainDeadProxyFor(final Class<T> clazz) {
+ public static <T> T createBrainDeadProxyFor(final Class<T> clazz, final Class<?> ... others) {
+ Class<?>[] clazzes = Arrays.copyOf(others, others.length + 2);
+ clazzes[others.length] = ZombieControl.class;
+ clazzes[others.length + 1] = clazz;
return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
- new Class[] { clazz , ZombieControl.class},
+ clazzes,
new InvocationHandler() {
private final Map<String,Object> results = new HashMap<String,Object>();
@@ -68,6 +74,8 @@ public class BrainDeadProxyFactory {
throw ((Throwable) result);
}
return result;
+ } else if (method.getName().equals("equals")){
+ return proxy == args[0];
} else {
log.error(String.format("No result for Method: '%s' on Class '%s'",method.getName(), method.getDeclaringClass().getName()));
throw new UnsupportedOperationException();
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockClockModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockClockModule.java
new file mode 100644
index 0000000..82605ae
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockClockModule.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.mock.glue;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+
+public class MockClockModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockDbHelperModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockDbHelperModule.java
new file mode 100644
index 0000000..232c4e8
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockDbHelperModule.java
@@ -0,0 +1,50 @@
+/*
+ * 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.mock.glue;
+
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.dbi.DBIProvider;
+import com.ning.billing.dbi.DbiConfig;
+import com.ning.billing.dbi.MysqlTestingHelper;
+
+public class MockDbHelperModule extends AbstractModule {
+
+
+ @Override
+ protected void configure() {
+ installMysqlTestingHelper();
+ }
+
+ public void installMysqlTestingHelper() {
+
+ final MysqlTestingHelper helper = new MysqlTestingHelper();
+ bind(MysqlTestingHelper.class).toInstance(helper);
+ if (helper.isUsingLocalInstance()) {
+ bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+ final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
+ bind(DbiConfig.class).toInstance(config);
+ } else {
+ final IDBI dbi = helper.getDBI();
+ bind(IDBI.class).toInstance(dbi);
+ }
+
+ }
+
+}
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.java
new file mode 100644
index 0000000..044618e
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockEntitlementModule.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.mock.glue;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.entitlement.api.EntitlementService;
+import com.ning.billing.entitlement.api.billing.ChargeThruApi;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
+import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.glue.EntitlementModule;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.util.glue.RealImplementation;
+
+public class MockEntitlementModule extends AbstractModule implements EntitlementModule {
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.mock.glue.EntitlementModule#installEntitlementService()
+ */
+ @Override
+ public void installEntitlementService() {
+ bind(EntitlementService.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementService.class));
+ }
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.mock.glue.EntitlementModule#installEntitlementUserApi()
+ */
+ @Override
+ public void installEntitlementUserApi() {
+ bind(EntitlementUserApi.class).annotatedWith(RealImplementation.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class));
+ }
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.mock.glue.EntitlementModule#installEntitlementMigrationApi()
+ */
+ @Override
+ public void installEntitlementMigrationApi() {
+ bind(EntitlementMigrationApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementMigrationApi.class));
+ }
+
+ /* (non-Javadoc)
+ * @see com.ning.billing.mock.glue.EntitlementModule#installChargeThruApi()
+ */
+ @Override
+ public void installChargeThruApi() {
+ bind(ChargeThruApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(ChargeThruApi.class));
+ }
+
+ @Override
+ protected void configure() {
+ installEntitlementService();
+ installEntitlementUserApi();
+ installEntitlementMigrationApi();
+ installChargeThruApi();
+ installEntitlementTimelineApi();
+ }
+
+ @Override
+ public void installEntitlementTimelineApi() {
+ bind(EntitlementTimelineApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementTimelineApi.class));
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockInvoiceModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockInvoiceModule.java
new file mode 100644
index 0000000..f499e9a
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockInvoiceModule.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.mock.glue;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.glue.InvoiceModule;
+import com.ning.billing.invoice.api.InvoiceMigrationApi;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.invoice.api.test.InvoiceTestApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+
+public class MockInvoiceModule extends AbstractModule implements InvoiceModule {
+
+ @Override
+ public void installInvoiceUserApi() {
+ bind(InvoiceUserApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceUserApi.class));
+ }
+
+ @Override
+ public void installInvoicePaymentApi() {
+ bind(InvoicePaymentApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(InvoicePaymentApi.class));
+ }
+
+ @Override
+ public void installInvoiceMigrationApi() {
+ bind(InvoiceMigrationApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceMigrationApi.class));
+ }
+
+ @Override
+ public void installInvoiceTestApi() {
+ bind(InvoiceTestApi.class).toInstance(BrainDeadProxyFactory.createBrainDeadProxyFor(InvoiceTestApi.class));
+ }
+
+ @Override
+ protected void configure() {
+ installInvoiceUserApi();
+ installInvoicePaymentApi();
+ installInvoiceMigrationApi();
+ installInvoiceTestApi();
+ }
+
+}
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockJunctionModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockJunctionModule.java
new file mode 100644
index 0000000..1549c0b
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockJunctionModule.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.mock.glue;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.glue.JunctionModule;
+import com.ning.billing.junction.api.BillingApi;
+import com.ning.billing.junction.api.BlockingApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+
+public class MockJunctionModule extends AbstractModule implements JunctionModule {
+ private BillingApi billingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
+ private BlockingApi blockingApi = BrainDeadProxyFactory.createBrainDeadProxyFor(BlockingApi.class);
+ private AccountUserApi userApi = BrainDeadProxyFactory.createBrainDeadProxyFor(AccountUserApi.class);
+ private EntitlementUserApi entUserApi = BrainDeadProxyFactory.createBrainDeadProxyFor(EntitlementUserApi.class);
+
+ @Override
+ protected void configure() {
+ installBlockingApi();
+ installAccountUserApi();
+ installBillingApi();
+ installEntitlementUserApi();
+ }
+
+ @Override
+ public void installBillingApi() {
+ bind(BillingApi.class).toInstance(billingApi);
+ }
+
+
+ @Override
+ public void installAccountUserApi() {
+ bind(AccountUserApi.class).toInstance(userApi);
+ }
+
+ @Override
+ public void installBlockingApi() {
+ bind(BlockingApi.class).toInstance(blockingApi);
+ }
+
+ @Override
+ public void installEntitlementUserApi() {
+ bind(EntitlementUserApi.class).toInstance(entUserApi);
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockNotificationQueueModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockNotificationQueueModule.java
new file mode 100644
index 0000000..899be2b
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockNotificationQueueModule.java
@@ -0,0 +1,30 @@
+/*
+ * 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.mock.glue;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.util.notificationq.MockNotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+
+public class MockNotificationQueueModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
+ }
+
+}
diff --git a/util/src/test/java/com/ning/billing/mock/glue/TestDbiModule.java b/util/src/test/java/com/ning/billing/mock/glue/TestDbiModule.java
new file mode 100644
index 0000000..c42717d
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/glue/TestDbiModule.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.mock.glue;
+
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.dbi.DBIProvider;
+import com.ning.billing.dbi.DbiConfig;
+import com.ning.billing.dbi.MysqlTestingHelper;
+
+public class TestDbiModule extends AbstractModule {
+
+ protected void configure() {
+
+ final MysqlTestingHelper helper = new MysqlTestingHelper();
+ bind(MysqlTestingHelper.class).toInstance(helper);
+ if (helper.isUsingLocalInstance()) {
+ bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+ final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
+ bind(DbiConfig.class).toInstance(config);
+ } else {
+ final IDBI dbi = helper.getDBI();
+ bind(IDBI.class).toInstance(dbi);
+ }
+
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/mock/MockAccountBuilder.java b/util/src/test/java/com/ning/billing/mock/MockAccountBuilder.java
new file mode 100644
index 0000000..8b9c2ee
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/MockAccountBuilder.java
@@ -0,0 +1,383 @@
+/*
+ * 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.mock;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.MutableAccountData;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+
+public class MockAccountBuilder {
+ private final UUID id;
+ private String externalKey;
+ private String email;
+ private String name;
+ private int firstNameLength;
+ private Currency currency;
+ private int billingCycleDay;
+ private String paymentProviderName;
+ private DateTimeZone timeZone;
+ private String locale;
+ private String address1;
+ private String address2;
+ private String companyName;
+ private String city;
+ private String stateOrProvince;
+ private String country;
+ private String postalCode;
+ private String phone;
+ private boolean migrated;
+ private boolean isNotifiedForInvoices;
+
+ public MockAccountBuilder() {
+ this(UUID.randomUUID());
+ }
+
+ public MockAccountBuilder(final UUID id) {
+ this.id = id;
+ }
+
+ public MockAccountBuilder externalKey(final String externalKey) {
+ this.externalKey = externalKey;
+ return this;
+ }
+
+ public MockAccountBuilder email(final String email) {
+ this.email = email;
+ return this;
+ }
+
+ public MockAccountBuilder name(final String name) {
+ this.name = name;
+ return this;
+ }
+
+ public MockAccountBuilder firstNameLength(final int firstNameLength) {
+ this.firstNameLength = firstNameLength;
+ return this;
+ }
+
+ public MockAccountBuilder billingCycleDay(final int billingCycleDay) {
+ this.billingCycleDay = billingCycleDay;
+ return this;
+ }
+
+ public MockAccountBuilder currency(final Currency currency) {
+ this.currency = currency;
+ return this;
+ }
+
+ public MockAccountBuilder paymentProviderName(final String paymentProviderName) {
+ this.paymentProviderName = paymentProviderName;
+ return this;
+ }
+
+ public MockAccountBuilder timeZone(final DateTimeZone timeZone) {
+ this.timeZone = timeZone;
+ return this;
+ }
+
+ public MockAccountBuilder locale(final String locale) {
+ this.locale = locale;
+ return this;
+ }
+
+ public MockAccountBuilder address1(final String address1) {
+ this.address1 = address1;
+ return this;
+ }
+
+ public MockAccountBuilder address2(final String address2) {
+ this.address2 = address2;
+ return this;
+ }
+
+ public MockAccountBuilder companyName(final String companyName) {
+ this.companyName = companyName;
+ return this;
+ }
+
+ public MockAccountBuilder city(final String city) {
+ this.city = city;
+ return this;
+ }
+
+ public MockAccountBuilder stateOrProvince(final String stateOrProvince) {
+ this.stateOrProvince = stateOrProvince;
+ return this;
+ }
+
+ public MockAccountBuilder postalCode(final String postalCode) {
+ this.postalCode = postalCode;
+ return this;
+ }
+
+ public MockAccountBuilder country(final String country) {
+ this.country = country;
+ return this;
+ }
+
+ public MockAccountBuilder phone(final String phone) {
+ this.phone = phone;
+ return this;
+ }
+
+ public MockAccountBuilder migrated(final boolean migrated) {
+ this.migrated = migrated;
+ return this;
+ }
+
+ public MockAccountBuilder isNotifiedForInvoices(final boolean isNotifiedForInvoices) {
+ this.isNotifiedForInvoices = isNotifiedForInvoices;
+ return this;
+ }
+
+ public Account build() {
+ return new Account(){
+
+ @Override
+ public String getExternalKey() {
+ return externalKey;
+ }
+
+ @Override
+ public String getName() {
+
+ return name;
+ }
+
+ @Override
+ public int getFirstNameLength() {
+
+ return firstNameLength;
+ }
+
+ @Override
+ public String getEmail() {
+
+ return email;
+ }
+
+ @Override
+ public int getBillCycleDay() {
+
+ return billingCycleDay;
+ }
+
+ @Override
+ public Currency getCurrency() {
+
+ return currency;
+ }
+
+ @Override
+ public String getPaymentProviderName() {
+
+ return paymentProviderName;
+ }
+
+ @Override
+ public DateTimeZone getTimeZone() {
+
+ return timeZone;
+ }
+
+ @Override
+ public String getLocale() {
+
+ return locale;
+ }
+
+ @Override
+ public String getAddress1() {
+
+ return address1;
+ }
+
+ @Override
+ public String getAddress2() {
+
+ return address2;
+ }
+
+ @Override
+ public String getCompanyName() {
+
+ return companyName;
+ }
+
+ @Override
+ public String getCity() {
+
+ return city;
+ }
+
+ @Override
+ public String getStateOrProvince() {
+
+ return stateOrProvince;
+ }
+
+ @Override
+ public String getPostalCode() {
+
+ return postalCode;
+ }
+
+ @Override
+ public String getCountry() {
+
+ return country;
+ }
+
+ @Override
+ public String getPhone() {
+
+ return phone;
+ }
+
+ @Override
+ public boolean isMigrated() {
+
+ return migrated;
+ }
+
+ @Override
+ public boolean isNotifiedForInvoices() {
+
+ return isNotifiedForInvoices;
+ }
+
+ @Override
+ public String getFieldValue(String fieldName) {
+
+ return null;
+ }
+
+ @Override
+ public void setFieldValue(String fieldName, String fieldValue) {
+
+
+ }
+
+ @Override
+ public void saveFieldValue(String fieldName, String fieldValue, CallContext context) {
+
+
+ }
+
+ @Override
+ public List<CustomField> getFieldList() {
+ return null;
+ }
+
+ @Override
+ public void setFields(List<CustomField> fields) {
+ }
+
+ @Override
+ public void saveFields(List<CustomField> fields, CallContext context) {
+ }
+
+ @Override
+ public void clearFields() {
+ }
+
+ @Override
+ public void clearPersistedFields(CallContext context) {
+ }
+
+ @Override
+ public ObjectType getObjectType() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public UUID getId() {
+ return id;
+ }
+
+ @Override
+ public List<Tag> getTagList() {
+ return null;
+ }
+
+ @Override
+ public boolean hasTag(TagDefinition tagDefinition) {
+ return false;
+ }
+
+ @Override
+ public boolean hasTag(ControlTagType controlTagType) {
+ return false;
+ }
+
+ @Override
+ public void addTag(TagDefinition definition) {
+ }
+
+ @Override
+ public void addTags(List<Tag> tags) {
+ }
+
+ @Override
+ public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+ }
+
+ @Override
+ public void clearTags() {
+ }
+
+ @Override
+ public void removeTag(TagDefinition definition) {
+ }
+
+ @Override
+ public boolean generateInvoice() {
+ return true;
+ }
+
+ @Override
+ public boolean processPayment() {
+ return true;
+ }
+
+ @Override
+ public BlockingState getBlockingState() {
+ return null;
+ }
+
+ @Override
+ public MutableAccountData toMutableAccountData() {
+ throw new NotImplementedException();
+ }
+ };
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/util/bus/TestEventBus.java b/util/src/test/java/com/ning/billing/util/bus/TestEventBus.java
index 2310f1c..a64aa3e 100644
--- a/util/src/test/java/com/ning/billing/util/bus/TestEventBus.java
+++ b/util/src/test/java/com/ning/billing/util/bus/TestEventBus.java
@@ -16,123 +16,26 @@
package com.ning.billing.util.bus;
-import com.google.common.eventbus.Subscribe;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
-public class TestEventBus {
-
- private static final Logger log = LoggerFactory.getLogger(TestEventBus.class);
-
- private Bus eventBus;
+@Test(groups={"slow"})
+public class TestEventBus extends TestEventBusBase {
@BeforeClass(groups = "slow")
- public void setup() {
+ public void setup() throws Exception {
eventBus = new InMemoryBus();
- eventBus.start();
- }
-
- @AfterClass(groups = "slow")
- public void tearDown() {
- eventBus.stop();
- }
-
- public static final class MyEvent implements BusEvent {
- String name;
- Long value;
-
- public MyEvent(String name, Long value) {
- this.name = name;
- this.value = value;
- }
- }
-
- public static final class MyOtherEvent implements BusEvent {
- String name;
- Long value;
-
- public MyOtherEvent(String name, Long value) {
- this.name = name;
- this.value = value;
- }
- }
-
- public static class MyEventHandler {
-
- private final int expectedEvents;
-
- private int gotEvents;
-
-
- public MyEventHandler(int exp) {
- this.expectedEvents = exp;
- this.gotEvents = 0;
- }
-
- public synchronized int getEvents() {
- return gotEvents;
- }
-
- @Subscribe
- public synchronized void processEvent(MyEvent event) {
- gotEvents++;
- //log.debug("Got event {} {}", event.name, event.value);
- }
-
- public synchronized boolean waitForCompletion(long timeoutMs) {
-
- while (gotEvents < expectedEvents) {
- try {
- wait(timeoutMs);
- break;
- } catch (InterruptedException ignore) {
- }
- }
- return (gotEvents == expectedEvents);
- }
+ super.setup();
}
-
+
@Test(groups = "slow")
public void testSimple() {
- try {
-
- int nbEvents = 127;
- MyEventHandler handler = new MyEventHandler(nbEvents);
- eventBus.register(handler);
-
- for (int i = 0; i < nbEvents; i++) {
- eventBus.post(new MyEvent("my-event", (long) i));
- }
-
- boolean completed = handler.waitForCompletion(3000);
- Assert.assertEquals(completed, true);
- } catch (Exception e) {
- Assert.fail("",e);
- }
+ super.testSimple();
}
@Test(groups = "slow")
public void testDifferentType() {
- try {
-
- MyEventHandler handler = new MyEventHandler(1);
- eventBus.register(handler);
-
- for (int i = 0; i < 10; i++) {
- eventBus.post(new MyOtherEvent("my-other-event", (long) i));
- }
- eventBus.post(new MyEvent("my-event", 11l));
-
- boolean completed = handler.waitForCompletion(3000);
- Assert.assertEquals(completed, true);
- } catch (Exception e) {
- Assert.fail("",e);
- }
-
+ super.testDifferentType();
}
}
diff --git a/util/src/test/java/com/ning/billing/util/bus/TestEventBusBase.java b/util/src/test/java/com/ning/billing/util/bus/TestEventBusBase.java
new file mode 100644
index 0000000..ddc89ed
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/bus/TestEventBusBase.java
@@ -0,0 +1,253 @@
+/*
+ * 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.bus;
+
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Inject;
+import com.ning.billing.util.bus.BusEvent.BusEventType;
+
+
+public class TestEventBusBase {
+
+ protected static final Logger log = LoggerFactory.getLogger(TestEventBusBase.class);
+
+ @Inject
+ protected Bus eventBus;
+
+ @BeforeClass(groups = "slow")
+ public void setup() throws Exception {
+ eventBus.start();
+ }
+
+ @AfterClass(groups = "slow")
+ public void tearDown() {
+ eventBus.stop();
+ }
+
+
+ public static class MyEvent implements BusEvent {
+
+ private String name;
+ private Long value;
+ private UUID userToken;
+ private String type;
+
+ @JsonCreator
+ public MyEvent(@JsonProperty("name") String name,
+ @JsonProperty("value") Long value,
+ @JsonProperty("token") UUID token,
+ @JsonProperty("type") String type) {
+
+ this.name = name;
+ this.value = value;
+ this.userToken = token;
+ this.type = type;
+ }
+
+ @JsonIgnore
+ @Override
+ public BusEventType getBusEventType() {
+ return BusEventType.valueOf(type);
+ }
+
+ @Override
+ public UUID getUserToken() {
+ return userToken;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Long getValue() {
+ return value;
+ }
+
+ public String getType() {
+ return type;
+ }
+ }
+
+ public static final class MyEventWithException extends MyEvent {
+
+ @JsonCreator
+ public MyEventWithException(@JsonProperty("name") String name,
+ @JsonProperty("value") Long value,
+ @JsonProperty("token") UUID token,
+ @JsonProperty("type") String type) {
+ super(name, value, token, type);
+ }
+ }
+
+
+ public static final class MyOtherEvent implements BusEvent {
+
+ private String name;
+ private Double value;
+ private UUID userToken;
+ private String type;
+
+
+ @JsonCreator
+ public MyOtherEvent(@JsonProperty("name") String name,
+ @JsonProperty("value") Double value,
+ @JsonProperty("token") UUID token,
+ @JsonProperty("type") String type) {
+
+ this.name = name;
+ this.value = value;
+ this.userToken = token;
+ this.type = type;
+ }
+
+ @JsonIgnore
+ @Override
+ public BusEventType getBusEventType() {
+ return BusEventType.valueOf(type);
+ }
+
+ @Override
+ public UUID getUserToken() {
+ return userToken;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Double getValue() {
+ return value;
+ }
+
+ public String getType() {
+ return type;
+ }
+ }
+
+ public static class MyEventHandlerException extends RuntimeException {
+ public MyEventHandlerException(String msg) {
+ super(msg);
+ }
+ }
+
+ public static class MyEventHandler {
+
+ private final int expectedEvents;
+
+ private volatile int gotEvents;
+
+
+ public MyEventHandler(int exp) {
+ this.expectedEvents = exp;
+ this.gotEvents = 0;
+ }
+
+ public synchronized int getEvents() {
+ return gotEvents;
+ }
+
+ @Subscribe
+ public synchronized void processEvent(MyEvent event) {
+ gotEvents++;
+ //log.debug("Got event {} {}", event.name, event.value);
+ }
+
+ @Subscribe
+ public synchronized void processEvent(MyEventWithException event) {
+ throw new MyEventHandlerException("FAIL");
+ }
+
+ public synchronized boolean waitForCompletion(long timeoutMs) {
+
+ long ini = System.currentTimeMillis();
+ long remaining = timeoutMs;
+ while (gotEvents < expectedEvents && remaining > 0) {
+ try {
+ wait(1000);
+ if (gotEvents == expectedEvents) {
+ break;
+ }
+ remaining = timeoutMs - (System.currentTimeMillis() - ini);
+ } catch (InterruptedException ignore) {
+ }
+ }
+ return (gotEvents == expectedEvents);
+ }
+ }
+
+ public void testSimpleWithException() {
+ try {
+ MyEventHandler handler = new MyEventHandler(1);
+ eventBus.register(handler);
+
+ eventBus.post(new MyEventWithException("my-event", 1L, UUID.randomUUID(), BusEventType.ACCOUNT_CHANGE.toString()));
+
+ Thread.sleep(50000);
+ } catch (Exception e) {
+
+ }
+
+ }
+
+ public void testSimple() {
+ try {
+
+ int nbEvents = 5;
+ MyEventHandler handler = new MyEventHandler(nbEvents);
+ eventBus.register(handler);
+
+ for (int i = 0; i < nbEvents; i++) {
+ eventBus.post(new MyEvent("my-event", (long) i, UUID.randomUUID(), BusEventType.ACCOUNT_CHANGE.toString()));
+ }
+
+ boolean completed = handler.waitForCompletion(10000);
+ Assert.assertEquals(completed, true);
+ } catch (Exception e) {
+ Assert.fail("",e);
+ }
+ }
+
+ public void testDifferentType() {
+ try {
+
+ MyEventHandler handler = new MyEventHandler(1);
+ eventBus.register(handler);
+
+ for (int i = 0; i < 5; i++) {
+ eventBus.post(new MyOtherEvent("my-other-event", (double) i, UUID.randomUUID(), BusEventType.BUNDLE_REPAIR.toString()));
+ }
+ eventBus.post(new MyEvent("my-event", 11l, UUID.randomUUID(), BusEventType.ACCOUNT_CHANGE.toString()));
+
+ boolean completed = handler.waitForCompletion(10000);
+ Assert.assertEquals(completed, true);
+ } catch (Exception e) {
+ Assert.fail("",e);
+ }
+
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/util/bus/TestPersistentEventBus.java b/util/src/test/java/com/ning/billing/util/bus/TestPersistentEventBus.java
new file mode 100644
index 0000000..b42b694
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/bus/TestPersistentEventBus.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.bus;
+
+
+import java.io.IOException;
+
+import org.apache.commons.io.IOUtils;
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+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.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.BusModule.BusType;
+
+@Guice(modules = TestPersistentEventBus.PersistentBusModuleTest.class)
+public class TestPersistentEventBus extends TestEventBusBase {
+
+ @Inject
+ private MysqlTestingHelper helper;
+
+ @BeforeClass(groups = {"slow"})
+ public void setup() throws Exception {
+ helper.startMysql();
+ final String ddl = IOUtils.toString(TestPersistentEventBus.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+ helper.initDb(ddl);
+ cleanup();
+ super.setup();
+ }
+
+ @BeforeMethod(groups = {"slow"})
+ public void cleanup() {
+ helper.cleanupTable("bus_events");
+ helper.cleanupTable("claimed_bus_events");
+ }
+
+ public static class PersistentBusModuleTest extends AbstractModule {
+
+ @Override
+ protected void configure() {
+
+ //System.setProperty("com.ning.billing.dbi.test.useLocalDb", "true");
+
+ bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+ bind(ClockMock.class).asEagerSingleton();
+
+ final MysqlTestingHelper helper = new MysqlTestingHelper();
+ bind(MysqlTestingHelper.class).toInstance(helper);
+ if (helper.isUsingLocalInstance()) {
+ bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+ final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
+ bind(DbiConfig.class).toInstance(config);
+ } else {
+ final IDBI dbi = helper.getDBI();
+ bind(IDBI.class).toInstance(dbi);
+ }
+ install(new BusModule(BusType.PERSISTENT));
+ }
+ }
+
+ @Test(groups = {"slow"})
+ public void testSimple() {
+ super.testSimple();
+ }
+
+ // Until Guava fixes exception handling, r13?
+ @Test(groups={"slow"}, enabled=false)
+ public void testSimpleWithException() {
+ super.testSimpleWithException();
+
+ }
+
+}
diff --git a/util/src/test/java/com/ning/billing/util/callcontext/TestCallContext.java b/util/src/test/java/com/ning/billing/util/callcontext/TestCallContext.java
index b632f2e..0185a69 100644
--- a/util/src/test/java/com/ning/billing/util/callcontext/TestCallContext.java
+++ b/util/src/test/java/com/ning/billing/util/callcontext/TestCallContext.java
@@ -16,25 +16,27 @@
package com.ning.billing.util.callcontext;
+import java.util.UUID;
+
import com.ning.billing.util.clock.DefaultClock;
import org.joda.time.DateTime;
public class TestCallContext implements CallContext {
+
private final String userName;
private final DateTime updatedDate;
private final DateTime createdDate;
-
+ private final UUID userToken;
+
public TestCallContext(String userName) {
- this.userName = userName;
- DateTime now = new DefaultClock().getUTCNow();
- this.updatedDate = now;
- this.createdDate = now;
+ this(userName, new DefaultClock().getUTCNow(), new DefaultClock().getUTCNow());
}
public TestCallContext(String userName, DateTime createdDate, DateTime updatedDate) {
this.userName = userName;
this.createdDate = createdDate;
this.updatedDate = updatedDate;
+ this.userToken = UUID.randomUUID();
}
@Override
@@ -61,4 +63,9 @@ public class TestCallContext implements CallContext {
public DateTime getUpdatedDate() {
return updatedDate;
}
+
+ @Override
+ public UUID getUserToken() {
+ return userToken;
+ }
}
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 8787cd1..ab702cb 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
@@ -16,141 +16,134 @@
package com.ning.billing.util.clock;
-import com.ning.billing.catalog.api.Duration;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
+import org.joda.time.Days;
+import org.joda.time.Months;
+import org.joda.time.MutablePeriod;
+import org.joda.time.Period;
+import org.joda.time.ReadablePeriod;
+import org.joda.time.Weeks;
+import org.joda.time.Years;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.ArrayList;
-import java.util.List;
-
-// STEPH should really be in tests but not accessible from other sub modules
-public class ClockMock extends DefaultClock {
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.TimeUnit;
+public class ClockMock implements Clock {
+
+ private MutablePeriod delta = new MutablePeriod();
private static final Logger log = LoggerFactory.getLogger(ClockMock.class);
- private enum DeltaType {
- DELTA_NONE,
- DELTA_DURATION,
- DELTA_ABS
- }
-
- private long deltaFromRealityMs;
- private List<Duration> deltaFromRealityDuration;
- private long deltaFromRealityDurationEpsilon;
- private DeltaType deltaType;
-
- public ClockMock() {
- deltaType = DeltaType.DELTA_NONE;
- deltaFromRealityMs = 0;
- deltaFromRealityDurationEpsilon = 0;
- deltaFromRealityDuration = null;
- }
-
+
@Override
public synchronized DateTime getNow(DateTimeZone tz) {
- return adjust(super.getNow(tz));
+ return getUTCNow().toDateTime(tz);
}
@Override
public synchronized DateTime getUTCNow() {
- return getNow(DateTimeZone.UTC);
+ return truncate(adjust(now()));
+ }
+
+ private DateTime adjust(DateTime now) {
+ return now.plus(delta);
}
- private void logClockAdjustment(DateTime prev, DateTime next) {
- log.info(String.format(" ************ ADJUSTING CLOCK FROM %s to %s ********************", prev, next));
+ public synchronized void setTime(DateTime time) {
+ DateTime prev = getUTCNow();
+ delta = new MutablePeriod(now(), time);
+ logChange(prev);
+ }
+
+ public synchronized void addDays(int days) {
+ adjustTo(Days.days(days));
+ }
+
+ public synchronized void addWeeks(int weeks) {
+ adjustTo(Weeks.weeks(weeks));
+ }
+
+ public synchronized void addMonths(int months) {
+ adjustTo(Months.months(months));
+ }
+
+ public synchronized void addYears(int years) {
+ adjustTo(Years.years(years));
+ }
+
+ public synchronized void reset() {
+ delta = new MutablePeriod();
+ }
+
+ @Override
+ public String toString() {
+ return getUTCNow().toString();
+ }
+
+ private void adjustTo(ReadablePeriod period) {
+ DateTime prev = getUTCNow();
+ delta.add(period);
+ logChange(prev);
+ }
+
+ private void logChange(DateTime prev) {
+ DateTime now = getUTCNow();
+ log.info(String.format(" ************ ADJUSTING CLOCK FROM %s to %s ********************", prev, now));
+ }
+
+ private DateTime now() {
+ return new DateTime(DateTimeZone.UTC);
}
- public synchronized void setDeltaFromReality(Duration delta, long epsilon) {
+ private DateTime truncate(DateTime time) {
+ return time.minus(time.getMillisOfSecond());
+ }
+
+ //
+ //Backward compatibility stuff
+ //
+ public synchronized void setDeltaFromReality(Duration duration, long epsilon) {
DateTime prev = getUTCNow();
- deltaType = DeltaType.DELTA_DURATION;
- deltaFromRealityDuration = new ArrayList<Duration>();
- deltaFromRealityDuration.add(delta);
- deltaFromRealityDurationEpsilon = epsilon;
- deltaFromRealityMs = 0;
- logClockAdjustment(prev, getUTCNow());
+ delta.addMillis((int)epsilon);
+ addDeltaFromReality(duration);
+ logChange(prev);
+
}
public synchronized void addDeltaFromReality(Duration delta) {
- DateTime prev = getUTCNow();
- if (deltaType != DeltaType.DELTA_DURATION) {
- throw new RuntimeException("ClockMock should be set with type DELTA_DURATION");
- }
- deltaFromRealityDuration.add(delta);
- logClockAdjustment(prev, getUTCNow());
+ adjustTo(periodFromDuration(delta));
}
public synchronized void setDeltaFromReality(long delta) {
- DateTime prev = getUTCNow();
- deltaType = DeltaType.DELTA_ABS;
- deltaFromRealityDuration = null;
- deltaFromRealityDurationEpsilon = 0;
- deltaFromRealityMs = delta;
- logClockAdjustment(prev, getUTCNow());
+ adjustTo(new Period(delta));
}
public synchronized void addDeltaFromReality(long delta) {
- DateTime prev = getUTCNow();
- if (deltaType != DeltaType.DELTA_ABS) {
- throw new RuntimeException("ClockMock should be set with type DELTA_ABS");
- }
- deltaFromRealityDuration = null;
- deltaFromRealityDurationEpsilon = 0;
- deltaFromRealityMs += delta;
- logClockAdjustment(prev, getUTCNow());
+ adjustTo(new Period(delta));
}
public synchronized void resetDeltaFromReality() {
- deltaType = DeltaType.DELTA_NONE;
- deltaFromRealityDuration = null;
- deltaFromRealityDurationEpsilon = 0;
- deltaFromRealityMs = 0;
- }
-
- private DateTime adjust(DateTime realNow) {
- switch(deltaType) {
- case DELTA_NONE:
- return realNow;
- case DELTA_ABS:
- return adjustFromAbsolute(realNow);
- case DELTA_DURATION:
- return adjustFromDuration(realNow);
- default:
- return null;
- }
+ reset();
}
+
+ public ReadablePeriod periodFromDuration(Duration duration) {
+ if (duration.getUnit() != TimeUnit.UNLIMITED) {return new Period();}
- private DateTime adjustFromDuration(DateTime input) {
-
- DateTime result = input;
- for (Duration cur : deltaFromRealityDuration) {
- switch (cur.getUnit()) {
+ switch (duration.getUnit()) {
case DAYS:
- result = result.plusDays(cur.getNumber());
- break;
-
+ return Days.days(duration.getNumber());
case MONTHS:
- result = result.plusMonths(cur.getNumber());
- break;
-
+ return Months.months(duration.getNumber());
case YEARS:
- result = result.plusYears(cur.getNumber());
- break;
-
+ return Years.years(duration.getNumber());
case UNLIMITED:
- default:
- throw new RuntimeException("ClockMock is adjusting an unlimited time period");
- }
- }
- if (deltaFromRealityDurationEpsilon != 0) {
- result = result.plus(deltaFromRealityDurationEpsilon);
+ return Years.years(100);
+ default:
+ return new Period();
}
- return result;
- }
-
- private DateTime adjustFromAbsolute(DateTime input) {
- return truncateMs(input.plus(deltaFromRealityMs));
}
+
}
diff --git a/util/src/test/java/com/ning/billing/util/clock/MockClockModule.java b/util/src/test/java/com/ning/billing/util/clock/MockClockModule.java
index 89de280..ece94b0 100644
--- a/util/src/test/java/com/ning/billing/util/clock/MockClockModule.java
+++ b/util/src/test/java/com/ning/billing/util/clock/MockClockModule.java
@@ -23,7 +23,9 @@ public class MockClockModule extends AbstractModule {
@Override
protected void configure() {
- bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+ bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+ bind(ClockMock.class).asEagerSingleton();
}
}
+
\ No newline at end of file
diff --git a/util/src/test/java/com/ning/billing/util/clock/OldClockMock.java b/util/src/test/java/com/ning/billing/util/clock/OldClockMock.java
new file mode 100644
index 0000000..b31db77
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/clock/OldClockMock.java
@@ -0,0 +1,162 @@
+/*
+ * 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.clock;
+
+import com.ning.billing.catalog.api.Duration;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+// STEPH should really be in tests but not accessible from other sub modules
+public class OldClockMock extends DefaultClock {
+
+ private static final Logger log = LoggerFactory.getLogger(OldClockMock.class);
+
+ private enum DeltaType {
+ DELTA_NONE,
+ DELTA_DURATION,
+ DELTA_ABS
+ }
+
+ private long deltaFromRealityMs;
+ private List<Duration> deltaFromRealityDuration;
+ private long deltaFromRealityDurationEpsilon;
+ private DeltaType deltaType;
+
+ public OldClockMock() {
+ deltaType = DeltaType.DELTA_NONE;
+ deltaFromRealityMs = 0;
+ deltaFromRealityDurationEpsilon = 0;
+ deltaFromRealityDuration = null;
+ }
+
+ @Override
+ public synchronized DateTime getNow(DateTimeZone tz) {
+ return adjust(super.getNow(tz));
+ }
+
+ @Override
+ public synchronized DateTime getUTCNow() {
+ return getNow(DateTimeZone.UTC);
+ }
+
+ private void logClockAdjustment(DateTime prev, DateTime next) {
+ log.info(String.format(" ************ ADJUSTING CLOCK FROM %s to %s ********************", prev, next));
+ }
+
+ public synchronized void setDeltaFromReality(Duration delta, long epsilon) {
+ DateTime prev = getUTCNow();
+ deltaType = DeltaType.DELTA_DURATION;
+ deltaFromRealityDuration = new ArrayList<Duration>();
+ deltaFromRealityDuration.add(delta);
+ deltaFromRealityDurationEpsilon = epsilon;
+ deltaFromRealityMs = 0;
+ logClockAdjustment(prev, getUTCNow());
+ }
+
+ public synchronized void addDeltaFromReality(Duration delta) {
+ DateTime prev = getUTCNow();
+ if (deltaType != DeltaType.DELTA_DURATION) {
+ throw new RuntimeException("ClockMock should be set with type DELTA_DURATION");
+ }
+ deltaFromRealityDuration.add(delta);
+ logClockAdjustment(prev, getUTCNow());
+ }
+
+ public synchronized void setDeltaFromReality(long delta) {
+ DateTime prev = getUTCNow();
+ deltaType = DeltaType.DELTA_ABS;
+ deltaFromRealityDuration = null;
+ deltaFromRealityDurationEpsilon = 0;
+ deltaFromRealityMs = delta;
+ logClockAdjustment(prev, getUTCNow());
+ }
+
+ public synchronized void addDeltaFromReality(long delta) {
+ DateTime prev = getUTCNow();
+ if (deltaType != DeltaType.DELTA_ABS) {
+ throw new RuntimeException("ClockMock should be set with type DELTA_ABS");
+ }
+ deltaFromRealityDuration = null;
+ deltaFromRealityDurationEpsilon = 0;
+ deltaFromRealityMs += delta;
+ logClockAdjustment(prev, getUTCNow());
+ }
+
+ public synchronized void resetDeltaFromReality() {
+ deltaType = DeltaType.DELTA_NONE;
+ deltaFromRealityDuration = null;
+ deltaFromRealityDurationEpsilon = 0;
+ deltaFromRealityMs = 0;
+ }
+
+ private DateTime adjust(DateTime realNow) {
+ switch(deltaType) {
+ case DELTA_NONE:
+ return realNow;
+ case DELTA_ABS:
+ return adjustFromAbsolute(realNow);
+ case DELTA_DURATION:
+ return adjustFromDuration(realNow);
+ default:
+ return null;
+ }
+ }
+
+ private DateTime adjustFromDuration(DateTime input) {
+
+ DateTime result = input;
+ for (Duration cur : deltaFromRealityDuration) {
+ switch (cur.getUnit()) {
+ case DAYS:
+ result = result.plusDays(cur.getNumber());
+ break;
+
+ case MONTHS:
+ result = result.plusMonths(cur.getNumber());
+ break;
+
+ case YEARS:
+ result = result.plusYears(cur.getNumber());
+ break;
+
+ case UNLIMITED:
+ default:
+ throw new RuntimeException("ClockMock is adjusting an unlimited time period");
+ }
+ }
+ if (deltaFromRealityDurationEpsilon != 0) {
+ result = result.plus(deltaFromRealityDurationEpsilon);
+ }
+ return result;
+ }
+
+ private DateTime adjustFromAbsolute(DateTime input) {
+ return truncateMs(input.plus(deltaFromRealityMs));
+ }
+
+ @Override
+ public String toString() {
+ return getUTCNow().toString();
+ }
+
+
+}
diff --git a/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java b/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
index 8a2385b..7a31ab8 100644
--- a/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
+++ b/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
@@ -19,18 +19,14 @@ package com.ning.billing.util.customfield;
import java.io.IOException;
import java.util.UUID;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.CallOrigin;
import com.ning.billing.util.callcontext.UserType;
import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.clock.ClockMock;
import com.ning.billing.util.customfield.dao.AuditedCustomFieldDao;
import com.ning.billing.util.customfield.dao.CustomFieldDao;
-import com.ning.billing.util.glue.FieldStoreModule;
+import com.ning.billing.util.dao.ObjectType;
import org.apache.commons.io.IOUtils;
import org.skife.jdbi.v2.IDBI;
import org.slf4j.Logger;
@@ -62,13 +58,8 @@ public class TestFieldStore {
helper.initDb(utilDdl);
dbi = helper.getDBI();
- customFieldDao = new AuditedCustomFieldDao();
-
- FieldStoreModule module = new FieldStoreModule();
- final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module, new MockClockModule());
- Clock clock = injector.getInstance(Clock.class);
- context = new DefaultCallContextFactory(clock).createCallContext("Fezzik", CallOrigin.TEST, UserType.TEST);
-
+ customFieldDao = new AuditedCustomFieldDao(dbi);
+ context = new DefaultCallContextFactory(new ClockMock()).createCallContext("Fezzik", CallOrigin.TEST, UserType.TEST);
}
catch (Throwable t) {
log.error("Setup failed", t);
@@ -79,13 +70,15 @@ public class TestFieldStore {
@AfterClass(groups = {"util", "slow"})
public void stopMysql()
{
- helper.stopMysql();
+ if (helper!= null) {
+ helper.stopMysql();
+ }
}
@Test
public void testFieldStore() {
final UUID id = UUID.randomUUID();
- final String objectType = "Test widget";
+ final ObjectType objectType = ObjectType.ACCOUNT;
final FieldStore fieldStore1 = new DefaultFieldStore(id, objectType);
@@ -94,7 +87,7 @@ public class TestFieldStore {
fieldStore1.setValue(fieldName, fieldValue);
CustomFieldSqlDao customFieldSqlDao = dbi.onDemand(CustomFieldSqlDao.class);
- customFieldDao.saveFields(customFieldSqlDao, id, objectType, fieldStore1.getEntityList(), context);
+ customFieldDao.saveEntitiesFromTransaction(customFieldSqlDao, id, objectType, fieldStore1.getEntityList(), context);
final FieldStore fieldStore2 = DefaultFieldStore.create(id, objectType);
fieldStore2.add(customFieldSqlDao.load(id.toString(), objectType));
@@ -104,7 +97,7 @@ public class TestFieldStore {
fieldValue = "Cape Canaveral";
fieldStore2.setValue(fieldName, fieldValue);
assertEquals(fieldStore2.getValue(fieldName), fieldValue);
- customFieldDao.saveFields(customFieldSqlDao, id, objectType, fieldStore2.getEntityList(), context);
+ customFieldDao.saveEntitiesFromTransaction(customFieldSqlDao, id, objectType, fieldStore2.getEntityList(), context);
final FieldStore fieldStore3 = DefaultFieldStore.create(id, objectType);
assertEquals(fieldStore3.getValue(fieldName), null);
diff --git a/util/src/test/java/com/ning/billing/util/email/DefaultCatalogTranslationTest.java b/util/src/test/java/com/ning/billing/util/email/DefaultCatalogTranslationTest.java
new file mode 100644
index 0000000..0be05ac
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/email/DefaultCatalogTranslationTest.java
@@ -0,0 +1,84 @@
+package com.ning.billing.util.email;/*
+ * 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.
+ */
+
+import com.ning.billing.util.template.translation.DefaultCatalogTranslator;
+import com.ning.billing.util.template.translation.Translator;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+import org.skife.config.ConfigurationObjectFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.Locale;
+
+import static org.testng.Assert.assertEquals;
+
+@Test(groups = {"fast", "email"})
+public class DefaultCatalogTranslationTest {
+ private Translator translation;
+
+ @BeforeClass(groups={"fast", "email"})
+ public void setup() {
+ final TranslatorConfig config = new ConfigurationObjectFactory(System.getProperties()).build(TranslatorConfig.class);
+ translation = new DefaultCatalogTranslator(config);
+ }
+
+ @Test(groups = {"fast", "email"})
+ public void testInitialization() {
+ String ningPlusText = "ning-plus";
+ String ningProText = "ning-pro";
+ String badText = "Bad text";
+
+ assertEquals(translation.getTranslation(Locale.US, ningPlusText), "Plus");
+ assertEquals(translation.getTranslation(Locale.US, ningProText), "Pro");
+ assertEquals(translation.getTranslation(Locale.US, badText), badText);
+
+ assertEquals(translation.getTranslation(Locale.CANADA_FRENCH, ningPlusText), "Plus en francais");
+ assertEquals(translation.getTranslation(Locale.CANADA_FRENCH, ningProText), "Pro");
+ assertEquals(translation.getTranslation(Locale.CANADA_FRENCH, badText), badText);
+
+ assertEquals(translation.getTranslation(Locale.CHINA, ningPlusText), "Plus");
+ assertEquals(translation.getTranslation(Locale.CHINA, ningProText), "Pro");
+ assertEquals(translation.getTranslation(Locale.CHINA, badText), badText);
+ }
+
+ @Test
+ public void testExistingTranslation() {
+ // if the translation exists, return the translation
+ String originalText = "ning-plus";
+ assertEquals(translation.getTranslation(Locale.US, originalText), "Plus");
+ }
+
+ @Test
+ public void testMissingTranslation() {
+ // if the translation is missing from the file, return the original text
+ String originalText = "missing translation";
+ assertEquals(translation.getTranslation(Locale.US, originalText), originalText);
+ }
+
+ @Test
+ public void testMissingTranslationFileWithEnglishText() {
+ // if the translation file doesn't exist, return the "English" translation
+ String originalText = "ning-plus";
+ assertEquals(translation.getTranslation(Locale.CHINA, originalText), "Plus");
+ }
+
+ @Test
+ public void testMissingFileAndText() {
+ // if the file is missing, and the "English" translation is missing, return the original text
+ String originalText = "missing translation";
+ assertEquals(translation.getTranslation(Locale.CHINA, originalText), originalText);
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/util/email/EmailSenderTest.java b/util/src/test/java/com/ning/billing/util/email/EmailSenderTest.java
new file mode 100644
index 0000000..1685628
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/email/EmailSenderTest.java
@@ -0,0 +1,42 @@
+package com.ning.billing.util.email;/*
+ * 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.
+ */
+
+import org.skife.config.ConfigurationObjectFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Test(groups = {"slow", "email"})
+public class EmailSenderTest {
+ private EmailConfig config;
+
+ @BeforeClass
+ public void setup() {
+ config = new ConfigurationObjectFactory(System.getProperties()).build(EmailConfig.class);
+ }
+
+ @Test
+ public void testSendEmail() throws Exception {
+ String html = "<html><body><h1>Test E-mail</h1></body></html>";
+ List<String> recipients = new ArrayList<String>();
+ recipients.add("killbill.ning@gmail.com");
+
+ EmailSender sender = new DefaultEmailSender(config);
+ sender.sendSecureEmail(recipients, null, "Test message", html);
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/util/globallocker/TestMysqlGlobalLocker.java b/util/src/test/java/com/ning/billing/util/globallocker/TestMysqlGlobalLocker.java
index 6f2facd..be642f8 100644
--- a/util/src/test/java/com/ning/billing/util/globallocker/TestMysqlGlobalLocker.java
+++ b/util/src/test/java/com/ning/billing/util/globallocker/TestMysqlGlobalLocker.java
@@ -58,7 +58,9 @@ public class TestMysqlGlobalLocker {
@AfterClass(groups = "slow")
public void tearDown() {
- helper.stopMysql();
+ if (helper != null) {
+ helper.stopMysql();
+ }
}
// Used as a manual test to validate the simple DAO by stepping through that locking is done and release correctly
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java b/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
index 891cadf..7c61363 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
@@ -20,11 +20,10 @@ 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.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.tweak.HandleCallback;
@@ -39,8 +38,8 @@ import com.google.inject.Inject;
import com.ning.billing.dbi.MysqlTestingHelper;
import com.ning.billing.util.notificationq.DefaultNotification;
import com.ning.billing.util.notificationq.Notification;
-import com.ning.billing.util.notificationq.NotificationLifecycle.NotificationLifecycleState;
import com.ning.billing.util.notificationq.dao.NotificationSqlDao.NotificationSqlMapper;
+import com.ning.billing.util.queue.PersistentQueueEntryLifecycle.PersistentQueueEntryLifecycleState;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
@@ -49,8 +48,8 @@ import static org.testng.Assert.assertNotNull;
@Guice(modules = TestNotificationSqlDao.TestNotificationSqlDaoModule.class)
public class TestNotificationSqlDao {
- private static AtomicInteger sequenceId = new AtomicInteger();
-
+ private final static String hostname = "Yop";
+
@Inject
private IDBI dbi;
@@ -78,7 +77,9 @@ public class TestNotificationSqlDao {
@AfterSuite(groups = "slow")
public void stopMysql()
{
- helper.stopMysql();
+ if (helper != null) {
+ helper.stopMysql();
+ }
}
@@ -102,12 +103,12 @@ public class TestNotificationSqlDao {
String notificationKey = UUID.randomUUID().toString();
DateTime effDt = new DateTime();
- Notification notif = new DefaultNotification("testBasic",notificationKey, effDt);
+ Notification notif = new DefaultNotification("testBasic", hostname, notificationKey, effDt);
dao.insertNotification(notif);
Thread.sleep(1000);
DateTime now = new DateTime();
- List<Notification> notifications = dao.getReadyNotifications(now.toDate(), 3, "testBasic");
+ List<Notification> notifications = dao.getReadyNotifications(now.toDate(), hostname, 3, "testBasic");
assertNotNull(notifications);
assertEquals(notifications.size(), 1);
@@ -115,28 +116,28 @@ public class TestNotificationSqlDao {
assertEquals(notification.getNotificationKey(), notificationKey);
validateDate(notification.getEffectiveDate(), effDt);
assertEquals(notification.getOwner(), null);
- assertEquals(notification.getProcessingState(), NotificationLifecycleState.AVAILABLE);
+ assertEquals(notification.getProcessingState(), PersistentQueueEntryLifecycleState.AVAILABLE);
assertEquals(notification.getNextAvailableDate(), null);
DateTime nextAvailable = now.plusMinutes(5);
- int res = dao.claimNotification(ownerId, nextAvailable.toDate(), notification.getId(), now.toDate());
+ int res = dao.claimNotification(ownerId, nextAvailable.toDate(), notification.getId().toString(), now.toDate());
assertEquals(res, 1);
- dao.insertClaimedHistory(sequenceId.incrementAndGet(), ownerId, now.toDate(), notification.getUUID().toString());
+ dao.insertClaimedHistory(ownerId, now.toDate(), notification.getId().toString());
- notification = fetchNotification(notification.getUUID().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);
+ assertEquals(notification.getProcessingState(), PersistentQueueEntryLifecycleState.IN_PROCESSING);
validateDate(notification.getNextAvailableDate(), nextAvailable);
- dao.clearNotification(notification.getId(), ownerId);
+ dao.clearNotification(notification.getId().toString(), ownerId);
- notification = fetchNotification(notification.getUUID().toString());
+ notification = fetchNotification(notification.getId().toString());
assertEquals(notification.getNotificationKey(), notificationKey);
validateDate(notification.getEffectiveDate(), effDt);
//assertEquals(notification.getOwner(), null);
- assertEquals(notification.getProcessingState(), NotificationLifecycleState.PROCESSED);
+ assertEquals(notification.getProcessingState(), PersistentQueueEntryLifecycleState.PROCESSED);
validateDate(notification.getNextAvailableDate(), nextAvailable);
}
@@ -147,18 +148,19 @@ public class TestNotificationSqlDao {
@Override
public Notification withHandle(Handle handle) throws Exception {
Notification res = handle.createQuery(" select" +
- " id " +
- ", notification_id" +
+ " record_id " +
+ ", id" +
", notification_key" +
- ", created_dt" +
- ", effective_dt" +
+ ", created_date" +
+ ", creating_owner" +
+ ", effective_date" +
", queue_name" +
", processing_owner" +
- ", processing_available_dt" +
+ ", processing_available_date" +
", processing_state" +
" from notifications " +
" where " +
- " notification_id = '" + notificationId + "';")
+ " id = '" + notificationId + "';")
.map(new NotificationSqlMapper())
.first();
return res;
@@ -195,12 +197,6 @@ public class TestNotificationSqlDao {
bind(MysqlTestingHelper.class).toInstance(helper);
IDBI dbi = helper.getDBI();
bind(IDBI.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/MockNotificationQueue.java b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
index 1c40988..8e9cce4 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
@@ -21,13 +21,15 @@ import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
+import java.util.UUID;
import org.joda.time.DateTime;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import com.ning.billing.config.NotificationConfig;
import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.notificationq.NotificationLifecycle.NotificationLifecycleState;
import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+import com.ning.billing.util.queue.PersistentQueueEntryLifecycle.PersistentQueueEntryLifecycleState;
public class MockNotificationQueue extends NotificationQueueBase implements NotificationQueue {
private final TreeSet<Notification> notifications;
@@ -48,7 +50,7 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
@Override
public void recordFutureNotification(DateTime futureNotificationTime, NotificationKey notificationKey) {
- Notification notification = new DefaultNotification("MockQueue", notificationKey.toString(), futureNotificationTime);
+ Notification notification = new DefaultNotification("MockQueue", hostname, notificationKey.toString(), futureNotificationTime);
synchronized(notifications) {
notifications.add(notification);
}
@@ -65,7 +67,7 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
List<Notification> result = new ArrayList<Notification>();
for (Notification notification : notifications) {
- if (notification.getProcessingState() == NotificationLifecycleState.AVAILABLE) {
+ if (notification.getProcessingState() == PersistentQueueEntryLifecycleState.AVAILABLE) {
result.add(notification);
}
}
@@ -73,7 +75,7 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
}
@Override
- protected int doProcessEvents(int sequenceId) {
+ public int doProcessEvents() {
int result = 0;
@@ -94,7 +96,7 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
result = readyNotifications.size();
for (Notification cur : readyNotifications) {
handler.handleReadyNotification(cur.getNotificationKey(), cur.getEffectiveDate());
- DefaultNotification processedNotification = new DefaultNotification(-1L, cur.getUUID(), hostname, "MockQueue", clock.getUTCNow().plus(config.getDaoClaimTimeMs()), NotificationLifecycleState.PROCESSED, cur.getNotificationKey(), cur.getEffectiveDate());
+ DefaultNotification processedNotification = new DefaultNotification(-1L, cur.getId(), hostname, hostname, "MockQueue", clock.getUTCNow().plus(CLAIM_TIME_MS), PersistentQueueEntryLifecycleState.PROCESSED, cur.getNotificationKey(), cur.getEffectiveDate());
oldNotifications.add(cur);
processedNotifications.add(processedNotification);
}
@@ -109,4 +111,20 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
}
return result;
}
+
+ @Override
+ public void removeNotificationsByKey(UUID key) {
+ List<Notification> toClearNotifications = new ArrayList<Notification>();
+ for (Notification notification : notifications) {
+ if (notification.getNotificationKey().equals(key.toString())) {
+ toClearNotifications.add(notification);
+ }
+ }
+ synchronized(notifications) {
+ if (toClearNotifications.size() > 0) {
+ notifications.removeAll(toClearNotifications);
+ }
+ }
+
+ }
}
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueueService.java b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueueService.java
index e9ce90e..9af43c1 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueueService.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueueService.java
@@ -17,6 +17,7 @@
package com.ning.billing.util.notificationq;
import com.google.inject.Inject;
+import com.ning.billing.config.NotificationConfig;
import com.ning.billing.util.clock.Clock;
public class MockNotificationQueueService extends NotificationQueueServiceBase {
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java b/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
index fefbcdb..50f0a68 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
@@ -50,6 +50,7 @@ import com.google.common.collect.Collections2;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.name.Names;
+import com.ning.billing.config.NotificationConfig;
import com.ning.billing.dbi.MysqlTestingHelper;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.ClockMock;
@@ -91,7 +92,9 @@ public class TestNotificationQueue {
@AfterClass(groups="slow")
public void tearDown() {
- helper.stopMysql();
+ if (helper != null) {
+ helper.stopMysql();
+ }
}
@BeforeTest(groups="slow")
@@ -108,6 +111,7 @@ public class TestNotificationQueue {
});
// Reset time to real value
((ClockMock) clock).resetDeltaFromReality();
+ eventsReceived=0;
}
@@ -129,7 +133,7 @@ public class TestNotificationQueue {
synchronized (expectedNotifications) {
log.info("Handler received key: " + notificationKey);
- expectedNotifications.put(notificationKey.toString(), Boolean.TRUE);
+ expectedNotifications.put(notificationKey, Boolean.TRUE);
expectedNotifications.notify();
}
}
@@ -284,17 +288,9 @@ public class TestNotificationQueue {
return false;
}
@Override
- public long getNotificationSleepTimeMs() {
+ public long getSleepTimeMs() {
return 10;
}
- @Override
- public int getDaoMaxReadyEvents() {
- return 1;
- }
- @Override
- public long getDaoClaimTimeMs() {
- return 60000;
- }
};
@@ -393,21 +389,89 @@ public class TestNotificationQueue {
return off;
}
@Override
- public long getNotificationSleepTimeMs() {
+ public long getSleepTimeMs() {
return sleepTime;
}
+ };
+ }
+
+
+ @Test(groups="slow")
+ public void testRemoveNotifications() throws InterruptedException {
+
+ final UUID key = UUID.randomUUID();
+ final NotificationKey notificationKey = new NotificationKey() {
+ @Override
+ public String toString() {
+ return key.toString();
+ }
+ };
+ final UUID key2 = UUID.randomUUID();
+ final NotificationKey notificationKey2 = new NotificationKey() {
@Override
- public int getDaoMaxReadyEvents() {
- return maxReadyEvents;
+ public String toString() {
+ return key2.toString();
}
+ };
+
+ final DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "many",
+ new NotificationQueueHandler() {
@Override
- public long getDaoClaimTimeMs() {
- return claimTimeMs;
+ public void handleReadyNotification(String key, DateTime eventDateTime) {
+ if(key.equals(notificationKey) || key.equals(notificationKey2)) { //ignore stray events from other tests
+ log.info("Received notification with key: " + notificationKey);
+ eventsReceived++;
+ }
}
- };
+ },
+ getNotificationConfig(false, 100, 10, 10000));
+
+
+ queue.startQueue();
+
+ final DateTime start = clock.getUTCNow().plusHours(1);
+ final int nextReadyTimeIncrementMs = 1000;
+
+ // add 3 events
+
+ dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+ @Override
+ public Void inTransaction(DummySqlTest transactional,
+ TransactionStatus status) throws Exception {
+
+ queue.recordFutureNotificationFromTransaction(transactional,
+ start.plus(nextReadyTimeIncrementMs), notificationKey);
+ queue.recordFutureNotificationFromTransaction(transactional,
+ start.plus(2 *nextReadyTimeIncrementMs), notificationKey);
+ queue.recordFutureNotificationFromTransaction(transactional,
+ start.plus(3 * nextReadyTimeIncrementMs), notificationKey2);
+ return null;
+ }
+ });
+
+
+ queue.removeNotificationsByKey(key); // should remove 2 of the 3
+
+ // Move time in the future after the notification effectiveDate
+ ((ClockMock) clock).setDeltaFromReality(4000000 + nextReadyTimeIncrementMs * 3 );
+
+ try {
+ await().atMost(10, TimeUnit.SECONDS).until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return eventsReceived >= 2;
+ }
+ });
+ Assert.fail("There should only have been only one event left in the queue we got: " + eventsReceived);
+ } catch (Exception e) {
+ // expected behavior
+ }
+ log.info("Received " + eventsReceived + " events");
+ queue.stopQueue();
}
+
public static class TestNotificationQueueModule extends AbstractModule {
@Override
protected void configure() {
diff --git a/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDao.java b/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDao.java
index 553bc3c..b64d7ff 100644
--- a/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDao.java
+++ b/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDao.java
@@ -16,11 +16,9 @@
package com.ning.billing.util.tag.dao;
-import com.google.inject.Inject;
import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.dao.ObjectType;
import com.ning.billing.util.tag.Tag;
-import org.joda.time.DateTime;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import java.util.HashMap;
@@ -31,39 +29,27 @@ import java.util.UUID;
public class MockTagDao implements TagDao {
private Map<UUID, List<Tag>> tagStore = new HashMap<UUID, List<Tag>>();
- private final Clock clock;
-
- @Inject
- public MockTagDao(Clock clock) {
- this.clock = clock;
- }
@Override
- public void saveTagsFromTransaction(final Transmogrifier dao, final UUID objectId, final String objectType,
+ public void saveEntitiesFromTransaction(final Transmogrifier dao, final UUID objectId, final ObjectType objectType,
final List<Tag> tags, final CallContext context) {
tagStore.put(objectId, tags);
}
@Override
- public void saveTags(UUID objectId, String objectType, List<Tag> tags, CallContext context) {
- tagStore.put(objectId, tags);
- }
-
- @Override
- public List<Tag> loadTags(UUID objectId, String objectType) {
+ public List<Tag> loadEntities(UUID objectId, ObjectType objectType) {
return tagStore.get(objectId);
}
@Override
- public List<Tag> loadTagsFromTransaction(Transmogrifier dao, UUID objectId, String objectType) {
+ public List<Tag> loadEntitiesFromTransaction(Transmogrifier dao, UUID objectId, ObjectType objectType) {
return tagStore.get(objectId);
}
@Override
- public void addTag(final String tagName, final UUID objectId, final String objectType, final CallContext context) {
+ public void addTag(final String tagName, final UUID objectId, final ObjectType objectType, final CallContext context) {
Tag tag = new Tag() {
private UUID id = UUID.randomUUID();
- private DateTime createdDate = clock.getUTCNow();
@Override
public String getTagDefinitionName() {
@@ -74,24 +60,13 @@ public class MockTagDao implements TagDao {
public UUID getId() {
return id;
}
-
- @Override
- public String getCreatedBy() {
- return context.getUserName();
- }
-
- @Override
- public DateTime getCreatedDate() {
- return createdDate;
- }
};
-
tagStore.get(objectId).add(tag);
}
@Override
- public void removeTag(String tagName, UUID objectId, String objectType, CallContext context) {
+ public void removeTag(String tagName, UUID objectId, ObjectType objectType, CallContext context) {
List<Tag> tags = tagStore.get(objectId);
if (tags != null) {
Iterator<Tag> tagIterator = tags.iterator();
diff --git a/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java b/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java
index adcb848..5b5956b 100644
--- a/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java
+++ b/util/src/test/java/com/ning/billing/util/tag/dao/MockTagDefinitionDao.java
@@ -42,7 +42,7 @@ public class MockTagDefinitionDao implements TagDefinitionDao {
@Override
public TagDefinition create(final String definitionName, final String description,
final CallContext context) throws TagDefinitionApiException {
- TagDefinition tag = new DefaultTagDefinition(definitionName, description);
+ TagDefinition tag = new DefaultTagDefinition(definitionName, description, false);
tags.put(definitionName, tag);
return tag;
diff --git a/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
index a8d353f..c9b40d1 100644
--- a/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
+++ b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
@@ -21,12 +21,12 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
-import com.ning.billing.account.api.Account;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.CallOrigin;
import com.ning.billing.util.callcontext.UserType;
import com.ning.billing.util.callcontext.DefaultCallContextFactory;
import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.dao.ObjectType;
import com.ning.billing.util.tag.dao.TagDao;
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
@@ -103,7 +103,9 @@ public class TestTagStore {
@AfterClass(groups="slow")
public void stopMysql()
{
- helper.stopMysql();
+ if (helper != null) {
+ helper.stopMysql();
+ }
}
private void cleanupTags() {
@@ -113,7 +115,7 @@ public class TestTagStore {
public Void withHandle(Handle handle) throws Exception {
handle.createScript("delete from tag_definitions").execute();
handle.createScript("delete from tag_definition_history").execute();
- handle.createScript("delete from tags").execute();
+ handle.createScript("delete from tagStore").execute();
handle.createScript("delete from tag_history").execute();
return null;
}
@@ -125,13 +127,13 @@ public class TestTagStore {
public void testTagCreationAndRetrieval() {
UUID accountId = UUID.randomUUID();
- TagStore tagStore = new DefaultTagStore(accountId, Account.ObjectType);
+ TagStore tagStore = new DefaultTagStore(accountId, ObjectType.ACCOUNT);
Tag tag = new DescriptiveTag(testTag);
tagStore.add(tag);
- tagDao.saveTagsFromTransaction(dao, accountId, Account.ObjectType, tagStore.getEntityList(), context);
+ tagDao.saveEntitiesFromTransaction(dao, accountId, ObjectType.ACCOUNT, tagStore.getEntityList(), context);
- List<Tag> savedTags = tagDao.loadTags(accountId, Account.ObjectType);
+ List<Tag> savedTags = tagDao.loadEntities(accountId, ObjectType.ACCOUNT);
assertEquals(savedTags.size(), 1);
Tag savedTag = savedTags.get(0);
@@ -143,19 +145,19 @@ public class TestTagStore {
@Test(groups="slow")
public void testControlTagCreation() {
UUID accountId = UUID.randomUUID();
- TagStore tagStore = new DefaultTagStore(accountId, Account.ObjectType);
+ TagStore tagStore = new DefaultTagStore(accountId, ObjectType.ACCOUNT);
ControlTag tag = new DefaultControlTag(ControlTagType.AUTO_INVOICING_OFF);
tagStore.add(tag);
assertEquals(tagStore.generateInvoice(), false);
List<Tag> tagList = tagStore.getEntityList();
- tagDao.saveTagsFromTransaction(dao, accountId, Account.ObjectType, tagList, context);
+ tagDao.saveEntitiesFromTransaction(dao, accountId, ObjectType.ACCOUNT, tagList, context);
tagStore.clear();
assertEquals(tagStore.getEntityList().size(), 0);
- tagList = tagDao.loadTags(accountId, Account.ObjectType);
+ tagList = tagDao.loadEntities(accountId, ObjectType.ACCOUNT);
tagStore.add(tagList);
assertEquals(tagList.size(), 1);
@@ -165,7 +167,7 @@ public class TestTagStore {
@Test(groups="slow")
public void testDescriptiveTagCreation() {
UUID accountId = UUID.randomUUID();
- TagStore tagStore = new DefaultTagStore(accountId, Account.ObjectType);
+ TagStore tagStore = new DefaultTagStore(accountId, ObjectType.ACCOUNT);
String definitionName = "SomeTestTag";
TagDefinition tagDefinition = null;
@@ -179,12 +181,12 @@ public class TestTagStore {
tagStore.add(tag);
assertEquals(tagStore.generateInvoice(), true);
- tagDao.saveTagsFromTransaction(dao, accountId, Account.ObjectType, tagStore.getEntityList(), context);
+ tagDao.saveEntitiesFromTransaction(dao, accountId, ObjectType.ACCOUNT, tagStore.getEntityList(), context);
tagStore.clear();
assertEquals(tagStore.getEntityList().size(), 0);
- List<Tag> tagList = tagDao.loadTags(accountId, Account.ObjectType);
+ List<Tag> tagList = tagDao.loadEntities(accountId, ObjectType.ACCOUNT);
tagStore.add(tagList);
assertEquals(tagList.size(), 1);
@@ -194,7 +196,7 @@ public class TestTagStore {
@Test(groups="slow")
public void testMixedTagCreation() {
UUID accountId = UUID.randomUUID();
- TagStore tagStore = new DefaultTagStore(accountId, Account.ObjectType);
+ TagStore tagStore = new DefaultTagStore(accountId, ObjectType.ACCOUNT);
String definitionName = "MixedTagTest";
TagDefinition tagDefinition = null;
@@ -212,12 +214,12 @@ public class TestTagStore {
tagStore.add(controlTag);
assertEquals(tagStore.generateInvoice(), false);
- tagDao.saveTagsFromTransaction(dao, accountId, Account.ObjectType, tagStore.getEntityList(), context);
+ tagDao.saveEntitiesFromTransaction(dao, accountId, ObjectType.ACCOUNT, tagStore.getEntityList(), context);
tagStore.clear();
assertEquals(tagStore.getEntityList().size(), 0);
- List<Tag> tagList = tagDao.loadTags(accountId, Account.ObjectType);
+ List<Tag> tagList = tagDao.loadEntities(accountId, ObjectType.ACCOUNT);
tagStore.add(tagList);
assertEquals(tagList.size(), 2);
@@ -227,7 +229,7 @@ public class TestTagStore {
@Test(groups="slow")
public void testControlTags() {
UUID accountId = UUID.randomUUID();
- TagStore tagStore = new DefaultTagStore(accountId, Account.ObjectType);
+ TagStore tagStore = new DefaultTagStore(accountId, ObjectType.ACCOUNT);
assertEquals(tagStore.generateInvoice(), true);
assertEquals(tagStore.processPayment(), true);
@@ -270,13 +272,13 @@ public class TestTagStore {
assertNotNull(tagDefinition);
UUID objectId = UUID.randomUUID();
- TagStore tagStore = new DefaultTagStore(objectId, Account.ObjectType);
+ TagStore tagStore = new DefaultTagStore(objectId, ObjectType.ACCOUNT);
Tag tag = new DescriptiveTag(tagDefinition);
tagStore.add(tag);
- tagDao.saveTags(objectId, Account.ObjectType, tagStore.getEntityList(), context);
+ tagDao.saveEntitiesFromTransaction(dao, objectId, ObjectType.ACCOUNT, tagStore.getEntityList(), context);
- List<Tag> tags = tagDao.loadTags(objectId, Account.ObjectType);
+ List<Tag> tags = tagDao.loadEntities(objectId, ObjectType.ACCOUNT);
assertEquals(tags.size(), 1);
tagDefinitionDao.deleteTagDefinition(definitionName, context);
@@ -295,19 +297,19 @@ public class TestTagStore {
assertNotNull(tagDefinition);
UUID objectId = UUID.randomUUID();
- TagStore tagStore = new DefaultTagStore(objectId, Account.ObjectType);
+ TagStore tagStore = new DefaultTagStore(objectId, ObjectType.ACCOUNT);
Tag tag = new DescriptiveTag(tagDefinition);
tagStore.add(tag);
- tagDao.saveTags(objectId, Account.ObjectType, tagStore.getEntityList(), context);
+ tagDao.saveEntitiesFromTransaction(dao, objectId, ObjectType.ACCOUNT, tagStore.getEntityList(), context);
- List<Tag> tags = tagDao.loadTags(objectId, Account.ObjectType);
+ List<Tag> tags = tagDao.loadEntities(objectId, ObjectType.ACCOUNT);
assertEquals(tags.size(), 1);
try {
tagDefinitionDao.deleteAllTagsForDefinition(definitionName, context);
} catch (TagDefinitionApiException e) {
- fail("Could not delete tags for tag definition", e);
+ fail("Could not delete tagStore for tag definition", e);
}
try {
@@ -332,7 +334,7 @@ public class TestTagStore {
try {
tagDefinitionDao.deleteAllTagsForDefinition(definitionName, context);
} catch (TagDefinitionApiException e) {
- fail("Could not delete tags for tag definition", e);
+ fail("Could not delete tagStore for tag definition", e);
}
try {
@@ -374,13 +376,13 @@ public class TestTagStore {
public void testTagInsertAudit() {
UUID accountId = UUID.randomUUID();
- TagStore tagStore = new DefaultTagStore(accountId, Account.ObjectType);
+ TagStore tagStore = new DefaultTagStore(accountId, ObjectType.ACCOUNT);
Tag tag = new DescriptiveTag(testTag);
tagStore.add(tag);
- tagDao.saveTagsFromTransaction(dao, accountId, Account.ObjectType, tagStore.getEntityList(), context);
+ tagDao.saveEntitiesFromTransaction(dao, accountId, ObjectType.ACCOUNT, tagStore.getEntityList(), context);
- List<Tag> savedTags = tagDao.loadTags(accountId, Account.ObjectType);
+ List<Tag> savedTags = tagDao.loadEntities(accountId, ObjectType.ACCOUNT);
assertEquals(savedTags.size(), 1);
Tag savedTag = savedTags.get(0);
@@ -391,6 +393,8 @@ public class TestTagStore {
String query = String.format("select * from audit_log a inner join tag_history th on a.record_id = th.history_record_id where a.table_name = 'tag_history' and th.id='%s' and a.change_type='INSERT'",
tag.getId().toString());
List<Map<String, Object>> result = handle.select(query);
+ handle.close();
+
assertNotNull(result);
assertEquals(result.size(), 1);
assertEquals(result.get(0).get("change_type"), "INSERT");
@@ -404,22 +408,24 @@ public class TestTagStore {
public void testTagDeleteAudit() {
UUID accountId = UUID.randomUUID();
- TagStore tagStore = new DefaultTagStore(accountId, Account.ObjectType);
+ TagStore tagStore = new DefaultTagStore(accountId, ObjectType.ACCOUNT);
Tag tag = new DescriptiveTag(testTag);
tagStore.add(tag);
- tagDao.saveTags(accountId, Account.ObjectType, tagStore.getEntityList(), context);
+ tagDao.saveEntitiesFromTransaction(dao, accountId, ObjectType.ACCOUNT, tagStore.getEntityList(), context);
tagStore.remove(tag);
- tagDao.saveTags(accountId, Account.ObjectType, tagStore.getEntityList(), context);
+ tagDao.saveEntitiesFromTransaction(dao, accountId, ObjectType.ACCOUNT, tagStore.getEntityList(), context);
- List<Tag> savedTags = tagDao.loadTags(accountId, Account.ObjectType);
+ List<Tag> savedTags = tagDao.loadEntities(accountId, ObjectType.ACCOUNT);
assertEquals(savedTags.size(), 0);
Handle handle = dbi.open();
String query = String.format("select * from audit_log a inner join tag_history th on a.record_id = th.history_record_id where a.table_name = 'tag_history' and th.id='%s' and a.change_type='DELETE'",
tag.getId().toString());
List<Map<String, Object>> result = handle.select(query);
+ handle.close();
+
assertNotNull(result);
assertEquals(result.size(), 1);
assertNotNull(result.get(0).get("change_date"));
diff --git a/util/src/test/java/com/ning/billing/util/validation/TestValidationManager.java b/util/src/test/java/com/ning/billing/util/validation/TestValidationManager.java
index 1848950..4e845db 100644
--- a/util/src/test/java/com/ning/billing/util/validation/TestValidationManager.java
+++ b/util/src/test/java/com/ning/billing/util/validation/TestValidationManager.java
@@ -67,7 +67,9 @@ public class TestValidationManager {
}
private void stopDatabase() {
- helper.stopMysql();
+ if (helper != null) {
+ helper.stopMysql();
+ }
}
@Test(groups = "slow")